diff --git a/CHANGELOG.md b/CHANGELOG.md index cebeea9..075be62 100644 --- a/CHANGELOG.md +++ b/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. diff --git a/ax25.py b/ax25.py deleted file mode 100644 index f032f7e..0000000 --- a/ax25.py +++ /dev/null @@ -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 . -''' - -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 - diff --git a/pe1rxf_aprs.py b/pe1rxf_aprs.py index 0e87304..87fff2d 100644 --- a/pe1rxf_aprs.py +++ b/pe1rxf_aprs.py @@ -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' diff --git a/pe1rxf_aprs.yml b/pe1rxf_aprs.yml index 1e3c65f..c86ff98 100644 --- a/pe1rxf_aprs.yml +++ b/pe1rxf_aprs.yml @@ -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 diff --git a/start_weater_station.sh b/start_weater_station.sh deleted file mode 100755 index db6766c..0000000 --- a/start_weater_station.sh +++ /dev/null @@ -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 & -