mirror of
https://github.com/meshcore-dev/meshcore.js.git
synced 2026-04-20 22:13:49 +00:00
implement login and status request for fetching metrics from repeaters
This commit is contained in:
parent
4010cfc3b2
commit
84df5aa57f
5 changed files with 240 additions and 0 deletions
27
index.html
27
index.html
|
|
@ -136,6 +136,7 @@
|
|||
<div class="flex my-auto space-x-2">
|
||||
<div @click="sendMessage(contact)" class="hover:underline cursor-pointer">Message</div>
|
||||
<div @click="setPath(contact)" class="hover:underline cursor-pointer">Set Path</div>
|
||||
<div @click="statusRequest(contact)" class="hover:underline cursor-pointer">GetStats</div>
|
||||
<div @click="shareContact(contact)" class="hover:underline cursor-pointer">Share (Zero Hop)</div>
|
||||
<div @click="exportContact(contact)" class="hover:underline cursor-pointer">Export</div>
|
||||
<div @click="resetPath(contact)" class="hover:underline cursor-pointer">Reset Path</div>
|
||||
|
|
@ -435,6 +436,32 @@
|
|||
alert("Failed to get battery voltage!");
|
||||
}
|
||||
},
|
||||
async statusRequest(contact) {
|
||||
|
||||
// ask user for password
|
||||
const password = prompt("Please enter Admin, or Guest password");
|
||||
if(!password){
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// log in to repeater
|
||||
const response = await this.connection.login(contact.publicKey, password);
|
||||
console.log("login response", response);
|
||||
|
||||
// request status
|
||||
const statusResponse = await this.connection.getStatus(contact.publicKey);
|
||||
console.log("status response", statusResponse);
|
||||
|
||||
// show status response
|
||||
alert(`status request success:\n${JSON.stringify(statusResponse, null, 4)}`);
|
||||
|
||||
} catch(e) {
|
||||
alert(`Failed to login: ${e}`);
|
||||
}
|
||||
|
||||
},
|
||||
bytesToHex(uint8Array) {
|
||||
return Array.from(uint8Array).map(byte => byte.toString(16).padStart(2, '0')).join('');
|
||||
},
|
||||
|
|
|
|||
|
|
@ -59,6 +59,12 @@ class BufferReader {
|
|||
return view.getUint32(0, true);
|
||||
}
|
||||
|
||||
readInt16LE() {
|
||||
const bytes = this.readBytes(2);
|
||||
const view = new DataView(bytes.buffer);
|
||||
return view.getInt16(0, true);
|
||||
}
|
||||
|
||||
readInt32LE() {
|
||||
const bytes = this.readBytes(4);
|
||||
const view = new DataView(bytes.buffer);
|
||||
|
|
|
|||
24
src/buffer_utils.js
Normal file
24
src/buffer_utils.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
class BufferUtils {
|
||||
|
||||
static areBuffersEqual(byteArray1, byteArray2) {
|
||||
|
||||
// ensure length is the same
|
||||
if(byteArray1.length !== byteArray2.length){
|
||||
return false;
|
||||
}
|
||||
|
||||
// ensure each item is the same
|
||||
for(let i = 0; i < byteArray1.length; i++){
|
||||
if(byteArray1[i] !== byteArray2[i]){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// arrays are the same
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BufferUtils;
|
||||
|
|
@ -2,6 +2,7 @@ import BufferWriter from "../buffer_writer.js";
|
|||
import BufferReader from "../buffer_reader.js";
|
||||
import Constants from "../constants.js";
|
||||
import EventEmitter from "../events.js";
|
||||
import BufferUtils from "../buffer_utils.js";
|
||||
|
||||
class Connection extends EventEmitter {
|
||||
|
||||
|
|
@ -205,6 +206,21 @@ class Connection extends EventEmitter {
|
|||
await this.sendToRadioFrame(data.toBytes());
|
||||
}
|
||||
|
||||
async sendCommandSendLogin(publicKey, password) {
|
||||
const data = new BufferWriter();
|
||||
data.writeByte(Constants.CommandCodes.SendLogin);
|
||||
data.writeBytes(publicKey); // 32 bytes - id of repeater or room server
|
||||
data.writeString(password); // password is remainder of frame, max 15 characters
|
||||
await this.sendToRadioFrame(data.toBytes());
|
||||
}
|
||||
|
||||
async sendCommandSendStatusReq(publicKey) {
|
||||
const data = new BufferWriter();
|
||||
data.writeByte(Constants.CommandCodes.SendStatusReq);
|
||||
data.writeBytes(publicKey); // 32 bytes - id of repeater or room server
|
||||
await this.sendToRadioFrame(data.toBytes());
|
||||
}
|
||||
|
||||
onFrameReceived(frame) {
|
||||
|
||||
// emit received frame
|
||||
|
|
@ -253,6 +269,10 @@ class Connection extends EventEmitter {
|
|||
this.onSendConfirmedPush(bufferReader);
|
||||
} else if(responseCode === Constants.PushCodes.MsgWaiting){
|
||||
this.onMsgWaitingPush(bufferReader);
|
||||
} else if(responseCode === Constants.PushCodes.LoginSuccess){
|
||||
this.onLoginSuccessPush(bufferReader);
|
||||
} else if(responseCode === Constants.PushCodes.StatusResponse){
|
||||
this.onStatusResponsePush(bufferReader);
|
||||
} else {
|
||||
console.log("unhandled frame", frame);
|
||||
}
|
||||
|
|
@ -284,6 +304,21 @@ class Connection extends EventEmitter {
|
|||
});
|
||||
}
|
||||
|
||||
onLoginSuccessPush(bufferReader) {
|
||||
this.emit(Constants.PushCodes.LoginSuccess, {
|
||||
reserved: bufferReader.readByte(), // reserved
|
||||
pubKeyPrefix: bufferReader.readBytes(6), // 6 bytes of public key this login success is from
|
||||
});
|
||||
}
|
||||
|
||||
onStatusResponsePush(bufferReader) {
|
||||
this.emit(Constants.PushCodes.StatusResponse, {
|
||||
reserved: bufferReader.readByte(), // reserved
|
||||
pubKeyPrefix: bufferReader.readBytes(6), // 6 bytes of public key this status response is from
|
||||
statusData: bufferReader.readRemainingBytes(),
|
||||
});
|
||||
}
|
||||
|
||||
onOkResponse(bufferReader) {
|
||||
this.emit(Constants.ResponseCodes.Ok, {
|
||||
|
||||
|
|
@ -1180,6 +1215,148 @@ class Connection extends EventEmitter {
|
|||
});
|
||||
}
|
||||
|
||||
login(contactPublicKey, password, extraTimeoutMillis = 1000) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
|
||||
// get public key prefix we expect in the login response
|
||||
const publicKeyPrefix = contactPublicKey.subarray(0, 6);
|
||||
|
||||
// listen for sent response so we can get estimated timeout
|
||||
const onSent = (response) => {
|
||||
|
||||
// remove error listener since we received sent response
|
||||
this.once(Constants.ResponseCodes.Err, onErr);
|
||||
|
||||
// reject login request as timed out after estimated delay, plus a bit extra
|
||||
const estTimeout = response.estTimeout + extraTimeoutMillis;
|
||||
setTimeout(() => {
|
||||
reject("timeout");
|
||||
}, estTimeout);
|
||||
|
||||
}
|
||||
|
||||
// resolve promise when we receive login success push code
|
||||
const onLoginSuccess = (response) => {
|
||||
|
||||
// make sure login success response is for this login request
|
||||
if(!BufferUtils.areBuffersEqual(publicKeyPrefix, response.pubKeyPrefix)){
|
||||
console.log("onLoginSuccess is not for this login request, ignoring...");
|
||||
return;
|
||||
}
|
||||
|
||||
// login successful
|
||||
this.off(Constants.ResponseCodes.Err, onErr);
|
||||
this.off(Constants.ResponseCodes.Sent, onSent);
|
||||
this.off(Constants.PushCodes.LoginSuccess, onLoginSuccess);
|
||||
resolve(response);
|
||||
|
||||
}
|
||||
|
||||
// reject promise when we receive err
|
||||
const onErr = () => {
|
||||
this.off(Constants.ResponseCodes.Err, onErr);
|
||||
this.off(Constants.ResponseCodes.Sent, onSent);
|
||||
this.off(Constants.PushCodes.LoginSuccess, onLoginSuccess);
|
||||
reject();
|
||||
}
|
||||
|
||||
// listen for events
|
||||
this.once(Constants.ResponseCodes.Err, onErr);
|
||||
this.once(Constants.ResponseCodes.Sent, onSent);
|
||||
this.once(Constants.PushCodes.LoginSuccess, onLoginSuccess);
|
||||
|
||||
// login
|
||||
await this.sendCommandSendLogin(contactPublicKey, password);
|
||||
|
||||
} catch(e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getStatus(contactPublicKey, extraTimeoutMillis = 1000) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
|
||||
// get public key prefix we expect in the status response
|
||||
const publicKeyPrefix = contactPublicKey.subarray(0, 6);
|
||||
|
||||
// listen for sent response so we can get estimated timeout
|
||||
const onSent = (response) => {
|
||||
|
||||
// remove error listener since we received sent response
|
||||
this.once(Constants.ResponseCodes.Err, onErr);
|
||||
|
||||
// reject login request as timed out after estimated delay, plus a bit extra
|
||||
const estTimeout = response.estTimeout + extraTimeoutMillis;
|
||||
setTimeout(() => {
|
||||
reject("timeout");
|
||||
}, estTimeout);
|
||||
|
||||
}
|
||||
|
||||
// resolve promise when we receive status response push code
|
||||
const onStatusResponsePush = (response) => {
|
||||
|
||||
// make sure login success response is for this login request
|
||||
if(!BufferUtils.areBuffersEqual(publicKeyPrefix, response.pubKeyPrefix)){
|
||||
console.log("onStatusResponsePush is not for this status request, ignoring...");
|
||||
return;
|
||||
}
|
||||
|
||||
// status request successful
|
||||
this.off(Constants.ResponseCodes.Err, onErr);
|
||||
this.off(Constants.ResponseCodes.Sent, onSent);
|
||||
this.off(Constants.PushCodes.StatusResponse, onStatusResponsePush);
|
||||
|
||||
// parse repeater stats from status data
|
||||
const bufferReader = new BufferReader(response.statusData);
|
||||
const repeaterStats = {
|
||||
batt_milli_volts: bufferReader.readUInt16LE(), // uint16_t batt_milli_volts;
|
||||
curr_tx_queue_len: bufferReader.readUInt16LE(), // uint16_t curr_tx_queue_len;
|
||||
curr_free_queue_len: bufferReader.readUInt16LE(), // uint16_t curr_free_queue_len;
|
||||
last_rssi: bufferReader.readInt16LE(), // int16_t last_rssi;
|
||||
n_packets_recv: bufferReader.readUInt32LE(), // uint32_t n_packets_recv;
|
||||
n_packets_sent: bufferReader.readUInt32LE(), // uint32_t n_packets_sent;
|
||||
total_air_time_secs: bufferReader.readUInt32LE(), // uint32_t total_air_time_secs;
|
||||
total_up_time_secs: bufferReader.readUInt32LE(), // uint32_t total_up_time_secs;
|
||||
n_sent_flood: bufferReader.readUInt32LE(), // uint32_t n_sent_flood
|
||||
n_sent_direct: bufferReader.readUInt32LE(), // uint32_t n_sent_direct
|
||||
n_recv_flood: bufferReader.readUInt32LE(), // uint32_t n_recv_flood
|
||||
n_recv_direct: bufferReader.readUInt32LE(), // uint32_t n_recv_direct
|
||||
n_full_events: bufferReader.readUInt16LE(), // uint16_t n_full_events
|
||||
reserved1: bufferReader.readUInt16LE(), // uint16_t reserved1
|
||||
n_direct_dups: bufferReader.readUInt16LE(), // uint16_t n_direct_dups
|
||||
n_flood_dups: bufferReader.readUInt16LE(), // uint16_t n_flood_dups
|
||||
}
|
||||
|
||||
resolve(repeaterStats);
|
||||
|
||||
}
|
||||
|
||||
// reject promise when we receive err
|
||||
const onErr = () => {
|
||||
this.off(Constants.ResponseCodes.Err, onErr);
|
||||
this.off(Constants.ResponseCodes.Sent, onSent);
|
||||
this.off(Constants.PushCodes.StatusResponse, onStatusResponsePush);
|
||||
reject();
|
||||
}
|
||||
|
||||
// listen for events
|
||||
this.once(Constants.ResponseCodes.Err, onErr);
|
||||
this.once(Constants.ResponseCodes.Sent, onSent);
|
||||
this.once(Constants.PushCodes.StatusResponse, onStatusResponsePush);
|
||||
|
||||
// request status
|
||||
await this.sendCommandSendStatusReq(contactPublicKey);
|
||||
|
||||
} catch(e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Connection;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ class Constants {
|
|||
DeviceQuery: 22,
|
||||
ExportPrivateKey: 23,
|
||||
ImportPrivateKey: 24,
|
||||
SendRawData: 25, // todo
|
||||
SendLogin: 26, // todo
|
||||
SendStatusReq: 27, // todo
|
||||
}
|
||||
|
||||
static ResponseCodes = {
|
||||
|
|
@ -62,6 +65,9 @@ class Constants {
|
|||
PathUpdated: 0x81,
|
||||
SendConfirmed: 0x82,
|
||||
MsgWaiting: 0x83,
|
||||
LoginSuccess: 0x85,
|
||||
LoginFail: 0x86, // not usable yet
|
||||
StatusResponse: 0x87,
|
||||
}
|
||||
|
||||
static AdvType = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue