From 8966f5bceb6c75b356f94a9cf13ae06cf339b910 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Fri, 22 Nov 2024 02:47:36 +1300 Subject: [PATCH] implement pinging a node --- src/components/nodes/NodeDropDownMenu.vue | 18 ++++- src/js/Connection.js | 24 +++++- src/js/NodeAPI.js | 91 +++++++++++++++++++++++ src/js/PacketUtils.js | 12 +++ 4 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 src/js/PacketUtils.js diff --git a/src/components/nodes/NodeDropDownMenu.vue b/src/components/nodes/NodeDropDownMenu.vue index b581d2c..c1dcf55 100644 --- a/src/components/nodes/NodeDropDownMenu.vue +++ b/src/components/nodes/NodeDropDownMenu.vue @@ -43,6 +43,14 @@ Request Node Info + + + + + + Ping Node + + @@ -122,9 +130,13 @@ export default { alert("A request has been sent to the node to send its info back to us."); }, - onTraceRouteClick(node) { - // todo show loading screen - NodeAPI.traceRoute(node.num); + async onPingNode(node) { + try { + const pingResult = await NodeAPI.ping(node.num); + const pingDurationMillis = pingResult.duration_millis; + const hopsAway = pingResult.hops_away; + alert(`[${this.getNodeShortName(node.num)}] replied to ping in ${pingDurationMillis}ms with ${hopsAway} hops back.`); + } catch(e){} }, async onDeleteMessageHistory() { diff --git a/src/js/Connection.js b/src/js/Connection.js index a52a0aa..a78dd46 100644 --- a/src/js/Connection.js +++ b/src/js/Connection.js @@ -2,13 +2,25 @@ import GlobalState from "./GlobalState.js"; import {BleConnection, Constants, HttpConnection, Protobuf, SerialConnection, Types,} from "@meshtastic/js"; import Database from "./Database.js"; import NodeAPI from "./NodeAPI.js"; +import PacketUtils from "./PacketUtils.js"; class Connection { + static packetAckListeners = []; static clientNotificationListeners = []; static messageListeners = []; static traceRouteListeners = []; + static addPacketAckListener(listener) { + this.packetAckListeners.push(listener); + } + + static removePacketAckListener(listenerToRemove) { + this.packetAckListeners = this.packetAckListeners.filter((listener) => { + return listener !== listenerToRemove; + }); + } + static addClientNotificationListener(listener) { this.clientNotificationListeners.push(listener); } @@ -202,7 +214,8 @@ class Connection { // todo handle nack for "no channel" etc const ackFrom = meshPacket.from; const requestId = dataPacket.requestId; - await this.onPacketAck(requestId, ackFrom); + const hopsAway = PacketUtils.getPacketHops(meshPacket); + await this.onPacketAck(requestId, ackFrom, hopsAway); } } } @@ -340,10 +353,17 @@ class Connection { } - static async onPacketAck(requestId, ackedByNodeId) { + static async onPacketAck(requestId, ackedByNodeId, hopsAway) { console.log(`got ack for request id ${requestId} from ${ackedByNodeId}`); + // send to packet ack listeners + for(const packetAckListener of this.packetAckListeners){ + try { + packetAckListener(requestId, ackedByNodeId, hopsAway); + } catch(e){} + } + // todo make sure request id was for a message, otherwise we might be updating an older packet for something else await Database.Message.setMessageAckedByNodeId(requestId, ackedByNodeId); diff --git a/src/js/NodeAPI.js b/src/js/NodeAPI.js index 171d18c..9b3eebe 100644 --- a/src/js/NodeAPI.js +++ b/src/js/NodeAPI.js @@ -1,6 +1,7 @@ import GlobalState from "./GlobalState.js"; import { Constants, Protobuf } from "@meshtastic/js"; import NodeUtils from "./NodeUtils.js"; +import Connection from "./Connection.js"; class NodeAPI { @@ -94,6 +95,96 @@ class NodeAPI { } + /** + * Sends a PRIVATE_APP packet with no data to the provided node id and listens for an ack back from the destination node. + * Returns how long it took to receive an ack from the destination node, along with how many hops the response took to get to us. + * @returns {Promise<*>} + */ + static async ping(nodeId, timeout = 30000) { + return new Promise(async (resolve, reject) => { + + // remember the packet id and when we started the ping + var packetId = null; + var timestampStart = null; + + // listen for packet acks + const packetIdAcks = []; + const packetAckListener = (requestId, ackedByNodeId, hopsAway) => { + + // remember ack for request id + packetIdAcks.push({ + packet_id: requestId, + acked_by_node_id: ackedByNodeId, + hops_away: hopsAway, + }); + + // we received an ack, check if it's from the destination node + checkForAck(); + + }; + + function checkForAck() { + + // check if we have a packet id yet + if(!packetId){ + return; + } + + // determine how long the ping reply took (without overhead of ack packet lookup) + const durationMillis = Date.now() - timestampStart; + + // find ack for packet id from destination node + const packetIdAck = packetIdAcks.find((packetIdAck) => { + return packetIdAck.packet_id === packetId && packetIdAck.acked_by_node_id === nodeId; + }); + + // do nothing if ack not found + if(!packetIdAck){ + return; + } + + // got ack from destination node + Connection.removePacketAckListener(packetAckListener); + resolve({ + duration_millis: durationMillis, + hops_away: packetIdAck.hops_away, + }); + + } + + // add packet listener + Connection.addPacketAckListener(packetAckListener); + + // timeout after delay + setTimeout(() => { + Connection.removePacketAckListener(packetAckListener); + reject("timeout"); + }, timeout); + + // send ping packet + try { + + // create packet data + const byteData = new Uint8Array([]).buffer; + const portNum = Protobuf.Portnums.PortNum.PRIVATE_APP; + const destination = nodeId; + const channel = NodeUtils.getNodeChannel(nodeId); + const wantAck = true; + const wantResponse = false; + + // send packet + timestampStart = Date.now(); + packetId = await GlobalState.connection.sendPacket(byteData, portNum, destination, channel, wantAck, wantResponse); + checkForAck(); + + } catch(e) { + Connection.removePacketAckListener(packetAckListener); + reject(e); + } + + }); + } + /** * Removes the node from global state, and also tells the meshtastic device to remove the node. * @param nodeId the node id to remove diff --git a/src/js/PacketUtils.js b/src/js/PacketUtils.js new file mode 100644 index 0000000..59141b6 --- /dev/null +++ b/src/js/PacketUtils.js @@ -0,0 +1,12 @@ +class PacketUtils { + + static getPacketHops(packet) { + const hopStart = packet.hopStart; + const hopLimit = packet.hopLimit; + const hopsAway = (hopStart === 0 || hopLimit > hopStart) ? -1 : hopStart - hopLimit; + return hopsAway; + } + +} + +export default PacketUtils;