Skip to content

Commit

Permalink
implement pinging a node
Browse files Browse the repository at this point in the history
  • Loading branch information
liamcottle committed Nov 21, 2024
1 parent ac6f709 commit 8966f5b
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 5 deletions.
18 changes: 15 additions & 3 deletions src/components/nodes/NodeDropDownMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
<span>Request Node Info</span>
</DropDownMenuItem>

<!-- ping button -->
<DropDownMenuItem @click="onPingNode(node)">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-5">
<path fill-rule="evenodd" d="M14.615 1.595a.75.75 0 0 1 .359.852L12.982 9.75h7.268a.75.75 0 0 1 .548 1.262l-10.5 11.25a.75.75 0 0 1-1.272-.71l1.992-7.302H3.75a.75.75 0 0 1-.548-1.262l10.5-11.25a.75.75 0 0 1 .913-.143Z" clip-rule="evenodd" />
</svg>
<span>Ping Node</span>
</DropDownMenuItem>

<!-- trace route button -->
<RouterLink :to="{ name: 'node.traceroutes', params: { nodeId: node.num }}">
<DropDownMenuItem>
Expand Down Expand Up @@ -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() {
Expand Down
24 changes: 22 additions & 2 deletions src/js/Connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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);

Expand Down
91 changes: 91 additions & 0 deletions src/js/NodeAPI.js
Original file line number Diff line number Diff line change
@@ -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 {

Expand Down Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions src/js/PacketUtils.js
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit 8966f5b

Please sign in to comment.