@meri-imperiumi/signalk-meshtastic 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -91,12 +91,19 @@ Metrics used:
91
91
 
92
92
  ## Changes
93
93
 
94
+ * 1.2.3 (2024-10-15)
95
+ - Nodes that haven't been seen in last two days are no longer registered to Signal K data structure
96
+ - Added safeties for various non-numeric telemetry and coordinate values
97
+ * 1.2.2 (2025-10-01)
98
+ - Set "last seen" timestamp of nodes based on packet payloads, not the time they're received
99
+ - Send timestamp with telemetry
100
+ - Fixed issue with persising node-to-vessel matches from `DE <callsign>`
94
101
  * 1.2.1 (2025-09-28)
95
102
  - Fixed issue with Signal K servers that don't have navigation.position set
96
103
  * 1.2.0 (2025-09-28)
104
+ - Support for Node.js older than 22.x, for example as seen in Venus OS Large
97
105
  - Safety for nodes in DB that don't have a "last seen" timestamp
98
106
  - Made connection status notifications clearer
99
- - Attempt at support for Node.js older than 22.x
100
107
  * 1.1.2 (2025-09-25)
101
108
  - Added support for the new roles from Meshtastic 2.7 (`ROUTER_LATE` and `CLIENT_BASE`)
102
109
  - Fixed issue with sending a bell with alerts that have sound enabled
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meri-imperiumi/signalk-meshtastic",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Signal K plugin for interfacing with the Meshtastic LoRa mesh network",
5
5
  "scripts": {
6
6
  "test": "eslint ."
@@ -47,7 +47,7 @@ module.exports = {
47
47
  latitudeI: Math.floor(waypointVessel.navigation.position.value.latitude / 1e-7),
48
48
  longitudeI: Math.floor(waypointVessel.navigation.position.value.longitude / 1e-7),
49
49
  expire: Math.floor((new Date().getTime() / 1000) + (length * 60 * 60)),
50
- name: waypointVessel.name,
50
+ name: waypointVessel.name || waypointVessel.mmsi,
51
51
  description: `AIS vessel ${waypointVessel.mmsi}`,
52
52
  icon: vesselIcon(waypointVessel),
53
53
  });
package/plugin/index.js CHANGED
@@ -101,7 +101,7 @@ function nodeToSignalK(app, node, nodeInfo, settings) {
101
101
  },
102
102
  ];
103
103
 
