Skip to content

Commit

Permalink
add button to refresh device metrics for nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
liamcottle committed Dec 5, 2024
1 parent a22cf5b commit 1b3d5d9
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 17 deletions.
14 changes: 14 additions & 0 deletions src/components/RefreshButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<template>
<button :disabled="isRefreshing" type="button" class="rounded bg-white px-2 py-1 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300" :class="[ isRefreshing ? 'cursor-not-allowed' : 'hover:bg-gray-50' ]">
<span v-if="isRefreshing">Refreshing...</span>
<span v-else>Refresh</span>
</button>
</template>
<script>
export default {
name: "RefreshButton",
props: {
isRefreshing: Boolean,
},
}
</script>
42 changes: 41 additions & 1 deletion src/components/pages/NodePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,12 @@

<!-- device metrics -->
<div>
<div class="flex bg-gray-200 p-2 font-semibold">Device Metrics</div>
<div class="flex bg-gray-200 p-2 font-semibold items-center">
<div>Device Metrics</div>
<div class="ml-auto">
<RefreshButton @click="requestDeviceMetrics" :is-refreshing="isRequestingDeviceMetrics"/>
</div>
</div>
<ul role="list" class="flex-1 divide-y divide-gray-200">

<!-- battery level -->
Expand Down Expand Up @@ -165,10 +170,14 @@ import Page from "./Page.vue";
import NodeUtils from "../../js/NodeUtils.js";
import moment from "moment";
import NodeDropDownMenu from "../nodes/NodeDropDownMenu.vue";
import NodeAPI from "../../js/NodeAPI.js";
import DialogUtils from "../../js/DialogUtils.js";
import RefreshButton from "../RefreshButton.vue";
export default {
name: 'NodePage',
components: {
RefreshButton,
NodeDropDownMenu,
Page,
NodeIcon,
Expand All @@ -177,6 +186,11 @@ export default {
props: {
nodeId: String | Number,
},
data() {
return {
isRequestingDeviceMetrics: false,
};
},
mounted() {
// redirect to main page if node not found
Expand Down Expand Up @@ -212,6 +226,32 @@ export default {
});
},
async requestDeviceMetrics() {
// do nothing if already requesting device metrics
if(this.isRequestingDeviceMetrics){
return;
}
// show loading
this.isRequestingDeviceMetrics = true;
try {
// fetch device metrics from node
const deviceMetrics = await NodeAPI.requestDeviceMetrics(this.node.num);
// update this nodes device metrics
this.node.deviceMetrics = deviceMetrics;
} catch(e) {
DialogUtils.showErrorAlert(e);
}
// no longer requesting device metrics
this.isRequestingDeviceMetrics = false;
},
},
computed: {
node() {
Expand Down
1 change: 1 addition & 0 deletions src/js/Connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ class Connection {
// check if we found our own node info
if(nodeId === GlobalState.myNodeId){
GlobalState.myNodeUser = data.user;
GlobalState.myNodeDeviceMetrics = data.deviceMetrics;
}

});
Expand Down
1 change: 1 addition & 0 deletions src/js/GlobalState.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const globalState = reactive({

myNodeId: null,
myNodeUser: null,
myNodeDeviceMetrics: null,

loraConfig: null,
channelsByIndex: {},
Expand Down
80 changes: 65 additions & 15 deletions src/js/NodeAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,35 +264,34 @@ class NodeAPI {
},
});

console.log(`sending packet: ${packetId}`, toRadioMessage);
console.log(`sending packet: ${packetId}`, toRadioMessage.toJson());

return await GlobalState.connection.sendRaw(toRadioMessage.toBinary(), packetId);

}

/**
* Sends an AdminMessage to the provided node id, and waits for a response, or timeouts out after the provided delay
* @param nodeId the node id to send the admin message to
* @param adminMessage the AdminMessage to send
* @param wantResponse if we want the destination to send us a response
* Sends a packet to the provided node id, and waits for a response, or timeouts out after the provided delay
* @param destination the node id to send the admin message to
* @param portNum the meshtastic portnum this message is for
* @param byteData raw protobuf message bytes
* @param channel the channel to send this message to
* @param wantResponse if we want the destination node to send a response
* @param timeoutMillis how long to wait before timing out
* @returns {Promise<unknown>}
*/
static async sendAdminPacketAndWaitForResponse(nodeId, adminMessage, wantResponse = true, timeoutMillis = 15000) {
static async sendPacketAndWaitForResponse(destination, portNum, byteData, channel = 0, wantResponse = true, timeoutMillis = 15000) {
var timeout = null;
var responseMeshPacketListener = null;
return new Promise(async (resolve, reject) => {
try {

// create packet data
const id = this.generatePacketId();
const byteData = adminMessage.toBinary();
const portNum = Protobuf.Portnums.PortNum.ADMIN_APP;
const destination = parseInt(nodeId);
const channel = ChannelUtils.getAdminChannelIndex(nodeId);
destination = parseInt(destination);
const wantAck = true; // always want ack, otherwise node doesn't send the packet on lora
const pkiEncrypted = channel === ChannelUtils.PKC_CHANNEL_INDEX;
const publicKey = channel === ChannelUtils.PKC_CHANNEL_INDEX ? NodeUtils.getPublicKey(nodeId) : new Uint8Array([]);
const publicKey = channel === ChannelUtils.PKC_CHANNEL_INDEX ? NodeUtils.getPublicKey(destination) : new Uint8Array([]);

// handle response packet
responseMeshPacketListener = (meshPacket) => {
Expand Down Expand Up @@ -339,11 +338,8 @@ class NodeAPI {
// stop listening for mesh packets
Connection.removeMeshPacketListener(responseMeshPacketListener);

// parse admin response message
const adminResponseMessage = Protobuf.Admin.AdminMessage.fromBinary(meshPacket.payloadVariant.value.payload);

// resolve promise
resolve(adminResponseMessage);
resolve(meshPacket);

};

Expand All @@ -367,6 +363,60 @@ class NodeAPI {
});
}

/**
* Sends our DeviceMetrics to, and requests the DeviceMetrics from the provided nodeId
* @param nodeId the node id to exchange DeviceMetrics with
* @returns {Promise<*>}
*/
static async requestDeviceMetrics(nodeId) {

// get our own device metrics
const myNodeDeviceMetrics = GlobalState.myNodeDeviceMetrics;
if(!myNodeDeviceMetrics){
return;
}

// create telemetry message
const telemetryMessage = Protobuf.Telemetry.Telemetry.fromJson({
deviceMetrics: myNodeDeviceMetrics,
});

// send packet and wait for response
const portNum = Protobuf.Portnums.PortNum.TELEMETRY_APP;
const byteData = telemetryMessage.toBinary();
const channel = NodeUtils.getNodeChannel(nodeId);
const responseMeshPacket = await this.sendPacketAndWaitForResponse(nodeId, portNum, byteData, channel, true);

// parse response message
const telemetryResponse = Protobuf.Telemetry.Telemetry.fromBinary(responseMeshPacket.payloadVariant.value.payload);

// return device metrics
return telemetryResponse.variant.value;

}

/**
* Sends an AdminMessage to the provided node id, and waits for a response, or timeouts out after the provided delay
* @param nodeId the node id to send the admin message to
* @param adminMessage the AdminMessage to send
* @param wantResponse if we want the destination to send us a response
* @param timeoutMillis how long to wait before timing out
* @returns {Promise<unknown>}
*/
static async sendAdminPacketAndWaitForResponse(nodeId, adminMessage, wantResponse = true, timeoutMillis = 15000) {

const portNum = Protobuf.Portnums.PortNum.ADMIN_APP;
const byteData = adminMessage.toBinary();
const channel = ChannelUtils.getAdminChannelIndex(nodeId);

// send admin packet and wait for response
const responseMeshPacket = await this.sendPacketAndWaitForResponse(nodeId, portNum, byteData, channel, wantResponse, timeoutMillis);

// parse admin response message
return Protobuf.Admin.AdminMessage.fromBinary(responseMeshPacket.payloadVariant.value.payload);

}

/**
* Sends an admin request to get the owner of the provided node id
* @param nodeId
Expand Down
6 changes: 5 additions & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "./style.css";

import App from './components/App.vue';
import GlobalState from "./js/GlobalState.js";
import {BleConnection, SerialConnection} from "@meshtastic/js";
import {BleConnection, Protobuf, SerialConnection} from "@meshtastic/js";

const router = createRouter({
history: createMemoryHistory(),
Expand Down Expand Up @@ -103,3 +103,7 @@ window.addEventListener("beforeunload", () => {
}
}
});

// debug access to global state and protobufs
window.GlobalState = GlobalState;
window.Protobuf = Protobuf;

0 comments on commit 1b3d5d9

Please sign in to comment.