diff options
author | Matthias P. Braendli <matthias.braendli@mpb.li> | 2023-07-13 11:23:58 +0200 |
---|---|---|
committer | Matthias P. Braendli <matthias.braendli@mpb.li> | 2023-07-13 11:23:58 +0200 |
commit | 09e294e240921cc8af73677a273abcbaf447b8de (patch) | |
tree | fd7b242cfa96beb6b0a06864bbfb2f6fae114379 | |
parent | 6f9e0d4ed4036fe3b0fe0ff85e02611cf8672ca2 (diff) | |
download | lora-aprs-hb9egm-09e294e240921cc8af73677a273abcbaf447b8de.tar.gz lora-aprs-hb9egm-09e294e240921cc8af73677a273abcbaf447b8de.tar.bz2 lora-aprs-hb9egm-09e294e240921cc8af73677a273abcbaf447b8de.zip |
Add raspi igate script
-rw-r--r-- | README.md | 15 | ||||
-rwxr-xr-x | python/igate.py | 120 |
2 files changed, 135 insertions, 0 deletions
@@ -9,3 +9,18 @@ Try to follow https://aprs434.github.io/ Contains STM32F103, u-blox NEO-M8N, RFM98W 433MHz LoRa Module and AFT09MS007NT1 PA: ![3d view](tracker-kicad/20230709-tracker-kicad-3d.png) + +## i-gate + +`python/igate.py` contains an APRS-IS i-gate that uses a RFM98W on a Raspberry Pi. +It only forwards position reports. + +### Installation + +Configuration: Modify the CALLSIGN and PASSCODE inside `igate.py` + + cd python + python3 -m venv venv + venv/bin/pip install -r requirements.txt + ./igate.py + diff --git a/python/igate.py b/python/igate.py new file mode 100755 index 0000000..9466304 --- /dev/null +++ b/python/igate.py @@ -0,0 +1,120 @@ +#!venv/bin/python +# +# This script is a very simple APRS-IS LoRa igate that +# runs on a raspberry pi and expects a RFM9x on the SPI bus. +# +# Also, there are two status LEDs on D23 and D24 that count +# the incoming messages. +# +# It is a send-only i-gate that uses the HTTP POST mechanism described +# on http://aprs-is.net/SendOnlyPorts.aspx +# +import datetime +import socket +import struct +import time + +import busio +from digitalio import DigitalInOut, Direction, Pull +import board +# Import the RFM9x radio module. +import adafruit_rfm9x + +# For APRS-IS +APRS_IS_SERVER = "france.aprs2.net" +APRS_IS_PORT = 8080 +#APRS_IS_PORT = 14580 +CALLSIGN = "HB9EGM-10" +PASSCODE = "22681" +REPORTING_DISTANCE = "10" + +def decode_encoded_position_report(packet): + """Returns a string with decoded latitude and longitude, or raises ValueError""" + ix = packet.index(b":!/") + gps_packet = packet[ix+3:] + encoded_latitude = gps_packet[:4] + encoded_longitude = gps_packet[4:8] + + (y1, y2, y3, y4) = struct.unpack("<bbbb", encoded_latitude) + + decodedLatitude = 90.0 - ((((y1-33) * pow(91,3)) + ((y2-33) * pow(91,2)) + ((y3-33) * 91) + y4-33) / 380926.0); + + (x1, x2, x3, x4) = struct.unpack("<bbbb", encoded_longitude) + + decodedLongitude = -180.0 + ((((x1-33) * pow(91,3)) + ((x2-33) * pow(91,2)) + ((x3-33) * 91) + x4-33) / 190463.0); + return f"{decodedLatitude}N / {decodedLongitude}E" + +def send_frame_to_aprs_is(frame): + headers = { "Accept": "text/plain", "Content-Type": "application/octet-stream" } + + aprsauth = f"user {CALLSIGN} pass {PASSCODE} vers HB9EGM_lorapy 1.0 filter t/m/{CALLSIGN}/{REPORTING_DISTANCE}\n"; + + # Prefix igate path + path, _, message = frame[3:].partition(b":") + new_frame = path + b",qAO," + CALLSIGN.encode() + b":" + message + b"\n" + data = aprsauth.encode() + new_frame + + r = requests.post(f"http://{APRS_IS_SERVER}:{APRS_IS_PORT}/", data=data, headers=headers) + print(f"{r.status_code} {r.content.decode()}") + + +led_yellow = DigitalInOut(board.D23) +led_yellow.direction = Direction.OUTPUT +led_green = DigitalInOut(board.D24) +led_green.direction = Direction.OUTPUT + +led_counter = 0 + +def update_leds(): + led_counter = (led_counter + 1) % 4 + + led_green.value = (led_counter & 0x1) != 0 + led_yellow.value = (led_counter & 0x2) != 0 + +update_leds() + +cs = DigitalInOut(board.CE1) +reset = DigitalInOut(board.D25) +spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) + +baudrate = 5000000 + +# see https://docs.circuitpython.org/projects/rfm9x/en/latest/ +try: + rfm9x = adafruit_rfm9x.RFM9x(spi, cs, reset, 433.775, baudrate=baudrate, preamble_length=8) + rfm9x.signal_bandwidth = 125000 + rfm9x.coding_rate = 5 + #rfm9x.tx_power = 23 # default 13 + + rfm9x.spreading_factor = 12 + + # Section 4.1.1.6 - Set low datarate automatically based on BW and SF + symbolDuration = 1000 / ( rfm9x.signal_bandwidth / (1 << rfm9x.spreading_factor) ) + if symbolDuration > 16: + rfm9x.low_datarate_optimize = 1 + else: + rfm9x.low_datarate_optimize = 0 + + print("Waiting for packets...") + while True: + packet = rfm9x.receive(timeout=4, with_header=True) + if packet is not None: + packet = bytes(packet) # otherwise it's a bytearray + + print("{}: {}".format(datetime.datetime.now(), packet)) + #print(" {}".format(" ".join(hex(x) for x in packet))) + + if b":!/" in packet: + print(decode_encoded_position_report(packet)) + try: + send_frame_to_aprs_is(packet) + except Exception as ex: + print("Error sending to APRS-IS") + print(ex) + + print(" RSSI: {}".format(rfm9x.last_rssi)) + print() + + update_leds() +except RuntimeError as error: + print(f'RFM9x Error: {error}') |