diff --git a/gui/preload-main.js b/gui/preload-main.js index 19a4cc2d..61d0f45f 100644 --- a/gui/preload-main.js +++ b/gui/preload-main.js @@ -2983,6 +2983,12 @@ ipcRenderer.on("run-tnc-command", (event, arg) => { if (arg.command == "responseSharedFile") { sock.sendResponseSharedFile(arg.dxcallsign, arg.file, arg.filedata); } + + if (arg.command == "mesh_ping") { + sock.sendMeshPing(arg.dxcallsign); + } + + }); // IPC ACTION FOR AUTO UPDATER diff --git a/gui/preload-mesh.js b/gui/preload-mesh.js index 7b57c4eb..f8e8cc50 100644 --- a/gui/preload-mesh.js +++ b/gui/preload-mesh.js @@ -13,6 +13,21 @@ const config = require(configPath); // WINDOW LISTENER window.addEventListener("DOMContentLoaded", () => { + + // startPing button clicked + document.getElementById("transmit_mesh_ping").addEventListener("click", () => { + var dxcallsign = document.getElementById("dxCallMesh").value.toUpperCase(); + if (dxcallsign == "" || dxcallsign == null || dxcallsign == undefined) + return; + //pauseButton(document.getElementById("transmit_mesh_ping"), 2000); + ipcRenderer.send("run-tnc-command", { + command: "mesh_ping", + dxcallsign: dxcallsign, + }); + }); + + + document .getElementById("enable_mesh") .addEventListener("click", () => { @@ -42,9 +57,18 @@ let Data = { ipcRenderer.on("action-update-mesh-table", (event, arg) => { var routes = arg.routing_table; + +if (typeof routes == "undefined") { + return; + } + + var tbl = document.getElementById("mesh-table"); +if (tbl !== null) { tbl.innerHTML = ""; + } + for (i = 0; i < routes.length; i++) { @@ -119,7 +143,10 @@ for (i = 0; i < routes.length; i++) { } +if (tbl !== null) { + // 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 18c23e19..182802dd 100644 --- a/gui/sock.js +++ b/gui/sock.js @@ -587,6 +587,15 @@ exports.sendPing = function (dxcallsign) { writeTncCommand(command); }; +// Send Mesh Ping +exports.sendMeshPing = function (dxcallsign) { + command = + '{"type" : "mesh", "command" : "ping", "dxcallsign" : "' + + dxcallsign + + '"}'; + writeTncCommand(command); +}; + // Send CQ exports.sendCQ = function () { command = '{"type" : "broadcast", "command" : "cqcqcq"}'; diff --git a/gui/src/mesh-module.html b/gui/src/mesh-module.html index ea9700e7..3b1982f5 100644 --- a/gui/src/mesh-module.html +++ b/gui/src/mesh-module.html @@ -31,8 +31,21 @@ + + + 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