diff --git a/tnc/helpers.py b/tnc/helpers.py index 29b60db0..84bd23ec 100644 --- a/tnc/helpers.py +++ b/tnc/helpers.py @@ -165,8 +165,7 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency): ] ) break - # trigger update of routing table - mesh.MeshRouter.get_from_heard_stations() + # for idx, item in enumerate(TNC.heard_stations): # if dxcallsign in item: diff --git a/tnc/main.py b/tnc/main.py index 36d5838a..06231b82 100755 --- a/tnc/main.py +++ b/tnc/main.py @@ -33,7 +33,7 @@ from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemP import structlog import explorer import json - +import mesh log = structlog.get_logger("main") def signal_handler(sig, frame): @@ -404,6 +404,9 @@ if __name__ == "__main__": # start modem modem = modem.RF() + # start mesh module + mesh = mesh.MeshRouter() + # optionally start explorer module if TNC.enable_explorer: log.info("[EXPLORER] Publishing to https://explorer.freedata.app", state=TNC.enable_explorer) diff --git a/tnc/mesh.py b/tnc/mesh.py index 00e167aa..cfdbaa37 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -16,11 +16,27 @@ HF mesh networking prototype and testing module # pylint: disable=invalid-name, line-too-long, c-extension-no-member # pylint: disable=import-outside-toplevel, attribute-defined-outside-init -from static import TNC, MeshParam +from static import TNC, MeshParam, FRAME_TYPE, Station, ModemParam +from codec2 import FREEDV_MODE +import numpy as np import time import threading +import modem +import helpers +from queues import MESH_RECEIVED_QUEUE class MeshRouter(): + def __init__(self): + self.mesh_broadcasting_thread = threading.Thread( + target=self.broadcast_routing_table, name="worker thread receive", daemon=True + ) + self.mesh_broadcasting_thread.start() + + self.mesh_rx_dispatcher_thread = threading.Thread( + target=self.mesh_rx_dispatcher, name="worker thread receive", daemon=True + ) + self.mesh_rx_dispatcher_thread.start() + def get_from_heard_stations(self): """ @@ -48,9 +64,9 @@ class MeshRouter(): offset = 5 frequency = 6 - for item in TNC.heard_stations: - new_router = [item[dxcallsign], 'direct', 0, item[snr], item[snr], item[timestamp]] + new_router = [helpers.get_crc_24(item[dxcallsign]), helpers.get_crc_24(b'direct'), 0, int(item[snr]), int(item[snr]), item[timestamp]] + self.add_router_to_routing_table(new_router) def add_router_to_routing_table(self, new_router): @@ -64,3 +80,112 @@ class MeshRouter(): # add new routing entry if not exists if new_router not in MeshParam.routing_table: MeshParam.routing_table.append(new_router) + + def broadcast_routing_table(self, interval=60): + # enable receiving for datac4 if broadcasting + modem.RECEIVE_DATAC4 = True + + while True: + # wait some time until sending routing table + threading.Event().wait(interval) + + # before we are transmitting, let us update our routing table + self.get_from_heard_stations() + + #[b'DJ2LS-0', 'direct', 0, 9.6, 9.6, 1684912305] + mesh_broadcast_frame_header = bytearray(4) + mesh_broadcast_frame_header[:1] = bytes([FRAME_TYPE.MESH_BROADCAST.value]) + mesh_broadcast_frame_header[1:4] = helpers.get_crc_24(Station.mycallsign) + + # callsign(6), router(6), hops(1), path_score(1) == 14 ==> 14 28 42 ==> 3 mesh routing entries + # callsign_crc(3), router_crc(3), hops(1), path_score(1) == 8 --> 6 + # callsign_crc(3), hops(1), path_score(1) == 5 --> 10 + + # Create a new bytearray with a fixed length of 50 + result = bytearray(50) + + # Iterate over the route subarrays and add the selected entries to the result bytearray + index = 0 + for route in MeshParam.routing_table: + dxcall = MeshParam.routing_table[route][0] + # router = MeshParam.routing_table[i][1] + hops = MeshParam.routing_table[route][2] + # snr = MeshParam.routing_table[i][3] + route_score = np.clip(MeshParam.routing_table[route][4], 0, 254) + # timestamp = MeshParam.routing_table[i][5] + result[index:index + 3] = dxcall + hops + route_score + index += len(route[0]) + index += len(route[2]) + index += len(route[4]) + + # Split the result bytearray into a list of fixed-length bytearrays + split_result = [result[i:i + 50] for i in range(0, len(result), 50)] + + frame_list = [] + for _ in split_result: + # make sure payload is always 50 + _[len(_):] = bytes(50 - len(_)) + #print(_) + #print(len(_)) + frame_list.apppend(mesh_broadcast_frame_header + _) + + print(frame_list) + TNC.transmitting = True + c2_mode = FREEDV_MODE.datac4.value + modem.MODEM_TRANSMIT_QUEUE.put([c2_mode, 1, 0, [frame_list]]) + + # Wait while transmitting + while TNC.transmitting: + threading.Event().wait(0.01) + + def mesh_rx_dispatcher(self): + while True: + data_in = MESH_RECEIVED_QUEUE.get() + if int.from_bytes(data_in[:1], "big") in [FRAME_TYPE.MESH_BROADCAST.value]: + self.received_routing_table(data_in) + else: + print("wrong mesh data received") + print(data_in) + def received_routing_table(self, data_in): + print("data received........") + print(data_in) + + router = data_in[1:4] # Extract the first 4 bytes (header) + payload = data_in[4:] # Extract the payload (excluding the header) + + print("Router:", router) # Output the header bytes + + for i in range(0, len(payload), 5): + callsign_checksum = payload[i:i + 3] # First 3 bytes of the information (callsign_checksum) + hops = payload[i + 3] # Fourth byte of the information (hops) + score = payload[i + 4] # Fifth byte of the information (score) + timestamp = int(time.time()) + snr = int(ModemParam.snr) + print("Callsign Checksum:", callsign_checksum) + print("Hops:", hops) + print("Score:", score) + + # use case 1: add new router to table only if callsign not empty + _use_case1 = callsign_checksum.startswith(b'\x00') + + # use case 2: add new router to table only if not own callsign + _use_case2 = callsign_checksum not in [helpers.get_crc_24(Station.mycallsign)] + + # use case 3: increment hop if not direct + if router not in [helpers.get_crc_24(b'direct')] and hops == 0: + hops += 1 + + # use case 4: calculate score + # TODO... + + if not _use_case1 \ + and _use_case2: + + new_router = [callsign_checksum, router, hops, snr, score, timestamp] + print(new_router) + self.add_router_to_routing_table(new_router) + + print("-------------------------") + for _ in MeshParam.routing_table: + print(MeshParam.routing_table[_]) + print("-------------------------") \ No newline at end of file diff --git a/tnc/modem.py b/tnc/modem.py index 5c783228..17da8ba7 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -29,7 +29,7 @@ import structlog import ujson as json import tci from queues import DATA_QUEUE_RECEIVED, MODEM_RECEIVED_QUEUE, MODEM_TRANSMIT_QUEUE, RIGCTLD_COMMAND_QUEUE, \ - AUDIO_RECEIVED_QUEUE, AUDIO_TRANSMIT_QUEUE + AUDIO_RECEIVED_QUEUE, AUDIO_TRANSMIT_QUEUE, MESH_RECEIVED_QUEUE TESTMODE = False RXCHANNEL = "" @@ -811,6 +811,7 @@ class RF: audiobuffer.pop(nin) nin = codec2.api.freedv_nin(freedv) if nbytes == bytes_per_frame: + print(bytes(bytes_out)) # process commands only if TNC.listen = True if TNC.listen: @@ -827,6 +828,14 @@ class RF: FRAME_TYPE.ARQ_DC_OPEN_ACK_N.value ]: print("dropp") + elif int.from_bytes(bytes(bytes_out[:1]), "big") in [ + FRAME_TYPE.MESH_BROADCAST.value + ]: + self.log.debug( + "[MDM] [demod_audio] moving data to mesh dispatcher", nbytes=nbytes + ) + MESH_RECEIVED_QUEUE.put(bytes(bytes_out)) + else: self.log.debug( "[MDM] [demod_audio] Pushing received data to received_queue", nbytes=nbytes diff --git a/tnc/queues.py b/tnc/queues.py index 16974151..c734cbcb 100644 --- a/tnc/queues.py +++ b/tnc/queues.py @@ -12,6 +12,9 @@ DATA_QUEUE_RECEIVED = queue.Queue() MODEM_RECEIVED_QUEUE = queue.Queue() MODEM_TRANSMIT_QUEUE = queue.Queue() +# Initialize FIFO queue to store received frames +MESH_RECEIVED_QUEUE = queue.Queue() + # Initialize FIFO queue to store audio frames AUDIO_RECEIVED_QUEUE = queue.Queue() AUDIO_TRANSMIT_QUEUE = queue.Queue() diff --git a/tnc/static.py b/tnc/static.py index a61e79a0..5a0783e4 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -130,7 +130,7 @@ class TCIParam: @dataclass class TNC: - version = "0.9.2-alpha.5" + version = "0.10.0-alpha.1-mesh" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds @@ -162,6 +162,7 @@ class FRAME_TYPE(Enum): FR_REPEAT = 62 FR_NACK = 63 BURST_NACK = 64 + MESH_BROADCAST = 100 CQ = 200 QRV = 201 PING = 210