From c174a543fdde0100a4cd671657efdefef497fda8 Mon Sep 17 00:00:00 2001 From: DJ2LS <75909252+DJ2LS@users.noreply.github.com> Date: Wed, 24 May 2023 09:00:54 +0200 Subject: [PATCH 01/66] initial work on mesh prototype --- tnc/helpers.py | 6 +++-- tnc/mesh.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ tnc/static.py | 5 +++- 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 tnc/mesh.py diff --git a/tnc/helpers.py b/tnc/helpers.py index 75c1aecf..29b60db0 100644 --- a/tnc/helpers.py +++ b/tnc/helpers.py @@ -8,10 +8,11 @@ import time from datetime import datetime,timezone import crcengine import static -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC, MeshParam import structlog import numpy as np import threading +import mesh log = structlog.get_logger("helpers") @@ -164,7 +165,8 @@ 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/mesh.py b/tnc/mesh.py new file mode 100644 index 00000000..00e167aa --- /dev/null +++ b/tnc/mesh.py @@ -0,0 +1,66 @@ +# -*- coding: UTF-8 -*- +""" + +@author: DJ2LS + +HF mesh networking prototype and testing module + + import time + MeshParam.routing_table = [['AA1AA', 'direct', 0, 1.0, 25, time.time(), ], ['AA1AA', 'AA2BB', 1, 3.1, 10, time.time(), ], + ['AA3CC', 'AA2BB', 5, -4.5, -3, time.time(), ]] + + print(MeshParam.routing_table) + print("---------------------------------") + +""" +# 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 +import time +import threading + +class MeshRouter(): + + def get_from_heard_stations(self): + """ + get data from heard stations + heard stations format: + [dxcallsign,dxgrid,int(time.time()),datatype,snr,offset,frequency] + + TNC.heard_stations.append( + [ + dxcallsign, + dxgrid, + int(time.time()), + datatype, + snr, + offset, + frequency, + ] + ) + """ + dxcallsign = 0 + dxgrid = 1 + timestamp = 2 + type = 3 + snr = 4 + offset = 5 + frequency = 6 + + + for item in TNC.heard_stations: + new_router = [item[dxcallsign], 'direct', 0, item[snr], item[snr], item[timestamp]] + self.add_router_to_routing_table(new_router) + + def add_router_to_routing_table(self, new_router): + # destination callsign # router callsign # hops # rx snr # route quality # timestamp + for _, item in enumerate(MeshParam.routing_table): + # update routing entry if exists + if new_router[0] in item[0] and new_router[1] in item[1]: + print(f"UPDATE {MeshParam.routing_table[_]} >>> {new_router}") + MeshParam.routing_table[_] = new_router + + # add new routing entry if not exists + if new_router not in MeshParam.routing_table: + MeshParam.routing_table.append(new_router) diff --git a/tnc/static.py b/tnc/static.py index 61640ccd..a61e79a0 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -92,7 +92,10 @@ class HamlibParam: hamlib_mode: str = "" hamlib_rf: int = 0 -@dataclass +@dataclass +class MeshParam: + routing_table = [] +@dataclass class ModemParam: tuning_range_fmin: float = -50.0 tuning_range_fmax: float = 50.0 From 24392a62dd5127479587e7c1c1bf4150126c4512 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Fri, 26 May 2023 14:25:48 +0200 Subject: [PATCH 02/66] some more mesh additions --- tnc/helpers.py | 3 +- tnc/main.py | 5 +- tnc/mesh.py | 131 +++++++++++++++++++++++++++++++++++++++++++++++-- tnc/modem.py | 11 ++++- tnc/queues.py | 3 ++ tnc/static.py | 3 +- 6 files changed, 148 insertions(+), 8 deletions(-) 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 From f8c0cf98843d9c70a3c47b9d5d397c3aabbf8004 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Fri, 26 May 2023 15:36:05 +0200 Subject: [PATCH 03/66] some more mesh additions --- tnc/mesh.py | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index cfdbaa37..a3102e56 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -65,8 +65,14 @@ class MeshRouter(): frequency = 6 for item in TNC.heard_stations: - new_router = [helpers.get_crc_24(item[dxcallsign]), helpers.get_crc_24(b'direct'), 0, int(item[snr]), int(item[snr]), item[timestamp]] + print(item[snr]) + try: + snr = item[snr].split("/") + snr = int(float(snr[0])) + except Exception as e: + snr = int(float(item[snr])) + new_router = [helpers.get_crc_24(item[dxcallsign]), helpers.get_crc_24(b'direct'), 0, snr, snr, item[timestamp]] self.add_router_to_routing_table(new_router) def add_router_to_routing_table(self, new_router): @@ -81,7 +87,7 @@ class MeshRouter(): if new_router not in MeshParam.routing_table: MeshParam.routing_table.append(new_router) - def broadcast_routing_table(self, interval=60): + def broadcast_routing_table(self, interval=240): # enable receiving for datac4 if broadcasting modem.RECEIVE_DATAC4 = True @@ -106,33 +112,34 @@ class MeshRouter(): # 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] + for route_id, route in enumerate(MeshParam.routing_table): + dxcall = MeshParam.routing_table[route_id][0] # router = MeshParam.routing_table[i][1] - hops = MeshParam.routing_table[route][2] + hops = MeshParam.routing_table[route_id][2] # snr = MeshParam.routing_table[i][3] - route_score = np.clip(MeshParam.routing_table[route][4], 0, 254) + route_score = np.clip(MeshParam.routing_table[route_id][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]) + print(dxcall) + result[index:index + 5] = dxcall + bytes([hops]) + bytes([route_score]) + index += 3 + index += 1 + index += 1 + print(len(result)) # 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)] - + print(len(split_result)) 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 + _) + frame_list.append(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]]) + modem.MODEM_TRANSMIT_QUEUE.put([c2_mode, 1, 0, frame_list]) # Wait while transmitting while TNC.transmitting: @@ -142,7 +149,7 @@ class MeshRouter(): 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) + self.received_routing_table(data_in[:-2]) else: print("wrong mesh data received") print(data_in) @@ -155,10 +162,10 @@ class MeshRouter(): print("Router:", router) # Output the header bytes - for i in range(0, len(payload), 5): + for i in range(0, len(payload)-1, 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) + hops = payload[i+3:i + 4] # Fourth byte of the information (hops) + score = payload[i+4:i + 5] # Fifth byte of the information (score) timestamp = int(time.time()) snr = int(ModemParam.snr) print("Callsign Checksum:", callsign_checksum) @@ -186,6 +193,6 @@ class MeshRouter(): self.add_router_to_routing_table(new_router) print("-------------------------") - for _ in MeshParam.routing_table: + for _, item in enumerate(MeshParam.routing_table): print(MeshParam.routing_table[_]) print("-------------------------") \ No newline at end of file From 0a62046441affc62b9959a0c61ecf8eebc32fc28 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Fri, 26 May 2023 18:15:21 +0200 Subject: [PATCH 04/66] adde config.ini to .gitignore --- tnc/.gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tnc/.gitignore b/tnc/.gitignore index b6e47617..23100a5e 100644 --- a/tnc/.gitignore +++ b/tnc/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ + +# FreeDATA config +config.ini \ No newline at end of file From 8216aa08f21db0e854af5caad2531e3bb1848c47 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sat, 27 May 2023 12:56:33 +0200 Subject: [PATCH 05/66] improved error handling --- tnc/mesh.py | 158 +++++++++++++++++++++++++++------------------------- 1 file changed, 82 insertions(+), 76 deletions(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index a3102e56..a313fd2b 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -92,58 +92,61 @@ class MeshRouter(): modem.RECEIVE_DATAC4 = True while True: - # wait some time until sending routing table - threading.Event().wait(interval) + try: + # 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() + # 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) + #[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 + # 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) + # 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_id, route in enumerate(MeshParam.routing_table): - dxcall = MeshParam.routing_table[route_id][0] - # router = MeshParam.routing_table[i][1] - hops = MeshParam.routing_table[route_id][2] - # snr = MeshParam.routing_table[i][3] - route_score = np.clip(MeshParam.routing_table[route_id][4], 0, 254) - # timestamp = MeshParam.routing_table[i][5] - print(dxcall) - result[index:index + 5] = dxcall + bytes([hops]) + bytes([route_score]) - index += 3 - index += 1 - index += 1 + # Iterate over the route subarrays and add the selected entries to the result bytearray + index = 0 + for route_id, route in enumerate(MeshParam.routing_table): + dxcall = MeshParam.routing_table[route_id][0] + # router = MeshParam.routing_table[i][1] + hops = MeshParam.routing_table[route_id][2] + # snr = MeshParam.routing_table[i][3] + route_score = np.clip(MeshParam.routing_table[route_id][4], 0, 254) + # timestamp = MeshParam.routing_table[i][5] + print(dxcall) + result[index:index + 5] = dxcall + bytes([hops]) + bytes([route_score]) + index += 3 + index += 1 + index += 1 - print(len(result)) - # 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)] - print(len(split_result)) - frame_list = [] - for _ in split_result: - # make sure payload is always 50 - _[len(_):] = bytes(50 - len(_)) - #print(len(_)) - frame_list.append(mesh_broadcast_frame_header + _) + print(len(result)) + # 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)] + print(len(split_result)) + frame_list = [] + for _ in split_result: + # make sure payload is always 50 + _[len(_):] = bytes(50 - len(_)) + #print(len(_)) + frame_list.append(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]) + 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) + # Wait while transmitting + while TNC.transmitting: + threading.Event().wait(0.01) + except Exception as e: + self.log.warning("[MESH] error fetching data from heard station list", e=e) def mesh_rx_dispatcher(self): while True: @@ -154,45 +157,48 @@ class MeshRouter(): print("wrong mesh data received") print(data_in) def received_routing_table(self, data_in): - print("data received........") - print(data_in) + try: + 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) + 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 + print("Router:", router) # Output the header bytes - for i in range(0, len(payload)-1, 5): - callsign_checksum = payload[i:i + 3] # First 3 bytes of the information (callsign_checksum) - hops = payload[i+3:i + 4] # Fourth byte of the information (hops) - score = payload[i+4:i + 5] # 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) + for i in range(0, len(payload)-1, 5): + callsign_checksum = payload[i:i + 3] # First 3 bytes of the information (callsign_checksum) + hops = payload[i+3:i + 4] # Fourth byte of the information (hops) + score = payload[i+4:i + 5] # 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 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 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 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... + # use case 4: calculate score + # TODO... - if not _use_case1 \ - and _use_case2: + 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) + new_router = [callsign_checksum, router, hops, snr, score, timestamp] + print(new_router) + self.add_router_to_routing_table(new_router) - print("-------------------------") - for _, item in enumerate(MeshParam.routing_table): - print(MeshParam.routing_table[_]) - print("-------------------------") \ No newline at end of file + print("-------------------------") + for _, item in enumerate(MeshParam.routing_table): + print(MeshParam.routing_table[_]) + print("-------------------------") + except Exception as e: + self.log.warning("[MESH] error processing received routing broadcast", e=e) From dc22051fefb0213dcf7d9c5f167242b4edf39246 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sat, 27 May 2023 13:47:04 +0200 Subject: [PATCH 06/66] added integer conversion --- tnc/mesh.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index a313fd2b..5d5192dc 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -168,8 +168,8 @@ class MeshRouter(): for i in range(0, len(payload)-1, 5): callsign_checksum = payload[i:i + 3] # First 3 bytes of the information (callsign_checksum) - hops = payload[i+3:i + 4] # Fourth byte of the information (hops) - score = payload[i+4:i + 5] # Fifth byte of the information (score) + hops = int.from_bytes(payload[i+3:i + 4], "big") # Fourth byte of the information (hops) + score = int.from_bytes(payload[i+4:i + 5], "big") # Fifth byte of the information (score) timestamp = int(time.time()) snr = int(ModemParam.snr) print("Callsign Checksum:", callsign_checksum) @@ -186,7 +186,8 @@ class MeshRouter(): if router not in [helpers.get_crc_24(b'direct')] and hops == 0: hops += 1 - # use case 4: calculate score + + # use case N: calculate score # TODO... if not _use_case1 \ From 36c03fa308e80f8b3777cce6a764711c65ede4c5 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sat, 27 May 2023 14:23:46 +0200 Subject: [PATCH 07/66] check if callsign as already available in routing table --- tnc/mesh.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index 5d5192dc..ccb2591f 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -182,16 +182,22 @@ class MeshRouter(): # 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 + # use case 3: increment hop if router not direct if router not in [helpers.get_crc_24(b'direct')] and hops == 0: hops += 1 + # use case 4: if callsign is directly available skip route for only keeping shortest way in db + _use_case4 = False + for _, call in enumerate(MeshParam.routing_table): + if callsign_checksum in MeshParam.routing_table[_][0]: + _use_case4 = True # use case N: calculate score # TODO... if not _use_case1 \ - and _use_case2: + and _use_case2\ + and not _use_case4: new_router = [callsign_checksum, router, hops, snr, score, timestamp] print(new_router) From ff322c2ea4e341edee6d8aecd5ca7cc633703578 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sat, 27 May 2023 14:26:42 +0200 Subject: [PATCH 08/66] check if callsign as already available in routing table --- tnc/mesh.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index ccb2591f..1df6cd54 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -189,7 +189,8 @@ class MeshRouter(): # use case 4: if callsign is directly available skip route for only keeping shortest way in db _use_case4 = False for _, call in enumerate(MeshParam.routing_table): - if callsign_checksum in MeshParam.routing_table[_][0]: + # check if callsign already in routing table and is direct connection + if callsign_checksum in [MeshParam.routing_table[_][0]] and MeshParam.routing_table[_][1] in [helpers.get_crc_24(b'direct')]: _use_case4 = True # use case N: calculate score From 783ff06cb0e9e31d2ad4648389f481540f315f08 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sat, 27 May 2023 19:52:09 +0200 Subject: [PATCH 09/66] fixed missing logging module --- tnc/mesh.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index 1df6cd54..2bdfa581 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -12,6 +12,20 @@ HF mesh networking prototype and testing module print(MeshParam.routing_table) print("---------------------------------") + + + +TODO: SIGNALLING FOR ACK/NACK: +- mesh-signalling burst is datac13 +- mesh-signalling frame contains [message id, status] +- create a list for signalling frames, contains [message id, message-status, attempts, state, timestamp] +- on "IRS", send ACK/NACK 10 times on receiving beacon? +- on "ROUTER", receive ACK/NACK, and store it in table, also send it 10 times +- if sent 10 times, set ACK/NACK state to "done" +- if done already in list, don't reset retry counter +- delete ACK/NACK if "done" and timestamp older than 1day + + """ # pylint: disable=invalid-name, line-too-long, c-extension-no-member # pylint: disable=import-outside-toplevel, attribute-defined-outside-init @@ -23,10 +37,16 @@ import time import threading import modem import helpers +import structlog + from queues import MESH_RECEIVED_QUEUE class MeshRouter(): def __init__(self): + + log = structlog.get_logger("RF") + + self.mesh_broadcasting_thread = threading.Thread( target=self.broadcast_routing_table, name="worker thread receive", daemon=True ) @@ -67,7 +87,8 @@ class MeshRouter(): for item in TNC.heard_stations: print(item[snr]) try: - snr = item[snr].split("/") + print(item[snr]) + snr = bytes(item[snr]).split(b"/") snr = int(float(snr[0])) except Exception as e: snr = int(float(item[snr])) From 8a85dfd1502164ce6e3b74b124d87551067895b2 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sat, 27 May 2023 19:53:17 +0200 Subject: [PATCH 10/66] added exp-indentificator --- tnc/static.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tnc/static.py b/tnc/static.py index 5a0783e4..9a302ff4 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -130,7 +130,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.0-alpha.1-mesh" + version = "0.10.0-alpha.1-mesh-exp1" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From ecc6312bc3208adceafa77cb465a63193818c46b Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sat, 27 May 2023 21:00:01 +0200 Subject: [PATCH 11/66] fixed encoding --- tnc/mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index 2bdfa581..47c3fc55 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -88,7 +88,7 @@ class MeshRouter(): print(item[snr]) try: print(item[snr]) - snr = bytes(item[snr]).split(b"/") + snr = bytes(item[snr], "utf-8").split(b"/") snr = int(float(snr[0])) except Exception as e: snr = int(float(item[snr])) From f836f9197c267bb4b17ac215cb83eeb3aa7ac2e2 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sat, 27 May 2023 21:00:46 +0200 Subject: [PATCH 12/66] hopefully fixed logging --- tnc/mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index 47c3fc55..5b78a738 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -44,7 +44,7 @@ from queues import MESH_RECEIVED_QUEUE class MeshRouter(): def __init__(self): - log = structlog.get_logger("RF") + self.log = structlog.get_logger("RF") self.mesh_broadcasting_thread = threading.Thread( From b2ee1a6a982d62d7306c6a0cbf91cc9aabfa6737 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 28 May 2023 09:23:01 +0200 Subject: [PATCH 13/66] improved logging --- tnc/mesh.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index 5b78a738..a5997e36 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -84,17 +84,20 @@ class MeshRouter(): offset = 5 frequency = 6 - for item in TNC.heard_stations: - print(item[snr]) - try: + try: + for item in TNC.heard_stations: print(item[snr]) - snr = bytes(item[snr], "utf-8").split(b"/") - snr = int(float(snr[0])) - except Exception as e: - snr = int(float(item[snr])) + try: + print(item[snr]) + snr = bytes(item[snr], "utf-8").split(b"/") + snr = int(float(snr[0])) + except Exception as e: + snr = int(float(item[snr])) - new_router = [helpers.get_crc_24(item[dxcallsign]), helpers.get_crc_24(b'direct'), 0, snr, snr, item[timestamp]] - self.add_router_to_routing_table(new_router) + new_router = [helpers.get_crc_24(item[dxcallsign]), helpers.get_crc_24(b'direct'), 0, snr, snr, item[timestamp]] + self.add_router_to_routing_table(new_router) + except Exception as e: + self.log.warning("[MESH] error fetching data from heard station list", e=e) def add_router_to_routing_table(self, new_router): # destination callsign # router callsign # hops # rx snr # route quality # timestamp @@ -114,6 +117,7 @@ class MeshRouter(): while True: try: + # wait some time until sending routing table threading.Event().wait(interval) @@ -135,17 +139,16 @@ class MeshRouter(): # Iterate over the route subarrays and add the selected entries to the result bytearray index = 0 for route_id, route in enumerate(MeshParam.routing_table): + # the value 5 is the length of crc24 + hops + score + dxcall = MeshParam.routing_table[route_id][0] # router = MeshParam.routing_table[i][1] hops = MeshParam.routing_table[route_id][2] # snr = MeshParam.routing_table[i][3] route_score = np.clip(MeshParam.routing_table[route_id][4], 0, 254) # timestamp = MeshParam.routing_table[i][5] - print(dxcall) result[index:index + 5] = dxcall + bytes([hops]) + bytes([route_score]) - index += 3 - index += 1 - index += 1 + index += 5 print(len(result)) # Split the result bytearray into a list of fixed-length bytearrays @@ -167,7 +170,7 @@ class MeshRouter(): while TNC.transmitting: threading.Event().wait(0.01) except Exception as e: - self.log.warning("[MESH] error fetching data from heard station list", e=e) + self.log.warning("[MESH] broadcasting routing table", e=e) def mesh_rx_dispatcher(self): while True: @@ -193,9 +196,6 @@ class MeshRouter(): score = int.from_bytes(payload[i+4:i + 5], "big") # 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') @@ -221,6 +221,10 @@ class MeshRouter(): and _use_case2\ and not _use_case4: + print("Callsign Checksum:", callsign_checksum) + print("Hops:", hops) + print("Score:", score) + new_router = [callsign_checksum, router, hops, snr, score, timestamp] print(new_router) self.add_router_to_routing_table(new_router) From f4896837acf7c87b735da0b5b785b6faa33dd538 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 28 May 2023 09:24:52 +0200 Subject: [PATCH 14/66] improved logging --- tnc/mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index a5997e36..84647a9b 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -91,7 +91,7 @@ class MeshRouter(): print(item[snr]) snr = bytes(item[snr], "utf-8").split(b"/") snr = int(float(snr[0])) - except Exception as e: + except Exception as err: snr = int(float(item[snr])) new_router = [helpers.get_crc_24(item[dxcallsign]), helpers.get_crc_24(b'direct'), 0, snr, snr, item[timestamp]] From 204cedd7e261d5867f4bb93fabdcecf6f3b91900 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 28 May 2023 09:30:54 +0200 Subject: [PATCH 15/66] improved logging --- tnc/mesh.py | 22 +++++++++++++--------- tnc/static.py | 2 +- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index 84647a9b..873c6eb9 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -100,16 +100,20 @@ class MeshRouter(): self.log.warning("[MESH] error fetching data from heard station list", e=e) def add_router_to_routing_table(self, new_router): - # destination callsign # router callsign # hops # rx snr # route quality # timestamp - for _, item in enumerate(MeshParam.routing_table): - # update routing entry if exists - if new_router[0] in item[0] and new_router[1] in item[1]: - print(f"UPDATE {MeshParam.routing_table[_]} >>> {new_router}") - MeshParam.routing_table[_] = new_router + try: + # destination callsign # router callsign # hops # rx snr # route quality # timestamp + for _, item in enumerate(MeshParam.routing_table): + # update routing entry if exists + if new_router[0] in item[0] and new_router[1] in item[1]: + print(f"UPDATE {MeshParam.routing_table[_]} >>> {new_router}") + MeshParam.routing_table[_] = new_router - # add new routing entry if not exists - if new_router not in MeshParam.routing_table: - MeshParam.routing_table.append(new_router) + # add new routing entry if not exists + if new_router not in MeshParam.routing_table: + print(f"INSERT {new_router} >>> ROUTING TABLE") + MeshParam.routing_table.append(new_router) + except Exception as e: + self.log.warning("[MESH] error adding data to routing table", e=e, router=new_router) def broadcast_routing_table(self, interval=240): # enable receiving for datac4 if broadcasting diff --git a/tnc/static.py b/tnc/static.py index 9a302ff4..859a2cdc 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -130,7 +130,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.0-alpha.1-mesh-exp1" + version = "0.10.0-alpha.1-mesh-exp2" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From e75c92c27c3b84189b555c52fe66f1bbdf0a9a83 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 28 May 2023 09:46:46 +0200 Subject: [PATCH 16/66] fixed wrong heard station data fetching --- tnc/mesh.py | 26 ++++++++++++++------------ tnc/static.py | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index 873c6eb9..117930ec 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -76,25 +76,27 @@ class MeshRouter(): ] ) """ - dxcallsign = 0 - dxgrid = 1 - timestamp = 2 - type = 3 - snr = 4 - offset = 5 - frequency = 6 + dxcallsign_position = 0 + dxgrid_position = 1 + timestamp_position = 2 + type_position = 3 + snr_position = 4 + offset_position = 5 + frequency_position = 6 try: for item in TNC.heard_stations: - print(item[snr]) + print("-----------") + print(item) + print(item[snr_position]) try: - print(item[snr]) - snr = bytes(item[snr], "utf-8").split(b"/") + print(item[snr_position]) + snr = bytes(item[snr_position], "utf-8").split(b"/") snr = int(float(snr[0])) except Exception as err: - snr = int(float(item[snr])) + snr = int(float(item[snr_position])) - new_router = [helpers.get_crc_24(item[dxcallsign]), helpers.get_crc_24(b'direct'), 0, snr, snr, item[timestamp]] + new_router = [helpers.get_crc_24(item[dxcallsign_position]), helpers.get_crc_24(b'direct'), 0, snr, snr, item[timestamp_position]] self.add_router_to_routing_table(new_router) except Exception as e: self.log.warning("[MESH] error fetching data from heard station list", e=e) diff --git a/tnc/static.py b/tnc/static.py index 859a2cdc..20d67598 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -130,7 +130,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.0-alpha.1-mesh-exp2" + version = "0.10.0-alpha.1-mesh-exp3" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From be4d158c370a3c4df47955c2b7265c056d03ce2b Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 28 May 2023 12:13:19 +0200 Subject: [PATCH 17/66] get routing table via network --- tnc/sock.py | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/tnc/sock.py b/tnc/sock.py index fb30e33c..53c888ee 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -27,7 +27,7 @@ import time import wave import helpers import static -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC, MeshParam import structlog from random import randrange import ujson as json @@ -391,6 +391,11 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): else: self.tnc_set_mode(received_json) + # GET ROUTING TABLE + if received_json["type"] == "get" and received_json["command"] == "routing_table": + self.tnc_get_mesh_routing_table(received_json) + + except Exception as err: log.error("[SCK] JSON decoding error", e=err) @@ -761,6 +766,40 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): "[SCK] STOP command execution error", e=err, command=received_json ) + def tnc_get_mesh_routing_table(self, received_json): + try: + if not RX_BUFFER.empty(): + output = { + "command": "routing_table", + "routes": [], + } + + for route in range(MeshParam.routing_table): + output["routes"].append( + { + "dxcall": MeshParam.routing_table[route][0], + "router": MeshParam.routing_table[route][1], + "hops": MeshParam.routing_table[route][2], + "snr": MeshParam.routing_table[route][3], + "score": MeshParam.routing_table[route][4], + "timestamp": MeshParam.routing_table[route][5], + } + ) + + + jsondata = json.dumps(output) + # self.request.sendall(bytes(jsondata, encoding)) + SOCKET_QUEUE.put(jsondata) + command_response("routing_table", True) + + except Exception as err: + command_response("routing_table", False) + log.warning( + "[SCK] Send RX buffer command execution error", + e=err, + command=received_json, + ) + def tnc_get_rx_buffer(self, received_json): try: if not RX_BUFFER.empty(): From 06d38c25859a985f687bfe090fd17d98e5cbe68b Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 28 May 2023 12:16:01 +0200 Subject: [PATCH 18/66] added routing table to tnc state --- tnc/sock.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tnc/sock.py b/tnc/sock.py index 53c888ee..ace66b3c 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -777,8 +777,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): for route in range(MeshParam.routing_table): output["routes"].append( { - "dxcall": MeshParam.routing_table[route][0], - "router": MeshParam.routing_table[route][1], + "dxcall": str(MeshParam.routing_table[route][0], "utf-8"), + "router": str(MeshParam.routing_table[route][1], "utf-8"), "hops": MeshParam.routing_table[route][2], "snr": MeshParam.routing_table[route][3], "score": MeshParam.routing_table[route][4], @@ -1121,6 +1121,7 @@ def send_tnc_state(): "total_bytes": str(ARQ.total_bytes), "beacon_state": str(Beacon.beacon_state), "stations": [], + "routing_table": [], "mycallsign": str(Station.mycallsign, encoding), "mygrid": str(Station.mygrid, encoding), "dxcallsign": str(Station.dxcallsign, encoding), @@ -1143,6 +1144,19 @@ def send_tnc_state(): "frequency": heard[6], } ) + + for route in range(MeshParam.routing_table): + output["routing_table"].append( + { + "dxcall": str(MeshParam.routing_table[route][0], encoding), + "router": str(MeshParam.routing_table[route][1], encoding), + "hops": MeshParam.routing_table[route][2], + "snr": MeshParam.routing_table[route][3], + "score": MeshParam.routing_table[route][4], + "timestamp": MeshParam.routing_table[route][5], + } + ) + return json.dumps(output) From ece6873ac79da2e2901accfc4946896ed8db17c5 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 28 May 2023 12:20:14 +0200 Subject: [PATCH 19/66] version udpate --- tnc/static.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tnc/static.py b/tnc/static.py index b5dc7bb3..8312c7e9 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -128,7 +128,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.0-alpha.1-mesh-exp3" + version = "0.10.0-alpha.1-mesh-exp4" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From f160775ec0d57609bf7fe1d9a1d64ff6b9f915b8 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 28 May 2023 12:23:36 +0200 Subject: [PATCH 20/66] hopefully fixes neTwork --- tnc/sock.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tnc/sock.py b/tnc/sock.py index 19d3f4df..438f7300 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -774,15 +774,15 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): "routes": [], } - for route in range(MeshParam.routing_table): + for _, route in enumerate(MeshParam.routing_table): output["routes"].append( { - "dxcall": str(MeshParam.routing_table[route][0], "utf-8"), - "router": str(MeshParam.routing_table[route][1], "utf-8"), - "hops": MeshParam.routing_table[route][2], - "snr": MeshParam.routing_table[route][3], - "score": MeshParam.routing_table[route][4], - "timestamp": MeshParam.routing_table[route][5], + "dxcall": str(MeshParam.routing_table[_][0], "utf-8"), + "router": str(MeshParam.routing_table[_][1], "utf-8"), + "hops": MeshParam.routing_table[_][2], + "snr": MeshParam.routing_table[_][3], + "score": MeshParam.routing_table[_][4], + "timestamp": MeshParam.routing_table[_][5], } ) @@ -1149,15 +1149,15 @@ def send_tnc_state(): } ) - for route in range(MeshParam.routing_table): + for _, route in enumerate(MeshParam.routing_table): output["routing_table"].append( { - "dxcall": str(MeshParam.routing_table[route][0], encoding), - "router": str(MeshParam.routing_table[route][1], encoding), - "hops": MeshParam.routing_table[route][2], - "snr": MeshParam.routing_table[route][3], - "score": MeshParam.routing_table[route][4], - "timestamp": MeshParam.routing_table[route][5], + "dxcall": str(MeshParam.routing_table[_][0], encoding), + "router": str(MeshParam.routing_table[_][1], encoding), + "hops": MeshParam.routing_table[_][2], + "snr": MeshParam.routing_table[_][3], + "score": MeshParam.routing_table[_][4], + "timestamp": MeshParam.routing_table[_][5], } ) From 1343d6e7ea58dffaf0456ed7cda5fbb4a188299d Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 28 May 2023 12:33:43 +0200 Subject: [PATCH 21/66] fixed network based decoding of string --- tnc/sock.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tnc/sock.py b/tnc/sock.py index 438f7300..05180664 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -777,8 +777,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): for _, route in enumerate(MeshParam.routing_table): output["routes"].append( { - "dxcall": str(MeshParam.routing_table[_][0], "utf-8"), - "router": str(MeshParam.routing_table[_][1], "utf-8"), + "dxcall": MeshParam.routing_table[_][0].decode('utf-8'), + "router": MeshParam.routing_table[_][1].decode('utf-8'), "hops": MeshParam.routing_table[_][2], "snr": MeshParam.routing_table[_][3], "score": MeshParam.routing_table[_][4], @@ -1152,8 +1152,8 @@ def send_tnc_state(): for _, route in enumerate(MeshParam.routing_table): output["routing_table"].append( { - "dxcall": str(MeshParam.routing_table[_][0], encoding), - "router": str(MeshParam.routing_table[_][1], encoding), + "dxcall": MeshParam.routing_table[_][0].decode('utf-8'), + "router": MeshParam.routing_table[_][1].decode('utf-8'), "hops": MeshParam.routing_table[_][2], "snr": MeshParam.routing_table[_][3], "score": MeshParam.routing_table[_][4], From 5a5d7ac55d47d27b3f8b62efe6a04dcf4c38b80e Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 28 May 2023 12:48:57 +0200 Subject: [PATCH 22/66] and some more networkrelated tests --- tnc/sock.py | 8 ++++---- tnc/static.py | 2 +- tools/freedata_cli_tools.py | 6 +++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tnc/sock.py b/tnc/sock.py index 05180664..0fbb2340 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -777,8 +777,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): for _, route in enumerate(MeshParam.routing_table): output["routes"].append( { - "dxcall": MeshParam.routing_table[_][0].decode('utf-8'), - "router": MeshParam.routing_table[_][1].decode('utf-8'), + "dxcall": MeshParam.routing_table[_][0], + "router": MeshParam.routing_table[_][1], "hops": MeshParam.routing_table[_][2], "snr": MeshParam.routing_table[_][3], "score": MeshParam.routing_table[_][4], @@ -1152,8 +1152,8 @@ def send_tnc_state(): for _, route in enumerate(MeshParam.routing_table): output["routing_table"].append( { - "dxcall": MeshParam.routing_table[_][0].decode('utf-8'), - "router": MeshParam.routing_table[_][1].decode('utf-8'), + "dxcall": MeshParam.routing_table[_][0], + "router": MeshParam.routing_table[_][1], "hops": MeshParam.routing_table[_][2], "snr": MeshParam.routing_table[_][3], "score": MeshParam.routing_table[_][4], diff --git a/tnc/static.py b/tnc/static.py index 8312c7e9..dddfdb6b 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -128,7 +128,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.0-alpha.1-mesh-exp4" + version = "0.10.0-alpha.1-mesh-exp5" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds diff --git a/tools/freedata_cli_tools.py b/tools/freedata_cli_tools.py index 420593a2..4f600225 100755 --- a/tools/freedata_cli_tools.py +++ b/tools/freedata_cli_tools.py @@ -29,7 +29,7 @@ def main_menu(): while True: time.sleep(0.1) title = 'Please select a command you want to run: ' - options = ['BEACON', 'PING', 'ARQ', 'LIST AUDIO DEVICES'] + options = ['BEACON', 'PING', 'ARQ', 'LIST AUDIO DEVICES','LIST ROUTING TABLE'] option, index = pick(options, title) # BEACON AREA @@ -87,6 +87,10 @@ def main_menu(): option, index = pick(device_list, "Audio devices") + elif option == 'LIST ROUTING TABLE': + run_network_command({"type": "get", "command": "routing_table"}) + + if option == '----- BACK -----': main_menu() From bdfb1658ba570545fa72293f94bac62bd459e9f4 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 28 May 2023 16:06:07 +0200 Subject: [PATCH 23/66] fixed network crash --- tnc/mesh.py | 2 +- tnc/sock.py | 18 +++++++++++++----- tnc/static.py | 2 +- tools/freedata_network_listener.py | 5 ++++- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index 117930ec..b22e294f 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -117,7 +117,7 @@ class MeshRouter(): except Exception as e: self.log.warning("[MESH] error adding data to routing table", e=e, router=new_router) - def broadcast_routing_table(self, interval=240): + def broadcast_routing_table(self, interval=40): # enable receiving for datac4 if broadcasting modem.RECEIVE_DATAC4 = True diff --git a/tnc/sock.py b/tnc/sock.py index 0fbb2340..03163748 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -775,10 +775,14 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): } for _, route in enumerate(MeshParam.routing_table): + if MeshParam.routing_table[_][0].hex() == helpers.get_crc_24(b"direct").hex(): + router = "direct" + else: + router = MeshParam.routing_table[_][0].hex() output["routes"].append( { - "dxcall": MeshParam.routing_table[_][0], - "router": MeshParam.routing_table[_][1], + "dxcall": MeshParam.routing_table[_][0].hex(), + "router": router, "hops": MeshParam.routing_table[_][2], "snr": MeshParam.routing_table[_][3], "score": MeshParam.routing_table[_][4], @@ -1150,17 +1154,21 @@ def send_tnc_state(): ) for _, route in enumerate(MeshParam.routing_table): + if MeshParam.routing_table[_][1].hex() == helpers.get_crc_24(b"direct").hex(): + router = "direct" + else: + router = MeshParam.routing_table[_][1].hex() output["routing_table"].append( { - "dxcall": MeshParam.routing_table[_][0], - "router": MeshParam.routing_table[_][1], + "dxcall": MeshParam.routing_table[_][0].hex(), + "router": router, "hops": MeshParam.routing_table[_][2], "snr": MeshParam.routing_table[_][3], "score": MeshParam.routing_table[_][4], "timestamp": MeshParam.routing_table[_][5], } ) - + #print(output) return json.dumps(output) diff --git a/tnc/static.py b/tnc/static.py index dddfdb6b..17b7cf73 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -128,7 +128,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.0-alpha.1-mesh-exp5" + version = "0.10.0-alpha.1-mesh-exp6" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds diff --git a/tools/freedata_network_listener.py b/tools/freedata_network_listener.py index a10466db..651ccfeb 100755 --- a/tools/freedata_network_listener.py +++ b/tools/freedata_network_listener.py @@ -89,7 +89,8 @@ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: jsondata = json.loads(command) if jsondata.get('command') == "tnc_state": - pass + #pass + print(jsondata.get("routing_table")) if jsondata.get('freedata') == "tnc-message": log.info(jsondata) @@ -108,6 +109,8 @@ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: if jsondata.get('status') == 'received' and jsondata.get('arq') == 'transmission': decode_and_save_data(jsondata["data"]) + + # clear data buffer as soon as data has been read data = bytes() From a77f8f5c038814630b0857bc9486281db895cd4e Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 28 May 2023 17:19:21 +0200 Subject: [PATCH 24/66] added mesh gui view --- gui/main.js | 31 ++++++++++++ gui/preload-main.js | 4 ++ gui/preload-mesh.js | 102 +++++++++++++++++++++++++++++++++++++++ gui/sock.js | 1 + gui/src/index.html | 4 ++ gui/src/mesh-module.html | 67 +++++++++++++++++++++++++ 6 files changed, 209 insertions(+) create mode 100644 gui/preload-mesh.js create mode 100644 gui/src/mesh-module.html diff --git a/gui/main.js b/gui/main.js index 482c8559..28fe54fc 100644 --- a/gui/main.js +++ b/gui/main.js @@ -165,6 +165,7 @@ fs.mkdir(receivedFilesFolder, { let win = null; let data = null; let logViewer = null; +let meshViewer = null; var daemonProcess = null; // create a splash screen @@ -248,6 +249,29 @@ function createWindow() { } }); + meshViewer = new BrowserWindow({ + height: 900, + width: 600, + show: false, + //parent: win, + webPreferences: { + preload: require.resolve("./preload-mesh.js"), + nodeIntegration: true, + }, + }); + + meshViewer.loadFile("src/mesh-module.html"); + meshViewer.setMenuBarVisibility(false); + + // Emitted when the window is closed. + meshViewer.on("close", function (evt) { + if (meshViewer !== null) { + evt.preventDefault(); + meshViewer.hide(); + } else { + this.close(); + } + }); // Emitted when the window is closed. win.on("closed", function () { console.log("closing all windows....."); @@ -398,6 +422,7 @@ ipcMain.on("request-update-daemon-ip", (event, data) => { ipcMain.on("request-update-tnc-state", (event, arg) => { win.webContents.send("action-update-tnc-state", arg); + meshViewer.send("action-update-mesh-table", arg) //data.webContents.send('action-update-tnc-state', arg); }); @@ -460,6 +485,11 @@ ipcMain.on("request-open-tnc-log", () => { logViewer.show(); }); +ipcMain.on("request-open-mesh-module", () => { + meshViewer.show(); +}); + + //file selector ipcMain.on("get-file-path", (event, data) => { dialog @@ -878,6 +908,7 @@ function close_all() { win.destroy(); chat.destroy(); logViewer.destroy(); + meshViewer.destroy(); app.quit(); } diff --git a/gui/preload-main.js b/gui/preload-main.js index b2da4b9a..8aecdb1c 100644 --- a/gui/preload-main.js +++ b/gui/preload-main.js @@ -1582,6 +1582,10 @@ window.addEventListener("DOMContentLoaded", () => { ipcRenderer.send("request-open-tnc-log"); }); + document.getElementById("meshtable").addEventListener("click", () => { + ipcRenderer.send("request-open-mesh-module"); + }); + // stopTNC button clicked document.getElementById("stopTNC").addEventListener("click", () => { if (!confirm("Stop the TNC?")) return; diff --git a/gui/preload-mesh.js b/gui/preload-mesh.js new file mode 100644 index 00000000..d9a5c8f8 --- /dev/null +++ b/gui/preload-mesh.js @@ -0,0 +1,102 @@ +const path = require("path"); +const { ipcRenderer } = require("electron"); + +// https://stackoverflow.com/a/26227660 +var appDataFolder = + process.env.APPDATA || + (process.platform == "darwin" + ? process.env.HOME + "/Library/Application Support" + : process.env.HOME + "/.config"); +var configFolder = path.join(appDataFolder, "FreeDATA"); +var configPath = path.join(configFolder, "config.json"); +const config = require(configPath); + +// WINDOW LISTENER +window.addEventListener("DOMContentLoaded", () => { + document + .getElementById("enable_mesh") + .addEventListener("click", () => { + if (document.getElementById("enable_mesh").checked) { + display_class("table-info", true); + } else { + display_class("table-info", false); + } + }); + + +}); + + +ipcRenderer.on("action-update-mesh-table", (event, arg) => { + var routes = arg.routing_table; + var tbl = document.getElementById("mesh-table"); + tbl.innerHTML = ""; + + +for (i = 0; i < routes.length; i++) { + + + /* + var myGrid = document.getElementById("myGrid").value; + try { + var dist = parseInt(distance(myGrid, dxGrid)) + " km"; + document.getElementById("dataModalPingDistance").textContent = dist; + } catch { + document.getElementById("dataModalPingDistance").textContent = "---"; + } + document.getElementById("dataModalPingDB").textContent = + arg.stations[i]["snr"]; + } + */ + + + + var row = document.createElement("tr"); + + var timestamp = document.createElement("td"); + var timestampText = document.createElement("span"); + timestampText.innerText = routes[i]["timestamp"]; + timestamp.appendChild(timestampText); + + var dxcall = document.createElement("td"); + var dxcallText = document.createElement("span"); + dxcallText.innerText = routes[i]["dxcall"]; + dxcall.appendChild(dxcallText); + + var router = document.createElement("td"); + var routerText = document.createElement("span"); + routerText.innerText = routes[i]["router"]; + router.appendChild(routerText); + + var hops = document.createElement("td"); + var hopsText = document.createElement("span"); + hopsText.innerText = routes[i]["hops"]; + hops.appendChild(hopsText); + + var score = document.createElement("td"); + var scoreText = document.createElement("span"); + scoreText.innerText = routes[i]["score"]; + score.appendChild(scoreText); + + var snr = document.createElement("td"); + var snrText = document.createElement("span"); + snrText.innerText = routes[i]["snr"]; + snr.appendChild(snrText); + + row.appendChild(timestamp); + row.appendChild(dxcall); + row.appendChild(router); + row.appendChild(hops); + row.appendChild(score); + row.appendChild(snr); + + + + tbl.appendChild(row); +} + + + // scroll to bottom of page + // https://stackoverflow.com/a/11715670 + window.scrollTo(0, document.body.scrollHeight); +}); diff --git a/gui/sock.js b/gui/sock.js index ec3893c9..eeb1c487 100644 --- a/gui/sock.js +++ b/gui/sock.js @@ -218,6 +218,7 @@ client.on("data", function (socketdata) { total_bytes: data["total_bytes"], arq_transmission_percent: data["arq_transmission_percent"], stations: data["stations"], + routing_table: data["routing_table"], beacon_state: data["beacon_state"], hamlib_status: data["hamlib_status"], listen: data["listen"], diff --git a/gui/src/index.html b/gui/src/index.html index eff74b0d..fb187444 100644 --- a/gui/src/index.html +++ b/gui/src/index.html @@ -219,6 +219,10 @@ TNC Live Logs +
  • + diff --git a/tnc/mesh.py b/tnc/mesh.py index 30e23924..b4243932 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -44,8 +44,11 @@ import threading import modem import helpers import structlog +import ujson as json -from queues import MESH_RECEIVED_QUEUE +from queues import MESH_RECEIVED_QUEUE, MESH_QUEUE_TRANSMIT + +MESH_SIGNALLING_TABLE = [] class MeshRouter(): def __init__(self): @@ -63,6 +66,15 @@ class MeshRouter(): ) self.mesh_rx_dispatcher_thread.start() + self.mesh_tx_dispatcher_thread = threading.Thread( + target=self.mesh_tx_dispatcher, name="worker thread receive", daemon=True + ) + self.mesh_tx_dispatcher_thread.start() + + self.mesh_signalling_dispatcher_thread = threading.Thread( + target=self.mesh_signalling_dispatcher, name="worker thread receive", daemon=True + ) + self.mesh_signalling_dispatcher_thread.start() def get_from_heard_stations(self): """ @@ -123,7 +135,7 @@ class MeshRouter(): except Exception as e: self.log.warning("[MESH] error adding data to routing table", e=e, router=new_router) - def broadcast_routing_table(self, interval=180): + def broadcast_routing_table(self, interval=600): while True: # always enable receiving for datac4 if broadcasting @@ -192,9 +204,50 @@ class MeshRouter(): 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[:-2]) + elif int.from_bytes(data_in[:1], "big") in [FRAME_TYPE.MESH_SIGNALLING_PING.value]: + self.received_mesh_ping(data_in[:-2]) else: print("wrong mesh data received") print(data_in) + + def mesh_tx_dispatcher(self): + while True: + data = MESH_QUEUE_TRANSMIT.get() + print(data) + if data[0] == "PING": + self.add_mesh_ping_to_signalling_table(data[2]) + else: + print("wrong mesh command") + + + + def mesh_signalling_dispatcher(self): + # [timestamp, destination, router, frametype, payload, attempt] + # --------------0------------1---------2---------3--------4---------5---- # + while True: + threading.Event().wait(1.0) + for entry in MESH_SIGNALLING_TABLE: + print(entry) + timestamp = entry[0] + attempt = entry[5] + # check for PING cases + if entry[3] == "PING" and attempt < 10: + + + # Calculate the transmission time with exponential increase + transmission_time = timestamp + (2 ** attempt) * 10 + + # check if it is time to transmit + if time.time() >= transmission_time: + entry[5] += 1 + print(attempt) + print("transmit mesh ping") + self.transmit_mesh_signalling(entry) + else: + print("wait some more time") + else: + print("...") + def received_routing_table(self, data_in): try: print("data received........") @@ -265,4 +318,81 @@ class MeshRouter(): return int(score) def calculate_new_avg_score(self, value_old, value): - return int((value_old + value) / 2) \ No newline at end of file + return int((value_old + value) / 2) + + def received_mesh_ping(self, data_in): + pass + + def add_mesh_ping_to_signalling_table(self, dxcallsign): + timestamp = time.time() + destination = helpers.get_crc_24(dxcallsign).hex() + router = "" + frametype = "PING" + payload = "" + attempt = 0 + + # [timestamp, destination, router, frametype, payload, attempt] + # --------------0------------1---------2---------3--------4---------5---- # + new_entry = [timestamp, destination, router, frametype, payload, attempt] + print(MESH_SIGNALLING_TABLE) + for _, item in enumerate(MESH_SIGNALLING_TABLE): + # update routing entry if exists + if new_entry[0] in item[0] and new_entry[1] in item[1]: + print(f"UPDATE {MESH_SIGNALLING_TABLE[_]} >>> {new_entry}") + MESH_SIGNALLING_TABLE[_] = new_entry + + # add new routing entry if not exists + if new_entry not in MESH_SIGNALLING_TABLE: + print(f"INSERT {new_entry} >>> SIGNALLING TABLE") + MESH_SIGNALLING_TABLE.append(new_entry) + + + + + def enqueue_frame_for_tx( + self, + frame_to_tx, # : list[bytearray], # this causes a crash on python 3.7 + c2_mode=FREEDV_MODE.sig0.value, + copies=1, + repeat_delay=0, + ) -> None: + """ + Send (transmit) supplied frame to TNC + + :param frame_to_tx: Frame data to send + :type frame_to_tx: list of bytearrays + :param c2_mode: Codec2 mode to use, defaults to datac13 + :type c2_mode: int, optional + :param copies: Number of frame copies to send, defaults to 1 + :type copies: int, optional + :param repeat_delay: Delay time before sending repeat frame, defaults to 0 + :type repeat_delay: int, optional + """ + #print(frame_to_tx[0]) + #print(frame_to_tx) + frame_type = FRAME_TYPE(int.from_bytes(frame_to_tx[0][:1], byteorder="big")).name + self.log.debug("[TNC] enqueue_frame_for_tx", c2_mode=FREEDV_MODE(c2_mode).name, data=frame_to_tx, + type=frame_type) + + # Set the TRANSMITTING flag before adding an object to the transmit queue + # TODO: This is not that nice, we could improve this somehow + TNC.transmitting = True + modem.MODEM_TRANSMIT_QUEUE.put([c2_mode, copies, repeat_delay, frame_to_tx]) + + # Wait while transmitting + while TNC.transmitting: + threading.Event().wait(0.01) + + + def transmit_mesh_signalling(self, data): + dxcallsign_crc = bytes.fromhex(data[1]) + + frame_type = bytes([FRAME_TYPE.MESH_SIGNALLING_PING.value]) + + ping_frame = bytearray(14) + ping_frame[:1] = frame_type + ping_frame[1:4] = dxcallsign_crc + ping_frame[4:7] = helpers.get_crc_24(Station.mycallsign) + ping_frame[7:13] = helpers.callsign_to_bytes(Station.mycallsign) + + self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.sig0.value) \ No newline at end of file diff --git a/tnc/queues.py b/tnc/queues.py index c734cbcb..6a9d9f88 100644 --- a/tnc/queues.py +++ b/tnc/queues.py @@ -14,6 +14,9 @@ MODEM_TRANSMIT_QUEUE = queue.Queue() # Initialize FIFO queue to store received frames MESH_RECEIVED_QUEUE = queue.Queue() +MESH_QUEUE_TRANSMIT = queue.Queue() +MESH_COMMAND_STATES = {} + # Initialize FIFO queue to store audio frames AUDIO_RECEIVED_QUEUE = queue.Queue() diff --git a/tnc/sock.py b/tnc/sock.py index 714220ea..613ce36e 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -32,7 +32,7 @@ import structlog from random import randrange import ujson as json from exceptions import NoCallsign -from queues import DATA_QUEUE_TRANSMIT, RX_BUFFER, RIGCTLD_COMMAND_QUEUE +from queues import DATA_QUEUE_TRANSMIT, RX_BUFFER, RIGCTLD_COMMAND_QUEUE, MESH_QUEUE_TRANSMIT SOCKET_QUEUE = queue.Queue() DAEMON_QUEUE = queue.Queue() @@ -402,6 +402,10 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): # DISABLE MESH if received_json["type"] == "set" and received_json["command"] == "disable_mesh": MeshParam.enable_protocol = False + # -------------- MESH ---------------- # + # MESH PING + if received_json["type"] == "mesh" and received_json["command"] == "ping": + self.tnc_mesh_ping(received_json) except Exception as err: log.error("[SCK] JSON decoding error", e=err) @@ -418,6 +422,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): ARQ.arq_session_state = "disconnecting" command_response("disconnect", True) + + except Exception as err: command_response("listen", False) log.warning( @@ -571,6 +577,40 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command=received_json, ) + + def tnc_mesh_ping(self, received_json): + # send ping frame and wait for ACK + try: + dxcallsign = received_json["dxcallsign"] + if not str(dxcallsign).strip(): + raise NoCallsign + + # additional step for being sure our callsign is correctly + # in case we are not getting a station ssid + # then we are forcing a station ssid = 0 + dxcallsign = helpers.callsign_to_bytes(dxcallsign) + dxcallsign = helpers.bytes_to_callsign(dxcallsign) + + # check if specific callsign is set with different SSID than the TNC is initialized + try: + mycallsign = received_json["mycallsign"] + mycallsign = helpers.callsign_to_bytes(mycallsign) + mycallsign = helpers.bytes_to_callsign(mycallsign) + + except Exception: + mycallsign = Station.mycallsign + + MESH_QUEUE_TRANSMIT.put(["PING", mycallsign, dxcallsign]) + command_response("ping", True) + except NoCallsign: + command_response("ping", False) + log.warning("[SCK] callsign required for ping", command=received_json) + except Exception as err: + command_response("ping", False) + log.warning( + "[SCK] PING command execution error", e=err, command=received_json + ) + def tnc_ping_ping(self, received_json): # send ping frame and wait for ACK diff --git a/tnc/static.py b/tnc/static.py index cdc322d1..6e4b134c 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -129,7 +129,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.0-alpha.1-mesh-exp8" + version = "0.10.0-alpha.1-mesh-exp9" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds @@ -162,6 +162,7 @@ class FRAME_TYPE(Enum): FR_NACK = 63 BURST_NACK = 64 MESH_BROADCAST = 100 + MESH_SIGNALLING_PING = 101 CQ = 200 QRV = 201 PING = 210 From 4c556e61fd6fae65e2a3b93011224f40144ed6ea Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Tue, 13 Jun 2023 14:03:36 +0200 Subject: [PATCH 34/66] introduced mesh state table --- gui/preload-mesh.js | 94 +++++++++++++++++++++++++------ gui/sock.js | 1 + gui/src/mesh-module.html | 44 +++++++++++---- tnc/data_handler.py | 2 +- tnc/mesh.py | 118 ++++++++++++++++++++++++++++++++------- tnc/modem.py | 13 +++-- tnc/queues.py | 2 +- tnc/sock.py | 19 ++++++- tnc/static.py | 1 + 9 files changed, 239 insertions(+), 55 deletions(-) diff --git a/gui/preload-mesh.js b/gui/preload-mesh.js index f8e8cc50..00657660 100644 --- a/gui/preload-mesh.js +++ b/gui/preload-mesh.js @@ -72,22 +72,6 @@ if (tbl !== null) { for (i = 0; i < routes.length; i++) { - - /* - var myGrid = document.getElementById("myGrid").value; - try { - var dist = parseInt(distance(myGrid, dxGrid)) + " km"; - document.getElementById("dataModalPingDistance").textContent = dist; - } catch { - document.getElementById("dataModalPingDistance").textContent = "---"; - } - document.getElementById("dataModalPingDB").textContent = - arg.stations[i]["snr"]; - } - */ - - - var row = document.createElement("tr"); var datetime = new Date(routes[i]["timestamp"] * 1000).toLocaleString( navigator.language,{ @@ -139,6 +123,82 @@ for (i = 0; i < routes.length; i++) { + tbl.appendChild(row); +} + /*-------------------------------------------*/ + var routes = arg.mesh_signalling_table; + +console.log(routes) +if (typeof routes == "undefined") { + return; + } + + + var tbl = document.getElementById("mesh-signalling-table"); +if (tbl !== null) { + tbl.innerHTML = ""; + + } + + +for (i = 0; i < routes.length; i++) { + + var row = document.createElement("tr"); + var datetime = new Date(routes[i]["timestamp"] * 1000).toLocaleString( + navigator.language,{ + hourCycle: 'h23', + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit" + } + ); + var timestamp = document.createElement("td"); + var timestampText = document.createElement("span"); + timestampText.innerText = datetime; + timestamp.appendChild(timestampText); + + var destination = document.createElement("td"); + var destinationText = document.createElement("span"); + destinationText.innerText = routes[i]["destination"]; + destination.appendChild(destinationText); + + var router = document.createElement("td"); + var routerText = document.createElement("span"); + routerText.innerText = routes[i]["router"]; + router.appendChild(routerText); + + var frametype = document.createElement("td"); + var frametypeText = document.createElement("span"); + frametypeText.innerText = routes[i]["frametype"]; + frametype.appendChild(frametypeText); + + var payload = document.createElement("td"); + var payloadText = document.createElement("span"); + payloadText.innerText = routes[i]["payload"]; + payload.appendChild(payloadText); + + var attempt = document.createElement("td"); + var attemptText = document.createElement("span"); + attemptText.innerText = routes[i]["attempt"]; + attempt.appendChild(attemptText); + + var status = document.createElement("td"); + var statusText = document.createElement("span"); + statusText.innerText = routes[i]["status"]; + status.appendChild(statusText); + + + row.appendChild(timestamp); + row.appendChild(destination); + row.appendChild(router); + row.appendChild(frametype); + row.appendChild(payload); + row.appendChild(attempt); + row.appendChild(status); + tbl.appendChild(row); } @@ -147,6 +207,6 @@ if (tbl !== null) { // scroll to bottom of page // https://stackoverflow.com/a/11715670 - window.scrollTo(0, document.body.scrollHeight); + //window.scrollTo(0, document.body.scrollHeight); } }); diff --git a/gui/sock.js b/gui/sock.js index 182802dd..7b7454b2 100644 --- a/gui/sock.js +++ b/gui/sock.js @@ -219,6 +219,7 @@ client.on("data", function (socketdata) { arq_transmission_percent: data["arq_transmission_percent"], stations: data["stations"], routing_table: data["routing_table"], + mesh_signalling_table: data["mesh_signalling_table"], beacon_state: data["beacon_state"], hamlib_status: data["hamlib_status"], listen: data["listen"], diff --git a/gui/src/mesh-module.html b/gui/src/mesh-module.html index 3b1982f5..ffea72d9 100644 --- a/gui/src/mesh-module.html +++ b/gui/src/mesh-module.html @@ -49,8 +49,10 @@ -
    -
    +
    + + +
    @@ -64,17 +66,39 @@ - +
    + + + + +
    + + + + + + + + + + + + + + +
    TimestampDestinationRouterFrametypePayloadAttemptStatus
    +
    + + +
    + + + + +
    diff --git a/tnc/data_handler.py b/tnc/data_handler.py index 71ec13a4..fba1a137 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -26,7 +26,7 @@ import structlog import stats import ujson as json from codec2 import FREEDV_MODE, FREEDV_MODE_USED_SLOTS -from queues import DATA_QUEUE_RECEIVED, DATA_QUEUE_TRANSMIT, RX_BUFFER +from queues import DATA_QUEUE_RECEIVED, DATA_QUEUE_TRANSMIT, RX_BUFFER, MESH_RECEIVED_QUEUE from static import FRAME_TYPE as FR_TYPE import broadcast diff --git a/tnc/mesh.py b/tnc/mesh.py index b4243932..85251428 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -46,9 +46,7 @@ import helpers import structlog import ujson as json -from queues import MESH_RECEIVED_QUEUE, MESH_QUEUE_TRANSMIT - -MESH_SIGNALLING_TABLE = [] +from queues import MESH_RECEIVED_QUEUE, MESH_QUEUE_TRANSMIT, MESH_SIGNALLING_TABLE class MeshRouter(): def __init__(self): @@ -206,6 +204,9 @@ class MeshRouter(): self.received_routing_table(data_in[:-2]) elif int.from_bytes(data_in[:1], "big") in [FRAME_TYPE.MESH_SIGNALLING_PING.value]: self.received_mesh_ping(data_in[:-2]) + elif int.from_bytes(data_in[:1], "big") in [FRAME_TYPE.MESH_SIGNALLING_PING_ACK.value]: + self.received_mesh_ping_ack(data_in[:-2]) + else: print("wrong mesh data received") print(data_in) @@ -215,23 +216,24 @@ class MeshRouter(): data = MESH_QUEUE_TRANSMIT.get() print(data) if data[0] == "PING": - self.add_mesh_ping_to_signalling_table(data[2]) + self.add_mesh_ping_to_signalling_table(helpers.get_crc_24(data[2]).hex(), status="awaiting_ack") else: print("wrong mesh command") def mesh_signalling_dispatcher(self): - # [timestamp, destination, router, frametype, payload, attempt] - # --------------0------------1---------2---------3--------4---------5---- # + # [timestamp, destination, router, frametype, payload, attempt, status] + # --------------0------------1---------2---------3--------4---------5--------6 # while True: threading.Event().wait(1.0) for entry in MESH_SIGNALLING_TABLE: print(entry) timestamp = entry[0] attempt = entry[5] + status = entry[6] # check for PING cases - if entry[3] == "PING" and attempt < 10: + if entry[3] in ["PING", "PING-ACK"] and attempt < 10 and status not in ["acknowledged"]: # Calculate the transmission time with exponential increase @@ -242,7 +244,7 @@ class MeshRouter(): entry[5] += 1 print(attempt) print("transmit mesh ping") - self.transmit_mesh_signalling(entry) + self.transmit_mesh_signalling_ping(entry) else: print("wait some more time") else: @@ -321,25 +323,88 @@ class MeshRouter(): return int((value_old + value) / 2) def received_mesh_ping(self, data_in): - pass + destination = data_in[1:4].hex() + if destination == Station.mycallsign_crc.hex(): + print("its a ping for us") + # use case 1: set status to acknowleding if we are the receiver of a PING + self.add_mesh_ping_to_signalling_table(destination, status="acknowledging") + dxcallsign_crc = Station.mycallsign_crc + self.transmit_mesh_signalling_ping_ack(dxcallsign_crc) + # use case 2: set status to acknowledged if we are out of retries + #self.add_mesh_ping_to_signalling_table(destination, status="acknowledged") + else: + print(f"its a ping for {destination}") + # use case 1: set status to forwarding if we are not hte receiver of a PING + self.add_mesh_ping_to_signalling_table(destination, status="forwarding") + dxcallsign_crc = bytes.fromhex(destination) + self.transmit_mesh_signalling_ping_ack(dxcallsign_crc) + # use case 2: set status to forwarded if we are not the receiver of a PING and out of retries + #self.add_mesh_ping_to_signalling_table(destination, status="forwarded") - def add_mesh_ping_to_signalling_table(self, dxcallsign): + def received_mesh_ping_ack(self, data_in): + # TODO: + # Check if we have a ping callsign already in signalling table + # if PING, then override and make it a PING-ACK + # if not, then add to table + + destination = data_in[1:4].hex() + timestamp = time.time() + router = "" + frametype = "PING-ACK" + payload = "" + attempt = 0 + status = "forwarding" + new_entry = [timestamp, destination, router, frametype, payload, attempt, status] + + print(MESH_SIGNALLING_TABLE) + for _, item in enumerate(MESH_SIGNALLING_TABLE): + + # use case 3: PING ACK sets state to processed if we are the initiator of a PING + if destination == Station.mycallsign_crc.hex(): + update_entry = [time.time(), destination, "", "PING-ACK", "", 0, "acknowledged"] + print(f"UPDATE AND CHANGE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") + MESH_SIGNALLING_TABLE[_] = update_entry + return + + + # use case 1: PING-ACK updates PING-ACK, but stay at attempts + if destination in item[1] and "PING-ACK" in item[3]: + update_entry = [item[0], destination, "", "PING-ACK", "", item[5], "forwarding"] + print(f"UPDATE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") + MESH_SIGNALLING_TABLE[_] = update_entry + return + + # use case 2: PING-ACK overwrites PING + # this avoids possible packet loops + if destination in item[1] and "PING" in item[3]: + update_entry = [time.time(), destination, "", "PING-ACK", "", 0, "forwarding"] + print(f"UPDATE AND CHANGE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") + MESH_SIGNALLING_TABLE[_] = update_entry + return + + + + if new_entry not in MESH_SIGNALLING_TABLE: + print(f"INSERT {new_entry} >>> SIGNALLING TABLE") + MESH_SIGNALLING_TABLE.append(new_entry) + + def add_mesh_ping_to_signalling_table(self, destination, status): timestamp = time.time() - destination = helpers.get_crc_24(dxcallsign).hex() router = "" frametype = "PING" payload = "" attempt = 0 - # [timestamp, destination, router, frametype, payload, attempt] - # --------------0------------1---------2---------3--------4---------5---- # - new_entry = [timestamp, destination, router, frametype, payload, attempt] - print(MESH_SIGNALLING_TABLE) + # [timestamp, destination, router, frametype, payload, attempt, status] + # --------------0------------1---------2---------3--------4---------5--------6-----# + new_entry = [timestamp, destination, router, frametype, payload, attempt, status] for _, item in enumerate(MESH_SIGNALLING_TABLE): - # update routing entry if exists - if new_entry[0] in item[0] and new_entry[1] in item[1]: - print(f"UPDATE {MESH_SIGNALLING_TABLE[_]} >>> {new_entry}") - MESH_SIGNALLING_TABLE[_] = new_entry + # update entry if exists + if destination in item[1] and "PING" in item[3]: + update_entry = [item[0], destination, "", "PING", "", item[5], status] + print(f"UPDATE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") + MESH_SIGNALLING_TABLE[_] = update_entry + return # add new routing entry if not exists if new_entry not in MESH_SIGNALLING_TABLE: @@ -384,7 +449,7 @@ class MeshRouter(): threading.Event().wait(0.01) - def transmit_mesh_signalling(self, data): + def transmit_mesh_signalling_ping(self, data): dxcallsign_crc = bytes.fromhex(data[1]) frame_type = bytes([FRAME_TYPE.MESH_SIGNALLING_PING.value]) @@ -395,4 +460,17 @@ class MeshRouter(): ping_frame[4:7] = helpers.get_crc_24(Station.mycallsign) ping_frame[7:13] = helpers.callsign_to_bytes(Station.mycallsign) + self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.sig0.value) + + def transmit_mesh_signalling_ping_ack(self, dxcallsign_crc): + #dxcallsign_crc = bytes.fromhex(data[1]) + + frame_type = bytes([FRAME_TYPE.MESH_SIGNALLING_PING_ACK.value]) + + ping_frame = bytearray(14) + ping_frame[:1] = frame_type + ping_frame[1:4] = dxcallsign_crc + #ping_frame[4:7] = helpers.get_crc_24(Station.mycallsign) + #ping_frame[7:13] = helpers.callsign_to_bytes(Station.mycallsign) + self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.sig0.value) \ No newline at end of file diff --git a/tnc/modem.py b/tnc/modem.py index ba95ed6c..182222e3 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -907,7 +907,9 @@ class RF: ]: print("dropp") elif int.from_bytes(bytes(bytes_out[:1]), "big") in [ - FRAME_TYPE.MESH_BROADCAST.value + FRAME_TYPE.MESH_BROADCAST.value, + FRAME_TYPE.MESH_SIGNALLING_PING.value, + FRAME_TYPE.MESH_SIGNALLING_PING_ACK.value, ]: self.log.debug( "[MDM] [demod_audio] moving data to mesh dispatcher", nbytes=nbytes @@ -1308,10 +1310,11 @@ class RF: raise ZeroDivisionError AudioParam.audio_dbfs = 20 * np.log10(rms / 32768) except Exception as e: - self.log.warning( - "[MDM] fft calculation error - please check your audio setup", - e=e, - ) + # FIXME: Disabled for cli cleanup + #self.log.warning( + # "[MDM] fft calculation error - please check your audio setup", + # e=e, + #) AudioParam.audio_dbfs = -100 rms_counter = 0 diff --git a/tnc/queues.py b/tnc/queues.py index 6a9d9f88..14c907a9 100644 --- a/tnc/queues.py +++ b/tnc/queues.py @@ -15,7 +15,7 @@ MODEM_TRANSMIT_QUEUE = queue.Queue() # Initialize FIFO queue to store received frames MESH_RECEIVED_QUEUE = queue.Queue() MESH_QUEUE_TRANSMIT = queue.Queue() -MESH_COMMAND_STATES = {} +MESH_SIGNALLING_TABLE = [] # Initialize FIFO queue to store audio frames diff --git a/tnc/sock.py b/tnc/sock.py index 613ce36e..f4999310 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -32,7 +32,7 @@ import structlog from random import randrange import ujson as json from exceptions import NoCallsign -from queues import DATA_QUEUE_TRANSMIT, RX_BUFFER, RIGCTLD_COMMAND_QUEUE, MESH_QUEUE_TRANSMIT +from queues import DATA_QUEUE_TRANSMIT, RX_BUFFER, RIGCTLD_COMMAND_QUEUE, MESH_QUEUE_TRANSMIT, MESH_SIGNALLING_TABLE SOCKET_QUEUE = queue.Queue() DAEMON_QUEUE = queue.Queue() @@ -1177,6 +1177,7 @@ def send_tnc_state(): "beacon_state": str(Beacon.beacon_state), "stations": [], "routing_table": [], + "mesh_signalling_table" : [], "mycallsign": str(Station.mycallsign, encoding), "mygrid": str(Station.mygrid, encoding), "dxcallsign": str(Station.dxcallsign, encoding), @@ -1215,6 +1216,22 @@ def send_tnc_state(): "timestamp": MeshParam.routing_table[_][5], } ) + + for _, entry in enumerate(MESH_SIGNALLING_TABLE): + + output["mesh_signalling_table"].append( + { + "timestamp": MESH_SIGNALLING_TABLE[_][0], + "destination": MESH_SIGNALLING_TABLE[_][1], + "router": MESH_SIGNALLING_TABLE[_][2], + "frametype": MESH_SIGNALLING_TABLE[_][3], + "payload": MESH_SIGNALLING_TABLE[_][4], + "attempt": MESH_SIGNALLING_TABLE[_][5], + "status": MESH_SIGNALLING_TABLE[_][6], + + } + ) + #print(output) return json.dumps(output) diff --git a/tnc/static.py b/tnc/static.py index 6e4b134c..84cbb921 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -163,6 +163,7 @@ class FRAME_TYPE(Enum): BURST_NACK = 64 MESH_BROADCAST = 100 MESH_SIGNALLING_PING = 101 + MESH_SIGNALLING_PING_ACK = 102 CQ = 200 QRV = 201 PING = 210 From 3736f1271261527f942b443391bfa8fe9c034311 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Tue, 13 Jun 2023 15:15:42 +0200 Subject: [PATCH 35/66] adjusted mesh ack --- tnc/mesh.py | 113 +++++++++++++++++++++++++++++++++----------------- tnc/static.py | 2 +- 2 files changed, 76 insertions(+), 39 deletions(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index 85251428..032ea1a6 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -242,13 +242,17 @@ class MeshRouter(): # check if it is time to transmit if time.time() >= transmission_time: entry[5] += 1 - print(attempt) print("transmit mesh ping") - self.transmit_mesh_signalling_ping(entry) + channel_busy_timeout = time.time() + 5 + while ModemParam.channel_busy and time.time() < channel_busy_timeout: + threading.Event().wait(0.01) + self.transmit_mesh_signalling_ping(bytes.fromhex(entry[1])) else: - print("wait some more time") + pass + #print("wait some more time") else: - print("...") + pass + #print("...") def received_routing_table(self, data_in): try: @@ -328,6 +332,11 @@ class MeshRouter(): print("its a ping for us") # use case 1: set status to acknowleding if we are the receiver of a PING self.add_mesh_ping_to_signalling_table(destination, status="acknowledging") + + channel_busy_timeout = time.time() + 5 + while ModemParam.channel_busy and time.time() < channel_busy_timeout: + threading.Event().wait(0.01) + dxcallsign_crc = Station.mycallsign_crc self.transmit_mesh_signalling_ping_ack(dxcallsign_crc) # use case 2: set status to acknowledged if we are out of retries @@ -337,7 +346,8 @@ class MeshRouter(): # use case 1: set status to forwarding if we are not hte receiver of a PING self.add_mesh_ping_to_signalling_table(destination, status="forwarding") dxcallsign_crc = bytes.fromhex(destination) - self.transmit_mesh_signalling_ping_ack(dxcallsign_crc) + + #self.transmit_mesh_signalling_ping(dxcallsign_crc) # use case 2: set status to forwarded if we are not the receiver of a PING and out of retries #self.add_mesh_ping_to_signalling_table(destination, status="forwarded") @@ -353,40 +363,19 @@ class MeshRouter(): frametype = "PING-ACK" payload = "" attempt = 0 - status = "forwarding" - new_entry = [timestamp, destination, router, frametype, payload, attempt, status] + + + if destination == Station.mycallsign_crc.hex(): + print("its a ping ack for us") + status = "acknowledged" + self.add_mesh_ping_ack_to_signalling_table(destination, status) + else: + status = "forwarding" + self.add_mesh_ping_ack_to_signalling_table(destination, status) + print(f"it is a ping ack for {destination}") print(MESH_SIGNALLING_TABLE) - for _, item in enumerate(MESH_SIGNALLING_TABLE): - # use case 3: PING ACK sets state to processed if we are the initiator of a PING - if destination == Station.mycallsign_crc.hex(): - update_entry = [time.time(), destination, "", "PING-ACK", "", 0, "acknowledged"] - print(f"UPDATE AND CHANGE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") - MESH_SIGNALLING_TABLE[_] = update_entry - return - - - # use case 1: PING-ACK updates PING-ACK, but stay at attempts - if destination in item[1] and "PING-ACK" in item[3]: - update_entry = [item[0], destination, "", "PING-ACK", "", item[5], "forwarding"] - print(f"UPDATE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") - MESH_SIGNALLING_TABLE[_] = update_entry - return - - # use case 2: PING-ACK overwrites PING - # this avoids possible packet loops - if destination in item[1] and "PING" in item[3]: - update_entry = [time.time(), destination, "", "PING-ACK", "", 0, "forwarding"] - print(f"UPDATE AND CHANGE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") - MESH_SIGNALLING_TABLE[_] = update_entry - return - - - - if new_entry not in MESH_SIGNALLING_TABLE: - print(f"INSERT {new_entry} >>> SIGNALLING TABLE") - MESH_SIGNALLING_TABLE.append(new_entry) def add_mesh_ping_to_signalling_table(self, destination, status): timestamp = time.time() @@ -411,8 +400,57 @@ class MeshRouter(): print(f"INSERT {new_entry} >>> SIGNALLING TABLE") MESH_SIGNALLING_TABLE.append(new_entry) + def add_mesh_ping_ack_to_signalling_table(self, destination, status): + timestamp = time.time() + router = "" + frametype = "PING-ACK" + payload = "" + attempt = 0 + new_entry = [timestamp, destination, router, frametype, payload, attempt, status] + for _, item in enumerate(MESH_SIGNALLING_TABLE): + # update entry if exists + if destination in item[1] and "PING" in item[3]: + update_entry = [item[0], destination, "", "PING-ACK", "", item[5], status] + print(f"UPDATE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") + MESH_SIGNALLING_TABLE[_] = update_entry + return + + # add new routing entry if not exists + if new_entry not in MESH_SIGNALLING_TABLE: + print(f"INSERT {new_entry} >>> SIGNALLING TABLE") + MESH_SIGNALLING_TABLE.append(new_entry) + + """ + for _, item in enumerate(MESH_SIGNALLING_TABLE): + print(item) + # use case 3: PING ACK sets state to processed if we are the initiator of a PING and it is not yet acknowledged + if destination == Station.mycallsign_crc.hex() and item[6] not in ["acknowledged"]: + update_entry = [time.time(), destination, "", "PING", "", 0, "acknowledged"] + print(f"UPDATE AND CHANGE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") + MESH_SIGNALLING_TABLE[_] = update_entry + return + + # use case 1: PING-ACK updates PING-ACK, but stay at attempts + if destination in item[1] and "PING-ACK" in item[3]: + update_entry = [item[0], destination, "", "PING-ACK", "", item[5], "forwarding"] + print(f"UPDATE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") + MESH_SIGNALLING_TABLE[_] = update_entry + return + + # use case 2: PING-ACK overwrites PING + # this avoids possible packet loops + if destination in item[1] and "PING" in item[3]: + update_entry = [time.time(), destination, "", "PING-ACK", "", 0, "forwarding"] + print(f"UPDATE AND CHANGE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") + MESH_SIGNALLING_TABLE[_] = update_entry + return + + if new_entry not in MESH_SIGNALLING_TABLE: + print(f"INSERT {new_entry} >>> SIGNALLING TABLE") + MESH_SIGNALLING_TABLE.append(new_entry) + """ def enqueue_frame_for_tx( self, @@ -449,8 +487,7 @@ class MeshRouter(): threading.Event().wait(0.01) - def transmit_mesh_signalling_ping(self, data): - dxcallsign_crc = bytes.fromhex(data[1]) + def transmit_mesh_signalling_ping(self, dxcallsign_crc): frame_type = bytes([FRAME_TYPE.MESH_SIGNALLING_PING.value]) diff --git a/tnc/static.py b/tnc/static.py index 84cbb921..01b2e4d2 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -129,7 +129,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.0-alpha.1-mesh-exp9" + version = "0.10.0-alpha.1-mesh-exp11" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From a9a2b63650492f85ea15400f54e7f63bc4542616 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Tue, 13 Jun 2023 17:06:56 +0200 Subject: [PATCH 36/66] adjusted attempt timing --- tnc/mesh.py | 5 ++++- tnc/static.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index 032ea1a6..c8354a78 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -237,7 +237,10 @@ class MeshRouter(): # Calculate the transmission time with exponential increase - transmission_time = timestamp + (2 ** attempt) * 10 + #transmission_time = timestamp + (2 ** attempt) * 10 + + # Calculate transmission times for attempts 0 to 10 with stronger S-curves in minutes + transmission_time = (4.5 / (1 + np.exp(-1. * (attempt - 5)))) * 60 # check if it is time to transmit if time.time() >= transmission_time: diff --git a/tnc/static.py b/tnc/static.py index 01b2e4d2..8a0c95ee 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -129,7 +129,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.0-alpha.1-mesh-exp11" + version = "0.10.0-alpha.1-mesh-exp12" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From fabc86962c37666bf84c1015f13744abd04b340c Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Fri, 16 Jun 2023 09:45:20 +0200 Subject: [PATCH 37/66] fixed timestamp for transmission time --- tnc/mesh.py | 2 +- tnc/static.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index c8354a78..ac7c50a3 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -240,7 +240,7 @@ class MeshRouter(): #transmission_time = timestamp + (2 ** attempt) * 10 # Calculate transmission times for attempts 0 to 10 with stronger S-curves in minutes - transmission_time = (4.5 / (1 + np.exp(-1. * (attempt - 5)))) * 60 + transmission_time = timestamp + (4.5 / (1 + np.exp(-1. * (attempt - 5)))) * 60 # check if it is time to transmit if time.time() >= transmission_time: diff --git a/tnc/static.py b/tnc/static.py index 8a0c95ee..52722770 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -129,7 +129,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.0-alpha.1-mesh-exp12" + version = "0.10.0-alpha.1-mesh-exp13" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From 78753b4f9282681cd70b6d9cdf34326c9aed1702 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Fri, 16 Jun 2023 10:27:38 +0200 Subject: [PATCH 38/66] increased attempts and timeout --- tnc/mesh.py | 8 ++++---- tnc/static.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index ac7c50a3..28f084a4 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -233,14 +233,14 @@ class MeshRouter(): attempt = entry[5] status = entry[6] # check for PING cases - if entry[3] in ["PING", "PING-ACK"] and attempt < 10 and status not in ["acknowledged"]: + if entry[3] in ["PING", "PING-ACK"] and attempt < 30 and status not in ["acknowledged"]: # Calculate the transmission time with exponential increase #transmission_time = timestamp + (2 ** attempt) * 10 - - # Calculate transmission times for attempts 0 to 10 with stronger S-curves in minutes - transmission_time = timestamp + (4.5 / (1 + np.exp(-1. * (attempt - 5)))) * 60 + # Calculate transmission times for attempts 0 to 30 with stronger S-curves in minutes + correction_factor = 750 + transmission_time = timestamp + (4.5 / (1 + np.exp(-1. * (attempt - 5)))) * correction_factor # check if it is time to transmit if time.time() >= transmission_time: diff --git a/tnc/static.py b/tnc/static.py index 52722770..25eeb704 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -129,7 +129,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.0-alpha.1-mesh-exp13" + version = "0.10.0-alpha.1-mesh-exp14" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From 43856ef115af8c78d215388a796823a61cabc63e Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Fri, 16 Jun 2023 12:02:56 +0200 Subject: [PATCH 39/66] enabled 24h attempts --- tnc/mesh.py | 2 +- tnc/static.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tnc/mesh.py b/tnc/mesh.py index 28f084a4..62748d1d 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -240,7 +240,7 @@ class MeshRouter(): #transmission_time = timestamp + (2 ** attempt) * 10 # Calculate transmission times for attempts 0 to 30 with stronger S-curves in minutes correction_factor = 750 - transmission_time = timestamp + (4.5 / (1 + np.exp(-1. * (attempt - 5)))) * correction_factor + transmission_time = timestamp + (4.5 / (1 + np.exp(-1. * (attempt - 5)))) * correction_factor * attempt # check if it is time to transmit if time.time() >= transmission_time: diff --git a/tnc/static.py b/tnc/static.py index 25eeb704..92c1130a 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -129,7 +129,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.0-alpha.1-mesh-exp14" + version = "0.10.0-alpha.1-mesh-exp15" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From 139ea334fc3093a5b40e94c1fae6080d5ccf6e88 Mon Sep 17 00:00:00 2001 From: Mashintime Date: Sun, 2 Jul 2023 11:33:53 -0400 Subject: [PATCH 40/66] Allow enabling/disabling mesh features. --- gui/main.js | 3 ++- gui/preload-main.js | 10 ++++++++++ gui/src/index.html | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/gui/main.js b/gui/main.js index de5dd6fc..cb647953 100644 --- a/gui/main.js +++ b/gui/main.js @@ -101,7 +101,8 @@ const configDefaultSettings = "enable_auto_retry" : "False", \ "tx_delay" : 0, \ "auto_start": 0, \ - "enable_sys_notification": 1 \ + "enable_sys_notification": 1, \ + "enable_mesh_features": 0 \ }'; if (!fs.existsSync(configPath)) { diff --git a/gui/preload-main.js b/gui/preload-main.js index 7f511d81..bf94e2fd 100644 --- a/gui/preload-main.js +++ b/gui/preload-main.js @@ -1760,7 +1760,17 @@ window.addEventListener("DOMContentLoaded", () => { }); autostart_rigctld(); + + //Enable mesh features by setting to 1, should be changed for releases + config.enable_mesh_features =1; + //config.enable_mesh_features =0; + + if (! config.enable_mesh_features == 1) { + document.getElementById("liMeshTable").style.visibility = "hidden"; + document.getElementById("liMeshTable").style.display = "none"; + } }); +//End of domcontentloaded function resetSortIcon() { document.getElementById("hslSort").remove(); diff --git a/gui/src/index.html b/gui/src/index.html index 1298b172..c2590fcd 100644 --- a/gui/src/index.html +++ b/gui/src/index.html @@ -215,14 +215,18 @@ Settings
  • +
  • +
  • +
  • +
  • - + diff --git a/tnc/static.py b/tnc/static.py index 370d6e9f..b4cd839e 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -129,7 +129,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.0-alpha.1-mesh-exp19" + version = "0.10.0-alpha.1" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From a3ed9060516ff401f6f833cd1f5b8ad323f83360 Mon Sep 17 00:00:00 2001 From: Mashintime Date: Tue, 4 Jul 2023 11:36:52 -0400 Subject: [PATCH 59/66] Update low bandwidth labels --- gui/preload-main.js | 10 +++++----- gui/src/index.html | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gui/preload-main.js b/gui/preload-main.js index 376da7db..ce00363b 100644 --- a/gui/preload-main.js +++ b/gui/preload-main.js @@ -344,9 +344,9 @@ window.addEventListener("DOMContentLoaded", () => { } if (config.low_bandwidth_mode == "True") { - document.getElementById("500HzModeSwitch").checked = true; + document.getElementById("250HzModeSwitch").checked = true; } else { - document.getElementById("500HzModeSwitch").checked = false; + document.getElementById("250HzModeSwitch").checked = false; } if (config.high_graphics == "True") { @@ -1158,8 +1158,8 @@ window.addEventListener("DOMContentLoaded", () => { }); // enable 500z Switch clicked - document.getElementById("500HzModeSwitch").addEventListener("click", () => { - if (document.getElementById("500HzModeSwitch").checked == true) { + document.getElementById("250HzModeSwitch").addEventListener("click", () => { + if (document.getElementById("250HzModeSwitch").checked == true) { config.low_bandwidth_mode = "True"; } else { config.low_bandwidth_mode = "False"; @@ -1484,7 +1484,7 @@ window.addEventListener("DOMContentLoaded", () => { var enable_fft = "False"; } - if (document.getElementById("500HzModeSwitch").checked == true) { + if (document.getElementById("250HzModeSwitch").checked == true) { var low_bandwidth_mode = "True"; } else { var low_bandwidth_mode = "False"; diff --git a/gui/src/index.html b/gui/src/index.html index 115c2d3a..55895522 100644 --- a/gui/src/index.html +++ b/gui/src/index.html @@ -3671,17 +3671,17 @@
    Enable 250Hz only mode From f1410e49afcf78961e9f47c23756e23287132c95 Mon Sep 17 00:00:00 2001 From: Mashintime Date: Tue, 4 Jul 2023 12:02:54 -0400 Subject: [PATCH 60/66] Fix logic --- gui/preload-chat.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gui/preload-chat.js b/gui/preload-chat.js index f437f5cd..ac5fece9 100644 --- a/gui/preload-chat.js +++ b/gui/preload-chat.js @@ -1510,8 +1510,8 @@ update_chat = function (obj) { // console.log(obj.attempt) if ( - !obj.status == "broadcast_transmit" || - !obj.status == "broadcast_received" + obj.status != "broadcast_transmit" || + obj.status != "broadcast_received" ) { document.getElementById("msg-" + obj._id + "-status").innerHTML = get_icon_for_state(obj.status); @@ -1577,8 +1577,8 @@ update_chat = function (obj) { "msg-" + obj._id + "-progress-information" ).innerHTML = "TRANSMITTED - " + obj.bytesperminute + " Bpm"; } else if ( - !obj.status == "broadcast_transmit" || - !obj.status == "broadcast_received" + obj.status != "broadcast_transmit" || + obj.status != "broadcast_received" ) { document .getElementById("msg-" + obj._id + "-progress") From 17926354e80a47363f392cf65af2594be8347505 Mon Sep 17 00:00:00 2001 From: Mashintime Date: Tue, 4 Jul 2023 12:04:54 -0400 Subject: [PATCH 61/66] May receive data without uuid, check for it --- gui/preload-chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/preload-chat.js b/gui/preload-chat.js index ac5fece9..0088e93c 100644 --- a/gui/preload-chat.js +++ b/gui/preload-chat.js @@ -684,7 +684,7 @@ ipcRenderer.on("action-update-transmission-status", (event, arg) => { document.getElementById("txtConnectedWithChat").textContent = data.dxcallsign; console.log(data.status); - if (data.uuid !== "no-uuid") { + if (typeof data.uuid !== "undefined" || data.uuid !== "no-uuid") { db.get(data.uuid, { attachments: true, }) From 8268dfd419947b65d5941e72e19d68e1063feb78 Mon Sep 17 00:00:00 2001 From: Mashintime Date: Tue, 4 Jul 2023 12:49:20 -0400 Subject: [PATCH 62/66] Fix message chat progress bars --- gui/preload-chat.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/gui/preload-chat.js b/gui/preload-chat.js index 0088e93c..a442c677 100644 --- a/gui/preload-chat.js +++ b/gui/preload-chat.js @@ -683,8 +683,10 @@ ipcRenderer.on("action-update-transmission-status", (event, arg) => { document.getElementById("txtConnectedWithChat").textContent = data.dxcallsign; - console.log(data.status); - if (typeof data.uuid !== "undefined" || data.uuid !== "no-uuid") { +if (typeof data.uuid === undefined) return; + + //console.log(data.status); + if (data.uuid !== "no-uuid") { db.get(data.uuid, { attachments: true, }) @@ -1445,7 +1447,7 @@ update_chat = function (obj) { var percent_value = "TRANSMITTED"; } else { var progressbar_bg = "bg-primary"; - var percent_value = obj.percent; + var percent_value = obj.percent + " %"; } //Sneak in low graphics mode if so enabled for progress bars @@ -1487,9 +1489,9 @@ update_chat = function (obj) { }%;" aria-valuenow="${ obj.percent }" aria-valuemin="0" aria-valuemax="100">
    -

    ${percent_value} % - ${obj.bytesperminute} Bpm

    + }-progress-information">${percent_value} - ${obj.bytesperminute} Bpm

    From 692d1c7b845a32d8fe2bf9a269048d673b689c11 Mon Sep 17 00:00:00 2001 From: Mashintime Date: Tue, 4 Jul 2023 13:29:56 -0400 Subject: [PATCH 63/66] Reset status on old messages in transmit status --- gui/preload-chat.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gui/preload-chat.js b/gui/preload-chat.js index a442c677..c252b0af 100644 --- a/gui/preload-chat.js +++ b/gui/preload-chat.js @@ -1056,16 +1056,16 @@ update_chat = function (obj) { } // check if wrong status message - if (obj.status == "transmit" && obj.percent == 0) { + if (obj.status == "transmit" && obj.type == "transmit" && obj.percent < 100) { var TimeDifference = new Date().getTime() / 1000 - obj.timestamp; if (TimeDifference > 3600) { - db.upsert(obj._id, function (doc) { - if (!doc.status) { - doc.status = "failed"; - } - return doc; - }); - obj.status = "failed"; + console.log("Resetting message to failed as it is in transmit status and older than an hour:") + console.log(obj); + db.upsert(obj._id, function (doc) { + doc.status = "failed"; + return doc; + }); + obj.status = "failed"; } } if (typeof obj.new == "undefined") { From b4c658165d30c2ab0103b8217b16e6bd2d53c7cd Mon Sep 17 00:00:00 2001 From: Mashintime Date: Tue, 4 Jul 2023 13:34:22 -0400 Subject: [PATCH 64/66] Fix failed progressbar rendering --- gui/preload-chat.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gui/preload-chat.js b/gui/preload-chat.js index c252b0af..d653d6d4 100644 --- a/gui/preload-chat.js +++ b/gui/preload-chat.js @@ -1442,6 +1442,8 @@ update_chat = function (obj) { if (obj.status == "failed") { var progressbar_bg = "bg-danger"; var percent_value = "TRANSMISSION FAILED"; + //Set to 100 so progressbar background populates + obj.percent=100; } else if (obj.status == "transmitted") { var progressbar_bg = "bg-success"; var percent_value = "TRANSMITTED"; From 31be9a66bc16c22b18dd3dc19fcc5f0eb32d3a93 Mon Sep 17 00:00:00 2001 From: Mashintime Date: Tue, 4 Jul 2023 13:49:23 -0400 Subject: [PATCH 65/66] Add spinner for database maintenance --- gui/main.js | 5 +++++ gui/preload-chat.js | 1 + gui/preload-main.js | 5 +++++ gui/src/index.html | 3 ++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/gui/main.js b/gui/main.js index 1e937fb4..03ea87fd 100644 --- a/gui/main.js +++ b/gui/main.js @@ -416,6 +416,11 @@ ipcMain.on("request-clear-chat-connected", () => { chat.webContents.send("action-clear-reception-status"); }); +ipcMain.on("request-update-dbclean-spinner", () => { + //Turn off dbclean spinner + win.webContents.send("action-update-dbclean-spinner"); +}); + // UPDATE TNC CONNECTION ipcMain.on("request-update-tnc-ip", (event, data) => { win.webContents.send("action-update-tnc-ip", data); diff --git a/gui/preload-chat.js b/gui/preload-chat.js index d653d6d4..553e4a92 100644 --- a/gui/preload-chat.js +++ b/gui/preload-chat.js @@ -2913,4 +2913,5 @@ async function dbClean() { itemCount + " items removed from database. It's recommended you now restart the GUI." ); + ipcRenderer.send("request-update-dbclean-spinner"); } diff --git a/gui/preload-main.js b/gui/preload-main.js index ce00363b..bcf99f48 100644 --- a/gui/preload-main.js +++ b/gui/preload-main.js @@ -1657,6 +1657,7 @@ window.addEventListener("DOMContentLoaded", () => { // btnCleanDB button clicked document.getElementById("btnCleanDB").addEventListener("click", () => { + document.getElementById("divCleanDBSpinner").classList.remove("invisible"); ipcRenderer.send("request-clean-db"); }); @@ -1855,6 +1856,10 @@ function connectedStation(data) { prefix + data.dxcallsign; } +//Called by chat to turn off db clean spinner +ipcRenderer.on("action-update-dbclean-spinner",() =>{ + document.getElementById("divCleanDBSpinner").classList.add("invisible"); +}); //Listen for events caused by tnc 'tnc-message' rx ipcRenderer.on("action-update-reception-status", (event, arg) => { var data = arg["data"][0]; diff --git a/gui/src/index.html b/gui/src/index.html index 55895522..52dee847 100644 --- a/gui/src/index.html +++ b/gui/src/index.html @@ -3830,7 +3830,8 @@ type="button" > Clean - +   +
    From 92908b0a636a4863378ebeb66e911a7c5b2eed9e Mon Sep 17 00:00:00 2001 From: Mashintime Date: Wed, 5 Jul 2023 11:30:55 -0400 Subject: [PATCH 66/66] Change timeout value for messages stuck in transmit --- gui/preload-chat.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/preload-chat.js b/gui/preload-chat.js index 553e4a92..bb0170a4 100644 --- a/gui/preload-chat.js +++ b/gui/preload-chat.js @@ -1058,8 +1058,8 @@ update_chat = function (obj) { // check if wrong status message if (obj.status == "transmit" && obj.type == "transmit" && obj.percent < 100) { var TimeDifference = new Date().getTime() / 1000 - obj.timestamp; - if (TimeDifference > 3600) { - console.log("Resetting message to failed as it is in transmit status and older than an hour:") + if (TimeDifference > 21600) { //Six hours + console.log("Resetting message to failed state since in transmit status for over 6 hours:") console.log(obj); db.upsert(obj._id, function (doc) { doc.status = "failed";