Merge pull request #66 from Skordy/master
MQTT connection status is now based on when the node last gated a packet
This commit is contained in:
@ -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`;
|
@ -48,7 +48,7 @@ model Node {
|
|||||||
neighbours Json?
|
neighbours Json?
|
||||||
neighbours_updated_at DateTime?
|
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?
|
mqtt_connection_state_updated_at DateTime?
|
||||||
|
|
||||||
created_at DateTime @default(now())
|
created_at DateTime @default(now())
|
||||||
|
44
src/mqtt.js
44
src/mqtt.js
@ -605,36 +605,6 @@ client.on("connect", () => {
|
|||||||
client.on("message", async (topic, message) => {
|
client.on("message", async (topic, message) => {
|
||||||
try {
|
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
|
// decode service envelope
|
||||||
const envelope = ServiceEnvelope.decode(message);
|
const envelope = ServiceEnvelope.decode(message);
|
||||||
if(!envelope.packet){
|
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
|
// attempt to decrypt encrypted packets
|
||||||
const isEncrypted = envelope.packet.encrypted?.length > 0;
|
const isEncrypted = envelope.packet.encrypted?.length > 0;
|
||||||
if(isEncrypted){
|
if(isEncrypted){
|
||||||
|
@ -1485,6 +1485,29 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- configNodesDisconnectedAgeInSeconds -->
|
||||||
|
<div class="p-2">
|
||||||
|
<label class="block text-sm font-medium text-gray-900">Nodes Disconnected Age</label>
|
||||||
|
<div class="text-xs text-gray-600 mb-2">Nodes that have not uplinked to MQTT in this time will show as blue icons. Reload to update map.</div>
|
||||||
|
<select v-model="configNodesDisconnectedAgeInSeconds" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
|
||||||
|
<option value="900">15 minutes</option>
|
||||||
|
<option value="1800">30 minutes</option>
|
||||||
|
<option value="2700">45 minutes</option>
|
||||||
|
<option value="3600">1 hour</option>
|
||||||
|
<option value="7200">2 hours</option>
|
||||||
|
<option value="10800">3 hours</option>
|
||||||
|
<option value="21600">6 hours</option>
|
||||||
|
<option value="43200">12 hours</option>
|
||||||
|
<option value="86400">24 hours</option>
|
||||||
|
<option value="172800">2 days</option>
|
||||||
|
<option value="259200">3 days</option>
|
||||||
|
<option value="345600">4 days</option>
|
||||||
|
<option value="432000">5 days</option>
|
||||||
|
<option value="518400">6 days</option>
|
||||||
|
<option value="604800">7 days</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- configNodesOfflineAgeInSeconds -->
|
<!-- configNodesOfflineAgeInSeconds -->
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<label class="block text-sm font-medium text-gray-900">Nodes Offline Age</label>
|
<label class="block text-sm font-medium text-gray-900">Nodes Offline Age</label>
|
||||||
@ -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() {
|
function getConfigNodesOfflineAgeInSeconds() {
|
||||||
const value = localStorage.getItem("config_nodes_offline_age_in_seconds");
|
const value = localStorage.getItem("config_nodes_offline_age_in_seconds");
|
||||||
return value != null ? parseInt(value) : null;
|
return value != null ? parseInt(value) : null;
|
||||||
@ -1836,6 +1873,7 @@
|
|||||||
isShowingAnnouncement: this.shouldShowAnnouncement(),
|
isShowingAnnouncement: this.shouldShowAnnouncement(),
|
||||||
|
|
||||||
configNodesMaxAgeInSeconds: window.getConfigNodesMaxAgeInSeconds(),
|
configNodesMaxAgeInSeconds: window.getConfigNodesMaxAgeInSeconds(),
|
||||||
|
configNodesDisconnectedAgeInSeconds: window.getConfigNodesDisconnectedAgeInSeconds(),
|
||||||
configNodesOfflineAgeInSeconds: window.getConfigNodesOfflineAgeInSeconds(),
|
configNodesOfflineAgeInSeconds: window.getConfigNodesOfflineAgeInSeconds(),
|
||||||
configWaypointsMaxAgeInSeconds: window.getConfigWaypointsMaxAgeInSeconds(),
|
configWaypointsMaxAgeInSeconds: window.getConfigWaypointsMaxAgeInSeconds(),
|
||||||
configNeighboursMaxDistanceInMeters: window.getConfigNeighboursMaxDistanceInMeters(),
|
configNeighboursMaxDistanceInMeters: window.getConfigNeighboursMaxDistanceInMeters(),
|
||||||
@ -2791,6 +2829,9 @@
|
|||||||
configNodesMaxAgeInSeconds() {
|
configNodesMaxAgeInSeconds() {
|
||||||
window.setConfigNodesMaxAgeInSeconds(this.configNodesMaxAgeInSeconds);
|
window.setConfigNodesMaxAgeInSeconds(this.configNodesMaxAgeInSeconds);
|
||||||
},
|
},
|
||||||
|
configNodesDisconnectedAgeInSeconds() {
|
||||||
|
window.setConfigNodesDisconnectedAgeInSeconds(this.configNodesDisconnectedAgeInSeconds);
|
||||||
|
},
|
||||||
configNodesOfflineAgeInSeconds() {
|
configNodesOfflineAgeInSeconds() {
|
||||||
window.setConfigNodesOfflineAgeInSeconds(this.configNodesOfflineAgeInSeconds);
|
window.setConfigNodesOfflineAgeInSeconds(this.configNodesOfflineAgeInSeconds);
|
||||||
},
|
},
|
||||||
@ -3455,6 +3496,14 @@
|
|||||||
return string.replace(/</g, "<").replace(/>/g, ">");
|
return string.replace(/</g, "<").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) {
|
function onNodesUpdated(updatedNodes) {
|
||||||
|
|
||||||
// clear nodes cache
|
// clear nodes cache
|
||||||
@ -3500,9 +3549,6 @@
|
|||||||
|
|
||||||
// icon based on mqtt connection state
|
// icon based on mqtt connection state
|
||||||
var icon = iconMqttDisconnected;
|
var icon = iconMqttDisconnected;
|
||||||
if(node.mqtt_connection_state === "online"){
|
|
||||||
icon = iconMqttConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
// use offline icon for nodes older than configured node offline age
|
// use offline icon for nodes older than configured node offline age
|
||||||
const now = moment();
|
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
|
// create node marker
|
||||||
var marker = L.marker([node.latitude, longitude], {
|
var marker = L.marker([node.latitude, longitude], {
|
||||||
icon: icon,
|
icon: icon,
|
||||||
tagName: node.node_id,
|
tagName: node.node_id,
|
||||||
// we want to show online nodes above offline, but without needing to use separate layer groups
|
// 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) {
|
}).on('click', function(event) {
|
||||||
// close tooltip on click to prevent tooltip and popup showing at same time
|
// close tooltip on click to prevent tooltip and popup showing at same time
|
||||||
event.target.closeTooltip();
|
event.target.closeTooltip();
|
||||||
@ -3935,15 +3987,16 @@
|
|||||||
|
|
||||||
function getTooltipContentForNode(node) {
|
function getTooltipContentForNode(node) {
|
||||||
|
|
||||||
// human friendly connection state
|
// determine if node was recently heard uplinking packets to mqtt
|
||||||
var mqttStatus = "";
|
const nodeHasUplinkedToMqttRecently = hasNodeUplinkedToMqttRecently(node);
|
||||||
var mqttStatusLastUpdated = node.mqtt_connection_state_updated_at ? `(${moment(new Date(node.mqtt_connection_state_updated_at)).fromNow()})` : "";
|
var mqttStatus = `<span class="text-blue-700">Disconnected</span>`;
|
||||||
if(node.mqtt_connection_state === "online"){
|
if(node.mqtt_connection_state_updated_at){
|
||||||
mqttStatus = `<span class="text-green-700">Connected</span> ${mqttStatusLastUpdated}`;
|
var mqttStatusUpdatedAt = moment(new Date(node.mqtt_connection_state_updated_at)).fromNow();
|
||||||
} else if(node.mqtt_connection_state === "offline"){
|
if(nodeHasUplinkedToMqttRecently){
|
||||||
mqttStatus = `<span class="text-blue-700">Disconnected</span> ${mqttStatusLastUpdated}`;
|
mqttStatus = `<span><span class="text-green-700">Connected</span> (${mqttStatusUpdatedAt})</span>`;
|
||||||
} else {
|
} else {
|
||||||
mqttStatus = `<span class="text-blue-700">Disconnected</span>`;
|
mqttStatus = `<span><span class="text-blue-700">Disconnected</span> (${mqttStatusUpdatedAt})</span>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var loraFrequencyRange = getRegionFrequencyRange(node.region_name);
|
var loraFrequencyRange = getRegionFrequencyRange(node.region_name);
|
||||||
|
Reference in New Issue
Block a user