diff --git a/AXUDPServer.py b/AXUDPServer.py new file mode 100644 index 0000000..8f1b0b4 --- /dev/null +++ b/AXUDPServer.py @@ -0,0 +1,237 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import print_function + +RECV_BUFFER_LENGTH = 1024 + +from threading import Thread +import socket +from KissHelper import SerialParser +from array import array + + +CRCTAB = array("H",[ +61560,57841,54122,49891,46684,42965,38222,33991,31792,28089,24354,20139,14868,11165,6406,2191, +57593,61808,50155,53858,42717,46932,34255,37958,27825,32056,20387,24106,10901,15132,2439,6158, +53626,49395,62056,58337,38750,34519,46156,42437,23858,19643,32288,28585,6934,2719,14340,10637, +49659,53362,58089,62304,34783,38486,42189,46404,19891,23610,28321,32552,2967,6686,10373,14604, +45692,41973,37230,32999,62552,58833,55114,50883,15924,12221,7462,3247,30736,27033,23298,19083, +41725,45940,33263,36966,58585,62800,51147,54850,11957,16188,3495,7214,26769,31000,19331,23050, +37758,33527,45164,41445,54618,50387,63048,59329,7990,3775,15396,11693,22802,18587,31232,27529, +33791,37494,41197,45412,50651,54354,59081,63296,4023,7742,11429,15660,18835,22554,27265,31496, +29808,26105,22370,18155,12884,9181,4422,207,63544,59825,56106,51875,48668,44949,40206,35975, +25841,30072,18403,22122,8917,13148,455,4174,59577,63792,52139,55842,44701,48916,36239,39942, +21874,17659,30304,26601,4950,735,12356,8653,55610,51379,64040,60321,40734,36503,48140,44421, +17907,21626,26337,30568,983,4702,8389,12620,51643,55346,60073,64288,36767,40470,44173,48388, +13940,10237,5478,1263,28752,25049,21314,17099,47676,43957,39214,34983,64536,60817,57098,52867, +9973,14204,1511,5230,24785,29016,17347,21066,43709,47924,35247,38950,60569,64784,53131,56834, +6006,1791,13412,9709,20818,16603,29248,25545,39742,35511,47148,43429,56602,52371,65032,61313, +2039,5758,9445,13676,16851,20570,25281,29512,35775,39478,43181,47396,52635,56338,61065,65280 +]) + +def logf(message): + import sys + print(message, file=sys.stderr) + + +class AXUDPServer(Thread): + '''AXUDP Server to communicate with the digipeater''' + + txQueue = None + + # host and port as configured in aprx/aprx.conf.lora-aprs < interface > section + def __init__(self, txQueue, localHost="127.0.0.1", localPort=10001, remoteHost="127.0.0.1", remotePort="20000"): + Thread.__init__(self) + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket.bind((localHost, localPort)) + self.remoteHost=remoteHost + self.remotePort=remotePort + self.data = str() + self.txQueue = txQueue + + def run(self): + while True: + frame = self.socket.recv(RECV_BUFFER_LENGTH) + print("TX:",self.axtostr(frame)) + self.txQueue.put(self.axtostr(frame).encode('utf-8'), block=False) + + def __del__(self): + self.socket.shutdown() + + def send(self, data, metadata): + self.sendax(data, (self.remoteHost, self.remotePort), metadata) + #self.socket.sendall(data) + + def axcall(self, text, pos): + l=len(text) + a="" + print(text) + while (pos=ord("0")) and (text[pos]<=ord("9")) or (text[pos]>=ord("A")) and (text[pos]<=ord("Z"))): + a+=chr(text[pos]<<1) + pos+=1 + while len(a)<6: a+=chr(ord(" ")<<1) #fill with spaces + ssid=0 + if (pos=ord("0")) and (text[pos]<=ord("9")): + ssid+=text[pos]-ord("0") + pos+=1 + if (pos=ord("0")) and (text[pos]<=ord("9")): + ssid=ssid*10 + text[pos]-ord("0") + pos+=1 + if ssid>15: ssid=15 + ssid=(ssid+48)<<1 + if (pos> 8) ^ CRCTAB[(ord(frame[p]) ^ c) & 0xff] + return c + def sendax(self, text, ip, values=False): + a,p=self.axcall(text, 0) #src call + if (p>=len(text)) or (text[p]!=ord(">")): + print("fehler 1") + return + ax,p=self.axcall(text, p+1) #dest call + ax+=a + hbit=0 + while True: #via calls + if p>=len(text): + print("found no end of address") + return #found no end of address + if text[p]==ord(":"): break #end of address field + if text[p]!=ord(","): + print("via path error") + return #via path error + if len(ax)>=70: + print("too many via calls") + return #too many via calls + a,p=self.axcall(text, p+1) + ax+=a + hp=len(ax)-1 + if (ord(ax[hp]) & 0x80)!=0: hbit=hp #store last h-bit + p+=1 + a="" + + if values: + a="\x01\x30" #axudp v2 start + + if 'level' in values.keys(): + v=values["level"] + a+="V"+str(round(v))+" " #axudp v2 append level + + if 'quality' in values.keys(): + v=values["quality"] + a+="Q"+str(round(v))+" " #axudp v2 append quality + + if 'txdel' in values.keys(): + v=values["txdel"] + a+="T"+str(round(v))+" " #axudp v2 append quality + + if 'snr' in values.keys(): + v=values["snr"] + a+="S"+str(round(v))+" " #axudp v2 append snr + + a+="\x00" #axudp2 end + + i=0 + for i in range(len(ax)): + ch=ord(ax[i]) + if (i%7==6) and (i>=20) and (i>8) + sa=array("B",[0]*len(a)) + for i in range(0,len(a)): sa[i]=ord(a[i]) + print(sa) + print(ip) + res=sock.sendto(sa, ip) + + ## RX: + def callstr(self, b, p): + s="" + for i in range(6): + ch=ord(b[p+i])>>1 + if ch<32: s+="^" #show forbidden ctrl in call + elif ch>32:s+=chr(ch) #call is filled with blanks + ssid=(ord(b[p+6])>>1) & 0x0f + if ssid: s+="-"+str(ssid) + return s + def axtostr(self, axbuf): + b="" + for x in axbuf: + b+=chr(x) + le=len(b) + if le<2: + return "" + le-=2 + c=self.udpcrc(b, le) + if (b[le]!=chr(c & 0xff)) or (b[le+1]!=chr(c>>8)): + return "" #crc error + + i=0 + if axbuf[0]==1: #axudp v2 + while (i=18: #2 calls + ctrl + pid + crc + le-=2 + s=self.callstr(b, 7) #src call + s+=">"+self.callstr(b, 0) #destination call + p=14 + hbit=False + while (((not (ord(b[p-1]) & 1)))) and (p+6=128: + hbit=True + elif hbit: #call before had hbit + s+="*" + hbit=False + s+=","+callstr(b, p) + p+=7 + if hbit: s+="*" #last call had hbit + p+=2 #pid, ctrl + s+=":" + while p +``` +cd +git clone https://github.com/tomelec/RPi-LoRa-KISS-TNC.git +cd RPi-LoRa-KISS-TNC +git clone https://github.com/mayeranalytics/pySX127x.git +``` +to change into homedirectory and then checkout the code and the LoRa Chip-Driver in the right directory. + +## Configuration +Afterwards configure as following: +### Edit aprx/aprx.conf.lora-aprs file +Type: +``` +cd +cd RPi-LoRa-KISS-TNC +sudo cp aprx/aprx.conf.lora-aprs /etc/aprx.conf +pico -w /etc/aprx.conf +``` +to copy and then open the config file. + +The most important settings are: +* **mycall**
+Your call with an apropriate SSID suffix
[Paper on SSID's from aprs.org](http://www.aprs.org/aprs11/SSIDs.txt) +* **myloc**
+NMEA lat/lon form: +``` +lat ddmm.mmN lon dddmm.mmE +``` +Example: +``` +lat 4812.52N lon 01622.39E +``` +(simplest way to find the right coordinats for this? Go to [aprs.fi](http://www.aprs.fi) on your location right-click and choose "Add marker" then click on the marker and you should see your coordinates in the NMEA style - enter this infos without any symbols into the config file as seen in the example above) + + +* **passcode**
+see [see here to generate appropiate setting](https://apps.magicbug.co.uk/passcode/) +* **server**
+either leave the default server or if you're connected to Hamnet as well insert an APRSIS Server within the hamnet - a List of Aprs Hamnet servers can be found in the [OEVSV Wiki](http://wiki.oevsv.at/index.php/Anwendungen_am_HAMNET#APRS_Server)) + + +to save and close the file do: +`Strg + x` -> Y -> Enter + +### Edit driver config file +Type +``` +pico -w pySX127x/SX127x/board_config.py +``` +change in line 36 +from +``` +DIO0 = 22 # RaspPi GPIO 22 +DIO0 = 5 # RaspPi GPIO 5 +``` +to fix the SPI connection #todo how can we config this from outside? + +## Start the LoRa KISS TNC and aprx server instance +``` +python3 Start_lora-tnc.py & +sudo aprx +``` + +## Stop the server's +``` +sudo killall aprx python3 +``` diff --git a/KissHelper.py b/KissHelper.py new file mode 100644 index 0000000..2510322 --- /dev/null +++ b/KissHelper.py @@ -0,0 +1,220 @@ +# This program 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. +# +# This program 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 this program. If not, see . + + +# This program provides basic KISS AX.25 APRS frame encoding and decoding. +# Note that only APRS relevant structures are tested. It might not work +# for generic AX.25 frames. +# 11/2019 by Thomas Kottek, OE9TKH +# +# Inspired by: +# * Python script to decode AX.25 from KISS frames over a serial TNC +# https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88 +# +# * Sending a raw AX.25 frame with Python +# https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html +# +# TODO: remove escapes on decoding + +import struct + +KISS_FEND = 0xC0 # Frame start/end marker +KISS_FESC = 0xDB # Escape character +KISS_TFEND = 0xDC # If after an escape, means there was an 0xC0 in the source message +KISS_TFESC = 0xDD # If after an escape, means there was an 0xDB in the source message + + +# Addresses must be 6 bytes plus the SSID byte, each character shifted left by 1 +# If it's the final address in the header, set the low bit to 1 +# Ignoring command/response for simple example +def encode_address(s, final): + if b"-" not in s: + s = s + b"-0" # default to SSID 0 + call, ssid = s.split(b'-') + if len(call) < 6: + call = call + b" "*(6 - len(call)) # pad with spaces + encoded_call = [x << 1 for x in call[0:6]] + encoded_ssid = (int(ssid) << 1) | 0b01100000 | (0b00000001 if final else 0) + return encoded_call + [encoded_ssid] + + +def decode_address(data, cursor): + (a1, a2, a3, a4, a5, a6, a7) = struct.unpack("> 5 + ssid = (a7 >> 1) & 0xf + ext = a7 & 0x1 + addr = struct.pack("> 1, a2 >> 1, a3 >> 1, a4 >> 1, a5 >> 1, a6 >> 1) + if ssid != 0: + call = addr.strip() + "-{}".format(ssid).encode() + else: + call = addr + return (call, hrr, ext) + + +def encode_kiss(frame): + # Ugly frame disassembling + if not b":" in frame: + return None + path = frame.split(b":")[0] + src_addr = path.split(b">")[0] + digis = path[path.find(b">") + 1:].split(b",") + # destination address + packet = encode_address(digis.pop(0).upper(), False) + # source address + packet += encode_address(path.split(b">")[0].upper(), len(digis) == 0) + # digipeaters + for digi in digis: + final_addr = digis.index(digi) == len(digis) - 1 + packet += encode_address(digi.upper(), final_addr) + # control field + packet += [0x03] # This is an UI frame + # protocol ID + packet += [0xF0] # No protocol + # information field + packet += frame[frame.find(b":") + 1:] + + # Escape the packet in case either KISS_FEND or KISS_FESC ended up in our stream + packet_escaped = [] + for x in packet: + if x == KISS_FEND: + packet_escaped += [KISS_FESC, KISS_TFEND] + elif x == KISS_FESC: + packet_escaped += [KISS_FESC, KISS_TFESC] + else: + packet_escaped += [x] + + # Build the frame that we will send to Dire Wolf and turn it into a string + kiss_cmd = 0x00 # Two nybbles combined - TNC 0, command 0 (send data) + kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND] + try: + output = bytearray(kiss_frame) + except ValueError: + print("Invalid value in frame.") + return None + return output + + +def decode_kiss(frame): + result = b"" + pos = 0 + if frame[pos] != 0xC0 or frame[len(frame) - 1] != 0xC0: + print(frame[pos], frame[len(frame) - 1]) + return None + pos += 1 + pos += 1 + + # DST + (dest_addr, dest_hrr, dest_ext) = decode_address(frame, pos) + pos += 7 + # print("DST: ", dest_addr) + + # SRC + (src_addr, src_hrr, src_ext) = decode_address(frame, pos) + pos += 7 + # print("SRC: ", src_addr) + + result += src_addr.strip() + # print(type(result), type(dest_addr.strip())) + result += b">" + dest_addr.strip() + + # REPEATERS + ext = src_ext + while ext == 0: + rpt_addr, rpt_hrr, ext = decode_address(frame, pos) + # print("RPT: ", rpt_addr) + pos += 7 + result += b"," + rpt_addr.strip() + + result += b":" + + # CTRL + # (ctrl,) = struct.unpack("APRS,RELAY,BLA:!4725.51N/00939.86E[322/002/A=001306 Batt=3") + # encoded = encode_kiss("OE9TKH-8>APRS,digi-3,digi-2:!4725.51N/00939.86E[322/002/A=001306 Batt=3") + # print((decode_kiss(encoded))) + + # print((decode_kiss("\xc0\x00\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90t\xae\x92\x88\x8ab@\x03\x03\xf0}OE9GHV-10>APMI06,TCPIP,OE9TKH-10*:@110104z4726.55N/00950.63E&WX3in1 op. Holger U=14.2V,T=8.8C\xc0"))) + + def newframe(frame): + print(repr(frame)) + + + two_example_frames = "\xc0\x00\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90u\x03\xf0}SOTA>APZS16,TCPIP,OE9TKH-10*::OE9TKH-8 : [call] [comment]{7ba\xc0\xc0\x00\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90u\x03\xf0}SOTA>APZS16,TCPIP,OE9TKH-10*::OE9TKH-8 :/mylast{7bb\xc0\xc0\x00\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90u\x03\xf0}SOTA>APZS16,TCPIP,OE9TKH-10*::OE9TKH-8 :/last{7bc\xc0\xc0\x00\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90u\x03\xf0}SOTA>APZS16,TCPIP,OE9TKH-10*::OE9TKH-8 :/time(/zone){7bd\xc0" + sp = SerialParser(newframe) + sp.parse(two_example_frames) diff --git a/LoraAprsKissTnc.py b/LoraAprsKissTnc.py new file mode 100644 index 0000000..ae070f9 --- /dev/null +++ b/LoraAprsKissTnc.py @@ -0,0 +1,140 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# This program 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. +# +# This program 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 this program. If not, see . + +import sys +from asyncio import QueueEmpty +import traceback +sys.path.insert(0, './pySX127x/') +from pySX127x.SX127x.LoRa import LoRa +from pySX127x.SX127x.constants import * +from pySX127x.SX127x.board_config import BOARD +import time +#import KissHelper + + +class LoraAprsKissTnc(LoRa): + LORA_APRS_HEADER = b"<\xff\x01" + + # APRS data types + DATA_TYPES_POSITION = b"!'/@`" + DATA_TYPE_MESSAGE = b":" + DATA_TYPE_THIRD_PARTY = b"}" + + queue = None + server = None + + # init has LoRa APRS default config settings - might be initialized different when creating object with parameters + def __init__(self, queue, server, frequency=433.775, preamble=8, spreadingFactor=12, bandwidth=BW.BW125, + codingrate=CODING_RATE.CR4_5, appendSignalReport = True, paSelect = 1, outputPower = 15, verbose=False): + # Init SX127x + BOARD.setup() + + super(LoraAprsKissTnc, self).__init__(verbose) + self.queue = queue + self.appendSignalReport = appendSignalReport + + self.set_mode(MODE.SLEEP) + + self.set_freq(frequency) + self.set_preamble(preamble) + self.set_spreading_factor(spreadingFactor) + self.set_bw(bandwidth) + self.set_low_data_rate_optim(True) + self.set_coding_rate(codingrate) + self.set_ocp_trim(100) + + self.set_pa_config(paSelect, outputPower) + self.set_max_payload_length(255) + self.set_dio_mapping([0] * 6) + self.server = server + + self.reset_ptr_rx() + self.set_mode(MODE.RXCONT) + + def startListening(self): + try: + while True: + # only transmit if no signal is detected to avoid collisions + if not self.get_modem_status()["signal_detected"]: + # print("RSSI: %idBm" % lora.get_rssi_value()) + # FIXME: Add noise floor measurement for telemetry + if not self.queue.empty(): + try: + data = self.queue.get(block=False) + if self.aprs_data_type(data) == self.DATA_TYPE_THIRD_PARTY: + # remove third party thing + data = data[data.find(self.DATA_TYPE_THIRD_PARTY) + 1:] + data = self.LORA_APRS_HEADER + data + print("LoRa TX: " + repr(data)) + self.transmit(data) + except QueueEmpty: + pass + + time.sleep(0.50) + except KeyboardInterrupt: + BOARD.teardown() + + def on_rx_done(self): + payload = self.read_payload(nocheck=True) + if not payload: + print("No Payload!") + return + rssi = self.get_pkt_rssi_value() + snr = self.get_pkt_snr_value() + data = bytes(payload) + print("LoRa RX[%idBm/%idB, %ibytes]: %s" %(rssi, snr, len(data), repr(data))) + + flags = self.get_irq_flags() + if any([flags[s] for s in ['crc_error', 'rx_timeout']]): + print("Receive Error, discarding frame.") + # print(self.get_irq_flags()) + self.clear_irq_flags(RxDone=1, PayloadCrcError=1, RxTimeout=1) # clear rxdone IRQ flag + self.reset_ptr_rx() + self.set_mode(MODE.RXCONT) + return + + if self.server: + # remove LoRa-APRS header if present + if data[0:len(self.LORA_APRS_HEADER)] == self.LORA_APRS_HEADER: + data = data[len(self.LORA_APRS_HEADER):] + if self.appendSignalReport: + # Signal report only for certain frames, not messages! + if self.aprs_data_type(data) in self.DATA_TYPES_POSITION: + data += b" RSSI=%idBm SNR=%idB" % (rssi, snr) + self.server.send(data, {"level":rssi, "snr":snr}) + self.clear_irq_flags(RxDone=1) # clear rxdone IRQ flag + self.reset_ptr_rx() + self.set_mode(MODE.RXCONT) + + # self.set_mode(MODE.CAD) + + def on_tx_done(self): + print("TX DONE") + self.clear_irq_flags(TxDone=1) # clear txdone IRQ flag + self.set_dio_mapping([0] * 6) + self.set_mode(MODE.RXCONT) + + def transmit(self, data): + self.write_payload([c for c in data]) + self.set_dio_mapping([1, 0, 0, 0, 0, 0]) + self.set_mode(MODE.TX) + + def aprs_data_type(self, lora_aprs_frame): + delimiter_position = lora_aprs_frame.find(b":") + try: + return lora_aprs_frame[delimiter_position + 1] + except IndexError: + return "" \ No newline at end of file diff --git a/README.md b/README.md index 9a2d237..f0691bc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,32 @@ -# RPi-LoRa-KISS-TNC +# Raspberry Pi LoRa KISS TNC -Custom version of https://github.com/tomelec/RPi-LoRa-KISS-TNC \ No newline at end of file +This project emulates a KISS TNC and controls a hardware LoRa transceiver +connected to the Raspberry´s SPI. That makes it possible to use existing +software, like digipeaters, with LoRa radio communication. The KISS TNC is +accessed via its TCP server. + +The current application is to run the KISS TNC together with the APRS digi [APRX](https://github.com/PhirePhly/aprx), which connects via TCP and provides +powerful APRS digipeating and I-gate functionality for LoRa-APRS. + +## Hardware + +The LoRa KISS TNC runs on Raspberry Pi 2 or newer together with the +*LoRa APRS Gateway Hat V2.0*. However, RFM98W oder RFM96W LoRa modules can +be wired up to the Raspberry Pi directly aswell. See the +[schematic](doc/LoRaAPRS-GW-RPI_V20_Schematic.pdf) for details. + +![Gateway on RPi](doc/images/LoRa-APRS_Gateway_V20.jpg) + +## Development + +This program and documentation is in a very early state and very experimental. +Only the LoRa radio modem of the gateway board is supported at the moment. +Display and buttons are not working. + +### To Do +* Python 3 compatibility +* Get display and buttons working +* Noise floor telemetry +* Installation guide and documentation +* Proper configuration file +* ... diff --git a/Start_lora-tnc.py b/Start_lora-tnc.py new file mode 100755 index 0000000..1979e31 --- /dev/null +++ b/Start_lora-tnc.py @@ -0,0 +1,41 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# This program 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. +# +# This program 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 this program. If not, see . +# +# Usage: python3 Start_lora-tnc.py +# +from queue import Queue +from TCPServer import KissServer +from AXUDPServer import AXUDPServer +import config +from LoraAprsKissTnc import LoraAprsKissTnc + +# TX KISS frames go here (Digipeater -> TNC) +kissQueue = Queue() + +# KISSTCP or AXUDP Server for the digipeater to connect +if config.USE_AXUDP: + server = AXUDPServer(kissQueue, config.AXUDP_LOCAL_IP, config.AXUDP_LOCAL_PORT, config.AXUDP_REMOTE_IP, config.AXUDP_REMOTE_PORT) +else: + server = KissServer(kissQueue, config.TCP_HOST, config.TCP_PORT) + +server.setDaemon(True) +server.start() + +# LoRa transceiver instance +lora = LoraAprsKissTnc(kissQueue, server, verbose=False, appendSignalReport = config.APPEND_SIGNAL_REPORT) + +# this call loops forever inside +lora.startListening() diff --git a/TCPServer.py b/TCPServer.py new file mode 100644 index 0000000..84e7eac --- /dev/null +++ b/TCPServer.py @@ -0,0 +1,94 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import print_function + +RECV_BUFFER_LENGTH = 1024 +import sys +from threading import Thread +import socket +from KissHelper import SerialParser +import KissHelper + + +def logf(message): + print(message, file=sys.stderr) + + +class KissServer(Thread): + '''TCP Server to be connected by the APRS digipeater''' + + txQueue = None + + # host and port as configured in aprx/aprx.conf.lora-aprs < interface > section + def __init__(self, txQueue, host="127.0.0.1", port=10001): + Thread.__init__(self) + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.bind((host, port)) + self.socket.listen(1) + self.data = str() + self.txQueue = txQueue + self.connection = None + + def run(self): + parser = SerialParser(self.queue_frame) + while True: + self.connection = None + self.connection, client_address = self.socket.accept() + parser.reset() + logf("KISS-Server: Connection from %s" % client_address[0]) + while True: + data = self.connection.recv(RECV_BUFFER_LENGTH) + if data: + parser.parse(data) + else: + self.connection.close() + break + + def queue_frame(self, frame): + print("KISS frame:", repr(frame)) + decoded_data = KissHelper.decode_kiss(frame) + print("Decoded:", decoded_data) + + self.txQueue.put(decoded_data, block=False) + + def __del__(self): + self.socket.shutdown() + + def send(self, data, metadata): + try: + encoded_data = KissHelper.encode_kiss(data) + except Exception as e: + print("KISS encoding went wrong (exception while parsing)") + traceback.print_tb(e.__traceback__) + encoded_data = None + + if encoded_data != None: + print("To Server: " + repr(encoded_data)) + if self.connection: + self.connection.sendall(encoded_data) + else: + print("KISS encoding went wrong") + + + +if __name__ == '__main__': + '''Test program''' + import time + from multiprocessing import Queue + + TCP_HOST = "0.0.0.0" + TCP_PORT = 10001 + + # frames to be sent go here + KissQueue = Queue() + + server = KissServer(TCP_HOST, TCP_PORT, KissQueue) + server.setDaemon(True) + server.start() + + while True: + server.send( + "\xc0\x00\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90q\x03\xf0!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V\xc0") + data = KissQueue.get() + print("Received KISS frame:" + repr(data)) diff --git a/aprx/aprx.conf.lora-aprs b/aprx/aprx.conf.lora-aprs new file mode 100644 index 0000000..80ba247 --- /dev/null +++ b/aprx/aprx.conf.lora-aprs @@ -0,0 +1,440 @@ +# +# Simple sample configuration file for the APRX-2 -- an APRS iGate and Digipeater +# +# This configuration is structured with Apache HTTPD style tags +# which then contain subsystem parameters. +# + +# +# For simple case, you need to adjust 4 things: +# - Mycall parameter +# - passcode parameter in APRS-IS configuration +# - Select correct type of interface (ax25-device or serial-device) +# - Optionally set a beacon telling where this system is +# - Optionally enable digipeater with or without tx-igate +# + +# +# +# Define the parameters in following order: +# 1) ** zero or one +# 2) ** zero or one +# 3) ** there can be multiple! +# 4) ** zero to many +# 5) ** zero to many +# 6) ** zero to many (at most one for each Tx) +# + +# +# Global macro for simplified callsign definition: +# Usable for 99+% of cases. +# + +mycall N0CALL-1 + +# +# Global macro for simplified "my location" definition in +# place of explicit "lat nn lon mm" at beacons. Will also +# give "my location" reference for "filter m/100". +# +#myloc lat ddmm.mmN lon dddmm.mmE + + +# The aprsis login parameter: +# Station callsignSSID used for relaying APRS frames into APRS-IS. +# Use this only to define other callsign for APRS\-IS login. +# +#login OTHERCALL-7 # login defaults to $mycall + +# +# Passcode for your callsign: +# Unique code for your callsign to allow transmitting packets +# into the APRS-IS. +# +passcode -1 + +# APRS-IS server name and optional portnumber. +# +# WARNING: Do not change from default port number [14580] +# unless you are absolutely certain you want +# something else, and you allow that something +# else also affect your tx-igate behaviour! +# +server rotate.aprs2.net +#server euro.aprs2.net +#server asia.aprs2.net +#server noam.aprs2.net +#server soam.aprs2.net +#server aunz.aprs2.net + +# Some APRS-IS servers tell every about 20 seconds to all contact +# ports that they are there and alive. Others are just silent. +# Default value is 3*"heartbeat" + some --> 120 (seconds) +# +#heartbeat-timeout 0 # Disabler of heartbeat timeout + +# APRS-IS server may support some filter commands. +# See: http://www.aprs-is.net/javAPRSFilter.aspx +# +# You can define the filter as single long quoted string, or as +# many short segments with explaining comments following them. +# +# Usability of these filters for a Tx-iGate is dubious, but +# they exist in case you for example want to Tx-iGate packets +# from some source callsigns in all cases even when they are +# not in your local area. +# +#filter "possibly multiple filter specs in quotes" +# +#filter "m/100" # My-Range filter: positions within 100 km from my location +#filter "f/OH2XYZ-3/50" # Friend-Range filter: 50 km of friend's last beacon position + + + + +# pidfile is UNIX way to tell that others that this program is +# running with given process-id number. This has compiled-in +# default value of: pidfile /var/run/aprx.pid +# +pidfile /var/run/aprx.pid + + +# rflog defines a rotatable file into which all RF-received packets +# are logged. The host system can rotate it at any time without +# need to signal the aprx that the file has been moved. +# +rflog /var/log/aprx/aprx-rf.log + +# aprxlog defines a rotatable file into which most important +# events on APRS-IS connection are logged, namely connects and +# disconnects. The host system can rotate it at any time without +# need to signal the aprx that the file has been moved. +# +aprxlog /var/log/aprx/aprx.log + +# dprslog defines a rotatable file into which most important +# events on DPRS receiver gateways are logged. +# The host system can rotate it at any time without need to +# signal the aprx that the file has been moved. +# +#dprslog /var/log/aprx/dprs.log + +# erlangfile defines a mmap():able binary file, which stores +# running sums of interfaces upon which the channel erlang +# estimator runs, and collects data. +# Depending on the system, it may be running on a filesystem +# that actually retains data over reboots, or it may not. +# With this backing store, the system does not loose cumulating +# erlang data over the current period, if the restart is quick, +# and does not stradle any exact minute. +# (Do restarts at 15 seconds over an even minute..) +# This file is around 0.7 MB per each interface talking APRS. +# If this file is not defined and it can not be created, +# internal non-persistent in-memory storage will be used. +# +# Built-in default value is: /var/run/aprx.state +# +#erlangfile /var/run/aprx.state + + + + +# *********** Multiple definitions can follow ********* + +# ax25-device Lists AX.25 ports by their callsigns that in Linux +# systems receive APRS packets. If none are defined, +# or the system is not Linux, the AX.25 network receiver +# is not enabled. Used technologies need at least +# Linux kernel 2.4.x +# +# tx-ok Boolean telling if this device is able to transmit. +# + +# +# ax25-device $mycall +# #tx-ok false # transmitter enable defaults to false +# #telem-to-is true # set to 'false' to disable +# + + +# +# The TNC serial options. Parameters are: +# - /dev/ttyUSB1 -- tty device +# - 19200 -- baud rate, supported ones are: +# 1200, 2400, 4800, 9600, 19200, 38400 +# - 8n1 -- 8-bits, no parity, one stop-bit, +# no other supported modes +# - "KISS" - plain basic KISS mode +# - "XORSUM" alias "BPQCRC" - KISS with BPQ "CRC" byte +# - "SMACK" alias "CRC16" - KISS with real CRC +# - "FLEXNET" - KISS with real CRC +# - "TNC2" - TNC2 monitor format +# - "DPRS" - DPRS (RX) GW +# + +# +# serial-device /dev/ttyUSB0 19200 8n1 KISS +# #callsign $mycall # callsign defaults to $mycall +# #tx-ok false # transmitter enable defaults to false +# #telem-to-is true # set to 'false' to disable +# + +# +# serial-device /dev/ttyUSB1 19200 8n1 TNC2 +# #callsign $mycall # callsign defaults to $mycall +# #tx-ok false # TNC2 monitor can not have transmitter +# #telem-to-is true # set to 'false' to disable +# + +# +# serial-device /dev/ttyUSB1 19200 8n1 DPRS +# callsign dprsgwcallsign # must define actual callsign +# #tx-ok false # DPRS monitor can not do transmit +# #telem-to-is true # set to 'false' to disable +# + +# +# tcp-device behaves identically to local serial port, but allows +# access to remote TCP/IP sockets. A common application is remote +# KISS modems connected to Ethernet-to-serial adapters from suppliers +# such as Lantronix. +# It's important that this remote socket is a raw TCP socket and not +# handle any byte codes as command escapes. +# +# tcp-device hostname portnumber mode +# - hostname may be a domain name, IPv4 address, or a IPv6 address +# - portnumber is any valid TCP port (1-65535) +# - mode is the same as serial-device (KISS, TNC2, etc.) +# + +# +# tcp-device 192.0.2.10 10001 KISS +# #callsign $mycall # callsign defaults to $mycall +# #tx-ok false # transmitter enable defaults to false +# #telem-to-is true # set to 'false' to disable +# + + + tcp-device 127.0.0.1 10001 KISS + callsign $mycall + tx-ok true + telem-to-is true # set to 'false' to disable + + + +# *********** Multiple definitions can follow ********* + +# +# Beacons are sent out to radio transmitters AND/OR APRSIS. +# Default is "both", other modes are settable. +# +#beaconmode { aprsis | both | radio } +# +# Beacons are sent from a circullar transmission queue, total cycle time +# of that queue is 20 minutes by default, and beacons are "evenly" +# distributed along it. Actual intervals are randomized to be anything +# in between 80% and 100% of the cycle-size / number-of-beacons. +# First beacon is sent out 30 seconds after system start. +# Tune the cycle-size to be suitable to your number of defined beacons. +# +#cycle-size 20m +# +# Basic beaconed thing is positional message of type "!": +# +#beacon symbol "R&" lat "0000.00N" lon "00000.00E" comment "Rx-only iGate" +#beacon symbol "R&" $myloc comment "Rx-only iGate" +# +#Following are basic options: +# 'symbol' no default, must be defined! +# 'lat' coordinate latitude: ddmm.mmN (no default!) +# 'lon' coordinate longitude: dddmm.mmE (no default!) +# '$myloc' coordinate values taken from global 'myloc' entry, +# and usable in place of explicit 'lat'+'lon'. +# 'comment' optional tail part of the item, default is nothing +# +# Sample symbols: +# R& is for "Rx-only iGate" +# I& is for "Tx-iGate" +# /# is for "Digipeater" +# I# is for "Tx-iGate + Digipeater"" +# +#Additional options are: +# 'srccall' parameter sets claimed origination address. +# 'dstcall' sets destination address, default "APRXnn" +# 'interface' parameter picks an interface (must be "tx-ok true" type) +# 'via' sets radio distribution pattern, default: none. +# 'timefix' On APRS messages with HMS timestamp (hour:min:sec), the +# system fixes appropriate field with transmit time timestamp. +# +# Message type is by default '!', which is positional no timestamp format. +# Other possible formats are definable with options: +# 'type' Single character setting type: ! = / @, default: ! +# 'item' Defines a name of Item (')') type beacons. +# 'object' Defines a name of Object (';') type beacons. +# +# 'file' option tells a file at which a _raw_ APRS message content is +# expected to be found as first line of text. Line ending newline +# is removed, and no escapes are supported. The timefix is +# available, though probably should not be used. +# No \-processing is done on read text line. +# +# 'exec' option tells a computer program which returns to stdout _raw_ APRS +# message content without newline. The timefix is +# available, though probably should not be used. +# No \-processing is done on read text line. +# +# The parameter sets can vary: +# a) 'srccall nnn-n dstcall "string" symbol "R&" lat "ddmm.mmN" lon "dddmm.mmE" [comment "any text"] +# b) 'srccall nnn-n dstcall "string" symbol "R&" $myloc [comment "any text"] +# c) 'srccall nnn-n dstcall "string" raw "string"' +# +# The a) form flags on some of possible syntax errors in parameters. +# It will also create only "!" type messages. The dest parameter +# defaults to "APRS", but can be used to give other destinations. +# The via parameter can be used to add other keywords, like "NOGATE". +# +# Writing correct RAW format beacon message is very hard, +# which is evidenced by the frequency of bad syntax texts +# people so often put there... If you can not be persuaded +# not to do it, then at least VERIFY the beacon result on +# web service like findu.com, or aprs.fi +# +# Do remember that the \ -character has special treatment in the +# Aprx configuration parser. If you want a '\' on APRS content, +# then you encode it on configuration file as: '\\' +# +# Stranger combinations with explicite "transmit this to interface X": +# +#beacon file /tmp/wxbeacon.txt +#beacon interface N0CALL-3 srccall N0CALL-3 \ +# raw "!0000.00NR00000.00E&Rx-only iGate" +#beacon interface N0CALL-3 srccall N0CALL-3 \ +# raw "!0000.00NI00000.00E&Tx-iGate" +#beacon interface $mycall symbol "R&" $myloc \ +# comment "Rx-only iGate" +#beacon interface $mycall symbol "I&" $myloc \ +# comment "Tx-iGate" +#beacon exec /usr/bin/telemetry.pl +#beacon timeout 20 exec /usr/bin/telemetry.pl +#beacon interface N0CALL-3 srccall N0CALL-3 \ +# timeout 20 exec /usr/bin/telemetry.pl +# + beaconmode aprsis + cycle-size 55m + beacon symbol "R&" $myloc comment "LoRa-APRS 433.775MHz/125kHz/SF12" + + + +# *********** definition(s) follow ********* +# +# The system will always send telemetry for all of its interfaces +# to APRSIS, but there is an option to define telemetry to be sent +# to radio channel by using following sections for each transmitter +# that is wanted to send out the telemetry. +# +# transmitter - callsign referring to +# via - optional via-path, only 1 callsign! +# source - one or more of callsigns for which +# the telemetry transmission is wanted for +# +# +# transmitter $mycall +# via TRACE1-1 +# source $mycall +# + +# *********** definition(s) follow ********* +# +# The digipeater definitions tell transmitters that receive +# AX.25 packets from possibly multiple sources, and then what +# to do on the AX.25 headers of those messages. +# +# There is one transmitter per digipeater -- and inversely, there +# can be at most one digipeater for each transmitter. +# +# In each digipeater there is at least one , usually same +# as the transmitter. You may use same on multiple +# s. Using multiple instances of same on +# a single does not crash the system, but it can cause +# packet duplication in case of non-APRS protocols (like AX.25 CONS) +# +# Use only at most two levels of viscous-delay in your . +# Immediate sending is by "0", and a delayed sending is any value +# from 1 to 9. This system does not correctly support other than +# immediate sending and one level of delay. +# +# Note: In order to igate correct when multiple receivers and +# transmitters are used on single channel, the +# definitions of each radio port must have associated +# "igate-group N" parameter which has N of value 1 to 3. +# See the aprx-manual.pdf for details. +# (Default software compilation allows you to have up to +# three channels of APRS operation.) +# +# +# transmitter $mycall +# #ratelimit 60 120 # default: average 60 packets/minute, +# # # burst max 120 packets/minute +# #srcratelimit 10 20 # Example: by sourcecall: +# # average 10 packets/minute, +# # burst max 20 packets/minute +# +# +# source $mycall +# # #relay-type digipeated # default mode is "digipeated" +# # viscous-delay 0 # no viscous delay for RF->RF digipeating +# # ratelimit 60 120 # default: average 60 packets/minute, +# # # burst max 120 packets/minute +# ## filter a/la/lo/la/lo # service area filter +# ## filter -b/CALL # always block these +# +# +# # Diversity receiver which combines to the primary +# # Tx/Rx transmitter. There can be as many of these +# # as you can connect on this machine. +# # +# # source RXPORT-1 +# # #relay-type digipeated # default mode is "digipeated" +# # viscous-delay 0 # no viscous delay for RF->RF digipeating +# # ratelimit 60 120 # default: average 60 packets/minute, +# # # burst max 120 packets/minute +# ## filter a/la/lo/la/lo # service area filter +# ## filter -b/CALL # always block these +# +# +# # # APRSIS source adds a TX-IGATE behaviour +# # source APRSIS +# # relay-type third-party # Must define this for APRSIS source! +# # viscous-delay 5 # Recommendation: 5 seconds delay to give +# # # RF delivery time make itself known. +# # ratelimit 60 120 # default: average 60 packets/minute, +# # # burst max 120 packets/minute +# ## filter a/la/lo/la/lo # service area filter +# ## filter -b/CALL # always block these +# # +# +# # # DPRS source adds a DPRS->APRS RF gate +# # interface DPRS +# # ratelimit 60 120 # default: average 60 packets/minute, +# # # burst max 120 packets/minute +# # relay-type third-party # Must define this for DPRS source! +# # +# + + + transmitter $mycall + + source $mycall + relay-type digipeated # default mode is "digipeated" + viscous-delay 5 # no viscous delay for RF->RF digipeating + ratelimit 10 20 + filter t/m + + + # APRSIS source adds a TX-IGATE behaviour + source APRSIS + relay-type third-party # Must define this for APRSIS source! + ratelimit 4 30 + filter t/m + + diff --git a/aprx/aprx.conf.original b/aprx/aprx.conf.original new file mode 100644 index 0000000..28561b5 --- /dev/null +++ b/aprx/aprx.conf.original @@ -0,0 +1,411 @@ +# +# Simple sample configuration file for the APRX-2 -- an APRS iGate and Digipeater +# +# This configuration is structured with Apache HTTPD style tags +# which then contain subsystem parameters. +# + +# +# For simple case, you need to adjust 4 things: +# - Mycall parameter +# - passcode parameter in APRS-IS configuration +# - Select correct type of interface (ax25-device or serial-device) +# - Optionally set a beacon telling where this system is +# - Optionally enable digipeater with or without tx-igate +# + +# +# +# Define the parameters in following order: +# 1) ** zero or one +# 2) ** zero or one +# 3) ** there can be multiple! +# 4) ** zero to many +# 5) ** zero to many +# 6) ** zero to many (at most one for each Tx) +# + +# +# Global macro for simplified callsign definition: +# Usable for 99+% of cases. +# + +mycall N0CALL-1 + +# +# Global macro for simplified "my location" definition in +# place of explicit "lat nn lon mm" at beacons. Will also +# give "my location" reference for "filter m/100". +# +#myloc lat ddmm.mmN lon dddmm.mmE + + +# The aprsis login parameter: +# Station callsignSSID used for relaying APRS frames into APRS-IS. +# Use this only to define other callsign for APRS\-IS login. +# +#login OTHERCALL-7 # login defaults to $mycall + +# +# Passcode for your callsign: +# Unique code for your callsign to allow transmitting packets +# into the APRS-IS. +# +passcode -1 + +# APRS-IS server name and optional portnumber. +# +# WARNING: Do not change from default port number [14580] +# unless you are absolutely certain you want +# something else, and you allow that something +# else also affect your tx-igate behaviour! +# +server rotate.aprs2.net +#server euro.aprs2.net +#server asia.aprs2.net +#server noam.aprs2.net +#server soam.aprs2.net +#server aunz.aprs2.net + +# Some APRS-IS servers tell every about 20 seconds to all contact +# ports that they are there and alive. Others are just silent. +# Default value is 3*"heartbeat" + some --> 120 (seconds) +# +#heartbeat-timeout 0 # Disabler of heartbeat timeout + +# APRS-IS server may support some filter commands. +# See: http://www.aprs-is.net/javAPRSFilter.aspx +# +# You can define the filter as single long quoted string, or as +# many short segments with explaining comments following them. +# +# Usability of these filters for a Tx-iGate is dubious, but +# they exist in case you for example want to Tx-iGate packets +# from some source callsigns in all cases even when they are +# not in your local area. +# +#filter "possibly multiple filter specs in quotes" +# +#filter "m/100" # My-Range filter: positions within 100 km from my location +#filter "f/OH2XYZ-3/50" # Friend-Range filter: 50 km of friend's last beacon position + + + + +# pidfile is UNIX way to tell that others that this program is +# running with given process-id number. This has compiled-in +# default value of: pidfile /var/run/aprx.pid +# +pidfile /var/run/aprx.pid + + +# rflog defines a rotatable file into which all RF-received packets +# are logged. The host system can rotate it at any time without +# need to signal the aprx that the file has been moved. +# +rflog /var/log/aprx/aprx-rf.log + +# aprxlog defines a rotatable file into which most important +# events on APRS-IS connection are logged, namely connects and +# disconnects. The host system can rotate it at any time without +# need to signal the aprx that the file has been moved. +# +aprxlog /var/log/aprx/aprx.log + +# dprslog defines a rotatable file into which most important +# events on DPRS receiver gateways are logged. +# The host system can rotate it at any time without need to +# signal the aprx that the file has been moved. +# +#dprslog /var/log/aprx/dprs.log + +# erlangfile defines a mmap():able binary file, which stores +# running sums of interfaces upon which the channel erlang +# estimator runs, and collects data. +# Depending on the system, it may be running on a filesystem +# that actually retains data over reboots, or it may not. +# With this backing store, the system does not loose cumulating +# erlang data over the current period, if the restart is quick, +# and does not stradle any exact minute. +# (Do restarts at 15 seconds over an even minute..) +# This file is around 0.7 MB per each interface talking APRS. +# If this file is not defined and it can not be created, +# internal non-persistent in-memory storage will be used. +# +# Built-in default value is: /var/run/aprx.state +# +#erlangfile /var/run/aprx.state + + + + +# *********** Multiple definitions can follow ********* + +# ax25-device Lists AX.25 ports by their callsigns that in Linux +# systems receive APRS packets. If none are defined, +# or the system is not Linux, the AX.25 network receiver +# is not enabled. Used technologies need at least +# Linux kernel 2.4.x +# +# tx-ok Boolean telling if this device is able to transmit. +# + +# +# ax25-device $mycall +# #tx-ok false # transmitter enable defaults to false +# #telem-to-is true # set to 'false' to disable +# + + +# +# The TNC serial options. Parameters are: +# - /dev/ttyUSB1 -- tty device +# - 19200 -- baud rate, supported ones are: +# 1200, 2400, 4800, 9600, 19200, 38400 +# - 8n1 -- 8-bits, no parity, one stop-bit, +# no other supported modes +# - "KISS" - plain basic KISS mode +# - "XORSUM" alias "BPQCRC" - KISS with BPQ "CRC" byte +# - "SMACK" alias "CRC16" - KISS with real CRC +# - "FLEXNET" - KISS with real CRC +# - "TNC2" - TNC2 monitor format +# - "DPRS" - DPRS (RX) GW +# + +# +# serial-device /dev/ttyUSB0 19200 8n1 KISS +# #callsign $mycall # callsign defaults to $mycall +# #tx-ok false # transmitter enable defaults to false +# #telem-to-is true # set to 'false' to disable +# + +# +# serial-device /dev/ttyUSB1 19200 8n1 TNC2 +# #callsign $mycall # callsign defaults to $mycall +# #tx-ok false # TNC2 monitor can not have transmitter +# #telem-to-is true # set to 'false' to disable +# + +# +# serial-device /dev/ttyUSB1 19200 8n1 DPRS +# callsign dprsgwcallsign # must define actual callsign +# #tx-ok false # DPRS monitor can not do transmit +# #telem-to-is true # set to 'false' to disable +# + +# +# tcp-device behaves identically to local serial port, but allows +# access to remote TCP/IP sockets. A common application is remote +# KISS modems connected to Ethernet-to-serial adapters from suppliers +# such as Lantronix. +# It's important that this remote socket is a raw TCP socket and not +# handle any byte codes as command escapes. +# +# tcp-device hostname portnumber mode +# - hostname may be a domain name, IPv4 address, or a IPv6 address +# - portnumber is any valid TCP port (1-65535) +# - mode is the same as serial-device (KISS, TNC2, etc.) +# + +# +# tcp-device 192.0.2.10 10001 KISS +# #callsign $mycall # callsign defaults to $mycall +# #tx-ok false # transmitter enable defaults to false +# #telem-to-is true # set to 'false' to disable +# + + +# *********** Multiple definitions can follow ********* + +# +# Beacons are sent out to radio transmitters AND/OR APRSIS. +# Default is "both", other modes are settable. +# +#beaconmode { aprsis | both | radio } +# +# Beacons are sent from a circullar transmission queue, total cycle time +# of that queue is 20 minutes by default, and beacons are "evenly" +# distributed along it. Actual intervals are randomized to be anything +# in between 80% and 100% of the cycle-size / number-of-beacons. +# First beacon is sent out 30 seconds after system start. +# Tune the cycle-size to be suitable to your number of defined beacons. +# +#cycle-size 20m +# +# Basic beaconed thing is positional message of type "!": +# +#beacon symbol "R&" lat "0000.00N" lon "00000.00E" comment "Rx-only iGate" +#beacon symbol "R&" $myloc comment "Rx-only iGate" +# +#Following are basic options: +# 'symbol' no default, must be defined! +# 'lat' coordinate latitude: ddmm.mmN (no default!) +# 'lon' coordinate longitude: dddmm.mmE (no default!) +# '$myloc' coordinate values taken from global 'myloc' entry, +# and usable in place of explicit 'lat'+'lon'. +# 'comment' optional tail part of the item, default is nothing +# +# Sample symbols: +# R& is for "Rx-only iGate" +# I& is for "Tx-iGate" +# /# is for "Digipeater" +# I# is for "Tx-iGate + Digipeater"" +# +#Additional options are: +# 'srccall' parameter sets claimed origination address. +# 'dstcall' sets destination address, default "APRXnn" +# 'interface' parameter picks an interface (must be "tx-ok true" type) +# 'via' sets radio distribution pattern, default: none. +# 'timefix' On APRS messages with HMS timestamp (hour:min:sec), the +# system fixes appropriate field with transmit time timestamp. +# +# Message type is by default '!', which is positional no timestamp format. +# Other possible formats are definable with options: +# 'type' Single character setting type: ! = / @, default: ! +# 'item' Defines a name of Item (')') type beacons. +# 'object' Defines a name of Object (';') type beacons. +# +# 'file' option tells a file at which a _raw_ APRS message content is +# expected to be found as first line of text. Line ending newline +# is removed, and no escapes are supported. The timefix is +# available, though probably should not be used. +# No \-processing is done on read text line. +# +# 'exec' option tells a computer program which returns to stdout _raw_ APRS +# message content without newline. The timefix is +# available, though probably should not be used. +# No \-processing is done on read text line. +# +# The parameter sets can vary: +# a) 'srccall nnn-n dstcall "string" symbol "R&" lat "ddmm.mmN" lon "dddmm.mmE" [comment "any text"] +# b) 'srccall nnn-n dstcall "string" symbol "R&" $myloc [comment "any text"] +# c) 'srccall nnn-n dstcall "string" raw "string"' +# +# The a) form flags on some of possible syntax errors in parameters. +# It will also create only "!" type messages. The dest parameter +# defaults to "APRS", but can be used to give other destinations. +# The via parameter can be used to add other keywords, like "NOGATE". +# +# Writing correct RAW format beacon message is very hard, +# which is evidenced by the frequency of bad syntax texts +# people so often put there... If you can not be persuaded +# not to do it, then at least VERIFY the beacon result on +# web service like findu.com, or aprs.fi +# +# Do remember that the \ -character has special treatment in the +# Aprx configuration parser. If you want a '\' on APRS content, +# then you encode it on configuration file as: '\\' +# +# Stranger combinations with explicite "transmit this to interface X": +# +#beacon file /tmp/wxbeacon.txt +#beacon interface N0CALL-3 srccall N0CALL-3 \ +# raw "!0000.00NR00000.00E&Rx-only iGate" +#beacon interface N0CALL-3 srccall N0CALL-3 \ +# raw "!0000.00NI00000.00E&Tx-iGate" +#beacon interface $mycall symbol "R&" $myloc \ +# comment "Rx-only iGate" +#beacon interface $mycall symbol "I&" $myloc \ +# comment "Tx-iGate" +#beacon exec /usr/bin/telemetry.pl +#beacon timeout 20 exec /usr/bin/telemetry.pl +#beacon interface N0CALL-3 srccall N0CALL-3 \ +# timeout 20 exec /usr/bin/telemetry.pl +# + + +# *********** definition(s) follow ********* +# +# The system will always send telemetry for all of its interfaces +# to APRSIS, but there is an option to define telemetry to be sent +# to radio channel by using following sections for each transmitter +# that is wanted to send out the telemetry. +# +# transmitter - callsign referring to +# via - optional via-path, only 1 callsign! +# source - one or more of callsigns for which +# the telemetry transmission is wanted for +# +# +# transmitter $mycall +# via TRACE1-1 +# source $mycall +# + +# *********** definition(s) follow ********* +# +# The digipeater definitions tell transmitters that receive +# AX.25 packets from possibly multiple sources, and then what +# to do on the AX.25 headers of those messages. +# +# There is one transmitter per digipeater -- and inversely, there +# can be at most one digipeater for each transmitter. +# +# In each digipeater there is at least one , usually same +# as the transmitter. You may use same on multiple +# s. Using multiple instances of same on +# a single does not crash the system, but it can cause +# packet duplication in case of non-APRS protocols (like AX.25 CONS) +# +# Use only at most two levels of viscous-delay in your . +# Immediate sending is by "0", and a delayed sending is any value +# from 1 to 9. This system does not correctly support other than +# immediate sending and one level of delay. +# +# Note: In order to igate correct when multiple receivers and +# transmitters are used on single channel, the +# definitions of each radio port must have associated +# "igate-group N" parameter which has N of value 1 to 3. +# See the aprx-manual.pdf for details. +# (Default software compilation allows you to have up to +# three channels of APRS operation.) +# +# +# transmitter $mycall +# #ratelimit 60 120 # default: average 60 packets/minute, +# # # burst max 120 packets/minute +# #srcratelimit 10 20 # Example: by sourcecall: +# # average 10 packets/minute, +# # burst max 20 packets/minute +# +# +# source $mycall +# # #relay-type digipeated # default mode is "digipeated" +# # viscous-delay 0 # no viscous delay for RF->RF digipeating +# # ratelimit 60 120 # default: average 60 packets/minute, +# # # burst max 120 packets/minute +# ## filter a/la/lo/la/lo # service area filter +# ## filter -b/CALL # always block these +# +# +# # Diversity receiver which combines to the primary +# # Tx/Rx transmitter. There can be as many of these +# # as you can connect on this machine. +# # +# # source RXPORT-1 +# # #relay-type digipeated # default mode is "digipeated" +# # viscous-delay 0 # no viscous delay for RF->RF digipeating +# # ratelimit 60 120 # default: average 60 packets/minute, +# # # burst max 120 packets/minute +# ## filter a/la/lo/la/lo # service area filter +# ## filter -b/CALL # always block these +# +# +# # # APRSIS source adds a TX-IGATE behaviour +# # source APRSIS +# # relay-type third-party # Must define this for APRSIS source! +# # viscous-delay 5 # Recommendation: 5 seconds delay to give +# # # RF delivery time make itself known. +# # ratelimit 60 120 # default: average 60 packets/minute, +# # # burst max 120 packets/minute +# ## filter a/la/lo/la/lo # service area filter +# ## filter -b/CALL # always block these +# # +# +# # # DPRS source adds a DPRS->APRS RF gate +# # interface DPRS +# # ratelimit 60 120 # default: average 60 packets/minute, +# # # burst max 120 packets/minute +# # relay-type third-party # Must define this for DPRS source! +# # +# diff --git a/config.py b/config.py new file mode 100644 index 0000000..45e877a --- /dev/null +++ b/config.py @@ -0,0 +1,24 @@ +## KISS Settings +# Where to listen? +# TCP_HOST can be "localhost", "0.0.0.0" or a specific interface address +# TCP_PORT as configured in aprx.conf section +TCP_HOST = "0.0.0.0" +TCP_PORT = 10001 + +## AXUDP Settings +# AXUDP_REMOTE_IP IP to wich udp packets are sent +# AXUDP_REMOTE_PORT UDP Port to wich udp packets are sent +# AXUDP_LOCAL_IP IP of Interface to listen on, 0.0.0.0 for all interfaces +# AXUDP_LOCAL_PORT Port to listen for incoming AXUDP packets + +AXUDP_REMOTE_IP = "192.168.0.185" +AXUDP_REMOTE_PORT = 20000 +AXUDP_LOCAL_IP = "0.0.0.0" +AXUDP_LOCAL_PORT = 20000 + +## Genral Settings +# USE_AXUDP Switch from KISS to AXUDP if True +# APPEND_SIGNAL_REPORT adds signal report to text of APRS-Message for debug purpose +# this will change the original message and could cause loops +USE_AXUDP = True +APPEND_SIGNAL_REPORT = True \ No newline at end of file diff --git a/doc/LoRaAPRS-GW-RPI_V20_Schematic.pdf b/doc/LoRaAPRS-GW-RPI_V20_Schematic.pdf new file mode 100644 index 0000000..893cf94 Binary files /dev/null and b/doc/LoRaAPRS-GW-RPI_V20_Schematic.pdf differ diff --git a/doc/images/LoRa-APRS_Gateway_V20.jpg b/doc/images/LoRa-APRS_Gateway_V20.jpg new file mode 100644 index 0000000..95fa45f Binary files /dev/null and b/doc/images/LoRa-APRS_Gateway_V20.jpg differ diff --git a/multi-sf-save.txt b/multi-sf-save.txt new file mode 100644 index 0000000..a5e4d0f --- /dev/null +++ b/multi-sf-save.txt @@ -0,0 +1,54 @@ + +# Experimental multi-SF reception. Does not yet work. +# lora.set_mode(MODE.STDBY) +# lora.set_mode(MODE.CAD) +# f = lora.get_irq_flags() +# while True: +# +# for sf in [10, 11, 12]: +# #m = lora.get_mode() # important to keep pySX127x updated +# lora.set_mode(MODE.STDBY) +# lora.set_spreading_factor(sf) +# lora.set_mode(MODE.CAD) +# #time.sleep(0.001) +# while f["cad_done"] == 0: +# time.sleep(0.05) +# f = lora.get_irq_flags() +# #print(sf, MODE.lookup[lora.get_mode()], f) +# if f["cad_detected"]: +# print("DET SF %i" % sf) +# lora.set_mode(MODE.RXSINGLE) +# time.sleep(3) +# lora.clear_irq_flags(CadDone=1, CadDetected=1) +# f = lora.get_irq_flags() +# #print((lora.get_irq_flags())) +# +# import sys +# sys.exit(0) + +# if f["cad_done"]: +# lora.clear_irq_flags(CadDone=1) +# if f["cad_detected"]: +# lora.clear_irq_flags(CadDetected=1) +# lora.set_mode(MODE.RXSINGLE) +# else: +# if mode == 1: +# lora.set_spreading_factor(SPREADINGFACTOR) +# lora.set_low_data_rate_optim(True) +# mode = 2 +# else: +# lora.set_spreading_factor(10) +# lora.set_low_data_rate_optim(False) +# mode = 1 +# lora.set_mode(MODE.CAD) +# elif f["rx_timeout"]: +# lora.clear_irq_flags(RxTimeout=1) +# lora.set_mode(MODE.CAD) +# +# if f["valid_header"]: +# lora.clear_irq_flags(ValidHeader=1) + + +# if lora.mode == MODE.RXCONT: +# lora.set_mode(MODE.CAD) +# time.sleep(0.001) \ No newline at end of file