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="OE5DXL" # for aprs output LAT=48.25152 LONG=13.03720 ALT=354 SYMB="M," # for aprs output JSONPORT=5200 # 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) POSINTERVALL=25*60 NODEINFOINTERVALL=35*60 TELEINTERVALL=43*60 NEIGHBORINTERVALL=37*60 MYMAC='00606500ed0e' LORASF=11 LORABW=250000 MYKEY="078755135d40e81b35a26b89b93087c97944bcd16aa06ae7026a24c3e19bed03" 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")) sock.sendto(json.dumps({"payload":s, "sf":int(sf), "cr":cr, "bw":int(bw), "preamb":preamb}).encode("ascii"), TXIP) 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 sendque(): # send not broadcast first to avoid retries for too long ack delay now=time.time() 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, data): nh=bytes([]) if machextobytes("ffffffff")==dest: wantack=False # for safety elif usenexthop: nh=bestnextnode(dest) viamttq=False src=mymac[0:4] hash=channels[chan]["channelhash"] 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]]), '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) #08 03 12 0d 1cc29900 15 07c551a0 2801 #08 03 12 25 0d 0000361c 150000de07 18cb04 259c4fb768 2802 589c01 7800 8001 f09dd601 9801 0ab8010e4800{ 1 5}{ 2 5}{ 3 0}{ 4 5}{ 5 0}{ 11 0}{ 15 0}{ 16 0}{ 19 5} sats: 214{ 0 1} unknown pos tag: 0 #nodeinfo:08 04 12 4a 0a 09 216138346335323730 12 0b 416e647265615f35323830 1a 04 35796170 22 06 84cca84c5270 28 05 42 20 6800fc4d33ab3e7ddb6675b2d45d2147bc2ac7592b44a600d85fbfc36e154c3e 4801 hw:[!a84c5270] n:[Andrea_5280] sn:[5yap] hw6:84cca84c5270 # ll tt ll ll tt ll tt ll tt ll #define meshtastic_User_id_tag 1 #define meshtastic_User_long_name_tag 2 #define meshtastic_User_short_name_tag 3 #define meshtastic_User_macaddr_tag 4 #define meshtastic_User_hw_model_tag 5 #define meshtastic_User_is_licensed_tag 6 #define meshtastic_User_role_tag 7 #define meshtastic_User_public_key_tag 8 #define meshtastic_User_is_unmessagable_tag 9 #text:[08][01][12][06]Servus #rx:A2EB4098 dst:FFFFFFFF mid:CE78364 [OE5DMO] 1.0dB -31Hz sf:11 rn:0c 1/7 hash:08 nh:00 text:[08][01][12]JSo, gerade Meshcore zum ersten mal ausprobiert: Mausetot in Steyr, 0 NodesH[00] 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], '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, 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): #08 46 1801 4802 buildframe(ch, dst, hops, True, True, False, 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"]) # flags=0xe5 # mph["dest"]=bytes([1,2,3,4]) # mph["dest"]=mymac[0:4] # print("####sendtra:",mph["dest"],mymac[0:4],mph["dest"]==mymac[0:4]) 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:",tg) 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, encodetext(txt)) print("++++++++ sendtext:"+bytestohex(adr)+":"+txt) def sendneighborinfo(): 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, 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 #08 46 12 5d 0a 1c d01ef635 707f1ca1 38e94752 9d156e77 e04304b5 abbf89fd 0c494ada 12 3d (b7 ffffffffffffffff 01) fe ffffffff ffffffff 01) 02 c7 ffffffffffffffff 01 b8 ffffffffffffffff 01 d4 ffffffffffffffff 01 fc ffffffffffffffff 01 18014802 #08 46 12 9f 010a1c915941301cc9 3cb0ffffffff9ca2c54308c235c275610560643f63da 12 3e 02c4ffffffffffffffff0180ffffffffffffffff01cdffffffffffffffff01eeffffffffffffffff01d3ffffffffffffffff01c2ffffffffffffffff01191a14643f63da972ee5abd49eb71d1a23d5b2ee910870222918f8ffffffffffffffff01c5ffffffffffffffff01c4ffffffffffffffff01e9ffffffffffffffff013574f4c7014801 #RX:direct heard A84C5270 dst:849A0518 mid:B5EC5CC5 [Andrea_5280] 9.6dB 6886Hz sf:11 rn:70 5/5 WantAck hash:08 nh:00 traceroute:[0846121d 0a 0c 28d4 3c43a51f d5 23ffffffff 120d 000680ffffffffffffffff0102 35 80810a99 4801] forw:433CD428(GW Spielberg) 23D51FA5( AT Untersberg www.rxtx.at) FFFFFFFF snr: len not mod 10: 13 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=" ") #define meshtastic_DeviceMetrics_battery_level_tag 1 #define meshtastic_DeviceMetrics_voltage_tag 2 #define meshtastic_DeviceMetrics_channel_utilization_tag 3 #define meshtastic_DeviceMetrics_air_util_tx_tag 4 #define meshtastic_DeviceMetrics_uptime_seconds_tag 5 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=" ") #define meshtastic_EnvironmentMetrics_temperature_tag 1 #define meshtastic_EnvironmentMetrics_relative_humidity_tag 2 #define meshtastic_EnvironmentMetrics_barometric_pressure_tag 3 #define meshtastic_EnvironmentMetrics_gas_resistance_tag 4 #define meshtastic_EnvironmentMetrics_voltage_tag 5 #define meshtastic_EnvironmentMetrics_current_tag 6 #define meshtastic_EnvironmentMetrics_iaq_tag 7 #define meshtastic_EnvironmentMetrics_distance_tag 8 #define meshtastic_EnvironmentMetrics_lux_tag 9 #define meshtastic_EnvironmentMetrics_white_lux_tag 10 #define meshtastic_EnvironmentMetrics_ir_lux_tag 11 #define meshtastic_EnvironmentMetrics_uv_lux_tag 12 #define meshtastic_EnvironmentMetrics_wind_direction_tag 13 #define meshtastic_EnvironmentMetrics_wind_speed_tag 14 #define meshtastic_EnvironmentMetrics_weight_tag 15 #define meshtastic_EnvironmentMetrics_wind_gust_tag 16 #define meshtastic_EnvironmentMetrics_wind_lull_tag 17 #define meshtastic_EnvironmentMetrics_radiation_tag 18 #define meshtastic_EnvironmentMetrics_rainfall_1h_tag 19 #define meshtastic_EnvironmentMetrics_rainfall_24h_tag 20 #define meshtastic_EnvironmentMetrics_soil_moisture_tag 21 #define meshtastic_EnvironmentMetrics_soil_temperature_tag 22 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") # else: print(" routing tag:", tg, end="") #routing: 08 05 12 02 18 00 35 d956fe38 4801 # 08 05 12 02 18 08 35 aef1d8a2 #routing: 08 05 12 02 18 08 35 b3c45ffa 4800 08 05 12 02 18 08 35 b3c45ffa 4800 #routing: 08 05 12 02 18 08 35 40dcdee9 4800+++++r1: 6 +++++r2: 7 #0805120218083540dcdee94800 #routing: 08 05 12 02 18 08 35 d33d6a08 # 08 05 12 02 18 08 35 d33d6a08 #routing: 08 05 12 02 18 00 35 55bed75b 4800routing-unmessageable: 0 #routing:[08 05 12 02 18 08 35 66dfb96c] 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("") #nodeinfo:08 04 12 4a 0a 09 216138346335323730 12 0b 416e647265615f35323830 1a 04 35796170 22 06 84cca84c5270 28 05 42 20 6800fc4d33ab3e7ddb6675b2d45d2147bc2ac7592b44a600d85fbfc36e154c3e 4801 hw:[!a84c5270] n:[Andrea_5280] sn:[5yap] hw6:84cca84c5270 # ll tt ll ll tt ll tt ll tt ll 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): #text:[08][01][12][04]Test=[D9]V[FE]8H[01] #text:[08][01][12][04]TestH[01] #text:[Wer?][08 01 12 04 5765723f 3d f702a178] # [08 01 12 04 f09f918d 3d 70447f0b 45 01000000 4801] """ p=0 if len(df)>3: p=df[3]+4 if len(df)=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 makechanname(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 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"]=makechanname(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 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 # print("tx:",hex(int32(0, mph["sender"]))[2:11].upper(), df) # print(s) # print("chhh:", mph['channelHash']) # chash=8 # for i in range(len(df)): print(hex(df[i]^mph['data'][i])[2:4].zfill(2), end="") # print("") 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"]) # df = hexstrtobytes("08011204546573743DD957FE384801") # df = hexstrtobytes("080312250deaf37b1c15b5a8310818b20325532dc968280258ef0178258001b881ab02980108b80120") # df = hexstrtobytes("0843121c0da542cb681215085d150c0283401dd406a04125ba239e4028ad840d4800") # df = hexstrtobytes("084312280dd44ccb681a210d8174674115fb9387421dd2ef714425ba9db4442d8fc2753d35cdcc4cbe3886014800") # df = hexstrtobytes("084312110dca62cb682a0a2d4260e53d35000000bf") # df = hexstrtobytes("084315c9687768276ee987df96") # df=hexstrtobytes("0846125d0a1cd01ef635707f1ca138e947529d156e77e04304b5abbf89fd0c494ada123db7ffffffffffffffff01feffffffffffffffff0102c7ffffffffffffffff01b8ffffffffffffffff01d4ffffffffffffffff01fcffffffffffffffff0118014802") # df=hexstrtobytes("0846129f010a1c915941301cc93cb0ffffffff9ca2c54308c235c275610560643f63da123e02c4ffffffffffffffff0180ffffffffffffffff01cdffffffffffffffff01eeffffffffffffffff01d3ffffffffffffffff01c2ffffffffffffffff01191a14643f63da972ee5abd49eb71d1a23d5b2ee910870222918f8ffffffffffffffff01c5ffffffffffffffff01c4ffffffffffffffff01e9ffffffffffffffff013574f4c7014801") # df=hexstrtobytes("084612660a1c500a0d03102f0e7438e94752c0d50913ffffffffd58c88c2439e74131246b8ffffffffffffffff01d3ffffffffffffffff01fbffffffffffffffff01efffffffffffffffff0180ffffffffffffffff01e1ffffffffffffffff01d1ffffffffffffffff0118014802") # df=hexstrtobytes("0846124b0a1c915941309adba63be87a399bca6b3c62300a9b840c494adaa51fd523122b14c8ffffffffffffffff01d5ffffffffffffffff01bfffffffffffffffff0111c0ffffffffffffffff010018014802") # df=hexstrtobytes("084612330a1038d85edae04304b5ee910870a51fd523121f13d9ffffffffffffffff01efffffffffffffffff01e3ffffffffffffffff0118014802") # if obj: # mph["dest"]=mymac[0:4] # df=hexstrtobytes("084612630a10439e741365ce9d938887b9ef4bef44611220ddffffffffffffffff0113c5ffffffffffffffff0119b8ffffffffffffffff011a148887b9efbc0672b2105f63da65ce9d93439e741322170feaffffffffffffffff010304e1ffffffffffffffff013599e76e164801") # df=hexstrtobytes("084618014802") # df = hexstrtobytes("08051202180835b3c45ffa4800") # mph["dest"]=bytes([1,2,3,4]) # df=hexstrtobytes("084618014802") # df=hexstrtobytes("084612130a0470524ca8120b14d1ffffffffffffffff013592242d0d4800") # df=hexstrtobytes("0846122c120af6ffffffffffffffff011a08d49eb71dee9108702214cfffffffffffffffff01d1ffffffffffffffff0135095d5a714801") # df=hexstrtobytes("084612250a0ce04304b5ee910870a51fd523121513efffffffffffffffff01e3ffffffffffffffff0118014802") # if obj: df=hexstrtobytes("084612410a0c61b56241cc1239a0a51fd523121ffeffffffffffffffff01c6ffffffffffffffff0115deffffffffffffffff011a040c494ada220acbffffffffffffffff0135ac47b71b4800") # df=hexstrtobytes("084612a30a1810e83d43b853c6c63cf1693338e94752e4288c0864e906bd123dbdffffffffffffffff0111e0ffffffffffffffff01daffffffffffffffff01fcffffffffffffffff01d0ffffffffffffffff01c0ffffffffffffffff011a1413ae86e9ffffffff68875a4365ce9d93ee9108702232c9ffffffffffffffff0180ffffffffffffffff01fdffffffffffffffff01fcffffffffffffffff01feffffffffffffffff013526ac87864800") repeatok=True if framech>=0 and (len(df)==0 or df[0]!=8): framech=-2 # bad frame or decryption failed if framech>=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("") #private_key = curve.generatePrivateKey(bytes.fromhex(MYKEY)) #public_key = message = curve.generatePublicKey(private_key) #randm32 = os.urandom(32) #test_private_key = curve.generatePrivateKey(randm32) #test_public_key = message = curve.generatePublicKey(private_key) #agreement = curve.calculateAgreement(private_key, public_key) #print("x1:",bytestohex(x25519(int.from_bytes(bytes.fromhex("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"), "little"), 9).to_bytes(32, "little"))) #r=x25519(int.from_bytes(bytes.fromhex(MYKEY), "little"), 9) #print("x2:", r, bytestohex(r.to_bytes(32, "little"))) #r=int.from_bytes(bytes.fromhex(MYKEY)) #print("priv:", bytestohex(private_key), bytestohex(public_key), bytestohex(agreement)) #priv: 08755135d40e81b35a26b89b93087c97944bcd16aa06ae726a24c3e19bed43 d49ab91a7cbbd6439ceabf8f998fef4bff810fd9118624141aba4336fd11039 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=[] channels.append({"band":CHANNAMES.index("LongFast"), "aeskey":b64decode("1PG7OiApB1nwvP+rz05pAQ==".encode('ascii'))}) channels.append({"band":CHANNAMES.index("LongFast"), "aeskey":b64decode("".encode('ascii'))}) for i in channels: i.update({"channelhash":makechanhash(i)}) # redundant but faster print("###ct:",channels) 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, encodetelemetry(uti, txi, ut, now)) elif neighbortime + scaleback(NEIGHBORINTERVALL) " + str(destID) + " " + str(text_payload) else: data = "TEXT_MESSAGE_APP " + str(sourceID) + " -> " + str(destID) + " " + "DIRECT MESSAGE CENSORED" case 2 : # REMOTE_HARDWARE_APP data = "REMOTE_HARDWARE_APP To be implemented" case 3 : # POSITION_APP pos = mesh_pb2.Position() pos.ParseFromString(data.payload) latitude = pos.latitude_i * 1e-7 longitude = pos.longitude_i * 1e-7 data="POSITION_APP " + str(sourceID) + " -> " + str(destID) + " " + str(latitude) +"," + str(longitude) case 4 : # NODEINFO_APP info = mesh_pb2.User() try: info.ParseFromString(data.payload) except: print("Unknown Nodeinfo_app parse error") data = "NODEINFO_APP " + str(info) case 5 : # ROUTING_APP rtng = mesh_pb2.Routing() rtng.ParseFromString(data.payload) data = "TELEMETRY_APP " + str(rtng) case 6 : # ADMIN_APP admn = admin_pb2.AdminMessage() admn.ParseFromString(data.payload) data = "ADMIN_APP " + str(admn) case 7 : # TEXT_MESSAGE_COMPRESSED_APP data = "TEXT_MESSAGE_COMPRESSED_APP To be implemented" case 10 : # DETECTION_SENSOR_APP data = "DETECTION_SENSOR_APP To be implemented" case 32 : # REPLY_APP data = "REPLY_APP To be implemented" case 33 : # IP_TUNNEL_APP data = "IP_TUNNEL_APP To be implemented" case 34 : # PAXCOUNTER_APP data = "PAXCOUNTER_APP To be implemented" case 64 : # SERIAL_APP print(" ") case 65 : # STORE_FORWARD_APP sfwd = mesh_pb2.StoreAndForward() sfwd.ParseFromString(data.payload) data = "STORE_FORWARD_APP " + str(sfwd) case 67 : # TELEMETRY_APP env = telemetry_pb2.Telemetry() env.ParseFromString(data.payload) data = "TELEMETRY_APP " + str(env) case 68 : # ZPS_APP z_info = mesh_pb2.zps() z_info.ParseFromString(data.payload) data = "ZPS_APP " + str(z_info) case 69 : # SIMULATOR_APP data = "SIMULATOR_APP To be implemented" case 70 : # TRACEROUTE_APP trct= mesh_pb2.RouteDiscovery() trct.ParseFromString(data.payload) data = "TRACEROUTE_APP " + str(sourceID) + " -> " + str(destID) + " " + str(trct) case 71 : # NEIGHBORINFO_APP ninfo = mesh_pb2.NeighborInfo() ninfo.ParseFromString(data.payload) data = "NEIGHBORINFO_APP " + str(ninfo) case 72 : # ATAK_PLUGIN data = "ATAK_PLUGIN To be implemented" case 73 : # MAP_REPORT_APP mrpt = mesh_pb2.MapReport() mrpt.ParseFromString(data.payload) data = "MAP_REPORT_APP " + str(mrpt) case 74 : # POWERSTRESS_APP data = "POWERSTRESS_APP To be implemented" case 256 : # PRIVATE_APP data = "PRIVATE_APP To be implemented" case 257 : # ATAK_FORWARDER data = "ATAK_FORWARD To be implemented" case _: data = "UNKNOWN PROTOBUF" meshtastic_HardwareModel_UNSET = 0, meshtastic_HardwareModel_TLORA_V2 = 1, meshtastic_HardwareModel_TLORA_V1 = 2, meshtastic_HardwareModel_TLORA_V2_1_1P6 = 3, meshtastic_HardwareModel_TBEAM = 4, meshtastic_HardwareModel_HELTEC_V2_0 = 5, meshtastic_HardwareModel_TBEAM_V0P7 = 6, meshtastic_HardwareModel_T_ECHO = 7, meshtastic_HardwareModel_TLORA_V1_1P3 = 8, meshtastic_HardwareModel_RAK4631 = 9, meshtastic_HardwareModel_HELTEC_V2_1 = 10, meshtastic_HardwareModel_HELTEC_V1 = 11, meshtastic_HardwareModel_LILYGO_TBEAM_S3_CORE = 12, meshtastic_HardwareModel_RAK11200 = 13, meshtastic_HardwareModel_NANO_G1 = 14, meshtastic_HardwareModel_TLORA_V2_1_1P8 = 15, meshtastic_HardwareModel_TLORA_T3_S3 = 16, meshtastic_HardwareModel_NANO_G1_EXPLORER = 17, meshtastic_HardwareModel_NANO_G2_ULTRA = 18, meshtastic_HardwareModel_LORA_TYPE = 19, meshtastic_HardwareModel_WIPHONE = 20, meshtastic_HardwareModel_WIO_WM1110 = 21, meshtastic_HardwareModel_RAK2560 = 22, meshtastic_HardwareModel_HELTEC_HRU_3601 = 23, meshtastic_HardwareModel_HELTEC_WIRELESS_BRIDGE = 24, meshtastic_HardwareModel_STATION_G1 = 25, meshtastic_HardwareModel_RAK11310 = 26, meshtastic_HardwareModel_SENSELORA_RP2040 = 27, meshtastic_HardwareModel_SENSELORA_S3 = 28, meshtastic_HardwareModel_CANARYONE = 29, meshtastic_HardwareModel_RP2040_LORA = 30, meshtastic_HardwareModel_STATION_G2 = 31, meshtastic_HardwareModel_LORA_RELAY_V1 = 32, meshtastic_HardwareModel_NRF52840DK = 33, meshtastic_HardwareModel_PPR = 34, meshtastic_HardwareModel_GENIEBLOCKS = 35, meshtastic_HardwareModel_NRF52_UNKNOWN = 36, meshtastic_HardwareModel_PORTDUINO = 37, meshtastic_HardwareModel_ANDROID_SIM = 38, meshtastic_HardwareModel_DIY_V1 = 39, meshtastic_HardwareModel_NRF52840_PCA10059 = 40, meshtastic_HardwareModel_DR_DEV = 41, meshtastic_HardwareModel_M5STACK = 42, meshtastic_HardwareModel_HELTEC_V3 = 43, meshtastic_HardwareModel_HELTEC_WSL_V3 = 44, meshtastic_HardwareModel_BETAFPV_2400_TX = 45, meshtastic_HardwareModel_BETAFPV_900_NANO_TX = 46, meshtastic_HardwareModel_RPI_PICO = 47, meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER = 48, meshtastic_HardwareModel_HELTEC_WIRELESS_PAPER = 49, meshtastic_HardwareModel_T_DECK = 50, meshtastic_HardwareModel_T_WATCH_S3 = 51, meshtastic_HardwareModel_PICOMPUTER_S3 = 52, meshtastic_HardwareModel_HELTEC_HT62 = 53, meshtastic_HardwareModel_EBYTE_ESP32_S3 = 54, meshtastic_HardwareModel_ESP32_S3_PICO = 55, meshtastic_HardwareModel_CHATTER_2 = 56, meshtastic_HardwareModel_HELTEC_WIRELESS_PAPER_V1_0 = 57, meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V1_0 = 58, meshtastic_HardwareModel_UNPHONE = 59, meshtastic_HardwareModel_TD_LORAC = 60, meshtastic_HardwareModel_CDEBYTE_EORA_S3 = 61, meshtastic_HardwareModel_TWC_MESH_V4 = 62, meshtastic_HardwareModel_NRF52_PROMICRO_DIY = 63, meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO = 64, meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3 = 65, meshtastic_HardwareModel_HELTEC_VISION_MASTER_T190 = 66, meshtastic_HardwareModel_HELTEC_VISION_MASTER_E213 = 67, meshtastic_HardwareModel_HELTEC_VISION_MASTER_E290 = 68, meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 = 69, meshtastic_HardwareModel_SENSECAP_INDICATOR = 70, meshtastic_HardwareModel_TRACKER_T1000_E = 71, meshtastic_HardwareModel_RAK3172 = 72, meshtastic_HardwareModel_WIO_E5 = 73, meshtastic_HardwareModel_RADIOMASTER_900_BANDIT = 74, meshtastic_HardwareModel_ME25LS01_4Y10TD = 75, meshtastic_HardwareModel_RP2040_FEATHER_RFM95 = 76, meshtastic_HardwareModel_M5STACK_COREBASIC = 77, meshtastic_HardwareModel_M5STACK_CORE2 = 78, meshtastic_HardwareModel_RPI_PICO2 = 79, meshtastic_HardwareModel_M5STACK_CORES3 = 80, meshtastic_HardwareModel_SEEED_XIAO_S3 = 81, meshtastic_HardwareModel_MS24SF1 = 82, meshtastic_HardwareModel_TLORA_C6 = 83, meshtastic_HardwareModel_WISMESH_TAP = 84, meshtastic_HardwareModel_ROUTASTIC = 85, meshtastic_HardwareModel_MESH_TAB = 86, meshtastic_HardwareModel_MESHLINK = 87, meshtastic_HardwareModel_XIAO_NRF52_KIT = 88, meshtastic_HardwareModel_THINKNODE_M1 = 89, meshtastic_HardwareModel_THINKNODE_M2 = 90, meshtastic_HardwareModel_T_ETH_ELITE = 91, meshtastic_HardwareModel_HELTEC_SENSOR_HUB = 92, meshtastic_HardwareModel_RESERVED_FRIED_CHICKEN = 93, meshtastic_HardwareModel_HELTEC_MESH_POCKET = 94, meshtastic_HardwareModel_SEEED_SOLAR_NODE = 95, meshtastic_HardwareModel_NOMADSTAR_METEOR_PRO = 96, meshtastic_HardwareModel_CROWPANEL = 97, meshtastic_HardwareModel_LINK_32 = 98, meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 = 99, meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK = 100, meshtastic_HardwareModel_QWANTZ_TINY_ARMS = 101, meshtastic_HardwareModel_T_DECK_PRO = 102, meshtastic_HardwareModel_T_LORA_PAGER = 103, meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104, meshtastic_HardwareModel_WISMESH_TAG = 105, meshtastic_HardwareModel_RAK3312 = 106, meshtastic_HardwareModel_THINKNODE_M5 = 107, meshtastic_HardwareModel_HELTEC_MESH_SOLAR = 108, meshtastic_HardwareModel_T_ECHO_LITE = 109, meshtastic_HardwareModel_PRIVATE_HW = 255 case PB_WT_VARINT: return pb_skip_varint(stream); case PB_WT_64BIT: return pb_read(stream, NULL, 8); case PB_WT_STRING: return pb_skip_string(stream); case PB_WT_32BIT: return pb_read(stream, NULL, 4); case PB_WT_PACKED: /* Calling pb_skip_field with a PB_WT_PACKED is an error. * Explicitly handle this case and fallthrough to default to avoid * compiler warnings. */ default: PB_RETURN_ERROR(stream, "invalid wire_type"); PB_WT_VARINT = 0, PB_WT_64BIT = 1, PB_WT_STRING = 2, PB_WT_32BIT = 5, PB_WT_PACKED = 255 /* PB_WT_PACKED is internal marker for packed arrays. */ case PB_WT_VARINT: *size = 0; do { (*size)++; if (*size > max_size) PB_RETURN_ERROR(stream, "varint overflow"); if (!pb_read(stream, buf, 1)) return false; } while (*buf++ & 0x80); return true; d 15 18 25 5 5 0 5 bool checkreturn pb_skip_varint(pb_istream_t *stream) { pb_byte_t byte; do { if (!pb_read(stream, &byte, 1)) return false; } while (byte & 0x80); return true; } #define meshtastic_Telemetry_time_tag 1 #define meshtastic_Telemetry_device_metrics_tag 2 #define meshtastic_Telemetry_environment_metrics_tag 3 #define meshtastic_Telemetry_air_quality_metrics_tag 4 #define meshtastic_Telemetry_power_metrics_tag 5 #define meshtastic_Telemetry_local_stats_tag 6 #define meshtastic_Telemetry_health_metrics_tag 7 #define meshtastic_Telemetry_host_metrics_tag 8 #define meshtastic_Nau7802Config_zeroOffset_tag 1 #define meshtastic_Nau7802Config_calibrationFactor_tag 2 #define meshtastic_DeviceMetrics_battery_level_tag 1 #define meshtastic_DeviceMetrics_voltage_tag 2 #define meshtastic_DeviceMetrics_channel_utilization_tag 3 #define meshtastic_DeviceMetrics_air_util_tx_tag 4 #define meshtastic_DeviceMetrics_uptime_seconds_tag 5 #define meshtastic_AirQualityMetrics_pm10_standard_tag 1 #define meshtastic_AirQualityMetrics_pm25_standard_tag 2 #define meshtastic_AirQualityMetrics_pm100_standard_tag 3 #define meshtastic_AirQualityMetrics_pm10_environmental_tag 4 #define meshtastic_AirQualityMetrics_pm25_environmental_tag 5 #define meshtastic_AirQualityMetrics_pm100_environmental_tag 6 #define meshtastic_AirQualityMetrics_particles_03um_tag 7 #define meshtastic_AirQualityMetrics_particles_05um_tag 8 #define meshtastic_AirQualityMetrics_particles_10um_tag 9 #define meshtastic_AirQualityMetrics_particles_25um_tag 10 #define meshtastic_AirQualityMetrics_particles_50um_tag 11 #define meshtastic_AirQualityMetrics_particles_100um_tag 12 #define meshtastic_AirQualityMetrics_co2_tag 13 #define meshtastic_AirQualityMetrics_co2_temperature_tag 14 #define meshtastic_AirQualityMetrics_co2_humidity_tag 15 #define meshtastic_AirQualityMetrics_form_formaldehyde_tag 16 #define meshtastic_AirQualityMetrics_form_humidity_tag 17 #define meshtastic_AirQualityMetrics_form_temperature_tag 18 #define meshtastic_AirQualityMetrics_pm40_standard_tag 19 #define meshtastic_AirQualityMetrics_particles_40um_tag 20 #define meshtastic_AirQualityMetrics_pm_temperature_tag 21 #define meshtastic_AirQualityMetrics_pm_humidity_tag 22 #define meshtastic_AirQualityMetrics_pm_voc_idx_tag 23 #define meshtastic_AirQualityMetrics_pm_nox_idx_tag 24 #define meshtastic_AirQualityMetrics_particles_tps_tag 25 : return useShortName ? "ShortT" : "ShortTurbo"; return useShortName ? "ShortS" : "ShortSlow"; return useShortName ? "ShortF" : "ShortFast"; return useShortName ? "MedS" : "MediumSlow"; return useShortName ? "MedF" : "MediumFast"; return useShortName ? "LongS" : "LongSlow"; return useShortName ? "LongF" : "LongFast"; return useShortName ? "LongM" : "LongMod"; case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: bw = (myRegion->wideLora) ? 1625.0 : 500; cr = 5; sf = 7; break; case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: bw = (myRegion->wideLora) ? 812.5 : 250; cr = 5; sf = 7; break; case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: bw = (myRegion->wideLora) ? 812.5 : 250; cr = 5; sf = 8; break; case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: bw = (myRegion->wideLora) ? 812.5 : 250; cr = 5; sf = 9; break; case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: bw = (myRegion->wideLora) ? 812.5 : 250; cr = 5; sf = 10; break; default: // Config_LoRaConfig_ModemPreset_LONG_FAST is default. Gracefully use this is preset is something illegal. bw = (myRegion->wideLora) ? 812.5 : 250; cr = 5; sf = 11; break; case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: bw = (myRegion->wideLora) ? 406.25 : 125; cr = 8; sf = 11; break; case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: bw = (myRegion->wideLora) ? 406.25 : 125; cr = 8; sf = 12; break; case meshtastic_Config_LoRaConfig_ModemPreset_VERY_LONG_SLOW: bw = (myRegion->wideLora) ? 203.125 : 62.5; cr = 8; sf = 12; break; #define meshtastic_NeighborInfo_init_default {0, 0, 0, 0, {meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default}} #define meshtastic_Neighbor_init_default {0, 0, 0, 0} RX: B03D32BC dst:4F9DDF27 mid:2EA58A90 [geekU] 2.3dB 89Hz sf:11 rn:0c 2/7 WantAck hash:08 nh:00 traceroute:[084612410a14ffffffff68ddeba2a4b65b43300a9b840c494ada 122980ffffffffffffffff01d5ffffffffffffffff01bfffffffffffffffff0118c3ffffffffffffffff01 18014803] forw:FFFFFFFF A2EBDD68(GridHop) 435BB6A4(DeadLink T5/VanGyver (S&F)) 849B0A30(DeadLink Base) DA4A490C(Kolomansberg) snr: len not mod10: 41 ?? -10.75 -16.25 -15.25 rx: B03D32BC dst:4F9DDF27 mid:2EA58A90 [geekU] -8.9dB 419Hz sf:11 rn:ee 0/7 WantAck hash:08 nh:00 traceroute:[084612540a1cffffffff68ddeba2a4b65b43300a9b840c494adaa51fd523ee910870123480ffffffffffffffff01d5ffffffffffffffff01bfffffffffffffffff0118c3ffffffffffffffff0103f5ffffffffffffffff0118014803] forw:FFFFFFFF A2EBDD68(GridHop) 435BB6A4(DeadLink T5/VanGyver (S&F)) 849B0A30(DeadLink Base) DA4A490C(Kolomansberg) 23D51FA5( AT Untersberg www.rxtx.at) 700891EE(SCHRDING ) snr: len not mod10: 52 ?? -10.75 -16.25 -15.25 -2.75 RX: DA627944 dst:433F12F4 mid:0DDD12D0 [LupusWorax Homebase] -10.9dB 833Hz sf:11 rn:91 6/7 WantAck hash:08 nh:00 traceroute:[08461209 0a0491594130 120111 18014802] forw:30415991( LoraMeshAustria Eibenberg) snr: len not mod10: 1 rx: DA627944 dst:433F12F4 mid:0DDD12D0 [LupusWorax Homebase] -10.0dB 415Hz sf:11 fec:1 rn:ee 5/7 WantAck hash:08 nh:00 traceroute:[08461217 0a0891594130ee910870 120b11e2ffffffffffffffff01 18014802] forw:30415991( LoraMeshAustria Eibenberg) 700891EE(SCHRDING ) snr: len not mod10: 11 -7.5 rx: DA627944 dst:433F12F4 mid:0DDD12D0 [LupusWorax Homebase] -13.6dB 305Hz sf:11 rn:f7 4/7 WantAck hash:08 nh:00 traceroute:[08461225 0a0c91594130ee910870f7af65c3 121511e2ffffffffffffffff01cbffffffffffffffff01 18014802] forw:30415991( LoraMeshAustria Eibenberg) 700891EE(SCHRDING ) C365AFF7(PV Test) snr: len not mod10: 21 -7.5 -13.25 rx: DA627944 dst:433F12F4 mid:0DDD12D0 [LupusWorax Homebase] 3.9dB 111Hz sf:11 rn:0c 4/7 WantAck hash:08 nh:00 traceroute:[08461225 0a0c91594130ee9108700c494ada 121511e2ffffffffffffffff01e4ffffffffffffffff01 18014802] forw:30415991( LoraMeshAustria Eibenberg) 700891EE(SCHRDING ) DA4A490C(Kolomansberg) snr: len not mod10: 21 -7.5 -7.0 rx: DA627944 dst:433F12F4 mid:0DDD12D0 [LupusWorax Homebase] 0.0dB 461Hz sf:11 rn:43 2/7 WantAck hash:08 nh:00 traceroute:[08461241 0a1491594130ee9108700c494adaa51fd523439e7413122911e2ffffffffffffffff01e4ffffffffffffffff01faffffffffffffffff01d7ffffffffffffffff0118014802] forw:30415991( LoraMeshAustria Eibenberg) 700891EE(SCHRDING ) DA4A490C(Kolomansberg) 23D51FA5( AT Untersberg www.rxtx.at) 13749E43(R_GAISBERG_PV) snr: len not mod10: 41 -7.5 -7.0 -1.5 -10.25 """ # text encrypted RX: 849A0518 dst:A84C5270 mid:990A8182 [GWOB 67MW] -12.6dB 431Hz sf:11 fec:7 rn:ee 2/5 WantAck hash:00 nh:00 unknown decrypt hash:[a29f68128600ec351403a094a447f6096a189f03cf8cb140] # ack RX:direct heard A84C5270 dst:849A0518 mid:C3E470C8 [Andrea_5280] 10.3dB 6912Hz sf:11 rn:70 5/5 hash:08 nh:00 routing:[0805120218003582810a994801]???: b'\x18\x00' mid:990A8182 unmes: 1 """ pub struct NeighborInfo { 5209 /// 5210 /// The node ID of the node sending info on its neighbors 5211 #[prost(uint32, tag="1")] 5212 pub node_id: u32, 5213 /// 5214 /// Field to pass neighbor info for the next sending cycle 5215 #[prost(uint32, tag="2")] 5216 pub last_sent_by_id: u32, 5217 /// 5218 /// Broadcast interval of the represented node (in seconds) 5219 #[prost(uint32, tag="3")] 5220 pub node_broadcast_interval_secs: u32, 5221 /// 5222 /// The list of out edges from this node 5223 #[prost(message, repeated, tag="4")] 5224 pub neighbors: ::prost::alloc::vec::Vec, 5225} 5226 /// A single edge in the mesh 5228#[allow(clippy::derive_partial_eq_without_eq)] 5229#[derive(Clone, Copy, PartialEq, ::prost::Message)] 5230pub struct Neighbor { 5231 /// 5232 /// Node ID of neighbor 5233 #[prost(uint32, tag="1")] 5234 pub node_id: u32, 5235 /// 5236 /// SNR of last heard message 5237 #[prost(float, tag="2")] 5238 pub snr: f32, 5239 /// 5240 /// Reception time (in secs since 1970) of last message that was last sent by this ID. 5241 /// Note: this is for local storage only and will not be sent out over the mesh. 5242 #[prost(fixed32, tag="3")] 5243 pub last_rx_time: u32, 5244 /// 5245 /// Broadcast interval of this neighbor (in seconds). 5246 /// Note: this is for local storage only and will not be sent out over the mesh. 5247 #[prost(uint32, tag="4")] 5248 pub node_broadcast_interval_secs: u32, 5249} 5250 """