Custom version of https://github.com/tomelec/RPi-LoRa-KISS-TNC
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
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))
|
|
|