diff --git a/packages/core/src/meshDevice.ts b/packages/core/src/meshDevice.ts index 7dbc5e07..b46bb51a 100755 --- a/packages/core/src/meshDevice.ts +++ b/packages/core/src/meshDevice.ts @@ -1,7 +1,6 @@ -import { Logger } from "tslog"; - import { create, fromBinary, toBinary } from "@bufbuild/protobuf"; import * as Protobuf from "@meshtastic/protobufs"; +import { Logger } from "tslog"; import { Constants } from "./constants.ts"; import type { Destination, PacketMetadata, Transport } from "./types.ts"; @@ -40,6 +39,8 @@ export class MeshDevice { public xModem: Xmodem; + private _heartbeatIntervalId: ReturnType | undefined; + constructor(transport: Transport, configId?: number) { this.log = new Logger({ name: "iMeshDevice", @@ -741,6 +742,18 @@ export class MeshDevice { return this.sendRaw(toBinary(Protobuf.Mesh.ToRadioSchema, toRadio)); } + /** + * Initializes the heartbeat interval, which sends a heartbeat ping every interval milliseconds. + */ + public setHeartbeatInterval(interval: number): void { + if (this._heartbeatIntervalId !== undefined) { + clearInterval(this._heartbeatIntervalId); + } + this._heartbeatIntervalId = setInterval(() => { + this.heartbeat(); + }, interval); + } + /** * Sends a trace route packet to the designated node */ @@ -798,6 +811,11 @@ export class MeshDevice { /** Disconnects from the device **/ public async disconnect(): Promise { this.log.debug(Emitter[Emitter.Disconnect], "🔌 Disconnecting from device"); + + if (this._heartbeatIntervalId !== undefined) { + clearInterval(this._heartbeatIntervalId); + } + this.complete(); await this.transport.toDevice.close(); } diff --git a/packages/core/src/utils/queue.ts b/packages/core/src/utils/queue.ts index bd14d229..55f23282 100644 --- a/packages/core/src/utils/queue.ts +++ b/packages/core/src/utils/queue.ts @@ -44,6 +44,16 @@ export class Queue { if (this.queue.findIndex((qi) => qi.id === item.id) !== -1) { this.remove(item.id); const decoded = fromBinary(Protobuf.Mesh.ToRadioSchema, item.data); + + if ( + decoded.payloadVariant.case === "heartbeat" || + decoded.payloadVariant.case === "wantConfigId" + ) { + // heartbeat and wantConfigId packets are not acknowledged by the device, assume success after timeout + resolve(item.id); + return; + } + console.warn( `Packet ${item.id} of type ${decoded.payloadVariant.case} timed out`, ); diff --git a/packages/web/src/components/PageComponents/Connect/BLE.tsx b/packages/web/src/components/PageComponents/Connect/BLE.tsx index 20a27ee5..46b39b3e 100644 --- a/packages/web/src/components/PageComponents/Connect/BLE.tsx +++ b/packages/web/src/components/PageComponents/Connect/BLE.tsx @@ -36,6 +36,10 @@ export const BLE = ({ closeDialog }: TabElementProps) => { setSelectedDevice(id); device.addConnection(connection); subscribeAll(device, connection, messageStore); + + const HEARTBEAT_INTERVAL = 5*60*1000; + connection.setHeartbeatInterval(HEARTBEAT_INTERVAL); + closeDialog(); }; diff --git a/packages/web/src/components/PageComponents/Connect/Serial.tsx b/packages/web/src/components/PageComponents/Connect/Serial.tsx index a11f890a..71d43710 100644 --- a/packages/web/src/components/PageComponents/Connect/Serial.tsx +++ b/packages/web/src/components/PageComponents/Connect/Serial.tsx @@ -43,6 +43,9 @@ export const Serial = ({ closeDialog }: TabElementProps) => { device.addConnection(connection); subscribeAll(device, connection, messageStore); + const HEARTBEAT_INTERVAL = 5*60*1000; + connection.setHeartbeatInterval(HEARTBEAT_INTERVAL); + closeDialog(); };