diff --git a/prisma/migrations/20240828105009_remove_mqtt_connection_state_column/migration.sql b/prisma/migrations/20240828105009_remove_mqtt_connection_state_column/migration.sql
new file mode 100644
index 0000000..03dd68b
--- /dev/null
+++ b/prisma/migrations/20240828105009_remove_mqtt_connection_state_column/migration.sql
@@ -0,0 +1,8 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `mqtt_connection_state` on the `nodes` table. All the data in the column will be lost.
+
+*/
+-- AlterTable
+ALTER TABLE `nodes` DROP COLUMN `mqtt_connection_state`;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index e656dc5..eeb0cb4 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -48,7 +48,7 @@ model Node {
neighbours Json?
neighbours_updated_at DateTime?
- mqtt_connection_state String?
+ // this column tracks when an mqtt gateway node uplinked a packet
mqtt_connection_state_updated_at DateTime?
created_at DateTime @default(now())
diff --git a/src/mqtt.js b/src/mqtt.js
index 6b8d86f..d8939d4 100644
--- a/src/mqtt.js
+++ b/src/mqtt.js
@@ -605,36 +605,6 @@ client.on("connect", () => {
client.on("message", async (topic, message) => {
try {
- // handle node status
- if(topic.includes("/stat/!")){
- try {
-
- // get node id and status
- const nodeIdHex = topic.split("/").pop();
- const mqttConnectionState = message.toString();
-
- // convert node id hex to int value
- const nodeId = convertHexIdToNumericId(nodeIdHex);
-
- // update mqtt connection state for node
- await prisma.node.updateMany({
- where: {
- node_id: nodeId,
- },
- data: {
- mqtt_connection_state: mqttConnectionState,
- mqtt_connection_state_updated_at: new Date(),
- },
- });
-
- // no need to continue with this mqtt message
- return;
-
- } catch(e) {
- console.error(e);
- }
- }
-
// decode service envelope
const envelope = ServiceEnvelope.decode(message);
if(!envelope.packet){
@@ -661,6 +631,20 @@ client.on("message", async (topic, message) => {
}
}
+ // track when a node last gated a packet to mqtt
+ try {
+ await prisma.node.updateMany({
+ where: {
+ node_id: convertHexIdToNumericId(envelope.gatewayId),
+ },
+ data: {
+ mqtt_connection_state_updated_at: new Date(),
+ },
+ });
+ } catch(e) {
+ // don't care if updating mqtt timestamp fails
+ }
+
// attempt to decrypt encrypted packets
const isEncrypted = envelope.packet.encrypted?.length > 0;
if(isEncrypted){
diff --git a/src/public/index.html b/src/public/index.html
index b6f2340..8ed838c 100644
--- a/src/public/index.html
+++ b/src/public/index.html
@@ -1485,6 +1485,29 @@
+
+
+
+
Nodes that have not uplinked to MQTT in this time will show as blue icons. Reload to update map.
+
+
+
@@ -1777,6 +1800,20 @@
}
}
+ function getConfigNodesDisconnectedAgeInSeconds() {
+ // default to showing nodes as recently uplinked if heard in the last 30 minutes
+ const value = localStorage.getItem("config_nodes_disconnected_age_in_seconds");
+ return value != null ? parseInt(value) : 1800;
+ }
+
+ function setConfigNodesDisconnectedAgeInSeconds(value) {
+ if(value != null){
+ return localStorage.setItem("config_nodes_disconnected_age_in_seconds", value);
+ } else {
+ return localStorage.removeItem("config_nodes_disconnected_age_in_seconds");
+ }
+ }
+
function getConfigNodesOfflineAgeInSeconds() {
const value = localStorage.getItem("config_nodes_offline_age_in_seconds");
return value != null ? parseInt(value) : null;
@@ -1836,6 +1873,7 @@
isShowingAnnouncement: this.shouldShowAnnouncement(),
configNodesMaxAgeInSeconds: window.getConfigNodesMaxAgeInSeconds(),
+ configNodesDisconnectedAgeInSeconds: window.getConfigNodesDisconnectedAgeInSeconds(),
configNodesOfflineAgeInSeconds: window.getConfigNodesOfflineAgeInSeconds(),
configWaypointsMaxAgeInSeconds: window.getConfigWaypointsMaxAgeInSeconds(),
configNeighboursMaxDistanceInMeters: window.getConfigNeighboursMaxDistanceInMeters(),
@@ -2791,6 +2829,9 @@
configNodesMaxAgeInSeconds() {
window.setConfigNodesMaxAgeInSeconds(this.configNodesMaxAgeInSeconds);
},
+ configNodesDisconnectedAgeInSeconds() {
+ window.setConfigNodesDisconnectedAgeInSeconds(this.configNodesDisconnectedAgeInSeconds);
+ },
configNodesOfflineAgeInSeconds() {
window.setConfigNodesOfflineAgeInSeconds(this.configNodesOfflineAgeInSeconds);
},
@@ -3455,6 +3496,14 @@
return string.replace(//g, ">");
}
+ // determine if node was recently heard uplinking packets to mqtt
+ function hasNodeUplinkedToMqttRecently(node) {
+ const now = moment();
+ const configNodesDisconnectedAgeInSeconds = getConfigNodesDisconnectedAgeInSeconds();
+ const millisecondsSinceNodeLastUplinkedToMqtt = now.diff(moment(node.mqtt_connection_state_updated_at));
+ return millisecondsSinceNodeLastUplinkedToMqtt < configNodesDisconnectedAgeInSeconds * 1000;
+ }
+
function onNodesUpdated(updatedNodes) {
// clear nodes cache
@@ -3500,9 +3549,6 @@
// icon based on mqtt connection state
var icon = iconMqttDisconnected;
- if(node.mqtt_connection_state === "online"){
- icon = iconMqttConnected;
- }
// use offline icon for nodes older than configured node offline age
const now = moment();
@@ -3514,12 +3560,18 @@
}
}
+ // determine if node was recently heard uplinking packets to mqtt
+ const nodeHasUplinkedToMqttRecently = hasNodeUplinkedToMqttRecently(node);
+ if(nodeHasUplinkedToMqttRecently){
+ icon = iconMqttConnected;
+ }
+
// create node marker
var marker = L.marker([node.latitude, longitude], {
icon: icon,
tagName: node.node_id,
// we want to show online nodes above offline, but without needing to use separate layer groups
- zIndexOffset: node.mqtt_connection_state === "online" ? 1000 : -1000,
+ zIndexOffset: nodeHasUplinkedToMqttRecently ? 1000 : -1000,
}).on('click', function(event) {
// close tooltip on click to prevent tooltip and popup showing at same time
event.target.closeTooltip();
@@ -3935,15 +3987,16 @@
function getTooltipContentForNode(node) {
- // human friendly connection state
- var mqttStatus = "";
- var mqttStatusLastUpdated = node.mqtt_connection_state_updated_at ? `(${moment(new Date(node.mqtt_connection_state_updated_at)).fromNow()})` : "";
- if(node.mqtt_connection_state === "online"){
- mqttStatus = `Connected ${mqttStatusLastUpdated}`;
- } else if(node.mqtt_connection_state === "offline"){
- mqttStatus = `Disconnected ${mqttStatusLastUpdated}`;
- } else {
- mqttStatus = `Disconnected`;
+ // determine if node was recently heard uplinking packets to mqtt
+ const nodeHasUplinkedToMqttRecently = hasNodeUplinkedToMqttRecently(node);
+ var mqttStatus = `Disconnected`;
+ if(node.mqtt_connection_state_updated_at){
+ var mqttStatusUpdatedAt = moment(new Date(node.mqtt_connection_state_updated_at)).fromNow();
+ if(nodeHasUplinkedToMqttRecently){
+ mqttStatus = `Connected (${mqttStatusUpdatedAt})`;
+ } else {
+ mqttStatus = `Disconnected (${mqttStatusUpdatedAt})`;
+ }
}
var loraFrequencyRange = getRegionFrequencyRange(node.region_name);