Working iGate
This commit is contained in:
12
CHANGELOG.md
12
CHANGELOG.md
@@ -31,10 +31,20 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
### Added
|
||||
|
||||
- connected to APRS-IS feed
|
||||
- connected to APRS-IS feed (seperate thread)
|
||||
|
||||
## [0.1.1] - 2024-02-17
|
||||
|
||||
### Added
|
||||
|
||||
- iGate functionality
|
||||
|
||||
## [0.1.2] - 2024-02-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- Convert aprs payload byte stings to character strings used utf-8, which gave errors. It now uses latin-1.
|
||||
|
||||
### Changed
|
||||
|
||||
- Reading weather station is now a scheduled task.
|
||||
|
36
ax25.py
36
ax25.py
@@ -1,36 +0,0 @@
|
||||
'''
|
||||
# A basic APRS iGate and APRS weather station with additional (optional) PE1RXF telemetry support
|
||||
#
|
||||
# This program reads the registers of the PE1RXF weather station via ModBus RTU and sends it as
|
||||
# an APRS WX report over APRS. Additionally, it sends beacons and forwards received APRS messages
|
||||
# to the APRS-IS network. All configurable via a YAML file called pe1rxf_aprs.yml.
|
||||
#
|
||||
# This program also has a PE1RXF APRS telemetry to MQTT bridge, which is configurable via pe1rxf_telemetry.yml
|
||||
#
|
||||
# Copyright (C) 2023, 2024 M.T. Konstapel https://meezenest.nl/mees
|
||||
#
|
||||
# This file is part of weather_station
|
||||
#
|
||||
# weather_station is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# weather_station is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with weather_station. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
import yaml
|
||||
from yaml.loader import SafeLoader
|
||||
|
||||
class config_reader:
|
||||
|
||||
# initiate class: define name configuration files
|
||||
def __init__(self, main_config_file, telemetry_config_file):
|
||||
return 0
|
||||
|
@@ -123,6 +123,8 @@ def parsePacket(string):
|
||||
# Split the address and payload separated by APRS PID
|
||||
buffer = string.split(b'\x03\xf0')
|
||||
address = buffer[0]
|
||||
#Define empty list in case packet does not has digipeaters in path
|
||||
digipeaters = []
|
||||
|
||||
# Check if the first byte indicates it is a data packet
|
||||
if address[0] == 0:
|
||||
@@ -207,7 +209,7 @@ def process_aprsis(packet):
|
||||
if rflog_file == 0:
|
||||
return 0
|
||||
|
||||
string = timestamp + ' ' + 'APRSIS ' + ' R ' + str(packet, 'utf-8') + '\n'
|
||||
string = timestamp + ' ' + 'APRSIS ' + ' R ' + str(packet, 'latin-1') + '\n'
|
||||
|
||||
try:
|
||||
with open(rflog_file, "a") as logfile:
|
||||
@@ -236,6 +238,17 @@ def send_aprsis(srcCall, dest, digi, msg):
|
||||
if ('NOGATE' in digi):
|
||||
print(">>> NOGATE, not igated")
|
||||
return 1
|
||||
if msg[0] == '}':
|
||||
if ('TCPIP' in msg) or ('TCPXX' in msg):
|
||||
print(">>> Third party packet, not igated")
|
||||
return 1
|
||||
# TODO: strip third party header and send to igate
|
||||
else:
|
||||
print(">>> Third party packet, not igated")
|
||||
return 1
|
||||
if msg[0] == '?':
|
||||
print(">>> Query, not igated")
|
||||
return 1
|
||||
|
||||
message = srcCall + '>' + dest + ',' + digi + ':' + msg
|
||||
|
||||
@@ -352,12 +365,25 @@ def send_telemetry():
|
||||
|
||||
send_ax25('PE1RXF-3', 'PE1RXF-13', "APZMDM", 0, message)
|
||||
|
||||
def read_weather_station(weather_station):
|
||||
global WxData
|
||||
#print ("Reading registers of weather station.")
|
||||
if weather_station.get_weather_data() == 0:
|
||||
print ('No response from ModBus, even after 5 retries. Keep trying.')
|
||||
else:
|
||||
WxData = weather_station.wx_data
|
||||
|
||||
def check_heater(weather_station):
|
||||
# Check if heater is off, if so, turn it on
|
||||
if weather_station.wx_data['Status bits'] & 0x4 == 0:
|
||||
weather_station.enable_heater()
|
||||
print("Heater was off, turned it back on gain.")
|
||||
|
||||
def check_thread_alive(thr):
|
||||
thr.join(timeout=0.0)
|
||||
# returns True if the thread is still running and False, otherwise
|
||||
return thr.is_alive()
|
||||
|
||||
def run():
|
||||
|
||||
global WxData
|
||||
@@ -396,14 +422,14 @@ def run():
|
||||
else:
|
||||
print("Got response from the weather station. Weather information is available.")
|
||||
|
||||
# NOTE: Should be done periodically! So when the weather station is unplugged and plugged back in, the heater will be enabled again.
|
||||
try:
|
||||
weather_station.enable_heater()
|
||||
except:
|
||||
print("No response from the weather station via the ModBus, even after several retries. Disabling the weather station.")
|
||||
sys.exit(1) # Make program work without weather station!
|
||||
else:
|
||||
print("Enabled the heater function on the weather station.")
|
||||
# NOTE: Is now done periodically. So when the weather station is unplugged and plugged back in, the heater will be enabled again.
|
||||
#try:
|
||||
# weather_station.enable_heater()
|
||||
#except:
|
||||
# print("No response from the weather station via the ModBus, even after several retries. Disabling the weather station.")
|
||||
# sys.exit(1) # Make program work without weather station!
|
||||
#else:
|
||||
# print("Enabled the heater function on the weather station.")
|
||||
|
||||
###
|
||||
# Schedule all periodic transmissions (use a little randomness)
|
||||
@@ -428,12 +454,16 @@ def run():
|
||||
print("Scheduled telemetry transmission.")
|
||||
schedule.every(10).minutes.do(send_telemetry)
|
||||
|
||||
# ScheduleL check if heater is still on
|
||||
# Schedule check if heater is still on
|
||||
# So when the weather station is unplugged and plugged back in, the heater will be enabled again.
|
||||
schedule.every(10).minutes.do(check_heater, weather_station)
|
||||
|
||||
# Schedule readout of weather station
|
||||
print("Scheduled readout of weather station.")
|
||||
schedule.every(1).minutes.do(read_weather_station, weather_station)
|
||||
|
||||
# Connect to incoming APRS-IS feed
|
||||
# by default `raw` is False, then each line is ran through aprslib.parse()
|
||||
|
||||
# Set filter on incomming feed
|
||||
APRSIS.set_filter(Configuration.config_file_settings['aprsis']['filter'])
|
||||
# This is a blocking call, should run as seperate thread
|
||||
@@ -445,22 +475,34 @@ def run():
|
||||
while (1):
|
||||
|
||||
#print ("Reading registers of weather station.")
|
||||
if weather_station.get_weather_data() == 0:
|
||||
print ('No response from ModBus, even after 5 retries. Keep trying.')
|
||||
else:
|
||||
WxData = weather_station.wx_data
|
||||
#if weather_station.get_weather_data() == 0:
|
||||
# print ('No response from ModBus, even after 5 retries. Keep trying.')
|
||||
#else:
|
||||
# WxData = weather_station.wx_data
|
||||
|
||||
# Scheduler
|
||||
schedule.run_pending()
|
||||
|
||||
# Check if APRS-IS thread is still running, if not restart it
|
||||
if check_thread_alive(thread) == False:
|
||||
# Set filter on incomming feed
|
||||
APRSIS.set_filter(Configuration.config_file_settings['aprsis']['filter'])
|
||||
# This is a blocking call, should run as seperate thread
|
||||
# create a thread
|
||||
thread = Thread(target=APRSIS.consumer, args=(process_aprsis, True, True, True))
|
||||
# run the thread
|
||||
thread.start()
|
||||
|
||||
# Listen on all ax25 ports and send to APRS-IS
|
||||
receive = receive_ax25(rx_socket)
|
||||
for port in range(len(axdevice)):
|
||||
if receive[0][1] == axdevice[port]:
|
||||
#print(receive)
|
||||
source, destination, digipeaters, payload = parsePacket(receive[1])
|
||||
#print(receive[1])
|
||||
# bug UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf8 in position 47: invalid start byte
|
||||
# Convert byte string to normal string
|
||||
try:
|
||||
payload = payload.decode()
|
||||
payload = payload.decode('latin-1')
|
||||
except:
|
||||
payload = 'NOT VALID'
|
||||
|
||||
|
@@ -25,7 +25,7 @@ global:
|
||||
log-rf: /home/marcel/test/pe1rxf_aprs-rf.log # Log RF traffic to file (0=no logging)
|
||||
|
||||
modbus:
|
||||
port: /dev/serial/by-path/platform-3f980000.usb-usb-0:1.1:1.0-port0 # USB port to which RS-485 dongle is connected
|
||||
port: /dev/serial/by-path/platform-3f980000.usb-usb-0:1.2:1.0-port0 # USB port to which RS-485 dongle is connected
|
||||
address: 14 # ModBus address of weather station
|
||||
|
||||
# APRS-IS section
|
||||
@@ -61,28 +61,28 @@ weather:
|
||||
beacon:
|
||||
- port: ax0 # The AX.25 port on which to transmit (use aprsis for beaconing to the internet via APRS-IS, set to 0 if you want to use the call assigned to the port in /etc/ax25/axports)
|
||||
call: PE1RXF-1 # Call from which transmissions are made (can be a different call from the call assigned to the AX.25 port)
|
||||
destination: APRX29 # APRS destination
|
||||
destination: APZMDM # APRS destination
|
||||
digi_path: WIDE2-1 # Specifie the digipeater path (best practise is to use WIDE2-1, WIDE2-2 or set to 0 for no path)
|
||||
position: "!5302.78NR00707.91E&" # The position string for the beacon (better to put this string between parentheses)
|
||||
message: APRS RX iGATE 144.800MHz # The beacon text
|
||||
interval: 30 # Beacon interval in minutes
|
||||
- port: ax1
|
||||
call: PE1RXF-3
|
||||
destination: APRX29
|
||||
destination: APZMDM
|
||||
digi_path: WIDE2-1
|
||||
position: "!5302.78NL00707.91E&"
|
||||
message: LoRa APRS RX iGATE 433.775MHz
|
||||
interval: 30
|
||||
- port: aprsis
|
||||
call: PE1RXF-1
|
||||
destination: APRX29
|
||||
destination: APZMDM
|
||||
digi_path: 0
|
||||
position: "!5302.78NR00707.91E&"
|
||||
message: APRS RX iGATE 144.800MHz
|
||||
interval: 10
|
||||
- port: aprsis
|
||||
call: PE1RXF-3
|
||||
destination: APRX29
|
||||
destination: APZMDM
|
||||
digi_path: 0
|
||||
position: "!5302.78NL00707.91E&"
|
||||
message: LoRa APRS RX iGATE 433.775MHz
|
||||
|
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Start weather_station software
|
||||
/usr/bin/python3 /home/marcel/ham/weather_station/weather_station_rs485_client.py -c /home/marcel/ham/weather_station/config.yml &
|
||||
|
Reference in New Issue
Block a user