from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend from base64 import b64encode, b64decode import json, socket, wx, time, os, select from random import randint from struct import pack, unpack #import axolotl_curve25519 as curve #from curvexxx25519 import x25519 #------------------------- config MYCALL="NOCALL" # for aprs output LAT=48.000000 LONG=13.00000 ALT=354 SYMB="M," # for aprs output JSONPORT=5201 # from lorarx AXUDP=("192.168.1.46",9002) # axudp output to igate or aprsmap TEXTF="/temp/nodes.txt" # store node database metainfo=True # axudp2 extension DEBUGVERB=True TXIP=("192.168.1.46",7000) # loratx POSINTERVALL=25*60 NODEINFOINTERVALL=35*60 TELEINTERVALL=43*60 NEIGHBORINTERVALL=37*60 MAINLORACH=3 MYMAC='00606511ed0e' LORASF=11 LORABW=250000 MYKEY="078755135d41e81b35a26b89b93087c97944bcd16aa06ae7026a24c3e19bed03" MAINCH=0 #------------------------- config CHANNAMES=("ShortTurbo", "ShortFast", "ShortSlow", "MediumFast", "MediumSlow", "LongFast", "LongMod", "LongSlow", "VLongSlow") BROADCAST=b"\xff\xff\xff\xff" MESHSIZETIME=7200 # seconds since last heard a node to use for meshsize count MAXRANDOM=20 # tickertimes random maximal wait before tx TICKERTIME=0.1 # base for dcd random waits IP=("0.0.0.0",JSONPORT) longcalls=False # True for 7..9 char calls or ssid >15 if axudp2 destination is able to read this LAYER2RETRYS=3 NEXTHOPRETRYS=3 MAXQUELEN=20 DCDTIMELIMIT=30 PACKETMAXAGE=300 FRAMESTARTBYTE=8 APPTRACEROUTE=70 APPNEIGHBORINFO=71 NODELONGNAME=0 NODESHORTNAME=1 NODEMAC=2 NODELAT=3 NODELONG=4 NODETIME=5 NODEDIRECTTIME=6 NODEDIRECTCNT=7 NODESNR=8 NODERELAYNODE=9 NODECNT=10 NODEHEARSME=11 NODEPUBKEY=12 DCDTABDCD=0 DCDTABRANDOM=1 DCDTABTIMEOUT=2 DCDTABDCDSUM=3 DCDTABDCDSUMTIME=4 def colorstr(col, s): return "\033["+str(30+(col&15))+";1;1m"+s+"\033[0m" def printred(col, s): if col: print(colorstr(1,s), end="\033[0m") else: print(s, end="") def hexstrtobytes(h): s=b"" for i in range(0, len(h), 2): s+=bytes([int(h[i:i+2], 16)]) return s def bytestohex(b): s="" for i in b: s+=hex(i)[2:4].zfill(2) return s def bytestostr(b): s="" for c in b: s+=chr(c) return s def card32(p, d): try: return int(d[p] + (d[p+1]<<8) + (d[p+2]<<16) + (d[p+3]<<24)) except: return 0 def int32(p, d): n=card32(p, d) if n>(1<<31): n-=1<<32 return n def card128(d): # 7 bit per byte l=len(d) n=0 while l>0: l-=1 n=(n<<7) + (d[l]&127) return n #def int128(d): # n=card128(d) # if n>=(1<<(len(d)*7-1)): n=-n # return n def mactostr(m): if type(m)!=type(0): m=card32(0, m) return hex(m)[2:11].upper().zfill(8) def printmac(m): if type(m)!=type(0): m=card32(0, m) printred(m==card32(0,mymac[0:4]), mactostr(m)) def machextobytes(m): return bytes([int(m[6:8], 16), int(m[4:6], 16), int(m[2:4], 16), int(m[0:2], 16)]) def save(nodes): fd=open(TEXTF+"~", "w") # for n in nodes: fd.write(hex(n)[2:11].upper().zfill(8) + str(nodes[n]) + "\n") # for n in nodes: fd.write(hex(n)[2:11].upper().zfill(8) + "," + nodes[n][0] + "," + nodes[n][1] + "," + str(nodes[n][2]) + "," + str(nodes[n][3]) + "\n") for n in nodes: fd.write(mactostr(n) + json.dumps(nodes[n]) + "\n") fd.close() os.rename(TEXTF+"~", TEXTF) def loadn(): PURGELONG=86400*7 PURGESHORT=86400*1 now=time.time() try: with open(TEXTF, "r") as fd: for s in fd: w=s[8:len(s)-1] n=json.loads(w) e=[] # c=0 for i in n: e+=[i] # if c==5: e+=[0] # c+=1 while len(e)<13: e+=[0] # e[11]="" # e+=b"" pt=PURGESHORT if e[NODELONGNAME] or (e[NODELAT] and e[NODELONG]): pt=PURGELONG elif e[NODECNT]<=1: pt=0 if e[NODETIME]+pt>=now: nodes[int(s[0:8],16)]=e except IOError: print("no database file "+ TEXTF) def int32bytes(n): return bytes([n&255, n>>8&255, n>>16&255, n>>24&255]) # return bytes([n>>24&255, n>>16&255, n>>8&255, n&255]) def cleanstr(s, withhex): h="" for c in s: if type(c)!=type("A"): c=chr(c) if c>=" " and c=" " and c0: i-=1 r+=bytes([b[i]]) return r def macextract(data): # destination : 4 bytes # sender : 4 bytes # packetID : 4 bytes # flags : 1 byte # channelHash : 1 byte # nexthop : 1 byte # relaynode : 1 byte # data : 0-237 bytes meshPacketHex = { 'dest' : data[0:4], 'sender' : data[4:8], 'packetID' : data[8:12], 'flags' : data[12:13], 'channelHash' : data[13:14], 'nexthop' : data[14:15], 'relaynode' : data[15:16], 'data' : data[16:len(data)] } return meshPacketHex #================== crypto stuff """ def parseAESKey(aesKey): # We look if there's a "NOKEY" declaration, a key provided, or an absence of key. We do the right thing depending on each choice. # The "NOKEY" is basically ham mode. You're forbidden from using encryption. # If you dont provide a key, we use the default one. We try to make it easy on our users! # Note this format is in Base64 try: if args.key == "0" or args.key == "NOKEY" or args.key == "nokey" or args.key == "NONE" or args.key == "none" or args.key == "HAM" or args.key == "ham": meshtasticFullKeyBase64 = "AAAAAAAAAAAAAAAAAAAAAA==" elif ( len(args.key) > 0 ): meshtasticFullKeyBase64 = args.key except: meshtasticFullKeyBase64 = "1PG7OiApB1nwvP+rz05pAQ==" # Validate the key is 128bit/32byte or 256bit/64byte long. Fail if not. aesKeyLength = len(base64.b64decode(meshtasticFullKeyBase64).hex()) if (aesKeyLength == 32 or aesKeyLength == 64): pass else: if DEBUGVERB: print("The included AES key appears to be invalid. The key length is" , aesKeyLength , "and is not the key length of 128 or 256 bits.") sys.exit() # Convert the key FROM Base64 TO hexadecimal. return base64.b64decode(meshtasticFullKeyBase64.encode('ascii')) """ def datacryptor(meshPacketHex, aesKey): # Build the nonce. This is (packetID)+(00000000)+(sender)+(00000000) for a total of 128bit # Even though sender is a 32 bit number, internally its used as a 64 bit number. # Needs to be a bytes array for AES function. aesNonce = meshPacketHex['packetID'] + b'\x00\x00\x00\x00' + meshPacketHex['sender'] + b'\x00\x00\x00\x00' # print("AES nonce is: ", aesNonce.hex()) # print("AES key used: ", str(b64encode(aesKey))) # print("Nonce length is:", len(aesNonce) ) # Initialize the cipher cipher = Cipher(algorithms.AES(aesKey), modes.CTR(aesNonce), backend=default_backend()) decryptor = cipher.decryptor() # Do the decryption. Note, that this cipher is reversible, so running the cipher on encrypted gives decrypted, and running the cipher on decrypted gives encrypted decryptedOutput = decryptor.update(meshPacketHex['data']) + decryptor.finalize() # print("dec: "+ decryptedOutput.hex()) return decryptedOutput def crypt(mph, aeskey): # meshtasticFullKeyBase64 = "1PG7OiApB1nwvP+rz05pAQ==" # aesKey=b64decode(meshtasticFullKeyBase64.encode('ascii')) if aeskey: return datacryptor(mph, aeskey) return mph["data"] #====================== end crypto #====================== send def updatehears(rn): PURGE=1000 PURGELIM=9999 global hearcnt if type(rn)!=type(0): rn=ord(rn) if hearcnt==0: for n in nodes: hearcnt=max(hearcnt, nodes[n][NODEHEARSME]) # find max count of heard me count if hearcnt>=PURGELIM: # reduce all or set zero for n in nodes: nodes[n][NODEHEARSME]=min(0, nodes[n][NODEHEARSME]-PURGE) hearcnt-=PURGE found=0 for n in nodes: if (n&255)==rn: nl=list(nodes[n]) nl[NODEHEARSME]=hearcnt # we do not know which node heard us so count up all with this realaynode nodes[n]=tuple(nl) found=n if found: hearcnt+=1 print("updatehears:",hex(n), found, hex(rn), hearcnt) def ack(mid, yes, fromend): print("********* ACK:",yes, fromend, mactostr(mid)) def loratx(payload, sf, cr, bw, preamb): global txtime be=b64encode(payload) s="" for c in be:s+=chr(c) print("txp:",end="") hexpr(payload[0:4], False) hexpr(payload[4:8], False) hexpr(payload[8:12], False) hexpr(payload[12:16], False) hexpr(payload[16:], False) print("") # print("t:",json.dumps({"payload":s, "cr":5, "sf":11, "bw":8}).encode("utf-8")) try: sock.sendto(json.dumps({"payload":s, "sf":int(sf), "cr":cr, "bw":int(bw), "preamb":preamb}).encode("ascii"), TXIP) except: print("json send failed") bwhz=((1<>8 bd=bwhz//(1<h[nr]: h[nr]=c else: h[nr]=c if c>m: m=c # print("hearsme:", h) for i in h: d=m-h[i] h[i]=max(1 ,100-d*d) # sore 100 hears me perfect """ while True: # delete all >=10 times another heard me d=-1 for i in h: if h[i]+10=0: h.pop(d) else: break """ # print("hearsme1:", h) return h def bestnextnode(dest): r=b"" cnt={} d=card32(0, dest) if dest!=BROADCAST and d in nodes: rtab=nodes[d][NODERELAYNODE] # list of counts by relaynode where destination came from # print("### rtab:", rtab) i=0 while i+4<=len(rtab): try: c=int(rtab[i+2:i+4],16) n=int(rtab[i:i+2],16) except: c=0 if c:cnt[n]=c i+=4 # print("### cnt:", cnt) score={} e=b"" if cnt: hm=findhearsme() # list of relaynodes which repeatet my frames 100=all, 1=rare for n in hm: # nodes that heared me if n in cnt: # node that destination came from s=cnt[n]*hm[n] # multiply count with heread me quality if s: score[s]=n # print("### score:", score) if score: s=score[max(score)] # best score if s: r=bytes([s]) del score[max(score)] if score: s=score[max(score)] # second best if s: r+=bytes([s]) return r def getbwsf(chnum): # "ShortTurbo", "ShortFast", "ShortSlow", "MediumFast", "MediumSlow", "LongFast", "LongMod", "LongSlow", "VLongSlow" if chnum==0: return 9, 7 elif chnum==1: return 8, 7 elif chnum==2: return 8, 8 elif chnum==3: return 8, 9 elif chnum==4: return 8, 10 elif chnum==5: return 8, 11 elif chnum==6: return 7, 11 elif chnum==7: return 7, 12 elif chnum==8: return 6, 12 else: return 0,0 def sendque(): # send not broadcast first to avoid retries for too long ack delay for bc in range(0, 2): # first send all addressed frames then broadcast after delay i=0 while i>5 or routingack: # only correct repeated frame shall ack i=0 while i0 and f["mymsg"]: ack(f["packetID"], True, False) if f["mymsg"]: ack(f["packetID"], True, False) txque[i]["acked"]=True txque[i]["retrycnt"]=0 txque[i]["sendat"]=layer2delay() + time.time() # start new retry timer for collecting repeated my frames if rn and ((flags&7)+1)==(ord(f["flags"])&7) and f["mymsg"]: updatehears(rn) # store who has my tx repeated if routingack: printred(f["sender"]==mymac[0:4], "----routing ack-deque:"+mactostr(f["packetID"])) else: print("-----deque:", mactostr(f["packetID"])) i+=1 def que(frame, retries, delay, mymsg): i=0 while i=len(txque) and len(txque)>i)&1) m=(m<<1)+((r>>(i<<1))&1) m=(m<<1)+((r>>(i<<2+1))&1) m=(m<<1)+((r>>20)&1) m=(m<<1)+((r>>21)&1) return m def buildframe(chan, dest, hops, wantack, retry, usenexthop, lorach, data): nh=bytes([]) if machextobytes("ffffffff")==dest: wantack=False # for safety elif usenexthop: nh=bestnextnode(dest) viamttq=False src=mymac[0:4] hash=-1 for i in channels: if i["band"]==lorach: hash=i["channelhash"] break if hash<0: print("----- channel hash not found") return # print("### ch, hash:",lorach, hash) mf = { 'dest' : dest, 'sender' : src, 'packetID' : int32bytes(genmid()), 'flags' : bytes([(hops&7) + (int(wantack)<<3) + (int(viamttq)<<4) + ((hops&7)<<5)]), 'channelHash' : bytes([hash]), 'nexthop' : nh, # if destination not broadcast insert relaynode of last heard the destination 'relaynode' : bytes([src[0]]), "lorach" : lorach, 'data' : data } mf["data"] = crypt(mf, channels[chan]["aeskey"]) addmid(mf["sender"], mf["packetID"]) # beware from repeating self gegerated frame decodemesh(mf, False) # monitor only retr=1 if retry: retr=LAYER2RETRYS que(mf, retr, 0.0, True) def mactobytes(m): b="" for n in m:b+=hex(int(n,16))[2:6] return alltobytes(b) def cardvar(n): #encode cardinal to 7 bit per byte with bit 7 set if a byte follows s=b"" while True: b=n&127 n=n>>7 if n: b+=128 s+=bytes([b]) if n==0: break return s def nodetag(tag, withlen, text): b=alltobytes(text) s=b"" if text: tag=tag<<3 if withlen: tag+=2 # len counter byte follows elif len(text)==4: tag+=5 # 32 bit follow elif len(text)==8: tag+=1 # 64 bit follow else bit 7 set until end s+=bytes([tag]) if withlen: s+=bytes([len(b)]) return s+b def encnodeinfo(longname, shortname, model, licensed, role, pubkey, unmessage): b=nodetag(1, True, b"!" + mactobytes(MYMAC[4:12])) if longname: b+=nodetag(2, True, longname) if shortname: b+=nodetag(3, True, shortname[0:4]) b+=nodetag(4, True, bytes.fromhex(MYMAC)) if model>=0: b+=nodetag(5, False, bytes([model])) if licensed: b+=nodetag(6, False, bytes([1])) if role>=0: b+=nodetag(7, False, bytes([role])) if len(pubkey)==32: b+=nodetag(8, True, pubkey) if unmessage: b+=nodetag(9, False, bytes([1])) return bytes([8,4]) + nodetag(2,True,b) def encodetelemetry(util, airtime, uptime, now): return bytes([8,67]) + nodetag(2,True, nodetag(1, False, pack("I",now)) + nodetag(2, True, nodetag(3, False, pack("f",util)) + nodetag(4, False, pack("f",airtime)) + nodetag(5, False, pack("I",uptime)) )) def encodetext(text): return bytes([8,1]) + nodetag(2,True,text) def postag(tag, tagl, b): return bytes([(tag<<3) + tagl]) + b def encpos(lat, long, alt, locsrc): if alt<0: alt=0 b=postag(1,5, int32bytes(int(lat*10000000.0))) + postag(2,5, int32bytes(int(long*10000000.0))) + postag(3,0, cardvar(alt)) + postag(5,0, cardvar(locsrc)) return bytes([8,3]) + nodetag(2,True,b) def snrtodelay(snr): d=(snr + LORASF*3 - 13)*((1>5) & 7 if starthops>0: deque(fr["packetID"], flags, fr["relaynode"], False) # ignor wrong frames and heared again befor layer0 sent it so remove it from queue nexthop=ord(fr["nexthop"]) if not (flags>>4)&1 and (isnew or nexthop): # repeat not via mqtt already heared if my node shall do nexthop routing dest=fr["dest"] if hops and hops<=starthops and ((nexthop==0) or (nexthop==ord(mymac[0:1]))): if nexthop: print("got nexthop-",end="") # nextnode=0 # if nexthop and (dest!=BROADCAST) and (dest in nodes): nextnode=nodes[dest][NODERELAYNODE] # nexthop routing # if (dest!=BROADCAST) and (dest in nodes): nextnode=nodes[dest][NODERELAYNODE] # nexthop routing mf = { 'dest' : fr["dest"], 'sender' : fr["sender"], 'packetID' : fr["packetID"], 'flags' : bytes([flags-1]), # decrement hops, it is >0 so no wrap 'channelHash' : fr["channelHash"], 'nexthop' : bestnextnode(fr["dest"]), # if destination not broadcast insert relaynode of last heard the destination 'relaynode' : mymac[0:1], "lorach" : fr["lorach"], 'data' : fr["data"] } print("repeat:",hops,"/", starthops, "nh:", nexthop, "in ", "{:.1f}".format(snrtodelay(snr))+"s", mactostr(fr["packetID"]), "paths:["+bytestohex(mf["nexthop"])+"]") if mf["nexthop"]: que(mf, LAYER2RETRYS, 0, False) # reliable nexthop with retry else: que(mf, 1, snrtodelay(snr), False) # single shot hop # print("-----------:", mf) def sendroute(ch, mph): #routing: 08 05 12 02 18 00 35 d956fe38 4801 if mph["dest"]==mymac[0:4] and (ord(mph["flags"])>>3)&1: # frame to me and wants ack buildframe(ch, mph["sender"], 3, False, False, True, mph["lorach"], bytes([0x08,0x05,0x12,0x02,0x18,0x00,0x35])+mph["packetID"]+bytes([0x48, 0])) print("send routing reply:", mactostr(mph["dest"]), mactostr(mph["packetID"])) def starttraceroute(ch, dst, hops, txch): #08 46 1801 4802 buildframe(ch, dst, hops, True, True, False, txch, bytes([FRAMESTARTBYTE, APPTRACEROUTE, 0x18, 1, 0x48, 2])) print("send traceroute to " + mactostr(dst)) def appmac(s, hops, withme): h=len(s) if h%4==0: h=hops - h//4 if h>=0: for i in range(h): s+=bytes([0xff,0xff,0xff,0xff]) # fill missing hops if withme: s+=mymac[0:4] return s def appsnr(s, havlen, hops, snr): if havlen%4==0: # for i in range(havlen//4, hops): s+=bytes([0x80, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 1]) # fill missing hops unknown snr and 72 funny bits fo for i in range(havlen//4, hops): s+=bytes([0x80]) # fill missing hops unknown snr and 72 funny bits fo # s+=bytes([snr&255, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 1]) # apend my snr and junk s+=bytes([snr&255]) # apend my snr and no junk return s def sendtraceroute(ch, df, mph, snr): snr=int(snr*4+0.5) if snr>127: snr=128 elif snr<-127: snr=-127 flags=ord(mph["flags"]) starthops=(flags>>5)&7 hops=flags&7 tome=mph["dest"]==mymac[0:4] if (tome or (mph["dest"]!=BROADCAST and hops>0)) and hops<=starthops: po=2 nv=b"" lv=b"" nr=b"" lr=b"" mid=b"" tg3=b"" tg9=-1 while True: po, tgo, var, so=tagdec(df, po) # print("#####1:",tome,po, tgo, var, so, bytestohex(df)) if len(so)==0: break p=0 if tgo==2: ff=b"" state=0 while True: p, tg, var, s=tagdec(so, p) if var or len(s)==0: break if tg==1: nv=s elif tg==2: lv=s #list of 10byte snr + ffffffffffffffff01 elif tg==3: nr=s #list of 10byte snr + ffffffffffffffff01 elif tg==4: lr=s #list of 10byte snr + ffffffffffffffff01 else: print("traceroute unkown tag:",tg) return elif tgo==3: tg3=so elif tgo==6: mid=so elif tgo==9: tg9=so[0] else: print("traceroute unkown tag:",tgo) return ff=b"" hopsdone=starthops-hops if tg9==2 and not mid: # way forward ff+=nodetag(1, True, appmac(nv, hopsdone, not tome)) ff+=nodetag(2, True, appsnr(lv, len(nv), hopsdone, snr)) ff=nodetag(2, True, ff) if tome: # ff+=nodetag(6, False, mph["packetID"]) # add old mid as start of back way ff+=nodetag(6, False, mph["packetID"]) ff+=bytes([0x48, 1]) mph["dest"]=mph["sender"] mph["sender"]=mymac[0:4] mph["packetID"]=int32bytes(genmid()) addmid(mph["sender"], mph["packetID"]) flags=(flags&0xf8) + ((flags>>5)&7) # start a new hop counter with original hoplimit else: ff+=bytes([0x18,1,0x48, 2]) # 48, 1 oder 2 ? flags-=1 elif tg9<2 and mid: # way back if tome: ack(mid, True, True) return ff+=nodetag(1, True, nv) ff+=nodetag(2, True, lv) ff+=nodetag(3, True, appmac(nr, hopsdone, True)) ff+=nodetag(4, True, appsnr(lr, len(nr), hopsdone, snr)) ff=nodetag(2, True, ff) ff+=nodetag(6, False, mid) ff+=bytes([0x48, 0]) flags-=1 else: print("traceroute unkown direction") return mph["nexthop"]=bytes([0]) mph["relaynode"]=mymac[0:1] mph["flags"]=bytes([flags]) mph["nexthop"]=bytes([0]) mph["data"] = bytes([FRAMESTARTBYTE, APPTRACEROUTE]) + ff print("-----------------------------------------------") mph["data"] = crypt(mph, channels[ch]["aeskey"]) decodemesh(mph, False) retr=1 if tome: retr=LAYER2RETRYS que(mph, retr, 0.0, False) def meshsize(): now=int(time.time()) cnt=0 for n in nodes: if nodes[n][NODETIME]+MESHSIZETIME>=now: cnt+=1 return cnt def scaleback(s): ms=meshsize()-60 # if ms>0: s=int(s*(1.0 + ms*0.0075)) # 0.075 return s def sendtext(): txt="" try: with open("/tmp/msg", "r") as fd: line=fd.read() print("msgfile;", line) for c in line: if c>=" ": txt+=c else: break # buildframe(BROADCAST, 0, False, encodetext(txt)) # buildframe(machextobytes("a84c5270"), 0, True, encodetext(txt)) # buildframe(machextobytes("a84c55fc"), 3, True, encodetext(txt)) os.unlink("/tmp/msg") except: return if txt: adr="ffffffff" if ":" in txt: if txt.index(":"): adr=txt[:txt.index(":")] txt=txt[txt.index(":")+1:] if txt: try:adr=machextobytes(adr) except:adr=b"" if adr: dir=adr!=BROADCAST buildframe(MAINCH, adr, 5, dir, True, dir, MAINLORACH, encodetext(txt)) print("++++++++ sendtext:"+bytestohex(adr)+":"+txt) def sendneighborinfo(txch): # will not work, never seen a frame so no revers engineering ne=[] for n in nodes: if nodes[n][NODEDIRECTTIME] and nodes[n][NODEDIRECTCNT]: ne+=[(n, nodes[n][NODESNR], nodes[n][NODEDIRECTTIME])] # print("###n1:", hex(n), nodes[n][NODESNR], time.ctime(nodes[n][NODEDIRECTTIME])) while len(ne)>3: # remove oldest d=0 tm=ne[0][2] for i in range(len(ne)): if tm>=ne[i][2]: # time last heard tm=ne[i][2] d=i if len(ne)<=10 and tm+86400>now: break ne.pop(d) print("###ni:") for n in ne: if n[0] in nodes: print(mactostr(n[0])+"[" + cleanstr(nodes[n[0]][NODELONGNAME], False) + "]", end="") print(n[1], now-n[2]) print("") if ne: ff=bytes([FRAMESTARTBYTE, APPNEIGHBORINFO]) + nodetag(1, False, mymac[0:4]) for n in ne: ff+=nodetag(4, True, nodetag(1, False, pack("I", n[0])) + nodetag(2, False, pack("f", n[1])) + nodetag(3, False, pack("I", n[2]))) buildframe(MAINCH, BROADCAST, 3, False, True, False, txch, ff) #================ end tx sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) sock.bind(IP) def sendaprs(obj, tx, lat, long, alt, speed, heading, relay, flags, name, sname, mac, dir, dirc, heardc): ms="" axmeta={} if obj and metainfo: if longcalls: axmeta["longcalls"]=2 try: v=obj["snr"] except: pass else: ms+=" snr="+"{:.1f}".format(v)+"dB" axmeta["snr"]=v try: v=obj["afc"] except: pass else: ms+=" afc="+"{:.0f}".format(v)+"Hz" axmeta["afc"]=v # try: v=obj["preamb"] # except: pass # else: # ms+=" txd="+"{:.0f}".format(v)+"ms" # axmeta["txdel"]=v try: v=obj["level"] except: pass else: ms+=" lev="+"{:.0f}".format(v)+"dB" axmeta["level"]=v try: v=obj["fec"] except: pass else: ms+=" fec="+"{:.0f}".format(v) try: v=obj["eye"] except: pass else: ms+=" q="+"{:.0f}".format(v)+"%" axmeta["quality"]=v try: v=obj["bw"] except: pass else: ms+=" bw="+"{:.0f}".format(v)+"Hz" try: v=obj["net"] except: pass else: ms+=" net="+"{:X}".format(v) try: v=obj["rxmhz"] except: pass else: ms+=" rx="+"{:.3f}".format(v)+"MHz" try: v=obj["sf"] vi=obj["invers"] except: pass else: ms+=" sf="+"{:.0f}".format(v) if vi: ms+="(inv)" try: v=obj["cr"] except: pass else: ms+=" cr="+"{:.0f}".format(v) # try: v=obj["ver"] # except: pass # else: ms+=" vers="+v # while len(tx)<9: tx+=" " hops=flags & 7 starthops=(flags>>5) & 7 if hops>starthops: starthops=8 s=MYCALL+">APZMTC,WIDE" + str(starthops) + "-" + str(hops) astr="" if alt>-1000: astr=wx.altaprs(alt) ds="" if heading<1: heading=360 if speed>0: ds=wx.speeddir(speed, heading) s+=":)"+tx + "!" + wx.lataprs(lat) + SYMB[0] + wx.longaprs(long) + SYMB[1] + ds + astr + wx.dao(lat,long) + "rn:" + hex(relay)[2:4].zfill(2) + " hop:" + str(flags&7) + "/" + str(flags>>5) if type(dirc)!=type(0): dirc=0 s+=" h/d:" + str(heardc) + "/" + str(dirc) if name: s+=" name:[" + name + "]" if sname: s+=" s:[" + sname + "]" if mac: s+=" mac:" + mac if (flags>>3)&1: s+=" WantAck" if (flags>>4)&1: s+=" ViaMQTT" s+=ms try: wx.sendax(sock, s, AXUDP, axmeta) except: print("axudp encode error") def stg(p, s): l=int(s[p]) p+=1 # print("pl=",p,l) st="" i=0 while i=" " and ch>3, var, s def traceroute(ch, df, mph, mysnr, isnew): flags=ord(mph["flags"]) starthops=(flags>>5)&7 donehops=starthops-(flags&7) if starthops>0: deque(mph["packetID"], flags, mph["relaynode"], False) # ignor wrong frames fcnt=0 bcnt=0 ok=0 po=2 lasttx=0 while True: po, tgo, var, so=tagdec(df, po) if len(so)==0: break if tgo==2: p=0 while True: p, tg, var, s=tagdec(so, p) # print("###tt:",p,tg,var,len(s)) if len(s)==0: break if tg==1 or tg==3: ok=1 if tg==1: print(" forw:",end="") else: print(" back:",end="") for i in range(len(s)//4): if tg==1: fcnt+=1 else: bcnt+=1 lasttx=card32(i*4, s) printmac(lasttx) if lasttx in nodes and nodes[lasttx][NODELONGNAME]: print("("+cleanstr(nodes[lasttx][NODELONGNAME], False)+")", end="") print(" ", end="") elif tg==2 or tg==4: if ok==1: ok=2 print(" snr:",end=" ") s=s.replace(bytes([255,255,255,255,255,255,255,255,1]), b"") # remove ff junk for i in s: snr=i if snr==128: print(" ?? ", end="") else: if snr>=128: snr-=256 # signed byte snr*=0.25 # make dB print(snr,end=" ") else: print(" tag1:", tg, end=" ") elif tgo==3: ok=2 # looks like empty way back elif tgo==6: fcnt=bcnt # way back print("mid:"+mactostr(so), end=" ") else: print(" tag2:", tgo, end=" ") if mysnr>=-50 and starthops>0 and donehops>=fcnt: s="my:"+str(mysnr) if donehops==fcnt: s+="["+mactostr(lasttx)+"]" s+="["+bytestohex(mph["relaynode"])+"]" if donehops==fcnt and ord(mph["relaynode"])==lasttx&255 and lasttx in nodes: if lasttx!=card32(0, mymac[0:4]): addnodemac(lasttx, ord(mph["relaynode"]), mysnr, True) # direct heard print(" updatedirect:", mactostr(lasttx), ord(mph["relaynode"]), mysnr, end=" ") s+="["+cleanstr(nodes[lasttx][NODELONGNAME],False)+"]" print(s,end="") if isnew and ok==2 and mysnr>-50: sendtraceroute(ch, df, mph, mysnr) def devicemetrics(df): p=0 print("device:",end="") while True: p, tg, var, s=tagdec(df, p) if len(s)==0: break v=0 if var: v=card128(s) elif len(s)==4: v=card32(0, s) [f]=unpack("f", s) else: print("devicemetrics unknown numeric", end=" ") if tg==1: print("batt:",v, end=" ") elif tg==2: print("volt:",f, end=" ") elif tg==3: print("util:",f, end=" ") elif tg==4: print("air:",f, end=" ") elif tg==5: print("uptime:",v, end=" ") else: print(" devicemetrics tag:", tg, len(s), var, end=" ") def environmentmetrics(df): p=0 print("environment:",end="") while True: p, tg, var, s=tagdec(df, p) if len(s)==0: break v=0 if var: v=card128(s) elif len(s)==4: v=card32(0, s) [f]=unpack("f", s) else: print("environmentmetrics unknown numeric", end=" ") if tg==1: print("temp:",f, end=" ") elif tg==2: print("hum:",f, end=" ") elif tg==3: print("baro:",f, end=" ") elif tg==4: print("gasres:",f, end=" ") elif tg==5: print("volt:",f, end=" ") elif tg==6: print("curr:",f, end=" ") elif tg==7: print("iaq:",v, end=" ") elif tg==8: print("dist:",f, end=" ") elif tg==9: print("lux:",f, end=" ") elif tg==13: print("windir:",v, end=" ") elif tg==14: print("wind:",f, end=" ") elif tg==16: print("gust:",f, end=" ") elif tg==18: print("rad:",f, end=" ") elif tg==19: print("rain1h:",f, end=" ") elif tg==20: print("rain24h:",f, end=" ") else: print(" environmentmetrics tag:", tg, len(s), var, end=" ") def powermetrics(df): p=0 print("power:",end="") while True: p, tg, var, s=tagdec(df, p) if len(s)==0: break v=0 if var: v=card128(s) elif len(s)==4: v=card32(0, s) [f]=unpack("f", s) else: print("powermetrics unknown numeric", end=" ") if tg&1: print("ch:",(tg+1)//2, f,"V", end=" ") else: print("ch:",(tg+1)//2, f,"A", end=" ") def airquality(df): p=0 print("airquality:",end="") while True: p, tg, var, s=tagdec(df, p) if len(s)==0: break v=0 if var: v=card128(s) elif len(s)==4: v=card32(0, s) [f]=unpack("f", s) else: print("airquality unknown numeric", end=" ") def telemetry(df, mph): po=2 po, tg, var, so=tagdec(df, po) if len(so)==0: return p=0 while True: p, tg, var, s=tagdec(so, p) if len(s)==0: break if tg==1: print("[",time.ctime(card32(0, s)),end="]") # telemetry time elif tg==2: devicemetrics(s) # telemetry device metrics elif tg==3: environmentmetrics(s) elif tg==4: airquality(s) elif tg==5: powermetrics(s) else: print(" telemetry tag:", tg, len(s), var) def wrtexts(t): try: filewrln("/temp/text", t) except: print("-------- msg file write error") def routing(ch, df, mph): p=2 while True: p, tg, var, s=tagdec(df, p) if len(s)==0: break if tg==2: print("???:", s, end=" ") elif tg==6: deque(s, 0, 0, True) # message done, end ack, remove from tx que printred(mph["dest"]==mymac[0:4], "mid:"+mactostr(s)) if mph["dest"]==mymac[0:4]: # ack only if its for me ack(s, True, True) wrtexts(str(ch)+" "+time.ctime(int(time.time())) + " from:"+mactostr(i) + "{"+mactostr(mph["packetID"])+"} ACK") def nodeinfo(df, mph): p=2 p, tg, var, so=tagdec(df, p) if len(so)==0: return ni=" " id="" longname="" shortname="" mac="" hwmodel=-1 licensed=False pubkey="" role=-1 pubkey="" unmessageable=False p=0 while True: p, tg, var, s=tagdec(so, p) if len(s)==0: break s=bytestostr(s) ## for i in range(le): st+=chr(df[p+i]) if tg==1: id=s elif tg==2: longname=s elif tg==3: shortname=s elif tg==4: mac="" for ch in s: mac+=hex(ord(ch))[2:4].zfill(2) elif tg==5: hwmodel=ord(s[0]) elif tg==6: licensed=s[0] elif tg==7: role=ord(s[0]) elif tg==8: pubkey=bytestohex(alltobytes(s)) elif tg==9: unmessageable=s[0] else: print(" unknown nodeinfo tag:", tg) if DEBUGVERB: hexpr(s, False) ## p+=le hexpr(id, True) print(" n:",end="") hexpr(longname, True) print(" sn:",end="") hexpr(shortname, True) print(" mac:" + mac, end="") if hwmodel>=0: print(" hwm:" + str(hwmodel),end="") if licensed: printred(True, " licensed") if role>=0: print(" role:" + str(role),end="") # if unmessageable: print(" nomsg",end="") if pubkey: print(" key("+str(len(pubkey))+")["+pubkey, end="]") print("") hwid=card32(0, mph["sender"]) newnodedb(hwid) nodl=list(nodes[hwid]) # ok=nodl[NODELONGNAME]!=longname or nodl[NODESHORTNAME]!=shortname nodl[NODELONGNAME]=longname nodl[NODESHORTNAME]=shortname nodl[NODEMAC]=mac nodl[NODETIME]=int(time.time()) # TX frame does not set new time nodl[NODEPUBKEY]=pubkey nodes[hwid]=tuple(nodl) def position(df, mph): p=2 p, tg, var, so=tagdec(df, p) lat=0.0 long=0.0 alt=-1000 timestamp=0 timestamp2=0 locsrc=-1 altsrc=-1 hae=-1 pdop=0.0 hdop=0.0 vdop=0.0 speed=-1 dir=-1 sats=-1 gpsacc=-1 p=0 while so: p, tg, var, s=tagdec(so, p) if len(s)==0: break val=0 if var: val=card128(s) elif len(s)==4: val=card32(0, s) vali=int32(0, s) print("{", tg, len(s), end="}") if tg==1: if len(s)==4: lat=vali*0.0000001 elif tg==2: if len(s)==4: long=vali*0.0000001 elif tg==3: alt=val # no negative altitude? elif tg==4: if len(s)==4: timestamp=val elif tg==5: locsrc=val print(" locsrc", locsrc, end="") elif tg==6: altsrc=s[0] elif tg==7: if len(s)==4: timestamp2=val elif tg==9: hae=val elif tg==11: pdop=val print(" pdop", "{:.1f}".format(pdop*0.01), end="") elif tg==12: hdop=val*0.01 elif tg==13: vdop=val*0.01 elif tg==14: gpsacc=val elif tg==15: speed=val # km/h or m/s find it out elif tg==16: dir=val*0.01 elif tg==17: fix=val print(" fix:", fix, end=" ") elif tg==18: fix=val print(" fix:", fix, end=" ") elif tg==19: sats=val print(" sats:", sats, end=" ") elif tg==22: seq=val print(" seq:", seq, end="") elif tg==23: precision=val print(" precision:", precision, end="") else: print(" --unknown pos tag:", tg, end="") if lat!=0 and abs(lat)<=90.0 and long!=0 and abs(long)<=180.0: altosm=os.popen("./profile -a " + str(lat) + " " + str(long) + " -p osm/").read() print(" ", "{:.5f}".format(lat), "{:.5f}".format(long), alt, "srtm:", altosm, end="") if speed>=0: print(" gspeed:", speed,end="") if dir>=0: print(" gdir:", dir,end="") if timestamp>0: print("[",time.ctime(timestamp),end="]") if timestamp2>0: print("[",time.ctime(timestamp2),end="]") #time 4 1970 #loc src #alt src #timstamp 4 #timestamp ms 4 #alt hae 4 #geoidal 4 hwid=card32(0, mph["sender"]) newnodedb(hwid) nodl=list(nodes[hwid]) nodl[NODELAT]=lat nodl[NODELONG]=long nodl[NODETIME]=int(time.time()) nodes[hwid]=tuple(nodl) return True, alt, speed, dir else: print("pos-error:", lat, long) return False, alt, speed, dir def textmsg(ch, df, mph, isnew): p=2 st="" while True: p, tg, var, s=tagdec(df, p) if len(s)==0: break if tg==2: st=bytestostr(s) elif tg==9: if s[0]: print("unmes", end="") else: print("unknown text tag:", tg, end=" ") if isnew:sendroute(ch, mph) i=card32(0, mph["sender"]) fmto=str(ch)+" "+time.ctime(int(time.time())) + " from:"+mactostr(i) if i in nodes: fmto+="("+cleanstr(nodes[i][NODELONGNAME],False)+")" if mph["dest"]!=BROADCAST: i=card32(0, mph["dest"]) fmto+=" to:"+mactostr(i) if i in nodes: fmto+="("+cleanstr(nodes[i][NODELONGNAME], False)+")" fmto+="{"+mactostr(mph["packetID"])+"}" else: fmto+=" to all" fmto+=":["+cleanstr(st, True)+"]" printred(mph["dest"]==mymac[0:4], fmto) if isnew: wrtexts(fmto) def newnodedb(hwid): if not hwid in nodes:nodes[hwid]=["", "", "", 0.0, 0.0, 0, 0, 0 ,0.0, "", 0, 0, ""] def addnodemac(hwid, n, snr, direct): # hwid=card32(0, mph["sender"]) newnodedb(hwid) nodl=list(nodes[hwid]) nodl[NODETIME]=int(time.time()) if direct: nodl[NODESNR]=snr nodl[NODEDIRECTTIME]=nodl[NODETIME] rtab=nodl[NODERELAYNODE] # n=ord(mph["relaynode"]) # print("+++++new routing:", nodl[NODERELAYNODE], end=" ") if n: # relaynode 0 will do nothing rnh=hex(n)[2:4].zfill(2) i=0 c0=0 while i+2<=len(rtab) and rtab[i:i+2]!=rnh: i+=4 if i+2<=len(rtab): try: c0=int(rtab[i+2:i+4], 16) except: c0=0 nh="" i=0 while i+2<=len(rtab): if rtab[i:i+2]!=rnh: try: c=int(rtab[i+2:i+4], 16) except: c=0 if c0>=255: if c>0: nh+=rtab[i:i+2] + hex(c-1)[2:4].zfill(2) else: nh+=rtab[i:i+4] i+=4 if c0<255: c0+=1 rtab=rnh + hex(c0)[2:4].zfill(2) + nh nodl[NODERELAYNODE]=rtab[0:32] # limit size # print("+++ :", nodl[NODERELAYNODE]) if direct: try: nodl[NODEDIRECTCNT]+=1 except: nodl[NODEDIRECTCNT]=0 else: try: nodl[NODECNT]+=1 except: nodl[NODECNT]=0 nodes[hwid]=tuple(nodl) def bytesint32(b): return b[0] + (b[1]<<8) + (b[2]<<16) + (b[3]<<24) def addmid(sender, mid): if len(mids)>10000: mids.pop(0) mids.append(sender+mid) def havemid(sender, mid): return sender+mid in mids def makelorachan(sf, bw): if bw==500000: if sf==7: return CHANNAMES.index("ShortTurbo") if bw==250000: if sf==7: return CHANNAMES.index("ShortFast") if sf==8: return CHANNAMES.index("ShortSlow") if sf==9: return CHANNAMES.index("MediumFast") if sf==10: return CHANNAMES.index("MediumSlow") if sf==11: return CHANNAMES.index("LongFast") if bw==125000: if sf==11: return CHANNAMES.index("LongMod") if sf==12: return CHANNAMES.index("LongSlow") if bw==62500: if sf==12: return CHANNAMES.index("VLongSlow") return "" def makechanhash(chan): # xor channelname and if given aeskey cn=CHANNAMES[chan["band"]] ch=0 for i in cn: ch^=ord(i) aeskey=chan["aeskey"] for i in aeskey: ch^=i # print("###ch:", cn, aeskey,ch) return ch def decodemesh(payload, obj): # global util if obj: mph=macextract(payload) mph["lorach"]=0 # if "duration" in obj: util+=obj["duration"] # if "preamb" in obj: util+=obj["preamb"] if "sf" in obj and "bw" in obj: mph["lorach"]=makelorachan(obj["sf"], int(obj["bw"])) else: mph=payload hwid=card32(0, mph["sender"]) starthop=ord(mph["flags"])>>5 hops=ord(mph["flags"])&7 isnew=not havemid(mph["sender"], mph["packetID"]) direct=starthop>0 and starthop==hops and ord(mph["relaynode"])==hwid&255 s="" tofromme=mph["dest"]==mymac[0:4] or mph["sender"]==mymac[0:4] # if mph["dest"]==mymac[0:4]: s=">>>>>>>>>>>\n" # elif mph["sender"]==mymac[0:4]: s="<<<<<<<<<<<\n" if not obj: s+="TX:" elif isnew: addmid(mph["sender"], mph["packetID"]) snr=0.0 if obj: snr=obj["snr"] addnodemac(card32(0, mph["sender"]), ord(mph["relaynode"]), snr, direct) s+="RX:" if direct: s+="direct heard " else: s+="rx:" if "lorach" in mph: s+=str(mph["lorach"])+" " if mph["lorach"]!=5: s=colorstr(1,s) elif direct: s=colorstr(2,s) s+=mactostr(hwid) + " dst:" + mactostr(mph["dest"]) + " mid:" + mactostr(mph["packetID"]) if hwid in nodes:s+=" [" + cleanstr(nodes[hwid][NODELONGNAME], True) + "]" if mph["dest"]!=BROADCAST and card32(0, mph["dest"]) in nodes:s+=">[" + cleanstr(nodes[card32(0, mph["dest"])][NODELONGNAME], True) + "]" if obj: s+=" " + "{:.1f}".format(obj["snr"]) + "dB" s+=" " + "{:.0f}".format(obj["afc"]) + "Hz" s+=" sf:" + "{:.0f}".format(obj["sf"]) if "fec" in obj and obj["fec"]: s+=" fec:" + "{:.0f}".format(obj["fec"]) s+=" rn:" + hex(ord(mph["relaynode"]))[2:4].zfill(2) s+=" " + str(hops) + "/" + str(starthop) if (ord(mph["flags"])>>3)&1: s+=" WantAck" if (ord(mph["flags"])>>4)&1: s+=" ViaMQTT" s+=" hash:" + hex(ord(mph["channelHash"]))[2:4].zfill(2) nh=mph["nexthop"] if nh: nh=ord(bytestostr(nh)[0]) s+=" nh:" + hex(nh)[2:4].zfill(2) s+=" " alt=-10000 speed=-1 dir=-1 aprsok=False chash=ord(mph['channelHash']) framech=-1 df=mph['data'] for i in channels: if i["channelhash"]==chash: framech=channels.index(i) break if framech>=0: df=crypt(mph, channels[framech]["aeskey"]) repeatok=True # if framech>=0 and (len(df)==0 or df[0]!=channels[framech]["channelhash"]): framech=-2 # bad frame or decryption failed if framech>=0 and (len(df)==0 or ord(mph["channelHash"])!=channels[framech]["channelhash"]): framech=-2 # bad frame or decryption failed if framech>=0 and len(df)>0: # looks like decrypt ok chan=df[1] if chan==3: printred(tofromme, s+"pos:") if DEBUGVERB: hexpr(df, False) aprsok, alt, speed, dir=position(df,mph) or aprsok print("") elif chan==4: printred(tofromme, s+"nodeinfo:") nodeinfo(df, mph) aprsok=True elif chan==67: printred(tofromme, s+"telemetry:") telemetry(df, mph) hexpr(df, False) print("") elif chan==1: printred(tofromme, s+"text:") textmsg(framech, df, mph, isnew) hexpr(df, False) print("") elif chan==7: printred(tofromme, s+"text compressed:") hexpr(df, False) filewrln("/temp/ctext", bytestohex(df)) print("") elif chan==32: printred(tofromme, s+"reply:") hexpr(df, True) print("") elif chan==APPTRACEROUTE: repeatok=False printred(tofromme, s+"traceroute:") hexpr(df, False) snr=-1000 if obj and "snr" in obj: snr=obj["snr"] traceroute(framech, df, mph, snr, isnew) print("") elif chan==APPNEIGHBORINFO: printred(True, s+"neighbor info:") hexpr(df, False) print("") elif chan==5: printred(tofromme, s+"routing:") hexpr(df, False) routing(framech, df, mph) print("") else: printred(tofromme, s+"chan:") hexpr(df, False) print("") if aprsok and (not obj or isnew or sendaprsnotnew) and (sendaprsmqtt or not(ord(mph["flags"])>>4)&1) and nodes[hwid][NODELAT]!=0.0 and nodes[hwid][NODELONG]!=0.0: # pos ok sendaprs(obj, hex(hwid)[2:11].zfill(8), nodes[hwid][NODELAT], nodes[hwid][NODELONG], alt, speed, dir, ord(mph["relaynode"]), ord(mph["flags"]), cleanaprs(nodes[hwid][NODELONGNAME]), cleanaprs(nodes[hwid][NODESHORTNAME]), cleanaprs(nodes[hwid][NODEMAC]), direct, nodes[hwid][NODEDIRECTCNT], nodes[hwid][NODECNT]) save(nodes) print("aprs:", hex(hwid)[2:11].zfill(8), cleanaprs(nodes[hwid][NODELONGNAME])) if repeatok and obj: repeater(mph, obj["snr"], isnew) if framech<0: print(s, end=" ") if framech==-1: print("unknown decrypt hash:", end="") elif framech==-2: print("unknown frame:", end="") if framech<0: hexpr(df, False) print("") mymac=revhex(MYMAC) nodes={} mids=[] loadn() #print(nodes) findhearsme() print(len(nodes), "nodes loaded, meshsize:",meshsize()) dcdtab={} txque=[] now=int(time.time()) fastticker=0.0 nodeinfotime=now #-10000 postime=now #-100000 teletime=now #-6*60-50 neighbortime=now #-3000 seconds=0 midnum=randint(0, (1<<32)-1) sendaprsnotnew=False sendaprsmqtt=False hearcnt=0 #util=0 #utiltime=now txtime=0 txtimetime=now uptimestart=now channels=[] for i in CHANNAMES: channels.append({"band":CHANNAMES.index(i), "aeskey":b64decode("1PG7OiApB1nwvP+rz05pAQ==".encode('ascii'))}) channels.append({"band":CHANNAMES.index(i), "aeskey":b64decode("".encode('ascii'))}) for i in channels: i.update({"channelhash":makechanhash(i)}) # redundant but faster print("###ct:",i) while True: rxe, txe, xxe=select.select([sock.fileno()],[],[],TICKERTIME) # if rxe==[]: print("sel", rxe) nowf=time.time() now=int(nowf) if sock.fileno() in rxe: # select event received frame ready data, addr=sock.recvfrom(1500) try: obj = json.loads(data.decode()) except: print("json decode error") else: sf=0 if "sf" in obj: sf=obj["sf"] if abs(sf)>12: sf=0 if not sf in dcdtab: dcdtab[sf]=[0,0,0,0,0] if "payload" in obj: try: payload=b64decode(obj["payload"]) # base64 coded frame except: print("base64 decode error") else: if "crc" in obj and obj["crc"]==1 and len(payload)>=16: decodemesh(payload, obj) # important!!!!!!: always check for crc ok before use if sf and "duration" in obj and "preamb" in obj: dcdtab[sf][DCDTABDCDSUM]+=obj["duration"]+obj["preamb"] # sum dcd time if dcdtab[sf][DCDTABDCDSUMTIME]==0: dcdtab[sf][DCDTABDCDSUMTIME]=now # set sum start time elif sf and ("dcd" in obj): dcdtab[sf][DCDTABDCD]=obj["dcd"] # do we have dcd on this sf if nowf>=fastticker: fastticker=nowf+TICKERTIME # print("dcd:",dcd) chanfree=True if LORASF in dcdtab: if dcdtab[LORASF][DCDTABDCD]: # dcd on this sf if dcdtab[LORASF][DCDTABRANDOM]==0: # dcd random time start dcdtab[LORASF][DCDTABRANDOM]=randint(0, MAXRANDOM) dcdtab[LORASF][DCDTABTIMEOUT]=now+DCDTIMELIMIT # channel busy, start random time for tx chanfree=dcdtab[LORASF][DCDTABRANDOM]<=0 or dcdtab[LORASF][DCDTABTIMEOUT]0: uti=max(0.0, min(100.0, dcdtab[11][DCDTABDCDSUM]*0.1/uti)) else: uti=0 dcdtab[11][DCDTABDCDSUM]=0 dcdtab[11][DCDTABDCDSUMTIME]=now txi=now-txtimetime if txi>0: txi=max(0.0, min(100.0, txtime*0.1/txi)) txtime=0 txtimetime=now else:txi=0 print("util:", uti, "%", " airtime:",txi) ut=max(0, now-uptimestart) buildframe(MAINCH, BROADCAST, 5, False, True, False, MAINLORACH, encodetelemetry(uti, txi, ut, now)) elif neighbortime + scaleback(NEIGHBORINTERVALL)