Merge branch 'liamcottle-master'
This commit is contained in:
commit
efdf5f850d
19 changed files with 800 additions and 48 deletions
1
.env.example
Normal file
1
.env.example
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
DATABASE_URL="mysql://root@localhost:3306/meshtastic-map?connection_limit=100"
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
|
.idea/
|
||||||
node_modules
|
node_modules
|
||||||
# Keep environment variables out of version control
|
# Keep environment variables out of version control
|
||||||
.env
|
.env
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,15 @@
|
||||||
<a href="https://twitter.com/liamcottle"><img src="https://img.shields.io/badge/Twitter-@liamcottle-%231DA1F2?style=flat&logo=twitter" alt="twitter"/></a>
|
<a href="https://twitter.com/liamcottle"><img src="https://img.shields.io/badge/Twitter-@liamcottle-%231DA1F2?style=flat&logo=twitter" alt="twitter"/></a>
|
||||||
<br/>
|
<br/>
|
||||||
<a href="https://ko-fi.com/liamcottle"><img src="https://img.shields.io/badge/Donate%20a%20Coffee-liamcottle-yellow?style=flat&logo=buy-me-a-coffee" alt="donate on ko-fi"/></a>
|
<a href="https://ko-fi.com/liamcottle"><img src="https://img.shields.io/badge/Donate%20a%20Coffee-liamcottle-yellow?style=flat&logo=buy-me-a-coffee" alt="donate on ko-fi"/></a>
|
||||||
<a href="./donate.md"><img src="https://img.shields.io/badge/Donate%20Bitcoin-3FPBfiEwioWHFix3kZqe5bdU9F5o8mG8dh-%23FF9900?style=flat&logo=bitcoin" alt="donate bitcoin"/></a>
|
<a href="./donate.md"><img src="https://img.shields.io/badge/Donate%20Bitcoin-bc1qy22smke8n4c54evdxmp7lpy9p0e6m9tavtlg2q-%23FF9900?style=flat&logo=bitcoin" alt="donate bitcoin"/></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
A map of all Meshtastic nodes heard via MQTT.
|
A map of all Meshtastic nodes heard via MQTT.
|
||||||
|
|
||||||
My version of the map is available at https://meshtastic.liamcottle.net
|
My version of the map is available at https://meshtastic.liamcottle.net
|
||||||
|
|
||||||
|
> Check out my new Meshtastic Web Client: [MeshTXT](https://github.com/liamcottle/meshtxt)
|
||||||
|
|
||||||
<img src="./screenshot.png">
|
<img src="./screenshot.png">
|
||||||
|
|
||||||
## How does it work?
|
## How does it work?
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ Thank you for considering donating, this helps support my work on this project
|
||||||
|
|
||||||
## How can I donate?
|
## How can I donate?
|
||||||
|
|
||||||
- Bitcoin: 3FPBfiEwioWHFix3kZqe5bdU9F5o8mG8dh
|
- Bitcoin: bc1qy22smke8n4c54evdxmp7lpy9p0e6m9tavtlg2q
|
||||||
|
- Ethereum: 0xc64CFbA5D0BF7664158c5671F64d446395b3bF3D
|
||||||
- Buy me a Coffee: [https://ko-fi.com/liamcottle](https://ko-fi.com/liamcottle)
|
- Buy me a Coffee: [https://ko-fi.com/liamcottle](https://ko-fi.com/liamcottle)
|
||||||
- Sponsor on GitHub: [https://github.com/sponsors/liamcottle](https://github.com/sponsors/liamcottle)
|
- Sponsor on GitHub: [https://github.com/sponsors/liamcottle](https://github.com/sponsors/liamcottle)
|
||||||
|
|
|
||||||
39
meshtastic-map-mqtt.service
Normal file
39
meshtastic-map-mqtt.service
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
[Unit]
|
||||||
|
Description=meshtastic-map-mqtt
|
||||||
|
After=network.target
|
||||||
|
StartLimitIntervalSec=0
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
RestartSec=1
|
||||||
|
User=liamcottle
|
||||||
|
WorkingDirectory=/home/liamcottle/meshtastic-map
|
||||||
|
ExecStart=/usr/bin/env node /home/liamcottle/meshtastic-map/src/mqtt.js \
|
||||||
|
--mqtt-broker-url mqtt://127.0.0.1 \
|
||||||
|
--mqtt-username username \
|
||||||
|
--mqtt-password password \
|
||||||
|
--mqtt-client-id meshtastic.example.com \
|
||||||
|
--mqtt-topic 'msh/#' \
|
||||||
|
--collect-positions \
|
||||||
|
--collect-text-messages \
|
||||||
|
--collect-waypoints \
|
||||||
|
--ignore-direct-messages \
|
||||||
|
--purge-interval-seconds 60 \
|
||||||
|
--purge-nodes-unheard-for-seconds 604800 \
|
||||||
|
--purge-device-metrics-after-seconds 604800 \
|
||||||
|
--purge-environment-metrics-after-seconds 604800 \
|
||||||
|
--purge-map-reports-after-seconds 604800 \
|
||||||
|
--purge-neighbour-infos-after-seconds 604800 \
|
||||||
|
--purge-power-metrics-after-seconds 604800 \
|
||||||
|
--purge-positions-after-seconds 604800 \
|
||||||
|
--purge-service-envelopes-after-seconds 604800 \
|
||||||
|
--purge-text-messages-after-seconds 604800 \
|
||||||
|
--purge-traceroutes-after-seconds 604800 \
|
||||||
|
--purge-waypoints-after-seconds 604800 \
|
||||||
|
--forget-outdated-node-positions-after-seconds 604800 \
|
||||||
|
--drop-packets-not-ok-to-mqtt \
|
||||||
|
--old-firmware-position-precision 16
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
15
meshtastic-map.service
Normal file
15
meshtastic-map.service
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[Unit]
|
||||||
|
Description=meshtastic-map
|
||||||
|
After=network.target
|
||||||
|
StartLimitIntervalSec=0
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
RestartSec=1
|
||||||
|
User=liamcottle
|
||||||
|
WorkingDirectory=/home/liamcottle/meshtastic-map
|
||||||
|
ExecStart=/usr/bin/env node /home/liamcottle/meshtastic-map/src/index.js
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX `nodes_position_updated_at_idx` ON `nodes`(`position_updated_at`);
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `environment_metrics` ADD COLUMN `wind_direction` INTEGER NULL,
|
||||||
|
ADD COLUMN `wind_gust` DECIMAL(65, 30) NULL,
|
||||||
|
ADD COLUMN `wind_lull` DECIMAL(65, 30) NULL,
|
||||||
|
ADD COLUMN `wind_speed` DECIMAL(65, 30) NULL;
|
||||||
|
|
@ -56,6 +56,7 @@ model Node {
|
||||||
|
|
||||||
@@index(created_at)
|
@@index(created_at)
|
||||||
@@index(updated_at)
|
@@index(updated_at)
|
||||||
|
@@index(position_updated_at)
|
||||||
@@index(node_id)
|
@@index(node_id)
|
||||||
@@map("nodes")
|
@@map("nodes")
|
||||||
}
|
}
|
||||||
|
|
@ -132,6 +133,10 @@ model EnvironmentMetric {
|
||||||
voltage Decimal?
|
voltage Decimal?
|
||||||
current Decimal?
|
current Decimal?
|
||||||
iaq Int?
|
iaq Int?
|
||||||
|
wind_direction Int?
|
||||||
|
wind_speed Decimal?
|
||||||
|
wind_gust Decimal?
|
||||||
|
wind_lull Decimal?
|
||||||
|
|
||||||
created_at DateTime @default(now())
|
created_at DateTime @default(now())
|
||||||
updated_at DateTime @default(now()) @updatedAt
|
updated_at DateTime @default(now()) @updatedAt
|
||||||
|
|
|
||||||
127
src/admin.js
Normal file
127
src/admin.js
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
// node src/admin.js --purge-node-id 123
|
||||||
|
// node src/admin.js --purge-node-id '!AABBCCDD'
|
||||||
|
|
||||||
|
const commandLineArgs = require("command-line-args");
|
||||||
|
const commandLineUsage = require("command-line-usage");
|
||||||
|
|
||||||
|
// create prisma db client
|
||||||
|
const { PrismaClient } = require("@prisma/client");
|
||||||
|
const NodeIdUtil = require("./utils/node_id_util");
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
const optionsList = [
|
||||||
|
{
|
||||||
|
name: 'help',
|
||||||
|
alias: 'h',
|
||||||
|
type: Boolean,
|
||||||
|
description: 'Display this usage guide.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "purge-node-id",
|
||||||
|
type: String,
|
||||||
|
description: "Purges all records for the provided node id.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// parse command line args
|
||||||
|
const options = commandLineArgs(optionsList);
|
||||||
|
|
||||||
|
// show help
|
||||||
|
if(options.help){
|
||||||
|
const usage = commandLineUsage([
|
||||||
|
{
|
||||||
|
header: 'Meshtastic Map Admin',
|
||||||
|
content: 'Command line admin tool for the Meshtastic Map',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Options',
|
||||||
|
optionList: optionsList,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
console.log(usage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get options and fallback to default values
|
||||||
|
const purgeNodeId = options["purge-node-id"] ?? null;
|
||||||
|
|
||||||
|
async function purgeNodeById(nodeId) {
|
||||||
|
|
||||||
|
// convert to numeric id
|
||||||
|
nodeId = NodeIdUtil.convertToNumeric(nodeId);
|
||||||
|
|
||||||
|
// purge environment metrics
|
||||||
|
await prisma.environmentMetric.deleteMany({
|
||||||
|
where: {
|
||||||
|
node_id: nodeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// purge map reports
|
||||||
|
await prisma.mapReport.deleteMany({
|
||||||
|
where: {
|
||||||
|
node_id: nodeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// purge neighbour infos
|
||||||
|
await prisma.neighbourInfo.deleteMany({
|
||||||
|
where: {
|
||||||
|
node_id: nodeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// purge this node
|
||||||
|
await prisma.node.deleteMany({
|
||||||
|
where: {
|
||||||
|
node_id: nodeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// purge positions
|
||||||
|
await prisma.position.deleteMany({
|
||||||
|
where: {
|
||||||
|
node_id: nodeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// purge power metrics
|
||||||
|
await prisma.powerMetric.deleteMany({
|
||||||
|
where: {
|
||||||
|
node_id: nodeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// purge text messages
|
||||||
|
await prisma.textMessage.deleteMany({
|
||||||
|
where: {
|
||||||
|
from: nodeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// purge traceroutes
|
||||||
|
await prisma.traceRoute.deleteMany({
|
||||||
|
where: {
|
||||||
|
from: nodeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// purge waypoints
|
||||||
|
await prisma.waypoint.deleteMany({
|
||||||
|
where: {
|
||||||
|
from: nodeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Node '${nodeId}' has been purged from the database.`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
|
||||||
|
// purge node by id
|
||||||
|
if(purgeNodeId){
|
||||||
|
await purgeNodeById(purgeNodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
90
src/index.js
90
src/index.js
|
|
@ -92,23 +92,101 @@ app.get('/api', async (req, res) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/api/v1/nodes",
|
"path": "/api/v1/nodes",
|
||||||
"description": "Meshtastic nodes in JSON format.",
|
"description": "All meshtastic nodes",
|
||||||
|
"params": {
|
||||||
|
"role": "Filter by role",
|
||||||
|
"hardware_model": "Filter by hardware model",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/api/v1/nodes/:nodeId",
|
||||||
|
"description": "A specific meshtastic node",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/api/v1/nodes/:nodeId/device-metrics",
|
||||||
|
"description": "Device metrics for a meshtastic node",
|
||||||
|
"params": {
|
||||||
|
"count": "How many results to return",
|
||||||
|
"time_from": "Only include metrics created after this unix timestamp (milliseconds)",
|
||||||
|
"time_to": "Only include metrics created before this unix timestamp (milliseconds)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/api/v1/nodes/:nodeId/environment-metrics",
|
||||||
|
"description": "Environment metrics for a meshtastic node",
|
||||||
|
"params": {
|
||||||
|
"count": "How many results to return",
|
||||||
|
"time_from": "Only include metrics created after this unix timestamp (milliseconds)",
|
||||||
|
"time_to": "Only include metrics created before this unix timestamp (milliseconds)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/api/v1/nodes/:nodeId/power-metrics",
|
||||||
|
"description": "Power metrics for a meshtastic node",
|
||||||
|
"params": {
|
||||||
|
"count": "How many results to return",
|
||||||
|
"time_from": "Only include metrics created after this unix timestamp (milliseconds)",
|
||||||
|
"time_to": "Only include metrics created before this unix timestamp (milliseconds)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/api/v1/nodes/:nodeId/neighbours",
|
||||||
|
"description": "Neighbours for a meshtastic node",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/api/v1/nodes/:nodeId/traceroutes",
|
||||||
|
"description": "Trace routes for a meshtastic node",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/api/v1/nodes/:nodeId/position-history",
|
||||||
|
"description": "Position history for a meshtastic node",
|
||||||
|
"params": {
|
||||||
|
"time_from": "Only include positions created after this unix timestamp (milliseconds)",
|
||||||
|
"time_to": "Only include positions created before this unix timestamp (milliseconds)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/api/v1/stats/hardware-models",
|
"path": "/api/v1/stats/hardware-models",
|
||||||
"description": "Database statistics about hardware models in JSON format.",
|
"description": "Database statistics about known hardware models",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/api/v1/text-messages",
|
||||||
|
"description": "Text messages",
|
||||||
|
"params": {
|
||||||
|
"to": "Only include messages to this node id",
|
||||||
|
"from": "Only include messages from this node id",
|
||||||
|
"channel_id": "Only include messages for this channel id",
|
||||||
|
"gateway_id": "Only include messages gated to mqtt by this node id",
|
||||||
|
"last_id": "Only include messages before or after this id, based on results order",
|
||||||
|
"count": "How many results to return",
|
||||||
|
"order": "Order to return results in: asc, desc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/api/v1/text-messages/embed",
|
||||||
|
"description": "Text messages rendered as an embeddable HTML page.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/api/v1/waypoints",
|
"path": "/api/v1/waypoints",
|
||||||
"description": "Meshtastic waypoints in JSON format.",
|
"description": "Waypoints",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const html = links.map((link) => {
|
const linksHtml = links.map((link) => {
|
||||||
return `<li><a href="${link.path}">${link.path}</a> - ${link.description}</li>`;
|
var line = `<li>`;
|
||||||
|
line += `<a href="${link.path}">${link.path}</a> - ${link.description}`;
|
||||||
|
line += `<ul>`;
|
||||||
|
for(const paramKey in (link.params ?? [])){
|
||||||
|
const paramDescription = link.params[paramKey];
|
||||||
|
line += "<li>";
|
||||||
|
line += `?${paramKey}: ${paramDescription}`;
|
||||||
|
line += `</li>`;
|
||||||
|
}
|
||||||
|
line += `</ul>`;
|
||||||
|
return line;
|
||||||
}).join("");
|
}).join("");
|
||||||
|
|
||||||
res.send(html);
|
res.send(`<b>API Docs</b><br/><ul>${linksHtml}</ul>`);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
54
src/mqtt.js
54
src/mqtt.js
|
|
@ -119,6 +119,11 @@ const optionsList = [
|
||||||
type: Number,
|
type: Number,
|
||||||
description: "If provided, position packets from firmware v2.4 and older will be truncated to this many decimal places.",
|
description: "If provided, position packets from firmware v2.4 and older will be truncated to this many decimal places.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "forget-outdated-node-positions-after-seconds",
|
||||||
|
type: Number,
|
||||||
|
description: "If provided, nodes that haven't sent a position report in this time will have their current position cleared.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "purge-interval-seconds",
|
name: "purge-interval-seconds",
|
||||||
type: Number,
|
type: Number,
|
||||||
|
|
@ -221,6 +226,7 @@ const decryptionKeys = options["decryption-keys"] ?? [
|
||||||
const dropPacketsNotOkToMqtt = options["drop-packets-not-ok-to-mqtt"] ?? false;
|
const dropPacketsNotOkToMqtt = options["drop-packets-not-ok-to-mqtt"] ?? false;
|
||||||
const dropPortnumsWithoutBitfield = options["drop-portnums-without-bitfield"] ?? null;
|
const dropPortnumsWithoutBitfield = options["drop-portnums-without-bitfield"] ?? null;
|
||||||
const oldFirmwarePositionPrecision = options["old-firmware-position-precision"] ?? null;
|
const oldFirmwarePositionPrecision = options["old-firmware-position-precision"] ?? null;
|
||||||
|
const forgetOutdatedNodePositionsAfterSeconds = options["forget-outdated-node-positions-after-seconds"] ?? null;
|
||||||
const purgeIntervalSeconds = options["purge-interval-seconds"] ?? 10;
|
const purgeIntervalSeconds = options["purge-interval-seconds"] ?? 10;
|
||||||
const purgeNodesUnheardForSeconds = options["purge-nodes-unheard-for-seconds"] ?? null;
|
const purgeNodesUnheardForSeconds = options["purge-nodes-unheard-for-seconds"] ?? null;
|
||||||
const purgeDeviceMetricsAfterSeconds = options["purge-device-metrics-after-seconds"] ?? null;
|
const purgeDeviceMetricsAfterSeconds = options["purge-device-metrics-after-seconds"] ?? null;
|
||||||
|
|
@ -269,6 +275,7 @@ if(purgeIntervalSeconds){
|
||||||
await purgeOldTextMessages();
|
await purgeOldTextMessages();
|
||||||
await purgeOldTraceroutes();
|
await purgeOldTraceroutes();
|
||||||
await purgeOldWaypoints();
|
await purgeOldWaypoints();
|
||||||
|
await forgetOutdatedNodePositions();
|
||||||
}, purgeIntervalSeconds * 1000);
|
}, purgeIntervalSeconds * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -558,6 +565,45 @@ async function purgeOldWaypoints() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the current position stored for nodes if the position hasn't been updated within the configured timeframe.
|
||||||
|
* This allows the node position to drop off the map if the user disabled position reporting, but still wants telemetry lookup etc
|
||||||
|
*/
|
||||||
|
async function forgetOutdatedNodePositions() {
|
||||||
|
|
||||||
|
// make sure seconds provided
|
||||||
|
if(!forgetOutdatedNodePositionsAfterSeconds){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear latitude/longitude/altitude for nodes that haven't updated their position in the configured timeframe
|
||||||
|
try {
|
||||||
|
await prisma.node.updateMany({
|
||||||
|
where: {
|
||||||
|
position_updated_at: {
|
||||||
|
// position_updated_at before x seconds ago
|
||||||
|
lt: new Date(Date.now() - forgetOutdatedNodePositionsAfterSeconds * 1000),
|
||||||
|
},
|
||||||
|
// don't forget outdated node positions for nodes that don't actually have a position set
|
||||||
|
// otherwise the updated_at is updated, when nothing changed
|
||||||
|
NOT: {
|
||||||
|
latitude: null,
|
||||||
|
longitude: null,
|
||||||
|
altitude: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
latitude: null,
|
||||||
|
longitude: null,
|
||||||
|
altitude: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function createNonce(packetId, fromNode) {
|
function createNonce(packetId, fromNode) {
|
||||||
|
|
||||||
// Expand packetId to 64 bits
|
// Expand packetId to 64 bits
|
||||||
|
|
@ -1072,6 +1118,10 @@ client.on("message", async (topic, message) => {
|
||||||
const voltage = telemetry.environmentMetrics.voltage !== 0 ? telemetry.environmentMetrics.voltage : null;
|
const voltage = telemetry.environmentMetrics.voltage !== 0 ? telemetry.environmentMetrics.voltage : null;
|
||||||
const current = telemetry.environmentMetrics.current !== 0 ? telemetry.environmentMetrics.current : null;
|
const current = telemetry.environmentMetrics.current !== 0 ? telemetry.environmentMetrics.current : null;
|
||||||
const iaq = telemetry.environmentMetrics.iaq !== 0 ? telemetry.environmentMetrics.iaq : null;
|
const iaq = telemetry.environmentMetrics.iaq !== 0 ? telemetry.environmentMetrics.iaq : null;
|
||||||
|
const windDirection = telemetry.environmentMetrics.windDirection;
|
||||||
|
const windSpeed = telemetry.environmentMetrics.windSpeed;
|
||||||
|
const windGust = telemetry.environmentMetrics.windGust;
|
||||||
|
const windLull = telemetry.environmentMetrics.windLull;
|
||||||
|
|
||||||
// set metrics to update on node table
|
// set metrics to update on node table
|
||||||
data.temperature = temperature;
|
data.temperature = temperature;
|
||||||
|
|
@ -1105,6 +1155,10 @@ client.on("message", async (topic, message) => {
|
||||||
voltage: voltage,
|
voltage: voltage,
|
||||||
current: current,
|
current: current,
|
||||||
iaq: iaq,
|
iaq: iaq,
|
||||||
|
wind_direction: windDirection,
|
||||||
|
wind_speed: windSpeed,
|
||||||
|
wind_gust: windGust,
|
||||||
|
wind_lull: windLull,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,15 @@ message Config {
|
||||||
* Uses position module configuration to determine TAK PLI broadcast interval.
|
* Uses position module configuration to determine TAK PLI broadcast interval.
|
||||||
*/
|
*/
|
||||||
TAK_TRACKER = 10;
|
TAK_TRACKER = 10;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Description: Will always rebroadcast packets, but will do so after all other modes.
|
||||||
|
* Technical Details: Used for router nodes that are intended to provide additional coverage
|
||||||
|
* in areas not already covered by other routers, or to bridge around problematic terrain,
|
||||||
|
* but should not be given priority over other routers in order to avoid unnecessaraily
|
||||||
|
* consuming hops.
|
||||||
|
*/
|
||||||
|
ROUTER_LATE = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -746,6 +755,21 @@ message Config {
|
||||||
* Singapore 923mhz
|
* Singapore 923mhz
|
||||||
*/
|
*/
|
||||||
SG_923 = 18;
|
SG_923 = 18;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Philippines 433mhz
|
||||||
|
*/
|
||||||
|
PH_433 = 19;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Philippines 868mhz
|
||||||
|
*/
|
||||||
|
PH_868 = 20;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Philippines 915mhz
|
||||||
|
*/
|
||||||
|
PH_915 = 21;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -792,6 +816,13 @@ message Config {
|
||||||
* Long Range - Moderately Fast
|
* Long Range - Moderately Fast
|
||||||
*/
|
*/
|
||||||
LONG_MODERATE = 7;
|
LONG_MODERATE = 7;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Short Range - Turbo
|
||||||
|
* This is the fastest preset and the only one with 500kHz bandwidth.
|
||||||
|
* It is not legal to use in all regions due to this wider bandwidth.
|
||||||
|
*/
|
||||||
|
SHORT_TURBO = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -339,6 +339,11 @@ enum HardwareModel {
|
||||||
*/
|
*/
|
||||||
HELTEC_HRU_3601 = 23;
|
HELTEC_HRU_3601 = 23;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Heltec Wireless Bridge
|
||||||
|
*/
|
||||||
|
HELTEC_WIRELESS_BRIDGE = 24;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
* B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
||||||
*/
|
*/
|
||||||
|
|
@ -427,7 +432,7 @@ enum HardwareModel {
|
||||||
DR_DEV = 41;
|
DR_DEV = 41;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/
|
* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/
|
||||||
*/
|
*/
|
||||||
M5STACK = 42;
|
M5STACK = 42;
|
||||||
|
|
||||||
|
|
@ -620,9 +625,50 @@ enum HardwareModel {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
RP2040_FEATHER_RFM95 = 76;
|
RP2040_FEATHER_RFM95 = 76;
|
||||||
/* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/ */
|
|
||||||
M5STACK_COREBASIC=77;
|
/* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/ */
|
||||||
M5STACK_CORE2=78;
|
M5STACK_COREBASIC = 77;
|
||||||
|
M5STACK_CORE2 = 78;
|
||||||
|
|
||||||
|
/* Pico2 with Waveshare Hat, same as Pico */
|
||||||
|
RPI_PICO2 = 79;
|
||||||
|
|
||||||
|
/* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/ */
|
||||||
|
M5STACK_CORES3 = 80;
|
||||||
|
|
||||||
|
/* Seeed XIAO S3 DK*/
|
||||||
|
SEEED_XIAO_S3 = 81;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Nordic nRF52840+Semtech SX1262 LoRa BLE Combo Module. nRF52840+SX1262 MS24SF1
|
||||||
|
*/
|
||||||
|
MS24SF1 = 82;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lilygo TLora-C6 with the new ESP32-C6 MCU
|
||||||
|
*/
|
||||||
|
TLORA_C6 = 83;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WisMesh Tap
|
||||||
|
* RAK-4631 w/ TFT in injection modled case
|
||||||
|
*/
|
||||||
|
WISMESH_TAP = 84;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Similar to PORTDUINO but used by Routastic devices, this is not any
|
||||||
|
* particular device and does not run Meshtastic's code but supports
|
||||||
|
* the same frame format.
|
||||||
|
* Runs on linux, see https://github.com/Jorropo/routastic
|
||||||
|
*/
|
||||||
|
ROUTASTIC = 85;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mesh-Tab, esp32 based
|
||||||
|
* https://github.com/valzzu/Mesh-Tab
|
||||||
|
*/
|
||||||
|
MESH_TAB = 86;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ------------------------------------------------------------------------------------------------------------------------------------------
|
* ------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
* Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
|
* Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
|
||||||
|
|
|
||||||
|
|
@ -15,27 +15,27 @@ message DeviceMetrics {
|
||||||
/*
|
/*
|
||||||
* 0-100 (>100 means powered)
|
* 0-100 (>100 means powered)
|
||||||
*/
|
*/
|
||||||
uint32 battery_level = 1;
|
optional uint32 battery_level = 1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Voltage measured
|
* Voltage measured
|
||||||
*/
|
*/
|
||||||
float voltage = 2;
|
optional float voltage = 2;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise).
|
* Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise).
|
||||||
*/
|
*/
|
||||||
float channel_utilization = 3;
|
optional float channel_utilization = 3;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Percent of airtime for transmission used within the last hour.
|
* Percent of airtime for transmission used within the last hour.
|
||||||
*/
|
*/
|
||||||
float air_util_tx = 4;
|
optional float air_util_tx = 4;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* How long the device has been running since the last reboot (in seconds)
|
* How long the device has been running since the last reboot (in seconds)
|
||||||
*/
|
*/
|
||||||
uint32 uptime_seconds = 5;
|
optional uint32 uptime_seconds = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -45,38 +45,95 @@ message EnvironmentMetrics {
|
||||||
/*
|
/*
|
||||||
* Temperature measured
|
* Temperature measured
|
||||||
*/
|
*/
|
||||||
float temperature = 1;
|
optional float temperature = 1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Relative humidity percent measured
|
* Relative humidity percent measured
|
||||||
*/
|
*/
|
||||||
float relative_humidity = 2;
|
optional float relative_humidity = 2;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Barometric pressure in hPA measured
|
* Barometric pressure in hPA measured
|
||||||
*/
|
*/
|
||||||
float barometric_pressure = 3;
|
optional float barometric_pressure = 3;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Gas resistance in MOhm measured
|
* Gas resistance in MOhm measured
|
||||||
*/
|
*/
|
||||||
float gas_resistance = 4;
|
optional float gas_resistance = 4;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Voltage measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x)
|
* Voltage measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x)
|
||||||
*/
|
*/
|
||||||
float voltage = 5;
|
optional float voltage = 5;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x)
|
* Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x)
|
||||||
*/
|
*/
|
||||||
float current = 6;
|
optional float current = 6;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* relative scale IAQ value as measured by Bosch BME680 . value 0-500.
|
* relative scale IAQ value as measured by Bosch BME680 . value 0-500.
|
||||||
* Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here.
|
* Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here.
|
||||||
*/
|
*/
|
||||||
uint32 iaq = 7;
|
optional uint32 iaq = 7;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm.
|
||||||
|
*/
|
||||||
|
optional float distance = 8;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor.
|
||||||
|
*/
|
||||||
|
optional float lux = 9;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor.
|
||||||
|
*/
|
||||||
|
optional float white_lux = 10;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Infrared lux
|
||||||
|
*/
|
||||||
|
optional float ir_lux = 11;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ultraviolet lux
|
||||||
|
*/
|
||||||
|
optional float uv_lux = 12;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wind direction in degrees
|
||||||
|
* 0 degrees = North, 90 = East, etc...
|
||||||
|
*/
|
||||||
|
optional uint32 wind_direction = 13;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wind speed in m/s
|
||||||
|
*/
|
||||||
|
optional float wind_speed = 14;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Weight in KG
|
||||||
|
*/
|
||||||
|
optional float weight = 15;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wind gust in m/s
|
||||||
|
*/
|
||||||
|
optional float wind_gust = 16;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wind lull in m/s
|
||||||
|
*/
|
||||||
|
optional float wind_lull = 17;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Radiation in µR/h
|
||||||
|
*/
|
||||||
|
optional float radiation = 18;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -86,32 +143,32 @@ message PowerMetrics {
|
||||||
/*
|
/*
|
||||||
* Voltage (Ch1)
|
* Voltage (Ch1)
|
||||||
*/
|
*/
|
||||||
float ch1_voltage = 1;
|
optional float ch1_voltage = 1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Current (Ch1)
|
* Current (Ch1)
|
||||||
*/
|
*/
|
||||||
float ch1_current = 2;
|
optional float ch1_current = 2;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Voltage (Ch2)
|
* Voltage (Ch2)
|
||||||
*/
|
*/
|
||||||
float ch2_voltage = 3;
|
optional float ch2_voltage = 3;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Current (Ch2)
|
* Current (Ch2)
|
||||||
*/
|
*/
|
||||||
float ch2_current = 4;
|
optional float ch2_current = 4;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Voltage (Ch3)
|
* Voltage (Ch3)
|
||||||
*/
|
*/
|
||||||
float ch3_voltage = 5;
|
optional float ch3_voltage = 5;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Current (Ch3)
|
* Current (Ch3)
|
||||||
*/
|
*/
|
||||||
float ch3_current = 6;
|
optional float ch3_current = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -121,62 +178,147 @@ message AirQualityMetrics {
|
||||||
/*
|
/*
|
||||||
* Concentration Units Standard PM1.0
|
* Concentration Units Standard PM1.0
|
||||||
*/
|
*/
|
||||||
uint32 pm10_standard = 1;
|
optional uint32 pm10_standard = 1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Concentration Units Standard PM2.5
|
* Concentration Units Standard PM2.5
|
||||||
*/
|
*/
|
||||||
uint32 pm25_standard = 2;
|
optional uint32 pm25_standard = 2;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Concentration Units Standard PM10.0
|
* Concentration Units Standard PM10.0
|
||||||
*/
|
*/
|
||||||
uint32 pm100_standard = 3;
|
optional uint32 pm100_standard = 3;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Concentration Units Environmental PM1.0
|
* Concentration Units Environmental PM1.0
|
||||||
*/
|
*/
|
||||||
uint32 pm10_environmental = 4;
|
optional uint32 pm10_environmental = 4;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Concentration Units Environmental PM2.5
|
* Concentration Units Environmental PM2.5
|
||||||
*/
|
*/
|
||||||
uint32 pm25_environmental = 5;
|
optional uint32 pm25_environmental = 5;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Concentration Units Environmental PM10.0
|
* Concentration Units Environmental PM10.0
|
||||||
*/
|
*/
|
||||||
uint32 pm100_environmental = 6;
|
optional uint32 pm100_environmental = 6;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 0.3um Particle Count
|
* 0.3um Particle Count
|
||||||
*/
|
*/
|
||||||
uint32 particles_03um = 7;
|
optional uint32 particles_03um = 7;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 0.5um Particle Count
|
* 0.5um Particle Count
|
||||||
*/
|
*/
|
||||||
uint32 particles_05um = 8;
|
optional uint32 particles_05um = 8;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 1.0um Particle Count
|
* 1.0um Particle Count
|
||||||
*/
|
*/
|
||||||
uint32 particles_10um = 9;
|
optional uint32 particles_10um = 9;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 2.5um Particle Count
|
* 2.5um Particle Count
|
||||||
*/
|
*/
|
||||||
uint32 particles_25um = 10;
|
optional uint32 particles_25um = 10;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 5.0um Particle Count
|
* 5.0um Particle Count
|
||||||
*/
|
*/
|
||||||
uint32 particles_50um = 11;
|
optional uint32 particles_50um = 11;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 10.0um Particle Count
|
* 10.0um Particle Count
|
||||||
*/
|
*/
|
||||||
uint32 particles_100um = 12;
|
optional uint32 particles_100um = 12;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 10.0um Particle Count
|
||||||
|
*/
|
||||||
|
optional uint32 co2 = 13;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Local device mesh statistics
|
||||||
|
*/
|
||||||
|
message LocalStats {
|
||||||
|
/*
|
||||||
|
* How long the device has been running since the last reboot (in seconds)
|
||||||
|
*/
|
||||||
|
uint32 uptime_seconds = 1;
|
||||||
|
/*
|
||||||
|
* Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise).
|
||||||
|
*/
|
||||||
|
float channel_utilization = 2;
|
||||||
|
/*
|
||||||
|
* Percent of airtime for transmission used within the last hour.
|
||||||
|
*/
|
||||||
|
float air_util_tx = 3;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of packets sent
|
||||||
|
*/
|
||||||
|
uint32 num_packets_tx = 4;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of packets received (both good and bad)
|
||||||
|
*/
|
||||||
|
uint32 num_packets_rx = 5;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of packets received that are malformed or violate the protocol
|
||||||
|
*/
|
||||||
|
uint32 num_packets_rx_bad = 6;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of nodes online (in the past 2 hours)
|
||||||
|
*/
|
||||||
|
uint32 num_online_nodes = 7;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of nodes total
|
||||||
|
*/
|
||||||
|
uint32 num_total_nodes = 8;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of received packets that were duplicates (due to multiple nodes relaying).
|
||||||
|
* If this number is high, there are nodes in the mesh relaying packets when it's unnecessary, for example due to the ROUTER/REPEATER role.
|
||||||
|
*/
|
||||||
|
uint32 num_rx_dupe = 9;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of packets we transmitted that were a relay for others (not originating from ourselves).
|
||||||
|
*/
|
||||||
|
uint32 num_tx_relay = 10;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of times we canceled a packet to be relayed, because someone else did it before us.
|
||||||
|
* This will always be zero for ROUTERs/REPEATERs. If this number is high, some other node(s) is/are relaying faster than you.
|
||||||
|
*/
|
||||||
|
uint32 num_tx_relay_canceled = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Health telemetry metrics
|
||||||
|
*/
|
||||||
|
message HealthMetrics {
|
||||||
|
/*
|
||||||
|
* Heart rate (beats per minute)
|
||||||
|
*/
|
||||||
|
optional uint32 heart_bpm = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SpO2 (blood oxygen saturation) level
|
||||||
|
*/
|
||||||
|
optional uint32 spO2 = 2;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Body temperature in degrees Celsius
|
||||||
|
*/
|
||||||
|
optional float temperature = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -208,6 +350,16 @@ message Telemetry {
|
||||||
* Power Metrics
|
* Power Metrics
|
||||||
*/
|
*/
|
||||||
PowerMetrics power_metrics = 5;
|
PowerMetrics power_metrics = 5;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Local device mesh statistics
|
||||||
|
*/
|
||||||
|
LocalStats local_stats = 6;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Health telemetry metrics
|
||||||
|
*/
|
||||||
|
HealthMetrics health_metrics = 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,4 +446,115 @@ enum TelemetrySensorType {
|
||||||
* BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280)
|
* BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280)
|
||||||
*/
|
*/
|
||||||
BMP085 = 15;
|
BMP085 = 15;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RCWL-9620 Doppler Radar Distance Sensor, used for water level detection
|
||||||
|
*/
|
||||||
|
RCWL9620 = 16;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sensirion High accuracy temperature and humidity
|
||||||
|
*/
|
||||||
|
SHT4X = 17;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor.
|
||||||
|
*/
|
||||||
|
VEML7700 = 18;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MLX90632 non-contact IR temperature sensor.
|
||||||
|
*/
|
||||||
|
MLX90632 = 19;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TI OPT3001 Ambient Light Sensor
|
||||||
|
*/
|
||||||
|
OPT3001 = 20;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lite On LTR-390UV-01 UV Light Sensor
|
||||||
|
*/
|
||||||
|
LTR390UV = 21;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AMS TSL25911FN RGB Light Sensor
|
||||||
|
*/
|
||||||
|
TSL25911FN = 22;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AHT10 Integrated temperature and humidity sensor
|
||||||
|
*/
|
||||||
|
AHT10 = 23;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DFRobot Lark Weather station (temperature, humidity, pressure, wind speed and direction)
|
||||||
|
*/
|
||||||
|
DFROBOT_LARK = 24;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAU7802 Scale Chip or compatible
|
||||||
|
*/
|
||||||
|
NAU7802 = 25;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BMP3XX High accuracy temperature and pressure
|
||||||
|
*/
|
||||||
|
BMP3XX = 26;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ICM-20948 9-Axis digital motion processor
|
||||||
|
*/
|
||||||
|
ICM20948 = 27;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MAX17048 1S lipo battery sensor (voltage, state of charge, time to go)
|
||||||
|
*/
|
||||||
|
MAX17048 = 28;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor
|
||||||
|
*/
|
||||||
|
CUSTOM_SENSOR = 29;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MAX30102 Pulse Oximeter and Heart-Rate Sensor
|
||||||
|
*/
|
||||||
|
MAX30102 = 30;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MLX90614 non-contact IR temperature sensor
|
||||||
|
*/
|
||||||
|
MLX90614 = 31;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SCD40/SCD41 CO2, humidity, temperature sensor
|
||||||
|
*/
|
||||||
|
SCD4X = 32;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ClimateGuard RadSens, radiation, Geiger-Muller Tube
|
||||||
|
*/
|
||||||
|
RADSENS = 33;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* High accuracy current and voltage
|
||||||
|
*/
|
||||||
|
INA226 = 34;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAU7802 Telemetry configuration, for saving to flash
|
||||||
|
*/
|
||||||
|
message Nau7802Config {
|
||||||
|
/*
|
||||||
|
* The offset setting for the NAU7802
|
||||||
|
*/
|
||||||
|
int32 zeroOffset = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The calibration factor for the NAU7802
|
||||||
|
*/
|
||||||
|
float calibrationFactor = 2;
|
||||||
|
}
|
||||||
BIN
src/public/images/devices/HELTEC_MESH_NODE_T114.png
Normal file
BIN
src/public/images/devices/HELTEC_MESH_NODE_T114.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
|
|
@ -2245,7 +2245,7 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
min: 0,
|
min: -20,
|
||||||
max: 100,
|
max: 100,
|
||||||
},
|
},
|
||||||
y1: {
|
y1: {
|
||||||
|
|
@ -2718,6 +2718,11 @@
|
||||||
attribution: 'Tiles © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> | Data from <a target="_blank" href="https://meshtastic.org/docs/software/integrations/mqtt/">Meshtastic</a>',
|
attribution: 'Tiles © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> | Data from <a target="_blank" href="https://meshtastic.org/docs/software/integrations/mqtt/">Meshtastic</a>',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var openTopoMapTileLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 17, // open topo map doesn't have tiles closer than this
|
||||||
|
attribution: 'Tiles © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||||
|
});
|
||||||
|
|
||||||
var esriWorldImageryTileLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
var esriWorldImageryTileLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
||||||
maxZoom: 21, // esri doesn't have tiles closer than this
|
maxZoom: 21, // esri doesn't have tiles closer than this
|
||||||
attribution: 'Tiles © <a href="https://developers.arcgis.com/documentation/mapping-apis-and-services/deployment/basemap-attribution/">Esri</a> | Data from <a target="_blank" href="https://meshtastic.org/docs/software/integrations/mqtt/">Meshtastic</a>'
|
attribution: 'Tiles © <a href="https://developers.arcgis.com/documentation/mapping-apis-and-services/deployment/basemap-attribution/">Esri</a> | Data from <a target="_blank" href="https://meshtastic.org/docs/software/integrations/mqtt/">Meshtastic</a>'
|
||||||
|
|
@ -2737,6 +2742,7 @@
|
||||||
|
|
||||||
var tileLayers = {
|
var tileLayers = {
|
||||||
"OpenStreetMap": openStreetMapTileLayer,
|
"OpenStreetMap": openStreetMapTileLayer,
|
||||||
|
"OpenTopoMap": openTopoMapTileLayer,
|
||||||
"Esri Satellite": esriWorldImageryTileLayer,
|
"Esri Satellite": esriWorldImageryTileLayer,
|
||||||
"Google Satellite": googleSatelliteTileLayer,
|
"Google Satellite": googleSatelliteTileLayer,
|
||||||
"Google Hybrid": googleHybridTileLayer,
|
"Google Hybrid": googleHybridTileLayer,
|
||||||
|
|
@ -3082,6 +3088,37 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTerrainProfileImage(node1, node2) {
|
||||||
|
|
||||||
|
// line colour between nodes
|
||||||
|
const lineColour = "0000FF"; // blue
|
||||||
|
|
||||||
|
// node 1 (left side of image)
|
||||||
|
const node1MarkerColour = "0000FF"; // blue
|
||||||
|
const node1Latitude = node1.latitude;
|
||||||
|
const node1Longitude = node1.longitude;
|
||||||
|
const node1ElevationMSL = ""; // node1.altitude ?? "";
|
||||||
|
|
||||||
|
// node 2 (right side of image)
|
||||||
|
const node2MarkerColour = "0000FF"; // blue
|
||||||
|
const node2Latitude = node2.latitude;
|
||||||
|
const node2Longitude = node2.longitude;
|
||||||
|
const node2ElevationMSL = ""; // node2.altitude ?? "";
|
||||||
|
|
||||||
|
// generate terrain profile image url
|
||||||
|
return "https://heywhatsthat.com/bin/profile-0904.cgi?" + new URLSearchParams({
|
||||||
|
src: "meshtastic.liamcottle.net",
|
||||||
|
axes: 1, // include grid lines and a scale
|
||||||
|
metric: 1, // show metric units
|
||||||
|
curvature: 0, // don't include the curvature of the earth in the graphic
|
||||||
|
width: 500,
|
||||||
|
height: 200,
|
||||||
|
pt0: `${node1Latitude},${node1Longitude},${lineColour},${node1ElevationMSL},${node1MarkerColour}`,
|
||||||
|
pt1: `${node2Latitude},${node2Longitude},${lineColour},${node2ElevationMSL},${node2MarkerColour}`,
|
||||||
|
}).toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function showNodeNeighboursThatWeHeard(id) {
|
function showNodeNeighboursThatWeHeard(id) {
|
||||||
|
|
||||||
cleanUpNodeNeighbours();
|
cleanUpNodeNeighbours();
|
||||||
|
|
@ -3161,12 +3198,16 @@
|
||||||
distance = `${distanceInKilometers} kilometers`;
|
distance = `${distanceInKilometers} kilometers`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const terrainImageUrl = getTerrainProfileImage(node, neighbourNode);
|
||||||
|
|
||||||
const tooltip = `<b>[${escapeString(node.short_name)}] ${escapeString(node.long_name)}</b> heard <b>[${escapeString(neighbourNode.short_name)}] ${escapeString(neighbourNode.long_name)}</b>`
|
const tooltip = `<b>[${escapeString(node.short_name)}] ${escapeString(node.long_name)}</b> heard <b>[${escapeString(neighbourNode.short_name)}] ${escapeString(neighbourNode.long_name)}</b>`
|
||||||
+ `<br/>SNR: ${neighbour.snr}dB`
|
+ `<br/>SNR: ${neighbour.snr}dB`
|
||||||
+ `<br/>Distance: ${distance}`
|
+ `<br/>Distance: ${distance}`
|
||||||
+ `<br/><br/>ID: ${node.node_id} heard ${neighbourNode.node_id}`
|
+ `<br/><br/>ID: ${node.node_id} heard ${neighbourNode.node_id}`
|
||||||
+ `<br/>Hex ID: ${node.node_id_hex} heard ${neighbourNode.node_id_hex}`
|
+ `<br/>Hex ID: ${node.node_id_hex} heard ${neighbourNode.node_id_hex}`
|
||||||
+ (node.neighbours_updated_at ? `<br/>Updated: ${moment(new Date(node.neighbours_updated_at)).fromNow()}` : '');
|
+ (node.neighbours_updated_at ? `<br/>Updated: ${moment(new Date(node.neighbours_updated_at)).fromNow()}` : '')
|
||||||
|
+ `<br/><br/>Terrain images from <a href="http://www.heywhatsthat.com" target="_blank">HeyWhatsThat.com</a>`
|
||||||
|
+ `<br/><a href="${terrainImageUrl}" target="_blank"><img src="${terrainImageUrl}" width="100%"></a>`;
|
||||||
|
|
||||||
line.bindTooltip(tooltip, {
|
line.bindTooltip(tooltip, {
|
||||||
sticky: true,
|
sticky: true,
|
||||||
|
|
@ -3278,12 +3319,16 @@
|
||||||
distance = `${distanceInKilometers} kilometers`;
|
distance = `${distanceInKilometers} kilometers`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const terrainImageUrl = getTerrainProfileImage(neighbourNode, node);
|
||||||
|
|
||||||
const tooltip = `<b>[${escapeString(neighbourNode.short_name)}] ${escapeString(neighbourNode.long_name)}</b> heard <b>[${escapeString(node.short_name)}] ${escapeString(node.long_name)}</b>`
|
const tooltip = `<b>[${escapeString(neighbourNode.short_name)}] ${escapeString(neighbourNode.long_name)}</b> heard <b>[${escapeString(node.short_name)}] ${escapeString(node.long_name)}</b>`
|
||||||
+ `<br/>SNR: ${neighbour.snr}dB`
|
+ `<br/>SNR: ${neighbour.snr}dB`
|
||||||
+ `<br/>Distance: ${distance}`
|
+ `<br/>Distance: ${distance}`
|
||||||
+ `<br/><br/>ID: ${neighbourNode.node_id} heard ${node.node_id}`
|
+ `<br/><br/>ID: ${neighbourNode.node_id} heard ${node.node_id}`
|
||||||
+ `<br/>Hex ID: ${neighbourNode.node_id_hex} heard ${node.node_id_hex}`
|
+ `<br/>Hex ID: ${neighbourNode.node_id_hex} heard ${node.node_id_hex}`
|
||||||
+ (neighbourNode.neighbours_updated_at ? `<br/>Updated: ${moment(new Date(neighbourNode.neighbours_updated_at)).fromNow()}` : '');
|
+ (neighbourNode.neighbours_updated_at ? `<br/>Updated: ${moment(new Date(neighbourNode.neighbours_updated_at)).fromNow()}` : '')
|
||||||
|
+ `<br/><br/>Terrain images from <a href="http://www.heywhatsthat.com" target="_blank">HeyWhatsThat.com</a>`
|
||||||
|
+ `<br/><a href="${terrainImageUrl}" target="_blank"><img src="${terrainImageUrl}" width="100%"></a>`;
|
||||||
|
|
||||||
line.bindTooltip(tooltip, {
|
line.bindTooltip(tooltip, {
|
||||||
sticky: true,
|
sticky: true,
|
||||||
|
|
@ -3422,6 +3467,7 @@
|
||||||
// add markers for routers and repeaters to routers layer group
|
// add markers for routers and repeaters to routers layer group
|
||||||
if(node.role_name === "ROUTER"
|
if(node.role_name === "ROUTER"
|
||||||
|| node.role_name === "ROUTER_CLIENT"
|
|| node.role_name === "ROUTER_CLIENT"
|
||||||
|
|| node.role_name === "ROUTER_LATE"
|
||||||
|| node.role_name === "REPEATER"){
|
|| node.role_name === "REPEATER"){
|
||||||
nodesRouterLayerGroup.addLayer(marker);
|
nodesRouterLayerGroup.addLayer(marker);
|
||||||
}
|
}
|
||||||
|
|
@ -3518,13 +3564,17 @@
|
||||||
distance = `${distanceInKilometers} kilometers`;
|
distance = `${distanceInKilometers} kilometers`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const terrainImageUrl = getTerrainProfileImage(node, neighbourNode);
|
||||||
|
|
||||||
const tooltip = `<b>[${escapeString(node.short_name)}] ${escapeString(node.long_name)}</b> heard <b>[${escapeString(neighbourNode.short_name)}] ${escapeString(neighbourNode.long_name)}</b>`
|
const tooltip = `<b>[${escapeString(node.short_name)}] ${escapeString(node.long_name)}</b> heard <b>[${escapeString(neighbourNode.short_name)}] ${escapeString(neighbourNode.long_name)}</b>`
|
||||||
+ `<br/>SNR: ${neighbour.snr}dB`
|
+ `<br/>SNR: ${neighbour.snr}dB`
|
||||||
+ `<br/>Distance: ${distance}`
|
+ `<br/>Distance: ${distance}`
|
||||||
+ `<br/><br/>ID: ${node.node_id} heard ${neighbourNode.node_id}`
|
+ `<br/><br/>ID: ${node.node_id} heard ${neighbourNode.node_id}`
|
||||||
+ `<br/>Hex ID: ${node.node_id_hex} heard ${neighbourNode.node_id_hex}`
|
+ `<br/>Hex ID: ${node.node_id_hex} heard ${neighbourNode.node_id_hex}`
|
||||||
+ (node.neighbours_updated_at ? `<br/>Updated: ${moment(new Date(node.neighbours_updated_at)).fromNow()}` : '')
|
+ (node.neighbours_updated_at ? `<br/>Updated: ${moment(new Date(node.neighbours_updated_at)).fromNow()}` : '')
|
||||||
|
+ `<br/><br/>Terrain images from <a href="http://www.heywhatsthat.com" target="_blank">HeyWhatsThat.com</a>`
|
||||||
|
+ `<br/><a href="${terrainImageUrl}" target="_blank"><img src="${terrainImageUrl}" width="100%"></a>`;
|
||||||
|
|
||||||
line.bindTooltip(tooltip, {
|
line.bindTooltip(tooltip, {
|
||||||
sticky: true,
|
sticky: true,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
|
|
|
||||||
23
src/utils/node_id_util.js
Normal file
23
src/utils/node_id_util.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
class NodeIdUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the provided hex id to a numeric id, for example: !FFFFFFFF to 4294967295
|
||||||
|
* Anything else will be converted as is to a BigInt, for example "4294967295" to 4294967295
|
||||||
|
* @param hexIdOrNumber a node id in hex format with a prepended "!", or a numeric node id as a string or number
|
||||||
|
* @returns {bigint} the node id in numeric form
|
||||||
|
*/
|
||||||
|
static convertToNumeric(hexIdOrNumber) {
|
||||||
|
|
||||||
|
// check if this is a hex id, and convert to numeric
|
||||||
|
if(hexIdOrNumber.toString().startsWith("!")){
|
||||||
|
return BigInt('0x' + hexIdOrNumber.replaceAll("!", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert string or number to numeric
|
||||||
|
return BigInt(hexIdOrNumber);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = NodeIdUtil;
|
||||||
9
src/utils/node_id_util.test.js
Normal file
9
src/utils/node_id_util.test.js
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
const NodeIdUtil = require("./node_id_util");
|
||||||
|
|
||||||
|
test('can convert hex id to numeric id', () => {
|
||||||
|
expect(NodeIdUtil.convertToNumeric("!FFFFFFFF")).toBe(BigInt(4294967295));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can convert numeric id to numeric id', () => {
|
||||||
|
expect(NodeIdUtil.convertToNumeric(4294967295)).toBe(BigInt(4294967295));
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue