build valid packets (hopefully)
This commit is contained in:
		| @@ -3,6 +3,8 @@ import threading | ||||
| import time | ||||
| import random | ||||
| from sched import scheduler | ||||
| from owrx.config import PropertyManager | ||||
| from owrx.version import openwebrx_version | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -10,7 +12,8 @@ logger = logging.getLogger(__name__) | ||||
| class PskReporter(object): | ||||
|     sharedInstance = None | ||||
|     creationLock = threading.Lock() | ||||
|     interval = 300 | ||||
|     interval = 60 | ||||
|     supportedModes = ["FT8", "FT4", "JT9", "JT65"] | ||||
|  | ||||
|     @staticmethod | ||||
|     def getSharedInstance(): | ||||
| @@ -22,6 +25,7 @@ class PskReporter(object): | ||||
|     def __init__(self): | ||||
|         self.spots = [] | ||||
|         self.spotLock = threading.Lock() | ||||
|         self.uploader = Uploader() | ||||
|         self.scheduler = scheduler(time.time, time.sleep) | ||||
|         self.scheduleNextUpload() | ||||
|         threading.Thread(target=self.scheduler.run).start() | ||||
| @@ -32,6 +36,8 @@ class PskReporter(object): | ||||
|         self.scheduler.enter(delay, 1, self.upload) | ||||
|  | ||||
|     def spot(self, spot): | ||||
|         if not spot["mode"] in PskReporter.supportedModes: | ||||
|             return | ||||
|         with self.spotLock: | ||||
|             self.spots.append(spot) | ||||
|  | ||||
| @@ -41,6 +47,131 @@ class PskReporter(object): | ||||
|             self.spots = [] | ||||
|  | ||||
|         if spots: | ||||
|             logger.debug("would now upload %i spots", len(spots)) | ||||
|             self.uploader.upload(spots) | ||||
|  | ||||
|         self.scheduleNextUpload() | ||||
|  | ||||
|  | ||||
| class Uploader(object): | ||||
|     receieverDelimiter = [0x99, 0x92] | ||||
|     senderDelimiter = [0x99, 0x93] | ||||
|  | ||||
|     def __init__(self): | ||||
|         self.sequence = 0 | ||||
|  | ||||
|     def upload(self, spots): | ||||
|         logger.debug("would now upload %i spots", len(spots)) | ||||
|         for packet in self.getPackets(spots): | ||||
|             l = int.from_bytes(packet[2:4], "big") | ||||
|             logger.debug("packet length: %i; indicated length: %i", len(packet), l) | ||||
|             logger.debug(packet) | ||||
|             # TODO actually send the packet | ||||
|  | ||||
|     def getPackets(self, spots): | ||||
|         encoded = [self.encodeSpot(spot) for spot in spots] | ||||
|  | ||||
|         def chunks(l, n): | ||||
|             """Yield successive n-sized chunks from l.""" | ||||
|             for i in range(0, len(l), n): | ||||
|                 yield l[i : i + n] | ||||
|  | ||||
|         rHeader = self.getReceiverInformationHeader() | ||||
|         rInfo = self.getReceiverInformation() | ||||
|         sHeader = self.getSenderInformationHeader() | ||||
|  | ||||
|         packets = [] | ||||
|         # 50 seems to be a safe bet | ||||
|         for chunk in chunks(encoded, 50): | ||||
|             sInfoLength = sum(map(len, chunk)) | ||||
|             length = sInfoLength + 16 + len(rHeader) + len(sHeader) + len(rInfo) + 4 | ||||
|             header = self.getHeader(length) | ||||
|             packets.append( | ||||
|                 header | ||||
|                 + rHeader | ||||
|                 + sHeader | ||||
|                 + rInfo | ||||
|                 + bytes(Uploader.senderDelimiter) | ||||
|                 + sInfoLength.to_bytes(2, "big") | ||||
|                 + b"".join(chunk) | ||||
|             ) | ||||
|  | ||||
|         return packets | ||||
|  | ||||
|     def getHeader(self, length): | ||||
|         self.sequence += 1 | ||||
|         return bytes( | ||||
|             # protocol version | ||||
|             [0x00, 0x0A] | ||||
|             + list(length.to_bytes(2, "big")) | ||||
|             + list(int(time.time()).to_bytes(4, "big")) | ||||
|             + list(self.sequence.to_bytes(4, "big")) | ||||
|             + list((id(self) & 0xFFFFFFFF).to_bytes(4, "big")) | ||||
|         ) | ||||
|  | ||||
|     def encodeString(self, s): | ||||
|         return [len(s)] + list(s.encode("utf-8")) | ||||
|  | ||||
|     def encodeSpot(self, spot): | ||||
|         return bytes( | ||||
|             self.encodeString(spot["callsign"]) | ||||
|             + list(spot["freq"].to_bytes(4, "big")) | ||||
|             + list(int(spot["db"]).to_bytes(1, "big", signed=True)) | ||||
|             + self.encodeString(spot["mode"]) | ||||
|             + self.encodeString(spot["locator"]) | ||||
|             # informationsource. 1 means "automatically extracted | ||||
|             + [0x01] | ||||
|             + list(int(spot["timestamp"] / 1000).to_bytes(4, "big")) | ||||
|         ) | ||||
|  | ||||
|     def getReceiverInformationHeader(self): | ||||
|         return bytes( | ||||
|             # id, length | ||||
|             [0x00, 0x03, 0x00, 0x24] | ||||
|             + Uploader.receieverDelimiter | ||||
|             # number of fields | ||||
|             + [0x00, 0x03, 0x00, 0x00] | ||||
|             # receiverCallsign | ||||
|             + [0x80, 0x02, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F] | ||||
|             # receiverLocator | ||||
|             + [0x80, 0x04, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F] | ||||
|             # decodingSoftware | ||||
|             + [0x80, 0x08, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F] | ||||
|             # padding | ||||
|             + [0x00, 0x00] | ||||
|         ) | ||||
|  | ||||
|     def getReceiverInformation(self): | ||||
|         pm = PropertyManager.getSharedInstance() | ||||
|         callsign = pm["pskreporter_callsign"] | ||||
|         locator = pm["receiver_qra"] | ||||
|         decodingSoftware = "OpenWebRX " + openwebrx_version | ||||
|         body = [b for s in [callsign, locator, decodingSoftware] for b in self.encodeString(s)] | ||||
|         body = self.pad(body, 4) | ||||
|         body = bytes(Uploader.receieverDelimiter + list((len(body) + 4).to_bytes(2, "big")) + body) | ||||
|         return body | ||||
|  | ||||
|     def getSenderInformationHeader(self): | ||||
|         return bytes( | ||||
|             # id, length | ||||
|             [0x00, 0x02, 0x00, 0x3C] | ||||
|             + Uploader.senderDelimiter | ||||
|             # number of fields | ||||
|             + [0x00, 0x07] | ||||
|             # senderCallsign | ||||
|             + [0x80, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F] | ||||
|             # frequency | ||||
|             + [0x80, 0x05, 0x00, 0x04, 0x00, 0x00, 0x76, 0x8F] | ||||
|             # sNR | ||||
|             + [0x80, 0x05, 0x00, 0x01, 0x00, 0x00, 0x76, 0x8F] | ||||
|             # mode | ||||
|             + [0x80, 0x0A, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F] | ||||
|             # senderLocator | ||||
|             + [0x80, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F] | ||||
|             # informationSource | ||||
|             + [0x80, 0x0B, 0x00, 0x01, 0x00, 0x00, 0x76, 0x8F] | ||||
|             # flowStartSeconds | ||||
|             + [0x00, 0x96, 0x00, 0x04] | ||||
|         ) | ||||
|  | ||||
|     def pad(self, bytes, l): | ||||
|         return bytes + [0x00 for _ in range(0, -1 * len(bytes) % l)] | ||||
|   | ||||
							
								
								
									
										10
									
								
								owrx/wsjt.py
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								owrx/wsjt.py
									
									
									
									
									
								
							| @@ -282,7 +282,7 @@ class WsjtParser(object): | ||||
|                 decoder = Jt9Decoder() | ||||
|             else: | ||||
|                 decoder = WsprDecoder() | ||||
|             out = decoder.parse(msg) | ||||
|             out = decoder.parse(msg, self.dial_freq) | ||||
|             if "mode" in out: | ||||
|                 self.pushDecode(out["mode"]) | ||||
|                 if "callsign" in out and "locator" in out: | ||||
| @@ -328,7 +328,7 @@ class Decoder(object): | ||||
| class Jt9Decoder(Decoder): | ||||
|     locator_pattern = re.compile("[A-Z0-9]+\\s([A-Z0-9]+)\\s([A-R]{2}[0-9]{2})$") | ||||
|  | ||||
|     def parse(self, msg): | ||||
|     def parse(self, msg, dial_freq): | ||||
|         # ft8 sample | ||||
|         # '222100 -15 -0.0  508 ~  CQ EA7MJ IM66' | ||||
|         # jt65 sample | ||||
| @@ -349,7 +349,7 @@ class Jt9Decoder(Decoder): | ||||
|             "timestamp": timestamp, | ||||
|             "db": float(msg[0:3]), | ||||
|             "dt": float(msg[4:8]), | ||||
|             "freq": int(msg[9:13]), | ||||
|             "freq": dial_freq + int(msg[9:13]), | ||||
|             "mode": mode, | ||||
|             "msg": wsjt_msg, | ||||
|         } | ||||
| @@ -370,7 +370,7 @@ class Jt9Decoder(Decoder): | ||||
| class WsprDecoder(Decoder): | ||||
|     wspr_splitter_pattern = re.compile("([A-Z0-9]*)\\s([A-R]{2}[0-9]{2})\\s([0-9]+)") | ||||
|  | ||||
|     def parse(self, msg): | ||||
|     def parse(self, msg, dial_freq): | ||||
|         # wspr sample | ||||
|         # '2600 -24  0.4   0.001492 -1  G8AXA JO01 33' | ||||
|         # '0052 -29  2.6   0.001486  0  G02CWT IO92 23' | ||||
| @@ -379,7 +379,7 @@ class WsprDecoder(Decoder): | ||||
|             "timestamp": self.parse_timestamp(msg[0:4], "%H%M"), | ||||
|             "db": float(msg[5:8]), | ||||
|             "dt": float(msg[9:13]), | ||||
|             "freq": float(msg[14:24]), | ||||
|             "freq": dial_freq + int(float(msg[14:24]) * 1e6), | ||||
|             "drift": int(msg[25:28]), | ||||
|             "mode": "WSPR", | ||||
|             "msg": wsjt_msg, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jakob Ketterl
					Jakob Ketterl