Working iGate

This commit is contained in:
marcel
2024-02-20 17:10:14 +01:00
parent 8d485233bd
commit e55915d2c7
5 changed files with 75 additions and 64 deletions

View File

@@ -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
View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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 &