A dual band aprs digipeater with enhanced telemetry capabilities.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

237 lines
9.1 KiB

#!/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))