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