104
- if (nodeInfo.position) {
104
+ if (nodeInfo.position && Number.isFinite(nodeInfo.position.latitudeI)) {
105
105
  values.push({
106
106
  path: 'navigation.position',
107
107
  value: {
@@ -278,6 +278,7 @@ module.exports = (app) => {
278
278
  return;
279
279
  }
280
280
  const telemetryMessage = create(Protobuf.Telemetry.TelemetrySchema, {
281
+ time: Math.floor(new Date().getTime() / 1000),
281
282
  variant: {
282
283
  case: 'environmentMetrics',
283
284
  value: create(Protobuf.Telemetry.EnvironmentMetricsSchema, values),
@@ -310,6 +311,13 @@ module.exports = (app) => {
310
311
  }, 60000 * minutes);
311
312
  }
312
313
 
314
+ function writeNodeDb() {
315
+ writeFile(nodeDbFile, JSON.stringify(nodes, null, 2), 'utf-8')
316
+ .catch((e) => {
317
+ app.error(`Failed to store node DB: ${e.message}`);
318
+ });
319
+ }
320
+
313
321
  function setConnectionStatus() {
314
322
  setWatchdog();
315
323
  const now = new Date();
@@ -553,22 +561,27 @@ module.exports = (app) => {
553
561
  nodes[nodeInfo.num].publicKey = Buffer.from(nodeInfo.user.publicKey).toString('base64');
554
562
  }
555
563
  nodes[nodeInfo.num].seen = new Date(nodeInfo.lastHeard * 1000);
556
- const ctx = nodeToSignalK(app, nodes[nodeInfo.num], nodeInfo, settings);
557
- if (ctx && ctx.indexOf('vessels.urn:mrn:imo:mmsi:') === 0) {
558
- // We have an MMSI match, store it
559
- nodes[nodeInfo.num].mmsi = ctx.split(':').at(-1);
564
+ if (nodes[nodeInfo.num].seen > Date.now() - (1000 * 60 * 60 * 24 * 2)) {
565
+ // Node seen less than a two days ago, register with SK
566
+ const ctx = nodeToSignalK(app, nodes[nodeInfo.num], nodeInfo, settings);
567
+ if (ctx && ctx.indexOf('vessels.urn:mrn:imo:mmsi:') === 0) {
568
+ // We have an MMSI match, store it
569
+ nodes[nodeInfo.num].mmsi = ctx.split(':').at(-1);
570
+ }
560
571
  }
561
572
  setConnectionStatus();
562
- writeFile(nodeDbFile, JSON.stringify(nodes, null, 2), 'utf-8')
563
- .catch((e) => {
564
- app.error(`Failed to store node DB: ${e.message}`);
565
- });
573
+ writeNodeDb();
566
574
  }),
567
575
  device.events.onMeshPacket.subscribe((packet) => {
568
576
  if (!nodes[packet.from]) {
569
577
  nodes[packet.from] = {};
570
578
  }
571
- nodes[packet.from].seen = new Date();
579
+ if (packet.rxTime) {
580
+ const packetDate = new Date(packet.rxTime * 1000);
581
+ if (packetDate > nodes[packet.from].seen) {
582
+ nodes[packet.from].seen = packetDate;
583
+ }
584
+ }
572
585
  setConnectionStatus();
573
586
  }),
574
587
  device.events.onMessagePacket.subscribe((message) => {
@@ -604,7 +617,12 @@ module.exports = (app) => {
604
617
  // Unknown node
605
618
  return;
606
619
  }
607
- nodes[packet.from].seen = new Date();
620
+ if (packet.data && packet.data.time) {
621
+ const telemetryDate = new Date(packet.data.time * 1000);
622
+ if (telemetryDate > nodes[packet.from].seen) {
623
+ nodes[packet.from].seen = telemetryDate;
624
+ }
625
+ }
608
626
  setConnectionStatus();
609
627
  const context = getNodeContext(app, nodes[packet.from], packet.from, settings);
610
628
  if (!context) {
@@ -697,7 +715,12 @@ module.exports = (app) => {
697
715
  // Unknown node
698
716
  return;
699
717
  }
700
- nodes[position.from].seen = new Date();
718
+ if (position.data && position.data.time) {
719
+ const positionDate = new Date(position.data.time * 1000);
720
+ if (positionDate > nodes[position.from].seen) {
721
+ nodes[position.from].seen = positionDate;
722
+ }
723
+ }
701
724
  setConnectionStatus();
702
725
  const context = getNodeContext(app, nodes[position.from], position.from, settings);
703
726
  if (!context) {
@@ -734,7 +757,7 @@ module.exports = (app) => {
734
757
  },
735
758
  {
736
759
  path: 'navigation.gnss.antennaAltitude',
737
- value: position.data.altitude,
760
+ value: position.data.altitude || 0,
738
761
  },
739
762
  ];
740
763
  app.handleMessage('signalk-meshtastic', {
@@ -750,6 +773,11 @@ module.exports = (app) => {
750
773
  },
751
774
  ],
752
775
  });
776
+ if (context && context.indexOf('vessels.urn:mrn:imo:mmsi:') === 0) {
777
+ // We have an MMSI match, store it
778
+ nodes[position.from].mmsi = context.split(':').at(-1);
779
+ }
780
+ writeNodeDb();
753
781
  }),
754
782
  );
755
783
 
@@ -822,7 +850,8 @@ module.exports = (app) => {
822
850
  // Not connected to Meshtastic yet
823
851
  return;
824
852
  }
825
- if (v.value.latitude === null) {
853
+ if (!Number.isFinite(v.value.latitude)
854
+ || !Number.isFinite(v.value.longitude)) {
826
855
  // No position
827
856
  return;
828
857
  }
@@ -886,7 +915,7 @@ module.exports = (app) => {
886
915
  }
887
916
  }
888
917
  }
889
- if (!mobPosition || !mobPosition.latitude) {
918
+ if (!mobPosition || !Number.isFinite(mobPosition.latitude)) {
890
919
  return;
891
920
  }
892
921
  const setWaypointMessage = create(Protobuf.Mesh.WaypointSchema, {
@@ -14,16 +14,16 @@ class Telemetry {
14
14
 
15
15
  toMeshtastic() {
16
16
  const values = {};
17
- if (this.data['environment.outside.temperature']) {
17
+ if (Number.isFinite(this.data['environment.outside.temperature'])) {
18
18
  values.temperature = this.data['environment.outside.temperature'] - 273.15;
19
19
  }
20
- if (this.data['environment.outside.relativeHumidity']) {
20
+ if (Number.isFinite(this.data['environment.outside.relativeHumidity'])) {
21
21
  values.relativeHumidity = this.data['environment.outside.relativeHumidity'] * 100;
22
22
  }
23
- if (this.data['environment.outside.pressure']) {
23
+ if (Number.isFinite(this.data['environment.outside.pressure'])) {
24
24
  values.barometricPressure = this.data['environment.outside.pressure'] / 100;
25
25
  }
26
- if (this.data['environment.wind.directionTrue']) {
26
+ if (Number.isFinite(this.data['environment.wind.directionTrue'])) {
27
27
  values.windDirection = Math.floor(this.data['environment.wind.directionTrue'] * (180 / Math.PI));
28
28
  }
29
29
  if (this.data['environment.wind.speedOverGround'] && this.data['environment.wind.speedOverGround'].length) {
@@ -38,16 +38,16 @@ class Telemetry {
38
38
  // Clear wind history
39
39
  this.data['environment.wind.speedOverGround'] = [];
40
40
  }
41
- if (this.data['electrical.batteries.house.voltage']) {
41
+ if (Number.isFinite(this.data['electrical.batteries.house.voltage'])) {
42
42
  values.voltage = this.data['electrical.batteries.house.voltage'];
43
43
  }
44
- if (this.data['electrical.batteries.house.current']) {
44
+ if (Number.isFinite(this.data['electrical.batteries.house.current'])) {
45
45
  values.current = this.data['electrical.batteries.house.current'] * 1000;
46
46
  }
47
- if (this.data['navigation.anchor.distanceFromBow']) {
47
+ if (Number.isFinite(this.data['navigation.anchor.distanceFromBow'])) {
48
48
  // Using distance is a bit silly here as the unit is mm, but what can we do
49
49
  values.distance = this.data['navigation.anchor.distanceFromBow'] * 1000;
50
- } else if (this.data['environment.depth.belowSurface']) {
50
+ } else if (Number.isFinite(this.data['environment.depth.belowSurface'])) {
51
51
  // If not anchored, report depth as distance. Still mm.
52
52
  values.distance = this.data['environment.depth.belowSurface'] * 1000;
53
53
  }
@@ -55,6 +55,9 @@ class Telemetry {
55
55
  }
56
56
 
57
57
  updateWindSpeed(windSpeed) {
58
+ if (!Number.isFinite(windSpeed)) {
59
+ return;
60
+ }
58
61
  if (!this.data['environment.wind.speedOverGround']) {
59
62
  this.data['environment.wind.speedOverGround'] = [];
60
63
  }