marcel 2 years ago
parent b031cb23dd
commit d7d52900c7
  1. 237
      AXUDPServer.py
  2. 76
      INSTALL.md
  3. 220
      KissHelper.py
  4. 140
      LoraAprsKissTnc.py
  5. 33
      README.md
  6. 41
      Start_lora-tnc.py
  7. 94
      TCPServer.py
  8. 440
      aprx/aprx.conf.lora-aprs
  9. 411
      aprx/aprx.conf.original
  10. 24
      config.py
  11. BIN
      doc/LoRaAPRS-GW-RPI_V20_Schematic.pdf
  12. BIN
      doc/images/LoRa-APRS_Gateway_V20.jpg
  13. 54
      multi-sf-save.txt

@ -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<l) and (len(a)<6) and ((text[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<l) and (text[pos]==ord("-")):
pos+=1
if (pos<l) and (text[pos]>=ord("0")) and (text[pos]<=ord("9")):
ssid+=text[pos]-ord("0")
pos+=1
if (pos<l) and (text[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<l) and (text[pos]==ord("*")):
ssid|=0x80
pos+=1
a+=chr(ssid)
return a, pos
def udpcrc(self, frame, topos):
c=0
for p in range(topos): c = (c >> 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<hbit): ch|=0x80 #set h-bit on all via calls before
if i+1==len(ax): ch|=1 #set ent of address bit
a+=chr(ch)
a+="\x03\xf0" #ui frame pid F0
i=0
while p<len(text) and i < 256: #append payload
a+=chr(text[p])
p+=1
i+=1 #max 256bytes
#for ch in b: print(hex(ord(ch)))
sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
c=self.udpcrc(a, len(a))
a+=chr(c & 0xff)
a+=chr(c>>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<len(axbuf)) and (axbuf[i]!=0): i+=1
i+=1
b=""
while i<len(axbuf):
b+=chr(axbuf[i])
i+=1
s=""
le=len(b)
if le>=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<le): #via path
if ord(b[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<le: #payload may contain ctrl characters
s+=b[p]
p+=1
return s
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 = AXUDPServer(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))

@ -0,0 +1,76 @@
# Installation and running the RPi-LoRa-Gateway
## Install needed packages
`
sudo apt install python3 python3-rpi.gpio python3-spidev aprx screen git python3-pil python3-smbus
`
## Checkout the code
Enter following commands:<br/>
```
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**<br/>
Your call with an apropriate SSID suffix<br/>[Paper on SSID's from aprs.org](http://www.aprs.org/aprs11/SSIDs.txt)
* **myloc**<br/>
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**<br/>
see [see here to generate appropiate setting](https://apps.magicbug.co.uk/passcode/)
* **server**<br/>
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
```

@ -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 <http://www.gnu.org/licenses/>.
# 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("<BBBBBBB", data[cursor:cursor + 7])
hrr = a7 >> 5
ssid = (a7 >> 1) & 0xf
ext = a7 & 0x1
addr = struct.pack("<BBBBBB", a1 >> 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("<B", frame[pos])
ctrl = frame[pos]
pos += 1
if (ctrl & 0x3) == 0x3:
#(pid,) = struct.unpack("<B", frame[pos])
pid = frame[pos]
# print("PID="+str(pid))
pos += 1
result += frame[pos:len(frame) - 1]
elif (ctrl & 0x3) == 0x1:
# decode_sframe(ctrl, frame, pos)
print("SFRAME")
return None
elif (ctrl & 0x1) == 0x0:
# decode_iframe(ctrl, frame, pos)
print("IFRAME")
return None
return result
class SerialParser():
'''Simple parser for KISS frames. It handles multiple frames in one packet
and calls the callback function on each frame'''
STATE_IDLE = 0
STATE_FEND = 1
STATE_DATA = 2
KISS_FEND = KISS_FEND
def __init__(self, frame_cb=None):
self.frame_cb = frame_cb
self.reset()
def reset(self):
self.state = self.STATE_IDLE
self.cur_frame = bytearray()
def parse(self, data):
'''Call parse with a string of one or more characters'''
for c in data:
if self.state == self.STATE_IDLE:
if c == self.KISS_FEND:
self.cur_frame.append(c)
self.state = self.STATE_FEND
elif self.state == self.STATE_FEND:
if c == self.KISS_FEND:
self.reset()
else:
self.cur_frame.append(c)
self.state = self.STATE_DATA
elif self.state == self.STATE_DATA:
self.cur_frame.append(c)
if c == self.KISS_FEND:
# frame complete
if self.frame_cb:
self.frame_cb(self.cur_frame)
self.reset()
if __name__ == "__main__":
# Playground for testing
# frame = "\xc0\x00\x82\xa0\xa4\xb0dr`\x9e\x8ar\xa8\x96\x90u\x03\xf0!4725.73NR00939.61E&Experimental LoRa iGate\xc0"
frame = "\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"
# print(decode_kiss(frame))
# encoded = encode_kiss("OE9TKH-8>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 :<Ass/Ref> <Freq> <Mode> [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)

@ -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 <http://www.gnu.org/licenses/>.
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 ""

@ -1,3 +1,32 @@
# RPi-LoRa-KISS-TNC
# Raspberry Pi LoRa KISS TNC
Custom version of https://github.com/tomelec/RPi-LoRa-KISS-TNC
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
* ...

@ -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 <http://www.gnu.org/licenses/>.
#
# 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()

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

@ -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) <aprsis> ** zero or one
# 2) <logging> ** zero or one
# 3) <interface> ** there can be multiple!
# 4) <beacon> ** zero to many
# 5) <telemetry> ** zero to many
# 6) <digipeater> ** 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
<aprsis>
# 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
</aprsis>
<logging>
# 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
</logging>
# *********** Multiple <interface> 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.
#
#<interface>
# ax25-device $mycall
# #tx-ok false # transmitter enable defaults to false
# #telem-to-is true # set to 'false' to disable
#</interface>
#
# 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
#
#<interface>
# 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
#</interface>
#<interface>
# 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
#</interface>
#<interface>
# 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
#</interface>
#
# 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.)
#
#<interface>
# 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
#</interface>
<interface>
tcp-device 127.0.0.1 10001 KISS
callsign $mycall
tx-ok true
telem-to-is true # set to 'false' to disable
</interface>
# *********** Multiple <beacon> definitions can follow *********
<beacon>
#
# 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"
</beacon>
# *********** <telemetry> 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 <interface>
# via - optional via-path, only 1 callsign!
# source - one or more of <interface> callsigns for which
# the telemetry transmission is wanted for
#
#<telemetry>
# transmitter $mycall
# via TRACE1-1
# source $mycall
#</telemetry>
# *********** <digipeater> 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 <source>, usually same
# as the transmitter. You may use same <source> on multiple
# <digipeater>s. Using multiple instances of same <source> on
# a single <digipeater> 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 <digipeater>.
# 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 <interface>
# 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.)
#
#<digipeater>
# 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>
# 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
# </source>
#
# # 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>
# # 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
# </source>
#
# #<source> # 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
# #</source>
#
# #<source> # 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!
# #</source>
#</digipeater>
<digipeater>
transmitter $mycall
<source>
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
</source>
<source> # 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
</source>
</digipeater>

@ -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) <aprsis> ** zero or one
# 2) <logging> ** zero or one
# 3) <interface> ** there can be multiple!
# 4) <beacon> ** zero to many
# 5) <telemetry> ** zero to many
# 6) <digipeater> ** 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
<aprsis>
# 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
</aprsis>
<logging>
# 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
</logging>
# *********** Multiple <interface> 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.
#
#<interface>
# ax25-device $mycall
# #tx-ok false # transmitter enable defaults to false
# #telem-to-is true # set to 'false' to disable
#</interface>
#
# 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
#
#<interface>
# 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
#</interface>
#<interface>
# 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
#</interface>
#<interface>
# 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
#</interface>
#
# tcp-device behaves identically to local serial port, but allows
# access to re