Compare commits
45 Commits
42dea25fd1
...
improvemen
Author | SHA1 | Date | |
---|---|---|---|
2a55bd056f | |||
702da27468 | |||
8e99669487 | |||
63b823a7a1 | |||
600ed7bbb3 | |||
82adde997e | |||
5ec119b6c4 | |||
629825b53d | |||
18df989fc6 | |||
e694ffa66a | |||
da99bfdeef | |||
b2a9488efd | |||
6b7149905a | |||
8497053aed | |||
bfdd6cba22 | |||
6554d270bc | |||
bcaa1f2c20 | |||
0a7e456173 | |||
d50fe75759 | |||
d963520486 | |||
e025140ab4 | |||
e8095fce81 | |||
af5690e524 | |||
33726de0cb | |||
7b3e6e4fd1 | |||
0456f8a7b3 | |||
913406d1b9 | |||
1f1c0e9881 | |||
fb055785a5 | |||
5035a35554 | |||
b2f5da9c88 | |||
9893ed0bcc | |||
77a3a5e288 | |||
e44c578195 | |||
911983d250 | |||
c0a59bd30a | |||
4173a97e51 | |||
8eaf49fd54 | |||
7f583e73b0 | |||
f20031e2ea | |||
2da0ee24e4 | |||
4c39cee484 | |||
196063c23b | |||
f97b3b5185 | |||
95cf93d747 |
@ -1,2 +1,8 @@
|
|||||||
.env
|
.env
|
||||||
node_modules
|
node_modules
|
||||||
|
*/prisma
|
||||||
|
!common/prisma
|
||||||
|
*/node_modules
|
||||||
|
*/Dockerfile
|
||||||
|
*/.dockerignore
|
||||||
|
webapp/frontend/node_modules
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
name: Build Docker containers
|
name: Build Docker containers
|
||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Build:
|
Build:
|
||||||
@ -12,7 +15,14 @@ jobs:
|
|||||||
run: docker login git.arinity.org -u matt -p ${{ secrets.DOCKER_PUSH_TOKEN }}
|
run: docker login git.arinity.org -u matt -p ${{ secrets.DOCKER_PUSH_TOKEN }}
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Build docker image
|
- name: Build web app image
|
||||||
run: docker build -t git.arinity.org/ctmesh/map:latest .
|
run: docker build --no-cache -f webapp/Dockerfile -t git.arinity.org/ctmesh/map:latest .
|
||||||
- name: Push image
|
- name: Build mqtt listener image
|
||||||
run: docker push git.arinity.org/ctmesh/map:latest
|
run: docker build --no-cache -f mqtt/Dockerfile -t git.arinity.org/ctmesh/map-mqtt:latest .
|
||||||
|
- name: Build cli image
|
||||||
|
run: docker build --no-cache -f cli/Dockerfile -t git.arinity.org/ctmesh/map-cli:latest .
|
||||||
|
- name: Push images
|
||||||
|
run: |
|
||||||
|
docker push git.arinity.org/ctmesh/map:latest
|
||||||
|
docker push git.arinity.org/ctmesh/map-mqtt:latest
|
||||||
|
docker push git.arinity.org/ctmesh/map-cli:latest
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,6 @@
|
|||||||
.idea/
|
.idea/
|
||||||
node_modules
|
node_modules
|
||||||
|
*/prisma
|
||||||
|
*/dist
|
||||||
# Keep environment variables out of version control
|
# Keep environment variables out of version control
|
||||||
.env
|
.env
|
||||||
|
10
Dockerfile
10
Dockerfile
@ -1,10 +0,0 @@
|
|||||||
FROM node:lts
|
|
||||||
|
|
||||||
# add project files to /app
|
|
||||||
ADD ./ /app
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# install node dependencies
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
12
cli/Dockerfile
Normal file
12
cli/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM node:lts-alpine3.17
|
||||||
|
|
||||||
|
# add project files to /app
|
||||||
|
ADD ./cli /app
|
||||||
|
ADD ./common /app
|
||||||
|
ADD ./mqtt/utils /app/utils
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# install node dependencies
|
||||||
|
RUN npm install && npx prisma generate
|
||||||
|
|
||||||
|
ENTRYPOINT ["node", "index.js"]
|
18
cli/package.json
Normal file
18
cli/package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "mqtt",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/client": "^5.11.0",
|
||||||
|
"command-line-args": "^5.2.1",
|
||||||
|
"command-line-usage": "^7.0.1",
|
||||||
|
"mqtt": "^5.11.0",
|
||||||
|
"protobufjs": "^7.5.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prisma": "^5.10.2"
|
||||||
|
}
|
||||||
|
}
|
8
common/entrypoint.sh
Executable file
8
common/entrypoint.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Running migrations"
|
||||||
|
npx prisma migrate deploy
|
||||||
|
|
||||||
|
echo "Starting application"
|
||||||
|
node index.js "$@"
|
@ -2,14 +2,13 @@ services:
|
|||||||
# listens to mqtt packets and saves to database
|
# listens to mqtt packets and saves to database
|
||||||
meshtastic-mqtt:
|
meshtastic-mqtt:
|
||||||
container_name: meshtastic-mqtt
|
container_name: meshtastic-mqtt
|
||||||
image: git.arinity.org/ctmesh/map:latest
|
image: git.arinity.org/ctmesh/map-mqtt:latest
|
||||||
depends_on:
|
depends_on:
|
||||||
database:
|
database:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
command: /app/docker/mqtt.sh
|
command: "--mqtt-broker-url= --mqtt-topic=msh/US/# --collect-service-envelopes --collect-neighbor-info --collect-waypoints"
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: "mysql://root:password@database:3306/meshtastic-map?connection_limit=100"
|
DATABASE_URL: "mysql://root:password@database:3306/meshtastic-map?connection_limit=100"
|
||||||
MQTT_OPTS: "--mqtt-broker-url=" # add any custom mqtt.js options here
|
|
||||||
|
|
||||||
# runs the web map ui
|
# runs the web map ui
|
||||||
meshtastic-map:
|
meshtastic-map:
|
||||||
@ -18,12 +17,11 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
database:
|
database:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
command: /app/docker/map.sh
|
command: "--port=8080"
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080/tcp
|
- 8080:8080/tcp
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: "mysql://root:password@database:3306/meshtastic-map?connection_limit=100"
|
DATABASE_URL: "mysql://root:password@database:3306/meshtastic-map?connection_limit=100"
|
||||||
MAP_OPTS: "" # add any custom index.js options here
|
|
||||||
|
|
||||||
# runs the database to store everything from mqtt
|
# runs the database to store everything from mqtt
|
||||||
database:
|
database:
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
echo "Running migrations"
|
|
||||||
npx prisma migrate dev
|
|
||||||
|
|
||||||
echo "Starting map ui"
|
|
||||||
exec node src/index.js ${MAP_OPTS}
|
|
@ -1,7 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
echo "Running migrations"
|
|
||||||
npx prisma migrate dev
|
|
||||||
|
|
||||||
echo "Starting mqtt listener"
|
|
||||||
exec node src/mqtt.js ${MQTT_OPTS}
|
|
10
donate.md
10
donate.md
@ -1,10 +0,0 @@
|
|||||||
# Donate
|
|
||||||
|
|
||||||
Thank you for considering donating, this helps support my work on this project 😁
|
|
||||||
|
|
||||||
## How can I donate?
|
|
||||||
|
|
||||||
- Bitcoin: bc1qy22smke8n4c54evdxmp7lpy9p0e6m9tavtlg2q
|
|
||||||
- Ethereum: 0xc64CFbA5D0BF7664158c5671F64d446395b3bF3D
|
|
||||||
- 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)
|
|
3701
frontend/package-lock.json
generated
3701
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,141 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
const emit = defineEmits(['reload', 'randomNode', 'searchClick']);
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { state, searchedNodes } from '../store.js';
|
|
||||||
import { getNodeColor, getNodeTextColor } from '../utils.js';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex bg-white p-2 border-gray-300 border-b h-16">
|
|
||||||
|
|
||||||
<!-- close mobile search button -->
|
|
||||||
<div v-if="state.mobileSearchVisible" class="my-auto">
|
|
||||||
<a @click="state.mobileSearchVisible = false" href="javascript:void(0)" class="rounded-full">
|
|
||||||
<div class="bg-gray-100 hover:bg-gray-200 p-2 rounded-full">
|
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- icon -->
|
|
||||||
<div v-if="!state.mobileSearchVisible" class="hidden sm:block my-auto mr-3">
|
|
||||||
<img class="w-10 h-10 rounded" src="/images/icon.png"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- app info -->
|
|
||||||
<div v-if="!state.mobileSearchVisible" class="my-auto leading-tight">
|
|
||||||
<div class="font-bold">CT Mesh Map</div>
|
|
||||||
<div class="text-sm">
|
|
||||||
Created by <a class="link" target="_blank" href="https://liamcottle.com">Liam Cottle</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- search bar -->
|
|
||||||
<div class="mx-3 flex-1 relative" :class="{ 'hidden lg:block': !state.mobileSearchVisible }">
|
|
||||||
<input v-model="state.searchText" type="text" 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" :placeholder="`Search ${state.nodes.length} nodes...`">
|
|
||||||
<div v-if="state.searchText !== ''" class="absolute z-search bg-white w-full border border-gray-200 rounded-lg shadow-md mt-1 overflow-y-scroll max-h-80 divide-y divide-gray-200">
|
|
||||||
|
|
||||||
<template v-if="searchedNodes.length > 0">
|
|
||||||
<div @click="$emit('searchClick', node)" class="flex space-x-2 p-2 hover:bg-gray-100 cursor-pointer" v-for="node of searchedNodes">
|
|
||||||
<div>
|
|
||||||
<div class="flex rounded-full h-12 w-12 text-white shadow" :style="{backgroundColor: getNodeColor(node.node_id)}" :class="[ `text-[${getNodeTextColor(node.node_id)}]` ]">
|
|
||||||
<div class="mx-auto my-auto drop-shadow-sm">{{ node.short_name }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="text-gray-900" :class="{ 'text-red-500': node.latitude == null || node.longitude == null }">{{ node.long_name !== '' ? node.long_name : "-" }}</div>
|
|
||||||
<div class="flex space-x-1 text-sm text-gray-700">
|
|
||||||
<div>{{ node.node_id_hex }} / {{ node.node_id }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="searchedNodes.length === 500" class="text-gray-500 text-sm px-2 py-1">
|
|
||||||
Only the first 500 results are shown.
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<div class="p-2">
|
|
||||||
No results found...
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- header action buttons -->
|
|
||||||
<div v-if="!state.mobileSearchVisible" class="flex my-auto ml-auto mr-0 sm:mr-2 space-x-1 sm:space-x-2">
|
|
||||||
<a @click="state.infoModalVisible = !state.infoModalVisible" href="javascript:void(0)" class="tooltip rounded-full">
|
|
||||||
<div class="bg-gray-100 hover:bg-gray-200 p-2 rounded-full">
|
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="hidden sm:block">
|
|
||||||
<span class="tooltip-text">About</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a @click="state.mobileSearchVisible = true" href="javascript:void(0)" class="tooltip rounded-full block lg:hidden">
|
|
||||||
<div id="search-button" class="bg-gray-100 hover:bg-gray-200 p-2 rounded-full">
|
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
||||||
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"></path>
|
|
||||||
<path d="M21 21l-6 -6"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="hidden sm:block">
|
|
||||||
<span class="tooltip-text">Search</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a @click="state.hardwareStatsVisible = !state.hardwareStatsVisible" href="javascript:void(0)" class="tooltip rounded-full hidden sm:block">
|
|
||||||
<div class="bg-gray-100 hover:bg-gray-200 p-2 rounded-full">
|
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 1.5H8.25A2.25 2.25 0 0 0 6 3.75v16.5a2.25 2.25 0 0 0 2.25 2.25h7.5A2.25 2.25 0 0 0 18 20.25V3.75a2.25 2.25 0 0 0-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="hidden sm:block">
|
|
||||||
<span class="tooltip-text">Devices</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a href="#" class="tooltip rounded-full hidden lg:block" @click="$emit('randomNode')">
|
|
||||||
<div id="random-button" class="bg-gray-100 hover:bg-gray-200 p-2 rounded-full">
|
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
||||||
<path d="M18 4l3 3l-3 3"></path>
|
|
||||||
<path d="M18 20l3 -3l-3 -3"></path>
|
|
||||||
<path d="M3 7h3a5 5 0 0 1 5 5a5 5 0 0 0 5 5h5"></path>
|
|
||||||
<path d="M21 7h-5a4.978 4.978 0 0 0 -3 1m-4 8a4.984 4.984 0 0 1 -3 1h-3"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="hidden sm:block">
|
|
||||||
<span class="tooltip-text">Random</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a @click="state.settingsVisible = !state.settingsVisible" href="javascript:void(0)" class="tooltip rounded-full">
|
|
||||||
<div class="bg-gray-100 hover:bg-gray-200 p-2 rounded-full">
|
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="hidden sm:block">
|
|
||||||
<span class="tooltip-text">Settings</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a href="#" class="tooltip rounded-full" @click="$emit('reload')">
|
|
||||||
<div id="reload-button" class="bg-gray-100 hover:bg-gray-200 p-2 rounded-full" :class="{'animate-spin': state.loading}">
|
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
||||||
<path d="M19.933 13.041a8 8 0 1 1 -9.925 -8.788c3.899 -1 7.935 1.007 9.425 4.747"></path>
|
|
||||||
<path d="M20 4v5h-5"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="hidden sm:block">
|
|
||||||
<span class="tooltip-text">Reload</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,207 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
const props = defineProps(['node']);
|
|
||||||
import axios from 'axios';
|
|
||||||
import moment from 'moment';
|
|
||||||
import { Chart, TimeScale, LinearScale, LineController, Tooltip, Legend, PointElement, LineElement } from 'chart.js';
|
|
||||||
import 'chartjs-adapter-moment';
|
|
||||||
import { onMounted, useTemplateRef, watch } from 'vue';
|
|
||||||
import { state } from '../../store.js';
|
|
||||||
import { deviceMetricsTimeRange } from '../../config.js';
|
|
||||||
import { formatUptimeSeconds, getTimeSpans } from '../../utils.js';
|
|
||||||
import { useStorage } from '@vueuse/core';
|
|
||||||
const deviceMetricsChartEl = useTemplateRef('device-metrics-chart');
|
|
||||||
|
|
||||||
function initChart() {
|
|
||||||
// destroy existing chart
|
|
||||||
const existingChart = Chart.getChart(deviceMetricsChartEl.value);
|
|
||||||
if (existingChart != null) {
|
|
||||||
existingChart.destroy();
|
|
||||||
}
|
|
||||||
// create chart data
|
|
||||||
const labels = [];
|
|
||||||
const batteryMetrics = [];
|
|
||||||
const channelUtilizationMetrics = [];
|
|
||||||
const airUtilTxMetrics = [];
|
|
||||||
for(const deviceMetric of state.selectedNodeDeviceMetrics) {
|
|
||||||
labels.push(moment(deviceMetric.created_at));
|
|
||||||
batteryMetrics.push(deviceMetric.battery_level);
|
|
||||||
channelUtilizationMetrics.push(deviceMetric.channel_utilization);
|
|
||||||
airUtilTxMetrics.push(deviceMetric.air_util_tx);
|
|
||||||
}
|
|
||||||
// create chart
|
|
||||||
new Chart(deviceMetricsChartEl.value, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: labels,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Battery Level',
|
|
||||||
borderColor: '#3b82f6',
|
|
||||||
backgroundColor: '#3b82f6',
|
|
||||||
pointStyle: false, // no points
|
|
||||||
fill: false,
|
|
||||||
data: batteryMetrics,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Channel Util',
|
|
||||||
borderColor: '#22c55e',
|
|
||||||
backgroundColor: '#22c55e',
|
|
||||||
showLine: false, // no lines between points
|
|
||||||
fill: false,
|
|
||||||
data: channelUtilizationMetrics,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Air Util TX',
|
|
||||||
borderColor: '#f97316',
|
|
||||||
backgroundColor: '#f97316',
|
|
||||||
showLine: false, // no lines between points
|
|
||||||
fill: false,
|
|
||||||
data: airUtilTxMetrics,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
borderWidth: 2,
|
|
||||||
elements: {
|
|
||||||
point: {
|
|
||||||
radius: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
position: 'top',
|
|
||||||
type: 'time',
|
|
||||||
time: {
|
|
||||||
unit: 'day',
|
|
||||||
displayFormats: {
|
|
||||||
day: 'MMM DD', // Jan 01
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
min: 0,
|
|
||||||
max: 101, // 101 is "Plugged In", need to include for tooltip to work
|
|
||||||
ticks: {
|
|
||||||
callback: (label) => `${label}%`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
mode: "index",
|
|
||||||
intersect: false,
|
|
||||||
callbacks: {
|
|
||||||
label: (item) => {
|
|
||||||
return `${item.dataset.label}: ${item.formattedValue}%`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
watch(
|
|
||||||
() => state.selectedNodeDeviceMetrics,
|
|
||||||
(newValue) => {
|
|
||||||
if (newValue !== []) {
|
|
||||||
initChart()
|
|
||||||
}
|
|
||||||
}, {deep: true}
|
|
||||||
)
|
|
||||||
onMounted(() => {
|
|
||||||
Chart.register(TimeScale, LinearScale, LineController, Tooltip, Legend, PointElement, LineElement)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<!-- device metrics -->
|
|
||||||
<div>
|
|
||||||
<div class="flex bg-gray-200 p-2 font-semibold">
|
|
||||||
<div class="my-auto">Device Metrics</div>
|
|
||||||
<div class="my-auto ml-auto">
|
|
||||||
<select v-model="deviceMetricsTimeRange" class="block w-full rounded-md border-0 py-0.5 pl-2 pr-8 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-blue-500 sm:text-sm sm:leading-6">
|
|
||||||
<option v-for="(range, index) in getTimeSpans()" :value="index">{{ range.name }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul role="list" class="flex-1 divide-y divide-gray-200">
|
|
||||||
|
|
||||||
<!-- device metrics chart -->
|
|
||||||
<li>
|
|
||||||
<div class="px-4 py-2">
|
|
||||||
<div class="w-full">
|
|
||||||
<canvas id="deviceMetricsChart" style="height:150px;" ref="device-metrics-chart"></canvas>
|
|
||||||
<div class="flex">
|
|
||||||
<div class="mx-auto flex space-x-2">
|
|
||||||
<div class="flex mx-auto">
|
|
||||||
<div class="my-auto w-2 h-2 bg-blue-500 rounded-full"></div>
|
|
||||||
<div class="my-auto ml-1 text-sm text-gray-500">Battery Level</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex mx-auto">
|
|
||||||
<div class="my-auto w-2 h-2 bg-green-500 rounded-full"></div>
|
|
||||||
<div class="my-auto ml-1 text-sm text-gray-500">Channel Utilization</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex mx-auto">
|
|
||||||
<div class="my-auto w-2 h-2 bg-orange-500 rounded-full"></div>
|
|
||||||
<div class="my-auto ml-1 text-sm text-gray-500">Air Util TX</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- battery level -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Battery Level</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.battery_level">
|
|
||||||
<span v-if="props.node.battery_level > 100">Plugged In</span>
|
|
||||||
<span v-else>{{ props.node.battery_level }}%</span>
|
|
||||||
</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- voltage -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Voltage</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.voltage">{{ Number(props.node.voltage).toFixed(2) }}V</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- channel utilization -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Channel Utilization</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.channel_utilization">{{ Number(props.node.channel_utilization).toFixed(2) }}%</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- air util tx -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Air Util Tx</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.air_util_tx">{{ Number(props.node.air_util_tx).toFixed(2) }}%</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- air util tx -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Uptime</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.uptime_seconds">{{ formatUptimeSeconds(props.node.uptime_seconds) }}</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,202 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
const props = defineProps(['node']);
|
|
||||||
import axios from 'axios';
|
|
||||||
import moment from 'moment';
|
|
||||||
import { Chart, TimeScale, LinearScale, LineController, Tooltip, Legend, PointElement, LineElement } from 'chart.js';
|
|
||||||
import 'chartjs-adapter-moment';
|
|
||||||
import { onMounted, useTemplateRef, watch } from 'vue';
|
|
||||||
import { state } from '../../store.js';
|
|
||||||
import { environmentMetricsTimeRange } from '../../config.js';
|
|
||||||
import { formatTemperature, getTimeSpans } from '../../utils.js';
|
|
||||||
const environmentMetricsChartEl = useTemplateRef('environment-metrics-chart');
|
|
||||||
|
|
||||||
function initChart() {
|
|
||||||
// destroy existing chart
|
|
||||||
const existingChart = Chart.getChart(environmentMetricsChartEl.value);
|
|
||||||
if (existingChart != null) {
|
|
||||||
existingChart.destroy();
|
|
||||||
}
|
|
||||||
// create chart data
|
|
||||||
const labels = [];
|
|
||||||
const temperatureMetrics = [];
|
|
||||||
const relativeHumidityMetrics = [];
|
|
||||||
const barometricPressureMetrics = [];
|
|
||||||
for(const deviceMetric of state.selectedNodeEnvironmentMetrics){
|
|
||||||
labels.push(moment(deviceMetric.created_at));
|
|
||||||
temperatureMetrics.push(deviceMetric.temperature);
|
|
||||||
relativeHumidityMetrics.push(deviceMetric.relative_humidity);
|
|
||||||
barometricPressureMetrics.push(deviceMetric.barometric_pressure);
|
|
||||||
}
|
|
||||||
// create chart
|
|
||||||
new Chart(environmentMetricsChartEl.value, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: labels,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Temperature',
|
|
||||||
suffix: 'ºC',
|
|
||||||
borderColor: '#3b82f6',
|
|
||||||
backgroundColor: '#3b82f6',
|
|
||||||
pointStyle: false, // no points
|
|
||||||
fill: false,
|
|
||||||
data: temperatureMetrics,
|
|
||||||
yAxisID: 'y',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Humidity',
|
|
||||||
suffix: '%',
|
|
||||||
borderColor: '#22c55e',
|
|
||||||
backgroundColor: '#22c55e',
|
|
||||||
pointStyle: false, // no points
|
|
||||||
fill: false,
|
|
||||||
data: relativeHumidityMetrics,
|
|
||||||
yAxisID: 'y',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Pressure',
|
|
||||||
suffix: 'hPa',
|
|
||||||
borderColor: '#f97316',
|
|
||||||
backgroundColor: '#f97316',
|
|
||||||
pointStyle: false, // no points
|
|
||||||
fill: false,
|
|
||||||
data: barometricPressureMetrics,
|
|
||||||
yAxisID: 'y1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
borderWidth: 2,
|
|
||||||
spanGaps: 1000 * 60 * 60 * 3, // only show lines between metrics with a 3 hour or less gap
|
|
||||||
elements: {
|
|
||||||
point: {
|
|
||||||
radius: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
position: 'top',
|
|
||||||
type: 'time',
|
|
||||||
time: {
|
|
||||||
unit: 'day',
|
|
||||||
displayFormats: {
|
|
||||||
day: 'MMM DD', // Jan 01
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
min: -20,
|
|
||||||
max: 100,
|
|
||||||
},
|
|
||||||
y1: {
|
|
||||||
min: 800,
|
|
||||||
max: 1100,
|
|
||||||
ticks: {
|
|
||||||
stepSize: 10,
|
|
||||||
callback: (label) => `${label} hPa`,
|
|
||||||
},
|
|
||||||
position: 'right',
|
|
||||||
grid: {
|
|
||||||
drawOnChartArea: false, // only want the grid lines for one axis to show up
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
mode: "index",
|
|
||||||
intersect: false,
|
|
||||||
callbacks: {
|
|
||||||
label: (item) => {
|
|
||||||
return `${item.dataset.label}: ${item.formattedValue}${item.dataset.suffix}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => state.selectedNodeEnvironmentMetrics,
|
|
||||||
(newValue) => {
|
|
||||||
if (newValue !== []) {
|
|
||||||
initChart()
|
|
||||||
}
|
|
||||||
}, {deep: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
Chart.register(TimeScale, LinearScale, LineController, Tooltip, Legend, PointElement, LineElement)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="flex bg-gray-200 p-2 font-semibold">
|
|
||||||
<div class="my-auto">Environment Metrics</div>
|
|
||||||
<div class="my-auto ml-auto">
|
|
||||||
<select v-model="environmentMetricsTimeRange" class="block w-full rounded-md border-0 py-0.5 pl-2 pr-8 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-blue-500 sm:text-sm sm:leading-6">
|
|
||||||
<option v-for="(range, index) in getTimeSpans()" :value="index">{{ range.name }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul role="list" class="flex-1 divide-y divide-gray-200">
|
|
||||||
|
|
||||||
<!-- environment metrics chart -->
|
|
||||||
<li>
|
|
||||||
<div class="px-4 py-2">
|
|
||||||
<div class="w-full">
|
|
||||||
<canvas id="environmentMetricsChart" style="height:150px;" ref="environment-metrics-chart"></canvas>
|
|
||||||
<div class="flex">
|
|
||||||
<div class="mx-auto flex space-x-2">
|
|
||||||
<div class="flex mx-auto">
|
|
||||||
<div class="my-auto w-2 h-2 bg-blue-500 rounded-full"></div>
|
|
||||||
<div class="my-auto ml-1 text-sm text-gray-500">Temperature</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex mx-auto">
|
|
||||||
<div class="my-auto w-2 h-2 bg-green-500 rounded-full"></div>
|
|
||||||
<div class="my-auto ml-1 text-sm text-gray-500">Humidity</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex mx-auto">
|
|
||||||
<div class="my-auto w-2 h-2 bg-orange-500 rounded-full"></div>
|
|
||||||
<div class="my-auto ml-1 text-sm text-gray-500">Pressure</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- temperature -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Temperature</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.temperature">{{ formatTemperature(props.node.temperature) }}</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- relative humidity -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Relative Humidity</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.relative_humidity">{{ Number(props.node.relative_humidity).toFixed(0) }}%</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- barometric pressure -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Barometric Pressure</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.barometric_pressure">{{ Number(props.node.barometric_pressure).toFixed(1) }}hPa</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,40 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { getRegionFrequencyRange } from '../../utils.js';
|
|
||||||
const props = defineProps(['node']);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<!-- lora config -->
|
|
||||||
<div>
|
|
||||||
<div class="bg-gray-200 p-2 font-semibold">LoRa Config</div>
|
|
||||||
<ul role="list" class="flex-1 divide-y divide-gray-200">
|
|
||||||
|
|
||||||
<!-- region -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Region</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.region_name">{{ props.node.region_name }} ({{ getRegionFrequencyRange(props.node.region_name) }})</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- modem preset -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Modem Preset</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.modem_preset_name">{{ props.node.modem_preset_name }}</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- has default channel -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Has Default Channel</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.has_default_channel != null">{{ props.node.has_default_channel ? "Yes" : "No" }}</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,41 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { state } from '../../store.js';
|
|
||||||
import moment from 'moment';
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="bg-gray-200 p-2">
|
|
||||||
<div class="font-semibold">MQTT</div>
|
|
||||||
<div class="text-sm text-gray-600">Topics this node sent packets to</div>
|
|
||||||
</div>
|
|
||||||
<ul role="list" class="flex-1 divide-y divide-gray-200">
|
|
||||||
<template v-if="state.selectedNodeMqttMetrics.length > 0">
|
|
||||||
<li v-for="mqttMetric of state.selectedNodeMqttMetrics">
|
|
||||||
<div class="relative flex items-center">
|
|
||||||
<div class="block flex-1 px-4 py-2">
|
|
||||||
<div class="relative flex min-w-0 flex-1 items-center">
|
|
||||||
<div class="truncate">
|
|
||||||
<p class="truncate text-sm font-medium text-gray-900">{{ mqttMetric.mqtt_topic }}</p>
|
|
||||||
<div class="text-sm text-gray-700">Last packet {{ moment(new Date(mqttMetric.last_packet_at)).fromNow() }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<li>
|
|
||||||
<div class="relative flex items-center">
|
|
||||||
<div class="block flex-1 px-4 py-2">
|
|
||||||
<div class="relative flex min-w-0 flex-1 items-center">
|
|
||||||
<div class="truncate">
|
|
||||||
<div class="text-sm text-gray-700">No packets seen on MQTT</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,45 +0,0 @@
|
|||||||
|
|
||||||
<script setup>
|
|
||||||
const props = defineProps(['node']);
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="bg-gray-200 p-2 font-semibold">Details</div>
|
|
||||||
<ul role="list" class="flex-1 divide-y divide-gray-200">
|
|
||||||
|
|
||||||
<!-- id -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">ID</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">{{ props.node.node_id }}</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- hex id -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Hex ID</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">{{ props.node.node_id_hex }}</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- role -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Role</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">{{ props.node.role_name }}</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- hardware -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Hardware</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">{{ props.node.hardware_model_name }}</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- firmware version -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Firmware</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.firmware_version">{{ props.node.firmware_version }}</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,40 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
const props = defineProps(['node']);
|
|
||||||
import moment from 'moment';
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="bg-gray-200 p-2 font-semibold">Other</div>
|
|
||||||
<ul role="list" class="flex-1 divide-y divide-gray-200">
|
|
||||||
<!-- first seen -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">First Seen</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">{{ moment(new Date(props.node.created_at)).fromNow() }}</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- last seen -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Last Seen</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">{{ moment(new Date(props.node.updated_at)).fromNow() }}</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- neighbours updated -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Neighbours Updated</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.neighbours_updated_at">{{ moment(new Date(props.node.neighbours_updated_at)).fromNow() }}</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- position updated -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Position Updated</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.position_updated_at">{{ moment(new Date(props.node.position_updated_at)).fromNow() }}</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,34 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
const props = defineProps(['node']);
|
|
||||||
const emit = defineEmits(['showPositionHistory']);
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div @click.stop class="flex bg-gray-200 p-2 font-semibold">
|
|
||||||
<div class="my-auto">Position</div>
|
|
||||||
<div class="ml-auto">
|
|
||||||
<button @click="$emit('showPositionHistory', props.node.node_id)" type="button" class="rounded-sm bg-white px-2 py-1 text-sm font-semibold text-gray-900 shadow-xs ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
|
||||||
Show History
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul role="list" class="flex-1 divide-y divide-gray-200">
|
|
||||||
<!-- position -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Lat/Long</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.latitude && props.node.longitude">{{ props.node.latitude }}, {{ props.node.longitude }}</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<!-- altitude -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Altitude</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="props.node.altitude">{{ props.node.altitude }}m</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,251 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import axios from 'axios';
|
|
||||||
import moment from 'moment';
|
|
||||||
import { Chart, TimeScale, LinearScale, LineController, Tooltip, Legend, PointElement, LineElement } from 'chart.js';
|
|
||||||
import 'chartjs-adapter-moment';
|
|
||||||
import { onMounted, useTemplateRef, watch } from 'vue';
|
|
||||||
import { state, selectedNodeLatestPowerMetric } from '../../store.js';
|
|
||||||
import { powerMetricsTimeRange } from '../../config.js';
|
|
||||||
import { getTimeSpans } from '../../utils.js';
|
|
||||||
const powerMetricsChartEl = useTemplateRef('power-metrics-chart');
|
|
||||||
|
|
||||||
function initChart() {
|
|
||||||
// destroy existing chart
|
|
||||||
const existingChart = Chart.getChart(powerMetricsChartEl.value);
|
|
||||||
if (existingChart != null) {
|
|
||||||
existingChart.destroy();
|
|
||||||
}
|
|
||||||
// create chart data
|
|
||||||
const labels = [];
|
|
||||||
const channel1VoltageReadings = [];
|
|
||||||
const channel2VoltageReadings = [];
|
|
||||||
const channel3VoltageReadings = [];
|
|
||||||
const channel1CurrentReadings = [];
|
|
||||||
const channel2CurrentReadings = [];
|
|
||||||
const channel3CurrentReadings = [];
|
|
||||||
for(const powerMetric of state.selectedNodePowerMetrics) {
|
|
||||||
labels.push(moment(powerMetric.created_at));
|
|
||||||
channel1VoltageReadings.push(powerMetric.ch1_voltage);
|
|
||||||
channel2VoltageReadings.push(powerMetric.ch2_voltage);
|
|
||||||
channel3VoltageReadings.push(powerMetric.ch3_voltage);
|
|
||||||
channel1CurrentReadings.push(powerMetric.ch1_current);
|
|
||||||
channel2CurrentReadings.push(powerMetric.ch2_current);
|
|
||||||
channel3CurrentReadings.push(powerMetric.ch3_current);
|
|
||||||
}
|
|
||||||
// create chart
|
|
||||||
new Chart(powerMetricsChartEl.value, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: labels,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Ch1 Voltage',
|
|
||||||
suffix: "V",
|
|
||||||
borderColor: '#3b82f6',
|
|
||||||
backgroundColor: '#3b82f6',
|
|
||||||
pointStyle: false, // no points
|
|
||||||
fill: false,
|
|
||||||
data: channel1VoltageReadings,
|
|
||||||
yAxisID: 'y',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Ch2 Voltage',
|
|
||||||
suffix: "V",
|
|
||||||
borderColor: '#22c55e',
|
|
||||||
backgroundColor: '#22c55e',
|
|
||||||
pointStyle: false, // no points
|
|
||||||
fill: false,
|
|
||||||
data: channel2VoltageReadings,
|
|
||||||
yAxisID: 'y',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Ch3 Voltage',
|
|
||||||
suffix: "V",
|
|
||||||
borderColor: '#f97316',
|
|
||||||
backgroundColor: '#f97316',
|
|
||||||
pointStyle: false, // no points
|
|
||||||
fill: false,
|
|
||||||
data: channel3VoltageReadings,
|
|
||||||
yAxisID: 'y',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Ch1 Current',
|
|
||||||
suffix: "mA",
|
|
||||||
borderColor: '#93c5fd',
|
|
||||||
backgroundColor: '#93c5fd',
|
|
||||||
pointStyle: false, // no points
|
|
||||||
fill: false,
|
|
||||||
data: channel1CurrentReadings,
|
|
||||||
yAxisID: 'y1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Ch2 Current',
|
|
||||||
suffix: "mA",
|
|
||||||
borderColor: '#86efac',
|
|
||||||
backgroundColor: '#86efac',
|
|
||||||
pointStyle: false, // no points
|
|
||||||
fill: false,
|
|
||||||
data: channel2CurrentReadings,
|
|
||||||
yAxisID: 'y1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Ch3 Current',
|
|
||||||
suffix: "mA",
|
|
||||||
borderColor: '#fdba74',
|
|
||||||
backgroundColor: '#fdba74',
|
|
||||||
pointStyle: false, // no points
|
|
||||||
fill: false,
|
|
||||||
data: channel3CurrentReadings,
|
|
||||||
yAxisID: 'y1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
borderWidth: 2,
|
|
||||||
spanGaps: 1000 * 60 * 60 * 3, // only show lines between metrics with a 3 hour or less gap
|
|
||||||
elements: {
|
|
||||||
point: {
|
|
||||||
radius: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
position: 'top',
|
|
||||||
type: 'time',
|
|
||||||
time: {
|
|
||||||
unit: 'day',
|
|
||||||
displayFormats: {
|
|
||||||
day: 'MMM DD', // Jan 01
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
min: 0,
|
|
||||||
max: 30,
|
|
||||||
ticks: {
|
|
||||||
callback: (label) => `${label}V`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y1: {
|
|
||||||
min: -500,
|
|
||||||
max: 500,
|
|
||||||
ticks: {
|
|
||||||
stepSize: 50,
|
|
||||||
callback: (label) => `${label}mA`,
|
|
||||||
},
|
|
||||||
position: 'right',
|
|
||||||
grid: {
|
|
||||||
drawOnChartArea: false, // only want the grid lines for one axis to show up
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
mode: "index",
|
|
||||||
intersect: false,
|
|
||||||
callbacks: {
|
|
||||||
label: (item) => {
|
|
||||||
return `${item.dataset.label}: ${item.formattedValue}${item.dataset.suffix}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
watch(
|
|
||||||
() => state.selectedNodePowerMetrics,
|
|
||||||
(newValue) => {
|
|
||||||
if (newValue !== []) {
|
|
||||||
initChart()
|
|
||||||
}
|
|
||||||
}, {deep: true}
|
|
||||||
)
|
|
||||||
onMounted(() => {
|
|
||||||
Chart.register(TimeScale, LinearScale, LineController, Tooltip, Legend, PointElement, LineElement)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<!-- power metrics -->
|
|
||||||
<div>
|
|
||||||
<div class="flex bg-gray-200 p-2 font-semibold">
|
|
||||||
<div class="my-auto">Power Metrics</div>
|
|
||||||
<div class="my-auto ml-auto">
|
|
||||||
<select v-model="powerMetricsTimeRange" class="block w-full rounded-md border-0 py-0.5 pl-2 pr-8 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-blue-500 sm:text-sm sm:leading-6">
|
|
||||||
<option v-for="(range, index) in getTimeSpans()" :value="index">{{ range.name }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul role="list" class="flex-1 divide-y divide-gray-200">
|
|
||||||
|
|
||||||
<!-- power metrics chart -->
|
|
||||||
<li>
|
|
||||||
<div class="px-4 py-2">
|
|
||||||
<div class="w-full">
|
|
||||||
<canvas id="powerMetricsChart" style="height:150px;" ref="power-metrics-chart"></canvas>
|
|
||||||
<div class="flex">
|
|
||||||
<div class="mx-auto flex space-x-2">
|
|
||||||
<div class="flex mx-auto">
|
|
||||||
<div class="my-auto w-2 h-2 bg-blue-500 rounded-full"></div>
|
|
||||||
<div class="my-auto ml-1 text-sm text-gray-500">Channel 1</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex mx-auto">
|
|
||||||
<div class="my-auto w-2 h-2 bg-green-500 rounded-full"></div>
|
|
||||||
<div class="my-auto ml-1 text-sm text-gray-500">Channel 2</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex mx-auto">
|
|
||||||
<div class="my-auto w-2 h-2 bg-orange-500 rounded-full"></div>
|
|
||||||
<div class="my-auto ml-1 text-sm text-gray-500">Channel 3</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- channel 1 -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Channel 1</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="selectedNodeLatestPowerMetric">
|
|
||||||
<span v-if="selectedNodeLatestPowerMetric?.ch1_voltage">{{ Number(selectedNodeLatestPowerMetric.ch1_voltage).toFixed(2) }}V</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
<span v-if="selectedNodeLatestPowerMetric?.ch1_current"> / {{ Number(selectedNodeLatestPowerMetric.ch1_current).toFixed(2) }}mA</span>
|
|
||||||
</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- channel 2 -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Channel 2</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="selectedNodeLatestPowerMetric">
|
|
||||||
<span v-if="selectedNodeLatestPowerMetric?.ch2_voltage">{{ Number(selectedNodeLatestPowerMetric.ch2_voltage).toFixed(2) }}V</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
<span v-if="selectedNodeLatestPowerMetric?.ch2_current"> / {{ Number(selectedNodeLatestPowerMetric.ch2_current).toFixed(2) }}mA</span>
|
|
||||||
</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- channel 3 -->
|
|
||||||
<li class="flex p-3">
|
|
||||||
<div class="text-sm font-medium text-gray-900">Channel 3</div>
|
|
||||||
<div class="ml-auto text-sm text-gray-700">
|
|
||||||
<span v-if="selectedNodeLatestPowerMetric">
|
|
||||||
<span v-if="selectedNodeLatestPowerMetric?.ch3_voltage">{{ Number(selectedNodeLatestPowerMetric.ch3_voltage).toFixed(2) }}V</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
<span v-if="selectedNodeLatestPowerMetric?.ch3_current"> / {{ Number(selectedNodeLatestPowerMetric.ch3_current).toFixed(2) }}mA</span>
|
|
||||||
</span>
|
|
||||||
<span v-else>???</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,27 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
const props = defineProps(['node']);
|
|
||||||
import { getShareLinkForNode, copyShareLinkForNode } from '../../utils.js';
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="flex bg-gray-200 p-2 font-semibold">
|
|
||||||
<div class="my-auto">Share Link</div>
|
|
||||||
<div class="ml-auto">
|
|
||||||
<button @click="copyShareLinkForNode(props.node.node_id)" type="button" class="rounded-sm bg-white px-2 py-1 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
|
||||||
Copy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul role="list" class="flex-1 divide-y divide-gray-200">
|
|
||||||
<li>
|
|
||||||
<div class="relative flex items-center">
|
|
||||||
<div class="block flex-1 p-2">
|
|
||||||
<div class="flex space-x-2">
|
|
||||||
<input type="text" readonly 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" :value="getShareLinkForNode(props.node.node_id)">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,47 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
const emit = defineEmits(['dismiss']);
|
|
||||||
import { state } from '../store.js';
|
|
||||||
function dismissShowingNodeNeighbours() {
|
|
||||||
state.selectedNodeToShowNeighbours = null;
|
|
||||||
emit('dismiss');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="relative z-sidebar" role="dialog" aria-modal="true">
|
|
||||||
<!-- sidebar -->
|
|
||||||
<transition
|
|
||||||
enter-active-class="transition duration-300 ease-in-out transform"
|
|
||||||
enter-from-class="translate-y-full"
|
|
||||||
enter-to-class="translate-y-0"
|
|
||||||
leave-active-class="transition duration-300 ease-in-out transform"
|
|
||||||
leave-from-class="translate-y-0"
|
|
||||||
leave-to-class="translate-y-full">
|
|
||||||
<div v-show="state.selectedNodeToShowNeighbours != null" class="fixed left-0 right-0 bottom-0">
|
|
||||||
<div v-if="state.selectedNodeToShowNeighbours != null" class="mx-auto w-screen max-w-md p-4">
|
|
||||||
<div class="flex h-full flex-col bg-white shadow-xl rounded-xl border">
|
|
||||||
<div class="p-2">
|
|
||||||
<div class="flex items-start justify-between">
|
|
||||||
<div>
|
|
||||||
<h2 class="font-bold">{{ state.selectedNodeToShowNeighbours.short_name }} Neighbours</h2>
|
|
||||||
<h3 v-if="state.selectedNodeToShowNeighboursType === 'weHeard'" class="text-sm">Nodes heard by {{ state.selectedNodeToShowNeighbours.short_name }}</h3>
|
|
||||||
<h3 v-if="state.selectedNodeToShowNeighboursType === 'theyHeard'" class="text-sm">Nodes that heard {{ state.selectedNodeToShowNeighbours.short_name }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="my-auto ml-3 flex h-7 items-center">
|
|
||||||
<a href="javascript:void(0)" class="rounded-full" @click="dismissShowingNodeNeighbours">
|
|
||||||
<div class="bg-gray-100 hover:bg-gray-200 p-2 rounded-full">
|
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
||||||
<path d="M18 6l-12 12"></path>
|
|
||||||
<path d="M6 6l12 12"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,210 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { state } from '../store.js';
|
|
||||||
import {
|
|
||||||
nodesMaxAge,
|
|
||||||
nodesDisconnectedAge,
|
|
||||||
nodesOfflineAge,
|
|
||||||
waypointsMaxAge,
|
|
||||||
neighboursMaxDistance,
|
|
||||||
goToNodeZoomLevel,
|
|
||||||
temperatureFormat,
|
|
||||||
autoUpdatePositionInUrl,
|
|
||||||
enableMapAnimations,
|
|
||||||
} from '../config.js';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<!-- settings sidebar -->
|
|
||||||
<div class="relative z-sidebar" role="dialog" aria-modal="true">
|
|
||||||
|
|
||||||
<!-- overlay -->
|
|
||||||
<transition
|
|
||||||
enter-active-class="transition-opacity duration-300 ease-linear"
|
|
||||||
enter-from-class="opacity-0"
|
|
||||||
enter-to-class="opacity-100"
|
|
||||||
leave-active-class="transition-opacity duration-300 ease-linear"
|
|
||||||
leave-from-class="opacity-100"
|
|
||||||
leave-to-class="opacity-0">
|
|
||||||
<div v-show="state.settingsVisible" @click="state.settingsVisible = !state.settingsVisible" class="fixed inset-0 bg-gray-900/75"></div>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<!-- sidebar -->
|
|
||||||
<transition
|
|
||||||
enter-active-class="transition duration-300 ease-in-out transform"
|
|
||||||
enter-from-class="-translate-x-full"
|
|
||||||
enter-to-class="translate-x-0"
|
|
||||||
leave-active-class="transition duration-300 ease-in-out transform"
|
|
||||||
leave-from-class="translate-x-0"
|
|
||||||
leave-to-class="-translate-x-full">
|
|
||||||
<div v-show="state.settingsVisible" class="fixed top-0 left-0 bottom-0">
|
|
||||||
<div v-if="state.settingsVisible" class="w-screen h-full max-w-md overflow-hidden">
|
|
||||||
<div class="flex h-full flex-col bg-white shadow-xl">
|
|
||||||
|
|
||||||
<!-- slideover header -->
|
|
||||||
<div class="p-2 border-b border-gray-200 shadow-sm">
|
|
||||||
<div class="flex items-start justify-between">
|
|
||||||
<div>
|
|
||||||
<h2 class="font-bold">Settings</h2>
|
|
||||||
<h3 class="text-sm">Changes are only saved in this browser.</h3>
|
|
||||||
</div>
|
|
||||||
<div class="my-auto ml-3 flex h-7 items-center">
|
|
||||||
<a href="javascript:void(0)" class="rounded-full" @click="state.settingsVisible = false">
|
|
||||||
<div class="bg-gray-100 hover:bg-gray-200 p-2 rounded-full">
|
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
||||||
<path d="M18 6l-12 12"></path>
|
|
||||||
<path d="M6 6l12 12"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="overflow-y-auto divide-y divide-gray-200">
|
|
||||||
|
|
||||||
<!-- configNodesMaxAgeInSeconds -->
|
|
||||||
<div class="p-2">
|
|
||||||
<label class="block text-sm font-medium text-gray-900">Nodes Max Age</label>
|
|
||||||
<div class="text-xs text-gray-600 mb-2">Nodes not updated within this time are hidden. Reload to update map.</div>
|
|
||||||
<select v-model="nodesMaxAge" 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="null">Show All</option>
|
|
||||||
<option value="900">15 minutes</option>
|
|
||||||
<option value="1800">30 minutes</option>
|
|
||||||
<option value="3600">1 hour</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>
|
|
||||||
|
|
||||||
<!-- 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="nodesDisconnectedAge" 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 -->
|
|
||||||
<div class="p-2">
|
|
||||||
<label class="block text-sm font-medium text-gray-900">Nodes Offline Age</label>
|
|
||||||
<div class="text-xs text-gray-600 mb-2">Nodes not updated within this time will show as red icons. Reload to update map.</div>
|
|
||||||
<select v-model="nodesOfflineAge" 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="null">Don't show as offline</option>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- configWaypointsMaxAgeInSeconds -->
|
|
||||||
<div class="p-2">
|
|
||||||
<label class="block text-sm font-medium text-gray-900">Waypoints Max Age</label>
|
|
||||||
<div class="text-xs text-gray-600 mb-2">Waypoints not updated within this time are hidden. Reload to update map.</div>
|
|
||||||
<select v-model="waypointsMaxAge" 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="null">Show All</option>
|
|
||||||
<option value="900">15 minutes</option>
|
|
||||||
<option value="1800">30 minutes</option>
|
|
||||||
<option value="3600">1 hour</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>
|
|
||||||
|
|
||||||
<!-- configNeighboursMaxDistanceInMeters -->
|
|
||||||
<div class="p-2">
|
|
||||||
<label class="block text-sm font-medium text-gray-900">Neighbours Max Distance (meters)</label>
|
|
||||||
<div class="text-xs text-gray-600 mb-2">Neighbours further than this are hidden. Reload to update map.</div>
|
|
||||||
<input type="number" v-model="neighboursMaxDistance" 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">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- configZoomLevelGoToNode -->
|
|
||||||
<div class="p-2">
|
|
||||||
<label class="block text-sm font-medium text-gray-900">Zoom Level (go to node)</label>
|
|
||||||
<div class="text-xs text-gray-600 mb-2">How far to zoom map when navigating to a node.</div>
|
|
||||||
<input type="number" v-model="goToNodeZoomLevel" 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">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- configTemperatureFormat -->
|
|
||||||
<div class="p-2">
|
|
||||||
<label class="block text-sm font-medium text-gray-900">Temperature Format</label>
|
|
||||||
<div class="text-xs text-gray-600 mb-2">Metrics will be shown in the selected format.</div>
|
|
||||||
<select v-model="temperatureFormat" 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="celsius">Celsius (ºC)</option>
|
|
||||||
<option value="fahrenheit">Fahrenheit (ºF)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- configAutoUpdatePositionInUrl -->
|
|
||||||
<div class="p-2">
|
|
||||||
<div class="flex items-start">
|
|
||||||
<div class="flex items-center h-5">
|
|
||||||
<input type="checkbox" v-model="autoUpdatePositionInUrl" class="w-4 h-4 border border-gray-300 rounded-sm bg-gray-50 focus:ring-3 focus:ring-blue-300" required>
|
|
||||||
</div>
|
|
||||||
<label class="ml-2 text-sm font-medium text-gray-900">Auto Update Position in URL</label>
|
|
||||||
</div>
|
|
||||||
<div class="text-xs text-gray-600">Sets lat/lng/zoom as query parameters.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- configEnableMapAnimations -->
|
|
||||||
<div class="p-2">
|
|
||||||
<div class="flex items-start">
|
|
||||||
<div class="flex items-center h-5">
|
|
||||||
<input type="checkbox" v-model="enableMapAnimations" class="w-4 h-4 border border-gray-300 rounded-sm bg-gray-50 focus:ring-3 focus:ring-blue-300" required>
|
|
||||||
</div>
|
|
||||||
<label class="ml-2 text-sm font-medium text-gray-900">Enable Map Animations</label>
|
|
||||||
</div>
|
|
||||||
<div class="text-xs text-gray-600">Map will animate flying to nodes.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,154 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
const emit = defineEmits(['goTo']);
|
|
||||||
import moment from 'moment';
|
|
||||||
import { state } from '../store.js';
|
|
||||||
import { getNodeColor, getNodeTextColor, findNodeById, findNodeMarkerById } from '../utils.js';
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="relative z-sidebar" role="dialog" aria-modal="true">
|
|
||||||
|
|
||||||
<!-- overlay -->
|
|
||||||
<transition
|
|
||||||
enter-active-class="transition-opacity duration-300 ease-linear"
|
|
||||||
enter-from-class="opacity-0"
|
|
||||||
enter-to-class="opacity-100"
|
|
||||||
leave-active-class="transition-opacity duration-300 ease-linear"
|
|
||||||
leave-from-class="opacity-100"
|
|
||||||
leave-to-class="opacity-0">
|
|
||||||
<div v-show="state.selectedTraceRoute != null" @click="state.selectedTraceRoute = null" class="fixed inset-0 bg-gray-900/75"></div>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<!-- sidebar -->
|
|
||||||
<transition
|
|
||||||
enter-active-class="transition duration-300 ease-in-out transform"
|
|
||||||
enter-from-class="-translate-x-full"
|
|
||||||
enter-to-class="translate-x-0"
|
|
||||||
leave-active-class="transition duration-300 ease-in-out transform"
|
|
||||||
leave-from-class="translate-x-0"
|
|
||||||
leave-to-class="-translate-x-full">
|
|
||||||
<div v-show="state.selectedTraceRoute != null" class="fixed top-0 left-0 bottom-0">
|
|
||||||
<div v-if="state.selectedTraceRoute != null" class="w-screen h-full max-w-md overflow-hidden">
|
|
||||||
<div class="flex h-full flex-col bg-white shadow-xl">
|
|
||||||
|
|
||||||
<!-- slideover header -->
|
|
||||||
<div class="p-2 border-b border-gray-200 shadow-sm">
|
|
||||||
<div class="flex items-start justify-between">
|
|
||||||
<div>
|
|
||||||
<h2 class="font-bold">Traceroute #{{ state.selectedTraceRoute.id }}</h2>
|
|
||||||
<h3 class="text-sm">{{ moment(new Date(state.selectedTraceRoute.updated_at)).fromNow() }} - {{ state.selectedTraceRoute.route.length }} hops {{ state.selectedTraceRoute.channel_id ? `on ${state.selectedTraceRoute.channel_id}` : '' }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="my-auto ml-3 flex h-7 items-center">
|
|
||||||
<a href="javascript:void(0)" class="rounded-full" @click="state.selectedTraceRoute = null">
|
|
||||||
<div class="bg-gray-100 hover:bg-gray-200 p-2 rounded-full">
|
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
||||||
<path d="M18 6l-12 12"></path>
|
|
||||||
<path d="M6 6l12 12"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="overflow-y-auto">
|
|
||||||
|
|
||||||
<!-- details -->
|
|
||||||
<div class="p-2">
|
|
||||||
<ul role="list" class="space-y-3">
|
|
||||||
|
|
||||||
<!-- node that initiated traceroute -->
|
|
||||||
<li @click="$emit('goTo', state.selectedTraceRoute.to)" class="relative flex gap-x-4">
|
|
||||||
<div class="absolute left-0 top-0 flex w-12 justify-center top-3 -bottom-3">
|
|
||||||
<div class="w-px bg-gray-200"></div>
|
|
||||||
</div>
|
|
||||||
<div class="my-auto relative flex flex-none items-center justify-center">
|
|
||||||
<div>
|
|
||||||
<div class="flex rounded-full h-12 w-12 text-white shadow-sm" :class="[ `bg-[${getNodeColor(state.selectedTraceRoute.to)}]`, `text-[${getNodeTextColor(state.selectedTraceRoute.to)}]` ]">
|
|
||||||
<div class="mx-auto my-auto drop-shadow-sm">{{ findNodeById(state.selectedTraceRoute.to)?.short_name ?? "?" }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-auto py-0.5 text-sm leading-5 text-gray-500">
|
|
||||||
<div class="font-medium text-gray-900">{{ findNodeById(state.selectedTraceRoute.to)?.long_name || '???' }}</div>
|
|
||||||
<div>Hex ID: !{{ Number(state.selectedTraceRoute.to).toString(16) }}</div>
|
|
||||||
<div>Started the traceroute</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- middleman nodes -->
|
|
||||||
<li @click="$emit('goTo', route)" v-for="route of state.selectedTraceRoute.route" class="relative flex gap-x-4">
|
|
||||||
<div class="absolute left-0 top-0 flex w-12 justify-center -bottom-3">
|
|
||||||
<div class="w-px bg-gray-200"></div>
|
|
||||||
</div>
|
|
||||||
<div class="my-auto relative flex flex-none items-center justify-center">
|
|
||||||
<div>
|
|
||||||
<div class="flex rounded-full h-12 w-12 text-white shadow" :class="[ `bg-[${getNodeColor(route)}]`, `text-[${getNodeTextColor(route)}]` ]">
|
|
||||||
<div class="mx-auto my-auto drop-shadow-sm">{{ findNodeById(route)?.short_name ?? "?" }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-auto py-0.5 text-sm leading-5 text-gray-500">
|
|
||||||
<div class="font-medium text-gray-900">{{ findNodeById(route)?.long_name || '???' }}</div>
|
|
||||||
<div>Hex ID: !{{ Number(route).toString(16) }}</div>
|
|
||||||
<div>Forwarded the packet</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- node that replied to traceroute -->
|
|
||||||
<li @click="$emit('goTo', state.selectedTraceRoute.from)" v-if="state.selectedTraceRoute.from" class="relative flex gap-x-4">
|
|
||||||
<div class="absolute left-0 top-0 flex w-12 justify-center -bottom-3">
|
|
||||||
<div class="w-px bg-gray-200"></div>
|
|
||||||
</div>
|
|
||||||
<div class="my-auto relative flex flex-none items-center justify-center">
|
|
||||||
<div>
|
|
||||||
<div class="flex rounded-full h-12 w-12 text-white shadow" :class="[ `bg-[${getNodeColor(state.selectedTraceRoute.from)}]`, `text-[${getNodeTextColor(state.selectedTraceRoute.from)}]` ]">
|
|
||||||
<div class="mx-auto my-auto drop-shadow-sm">{{ findNodeById(state.selectedTraceRoute.from)?.short_name ?? "?" }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-auto py-0.5 text-sm leading-5 text-gray-500">
|
|
||||||
<div class="font-medium text-gray-900">{{ findNodeById(state.selectedTraceRoute.from)?.long_name || '???' }}</div>
|
|
||||||
<div>Hex ID: !{{ Number(state.selectedTraceRoute.from).toString(16) }}</div>
|
|
||||||
<div>Replied to traceroute</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!-- node that gated traceroute to mqtt -->
|
|
||||||
<li @click="$emit('goTo', state.selectedTraceRoute.gateway_id)" v-if="state.selectedTraceRoute.gateway_id" class="relative flex gap-x-4">
|
|
||||||
<div class="absolute left-0 top-0 flex w-12 justify-center h-6">
|
|
||||||
<div class="w-px bg-gray-200"></div>
|
|
||||||
</div>
|
|
||||||
<div class="my-auto relative flex flex-none items-center justify-center">
|
|
||||||
<div>
|
|
||||||
<div class="flex rounded-full h-12 w-12 text-white shadow" :class="[ `bg-[${getNodeColor(state.selectedTraceRoute.gateway_id)}]`, `text-[${getNodeTextColor(state.selectedTraceRoute.gateway_id)}]` ]">
|
|
||||||
<div class="mx-auto my-auto drop-shadow-sm">{{ findNodeById(state.selectedTraceRoute.gateway_id)?.short_name ?? "?" }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-auto py-0.5 text-sm leading-5 text-gray-500">
|
|
||||||
<div class="font-medium text-gray-900">{{ findNodeById(state.selectedTraceRoute.gateway_id)?.long_name || '???' }}</div>
|
|
||||||
<div>Hex ID: !{{ Number(state.selectedTraceRoute.gateway_id).toString(16) }}</div>
|
|
||||||
<div>Gated the packet to MQTT</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="bg-gray-200 p-2 font-semibold">Raw Data</div>
|
|
||||||
<div class="text-sm text-gray-700">
|
|
||||||
<pre class="bg-gray-100 rounded-sm p-2 overflow-x-auto">{{ JSON.stringify(state.selectedTraceRoute, null, 4) }}</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,29 +0,0 @@
|
|||||||
import { useStorage } from '@vueuse/core';
|
|
||||||
|
|
||||||
// static
|
|
||||||
export const CURRENT_ANNOUNCEMENT_ID = 1;
|
|
||||||
export const BASE_PATH = '';
|
|
||||||
|
|
||||||
// string
|
|
||||||
export const temperatureFormat = useStorage('temperature-display', 'fahrenheit');
|
|
||||||
// boolean
|
|
||||||
export const autoUpdatePositionInUrl = useStorage('auto-update-url', true);
|
|
||||||
export const enableMapAnimations = useStorage('map-animations', true);
|
|
||||||
export const hasSeenInfoModal = useStorage('seen-info-modal', false);
|
|
||||||
// time in seconds
|
|
||||||
export const nodesMaxAge = useStorage('nodes-max-age', null);
|
|
||||||
export const nodesDisconnectedAge = useStorage('nodes-max-disconnected-age', 604800);
|
|
||||||
export const nodesOfflineAge = useStorage('nodes-offline-age', null);
|
|
||||||
export const waypointsMaxAge = useStorage('waypoints-max-age', 604800);
|
|
||||||
// number
|
|
||||||
export const goToNodeZoomLevel = useStorage('zoom-to-node', 15);
|
|
||||||
export const lastSeenAnnouncementId = useStorage('last-seen-announcement-id', 1);
|
|
||||||
// distance in meters
|
|
||||||
export const neighboursMaxDistance = useStorage('neighbors-distance', null);
|
|
||||||
// device info ranges
|
|
||||||
export const deviceMetricsTimeRange = useStorage('device-metrics-range', '3d');
|
|
||||||
export const powerMetricsTimeRange = useStorage('power-metrics-range', '3d');
|
|
||||||
export const environmentMetricsTimeRange = useStorage('environment-metrics-range', '3d');
|
|
||||||
// map config
|
|
||||||
export const enabledOverlayLayers = useStorage('enabled-overlay-layers', ['Legend', 'Position History']);
|
|
||||||
export const selectedTileLayerName = useStorage('selected-tile-layer', 'OpenStreetMap');
|
|
@ -1,11 +0,0 @@
|
|||||||
import './assets/main.css'
|
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
|
||||||
import App from './App.vue'
|
|
||||||
import router from './router'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
|
||||||
|
|
||||||
app.use(router)
|
|
||||||
|
|
||||||
app.mount('#app')
|
|
@ -1,289 +0,0 @@
|
|||||||
import moment from 'moment';
|
|
||||||
import L from 'leaflet/dist/leaflet.js';
|
|
||||||
import 'leaflet.markercluster/dist/leaflet.markercluster.js';
|
|
||||||
import 'leaflet-groupedlayercontrol/dist/leaflet.groupedlayercontrol.min.js';
|
|
||||||
import {
|
|
||||||
escapeString,
|
|
||||||
findNodeById,
|
|
||||||
getRegionFrequencyRange,
|
|
||||||
hasNodeUplinkedToMqttRecently,
|
|
||||||
formatPositionPrecision,
|
|
||||||
getTerrainProfileImage,
|
|
||||||
} from './utils.js';
|
|
||||||
import { state } from './store.js';
|
|
||||||
import { markRaw } from 'vue';
|
|
||||||
|
|
||||||
// state/config
|
|
||||||
let instance = null;
|
|
||||||
export function setMap(map) {
|
|
||||||
instance = markRaw(map);
|
|
||||||
}
|
|
||||||
export function getMap() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const layerGroups = {
|
|
||||||
nodes: new L.LayerGroup(),
|
|
||||||
neighbors: new L.LayerGroup(),
|
|
||||||
waypoints: new L.LayerGroup(),
|
|
||||||
nodePositionHistory: new L.LayerGroup(),
|
|
||||||
nodeNeighbors: new L.LayerGroup(),
|
|
||||||
nodesRouter: L.markerClusterGroup({
|
|
||||||
showCoverageOnHover: false,
|
|
||||||
disableClusteringAtZoom: 10, // zoom level where node clustering is disabled
|
|
||||||
}),
|
|
||||||
nodesClustered: L.markerClusterGroup({
|
|
||||||
showCoverageOnHover: false,
|
|
||||||
disableClusteringAtZoom: 10, // zoom level where node clustering is disabled
|
|
||||||
}),
|
|
||||||
legend: new L.LayerGroup(),
|
|
||||||
none: new L.LayerGroup(),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const tileLayers = {
|
|
||||||
"OpenStreetMap": L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
||||||
maxZoom: 22, // increase from 18 to 22
|
|
||||||
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>',
|
|
||||||
}),
|
|
||||||
"OpenTopoMap": 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>',
|
|
||||||
}),
|
|
||||||
"Esri Satellite": 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
|
|
||||||
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>'
|
|
||||||
}),
|
|
||||||
"Google Satellite": L.tileLayer('https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', {
|
|
||||||
maxZoom: 21,
|
|
||||||
subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
|
|
||||||
attribution: 'Tiles © Google | Data from <a target="_blank" href="https://meshtastic.org/docs/software/integrations/mqtt/">Meshtastic</a>'
|
|
||||||
}),
|
|
||||||
"Google Hybrid": L.tileLayer('https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
|
|
||||||
maxZoom: 21,
|
|
||||||
subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
|
|
||||||
attribution: 'Tiles © Google | Data from <a target="_blank" href="https://meshtastic.org/docs/software/integrations/mqtt/">Meshtastic</a>'
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const icons = {
|
|
||||||
mqttConnected: L.divIcon({
|
|
||||||
className: 'icon-mqtt-connected',
|
|
||||||
iconSize: [16, 16], // increase from 12px to 16px to make hover easier
|
|
||||||
}),
|
|
||||||
mqttDisconnected: L.divIcon({
|
|
||||||
className: 'icon-mqtt-disconnected',
|
|
||||||
iconSize: [16, 16], // increase from 12px to 16px to make hover easier
|
|
||||||
}),
|
|
||||||
offline: L.divIcon({
|
|
||||||
className: 'icon-offline',
|
|
||||||
iconSize: [16, 16], // increase from 12px to 16px to make hover easier
|
|
||||||
}),
|
|
||||||
positionHistory: L.divIcon({
|
|
||||||
className: 'icon-position-history',
|
|
||||||
iconSize: [16, 16], // increase from 12px to 16px to make hover easier
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
// tooltips
|
|
||||||
|
|
||||||
export function getTooltipContentForWaypoint(waypoint) {
|
|
||||||
// get from node name
|
|
||||||
var fromNode = findNodeById(waypoint.from);
|
|
||||||
|
|
||||||
var tooltip = `<b>${escapeString(waypoint.name)}</b>` +
|
|
||||||
(waypoint.description ? `<br/>${escapeString(waypoint.description)}` : '') +
|
|
||||||
`<br/><br/>Expires: ${moment(new Date(waypoint.expire * 1000)).fromNow()}` +
|
|
||||||
`<br/>Lat/Lng: ${waypoint.latitude}, ${waypoint.longitude}` +
|
|
||||||
`<br/><br/>From ID: ${waypoint.from}` +
|
|
||||||
`<br/>From Hex ID: !${Number(waypoint.from).toString(16)}`;
|
|
||||||
|
|
||||||
// show node name this waypoint is from, if possible
|
|
||||||
if(fromNode != null){
|
|
||||||
tooltip += `<br/>From Node: <a href="#" onclick="goToNode(${waypoint.from})">${escapeString(fromNode.long_name) || 'Unnamed Node'}</a>`;
|
|
||||||
} else {
|
|
||||||
tooltip += `<br/>From Node: ???`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// bottom info
|
|
||||||
tooltip += `<br/><br/>ID: ${waypoint.waypoint_id}`;
|
|
||||||
tooltip += `<br/>Updated: ${moment(new Date(waypoint.updated_at)).fromNow()}`;
|
|
||||||
|
|
||||||
return tooltip;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function getTooltipContentForNode(node) {
|
|
||||||
// determine if node was recently heard uplinking packets to mqtt
|
|
||||||
const nodeHasUplinkedToMqttRecently = hasNodeUplinkedToMqttRecently(node);
|
|
||||||
var mqttStatus = `<span class="text-blue-700">Disconnected</span>`;
|
|
||||||
if(node.mqtt_connection_state_updated_at){
|
|
||||||
var mqttStatusUpdatedAt = moment(new Date(node.mqtt_connection_state_updated_at)).fromNow();
|
|
||||||
if(nodeHasUplinkedToMqttRecently){
|
|
||||||
mqttStatus = `<span><span class="text-green-700">Connected</span> (${mqttStatusUpdatedAt})</span>`;
|
|
||||||
} else {
|
|
||||||
mqttStatus = `<span><span class="text-blue-700">Disconnected</span> (${mqttStatusUpdatedAt})</span>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var loraFrequencyRange = getRegionFrequencyRange(node.region_name);
|
|
||||||
|
|
||||||
var tooltip = `<img class="mb-4 w-40 mx-auto" src="/images/devices/${node.hardware_model_name}.png" onerror="this.classList.add('hidden')"/>` +
|
|
||||||
`<b>${escapeString(node.long_name)}</b>` +
|
|
||||||
`<br/>Short Name: ${escapeString(node.short_name)}` +
|
|
||||||
`<br/>MQTT: ${mqttStatus}` +
|
|
||||||
(node.num_online_local_nodes != null ? `<br/>Local Nodes Online: ${node.num_online_local_nodes}` : '') +
|
|
||||||
(node.position_precision != null && node.position_precision !== 32 ? `<br/>Position Precision: ${formatPositionPrecision(node.position_precision)}` : '') +
|
|
||||||
`<br/><br/>Role: ${node.role_name}` +
|
|
||||||
`<br/>Hardware: ${node.hardware_model_name}` +
|
|
||||||
(node.firmware_version != null ? `<br/>Firmware: ${node.firmware_version}` : '') +
|
|
||||||
(node.region_name != null ? `<br/>LoRa Region: ${node.region_name} (${loraFrequencyRange})` : '') +
|
|
||||||
(node.modem_preset_name != null ? `<br/>Modem Preset: ${node.modem_preset_name}` : '') +
|
|
||||||
(node.has_default_channel != null ? `<br/>Has Default Channel: ${node.has_default_channel ? "Yes" : "No"}` : '');
|
|
||||||
|
|
||||||
if(node.battery_level){
|
|
||||||
if(node.battery_level > 100){
|
|
||||||
tooltip += `<br/>Battery: ${node.battery_level > 100 ? 'Plugged In' : node.battery_level}`;
|
|
||||||
} else {
|
|
||||||
tooltip += `<br/>Battery: ${node.battery_level}%`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(node.voltage){
|
|
||||||
tooltip += `<br/>Voltage: ${Number(node.voltage).toFixed(2)}V`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(node.channel_utilization){
|
|
||||||
tooltip += `<br/>Ch Util: ${Number(node.channel_utilization).toFixed(2)}%`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(node.air_util_tx){
|
|
||||||
tooltip += `<br/>Air Util: ${Number(node.air_util_tx).toFixed(2)}%`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore alt above 42949000 due to https://github.com/meshtastic/firmware/issues/3109
|
|
||||||
if(node.altitude && node.altitude < 42949000){
|
|
||||||
tooltip += `<br/>Altitude: ${node.altitude}m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// bottom info
|
|
||||||
tooltip += `<br/><br/>ID: ${node.node_id}`;
|
|
||||||
tooltip += `<br/>Hex ID: ${node.node_id_hex}`;
|
|
||||||
tooltip += `<br/>Updated: ${moment(new Date(node.updated_at)).fromNow()}`;
|
|
||||||
tooltip += (node.neighbours_updated_at ? `<br/>Neighbours Updated: ${moment(new Date(node.neighbours_updated_at)).fromNow()}` : '');
|
|
||||||
tooltip += (node.position_updated_at ? `<br/>Position Updated: ${moment(new Date(node.position_updated_at)).fromNow()}` : '');
|
|
||||||
|
|
||||||
// show details button
|
|
||||||
tooltip += `<br/><br/><button onclick="showNodeDetails(${node.node_id});" class="border border-gray-300 bg-gray-100 p-1 w-full rounded hover:bg-gray-200 mb-1">Show Full Details</button>`;
|
|
||||||
tooltip += `<br/><button onclick="window.showNodeNeighboursThatHeardUs(${node.node_id});" class="border border-gray-300 bg-gray-100 p-1 w-full rounded hover:bg-gray-200 mb-1">Show Neighbours (Heard Us)</button>`;
|
|
||||||
tooltip += `<br/><button onclick="window.showNodeNeighboursThatWeHeard(${node.node_id});" class="border border-gray-300 bg-gray-100 p-1 w-full rounded hover:bg-gray-200">Show Neighbours (We Heard)</button>`;
|
|
||||||
tooltip += `</div>`;
|
|
||||||
|
|
||||||
return tooltip;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getNeighbourTooltipContent(type, node, neighbourNode, distanceInMeters, snr) {
|
|
||||||
// default to showing distance in meters
|
|
||||||
let distance = `${distanceInMeters} meters`;
|
|
||||||
|
|
||||||
// scale to distance in kms
|
|
||||||
if (distanceInMeters >= 1000) {
|
|
||||||
const distanceInKilometers = (distanceInMeters / 1000).toFixed(2);
|
|
||||||
distance = `${distanceInKilometers} kilometers`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const terrainImageUrl = type === 'weHeard' ? getTerrainProfileImage(node, neighbourNode) : getTerrainProfileImage(neighbourNode, node);
|
|
||||||
|
|
||||||
const templates = {
|
|
||||||
'weHeard': `<b>[${escapeString(node.short_name)}] ${escapeString(node.long_name)}</b> heard <b>[${escapeString(neighbourNode.short_name)}] ${escapeString(neighbourNode.long_name)}</b>`
|
|
||||||
+ `<br/>SNR: ${snr}dB`
|
|
||||||
+ `<br/>Distance: ${distance}`
|
|
||||||
+ `<br/><br/>ID: ${node.node_id} heard ${neighbourNode.node_id}`
|
|
||||||
+ `<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()}` : '')
|
|
||||||
+ `<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>`,
|
|
||||||
'theyHeard': `<b>[${escapeString(neighbourNode.short_name)}] ${escapeString(neighbourNode.long_name)}</b> heard <b>[${escapeString(node.short_name)}] ${escapeString(node.long_name)}</b>`
|
|
||||||
+ `<br/>SNR: ${snr}dB`
|
|
||||||
+ `<br/>Distance: ${distance}`
|
|
||||||
+ `<br/><br/>ID: ${neighbourNode.node_id} heard ${node.node_id}`
|
|
||||||
+ `<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()}` : '')
|
|
||||||
+ `<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>`,
|
|
||||||
}
|
|
||||||
|
|
||||||
return templates[type];
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanup
|
|
||||||
|
|
||||||
export function clearAllNodes() {
|
|
||||||
layerGroups.nodes.clearLayers();
|
|
||||||
layerGroups.nodesClustered.clearLayers();
|
|
||||||
layerGroups.nodesRouter.clearLayers();
|
|
||||||
};
|
|
||||||
|
|
||||||
export function clearAllNeighbors() {
|
|
||||||
layerGroups.neighbors.clearLayers();
|
|
||||||
};
|
|
||||||
|
|
||||||
export function clearAllWaypoints() {
|
|
||||||
layerGroups.waypoints.clearLayers();
|
|
||||||
};
|
|
||||||
|
|
||||||
export function clearAllPositionHistory() {
|
|
||||||
layerGroups.nodePositionHistory.clearLayers();
|
|
||||||
};
|
|
||||||
|
|
||||||
export function cleanUpPositionHistory() {
|
|
||||||
// close tooltips and popups
|
|
||||||
closeAllPopups();
|
|
||||||
closeAllTooltips();
|
|
||||||
|
|
||||||
// setup node neighbours layer
|
|
||||||
layerGroups.nodePositionHistory.clearLayers();
|
|
||||||
layerGroups.nodePositionHistory.removeFrom(getMap());
|
|
||||||
layerGroups.nodePositionHistory.addTo(getMap());
|
|
||||||
};
|
|
||||||
|
|
||||||
export function closeAllTooltips() {
|
|
||||||
getMap().eachLayer(function(layer) {
|
|
||||||
if (layer.options.pane === 'tooltipPane') {
|
|
||||||
layer.removeFrom(getMap());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export function closeAllPopups() {
|
|
||||||
getMap().eachLayer(function(layer) {
|
|
||||||
if (layer.options.pane === 'popupPane') {
|
|
||||||
layer.removeFrom(getMap());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export function cleanUpNodeNeighbors() {
|
|
||||||
// close tooltips and popups
|
|
||||||
closeAllPopups();
|
|
||||||
closeAllTooltips();
|
|
||||||
// setup node neighbours layer
|
|
||||||
layerGroups.nodeNeighbors.clearLayers();
|
|
||||||
layerGroups.nodeNeighbors.removeFrom(getMap());
|
|
||||||
layerGroups.nodeNeighbors.addTo(getMap());
|
|
||||||
};
|
|
||||||
|
|
||||||
export function clearNodeOutline() {
|
|
||||||
if (state.selectedNodeOutlineCircle) {
|
|
||||||
state.selectedNodeOutlineCircle.removeFrom(getMap());
|
|
||||||
state.selectedNodeOutlineCircle = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export function clearMap() {
|
|
||||||
closeAllPopups();
|
|
||||||
closeAllTooltips();
|
|
||||||
clearAllNodes();
|
|
||||||
clearAllNeighbors();
|
|
||||||
clearAllWaypoints();
|
|
||||||
clearNodeOutline();
|
|
||||||
cleanUpNodeNeighbors();
|
|
||||||
};
|
|
@ -1,60 +0,0 @@
|
|||||||
import { reactive, computed } from 'vue';
|
|
||||||
|
|
||||||
export const state = reactive({
|
|
||||||
// caches
|
|
||||||
nodes: [],
|
|
||||||
waypoints: [],
|
|
||||||
nodeMarkers: {},
|
|
||||||
|
|
||||||
// state
|
|
||||||
searchText: '',
|
|
||||||
selectedNodeOutlineCircle: null,
|
|
||||||
selectedNodeMqttMetrics: [],
|
|
||||||
selectedNodeTraceroutes: [],
|
|
||||||
selectedNodeDeviceMetrics: [],
|
|
||||||
selectedNodePowerMetrics: [],
|
|
||||||
selectedNodeEnvironmentMetrics: [],
|
|
||||||
selectedNodeToShowNeighbours: null,
|
|
||||||
selectedNodeToShowNeighbours: null,
|
|
||||||
selectedNodeToShowNeighboursType: null,
|
|
||||||
|
|
||||||
// position history
|
|
||||||
selectedNodeToShowPositionHistory: null,
|
|
||||||
positionHistoryDateTimeTo: null,
|
|
||||||
positionHistoryDateTimeFrom: null,
|
|
||||||
|
|
||||||
// ui
|
|
||||||
loading: false,
|
|
||||||
settingsVisible: false,
|
|
||||||
hardwareStatsVisible: false,
|
|
||||||
infoModalVisible: false,
|
|
||||||
mobileSearchVisible: false,
|
|
||||||
positionHistoryModalExpanded: false,
|
|
||||||
announcementVisible: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const searchedNodes = computed(() => {
|
|
||||||
// search nodes
|
|
||||||
const nodes = state.nodes.filter((node) => {
|
|
||||||
const matchesId = node.node_id?.toLowerCase()?.includes(state.searchText.toLowerCase());
|
|
||||||
const matchesHexId = node.node_id_hex?.toLowerCase()?.includes(state.searchText.toLowerCase());
|
|
||||||
const matchesLongName = node.long_name?.toLowerCase()?.includes(state.searchText.toLowerCase());
|
|
||||||
const matchesShortName = node.short_name?.toLowerCase()?.includes(state.searchText.toLowerCase());
|
|
||||||
return matchesId || matchesHexId || matchesLongName || matchesShortName;
|
|
||||||
});
|
|
||||||
|
|
||||||
// order alphabetically by long name
|
|
||||||
nodes.sort((nodeA, nodeB) => {
|
|
||||||
const nodeALongName = nodeA.long_name || "";
|
|
||||||
const nodeBLongName = nodeB.long_name || "";
|
|
||||||
return nodeALongName.localeCompare(nodeBLongName);
|
|
||||||
});
|
|
||||||
|
|
||||||
// only return the first 500 results to avoid ui lag...
|
|
||||||
return nodes.slice(0, 500);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const selectedNodeLatestPowerMetric = computed(() => {
|
|
||||||
const [ latestPowerMetric ] = state.selectedNodePowerMetrics.slice(-1);
|
|
||||||
return latestPowerMetric;
|
|
||||||
});
|
|
@ -1,911 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import Header from '../components/Header.vue';
|
|
||||||
import InfoModal from '../components/InfoModal.vue';
|
|
||||||
import HardwareModelList from '../components/HardwareModelList.vue';
|
|
||||||
import Settings from '../components/Settings.vue';
|
|
||||||
import NodeInfo from '../components/NodeInfo.vue';
|
|
||||||
import NodeNeighborsModal from '../components/NodeNeighborsModal.vue';
|
|
||||||
import NodePositionHistoryModal from '../components/NodePositionHistoryModal.vue';
|
|
||||||
import TracerouteInfo from '../components/TracerouteInfo.vue';
|
|
||||||
import Announcement from '../components/Announcement.vue';
|
|
||||||
|
|
||||||
import axios from 'axios';
|
|
||||||
import moment from 'moment';
|
|
||||||
import L from 'leaflet/dist/leaflet.js';
|
|
||||||
import 'leaflet.markercluster/dist/leaflet.markercluster.js';
|
|
||||||
import 'leaflet-groupedlayercontrol/dist/leaflet.groupedlayercontrol.min.js';
|
|
||||||
import { onMounted, useTemplateRef, ref, watch, markRaw } from 'vue';
|
|
||||||
import { state } from '../store.js';
|
|
||||||
import {
|
|
||||||
layerGroups,
|
|
||||||
tileLayers,
|
|
||||||
icons,
|
|
||||||
getTooltipContentForWaypoint,
|
|
||||||
getTooltipContentForNode,
|
|
||||||
getNeighbourTooltipContent,
|
|
||||||
clearAllNodes,
|
|
||||||
clearAllNeighbors,
|
|
||||||
clearAllWaypoints,
|
|
||||||
clearAllPositionHistory,
|
|
||||||
cleanUpPositionHistory,
|
|
||||||
closeAllTooltips,
|
|
||||||
closeAllPopups,
|
|
||||||
cleanUpNodeNeighbors,
|
|
||||||
clearNodeOutline,
|
|
||||||
clearMap,
|
|
||||||
setMap,
|
|
||||||
getMap,
|
|
||||||
} from '../map.js';
|
|
||||||
import {
|
|
||||||
nodesMaxAge,
|
|
||||||
nodesDisconnectedAge,
|
|
||||||
nodesOfflineAge,
|
|
||||||
waypointsMaxAge,
|
|
||||||
enableMapAnimations,
|
|
||||||
goToNodeZoomLevel,
|
|
||||||
autoUpdatePositionInUrl,
|
|
||||||
neighboursMaxDistance,
|
|
||||||
enabledOverlayLayers,
|
|
||||||
selectedTileLayerName,
|
|
||||||
hasSeenInfoModal,
|
|
||||||
lastSeenAnnouncementId,
|
|
||||||
CURRENT_ANNOUNCEMENT_ID,
|
|
||||||
} from '../config.js';
|
|
||||||
import {
|
|
||||||
getColorForSnr,
|
|
||||||
getPositionPrecisionInMeters,
|
|
||||||
getTerrainProfileImage,
|
|
||||||
getRegionFrequencyRange,
|
|
||||||
escapeString,
|
|
||||||
formatPositionPrecision,
|
|
||||||
isMobile,
|
|
||||||
elementOrAnyAncestorHasClass,
|
|
||||||
findNodeById,
|
|
||||||
findNodeMarkerById,
|
|
||||||
hasNodeUplinkedToMqttRecently,
|
|
||||||
buildPath,
|
|
||||||
} from '../utils.js';
|
|
||||||
const mapEl = useTemplateRef('appMap');
|
|
||||||
|
|
||||||
// watchers
|
|
||||||
watch(
|
|
||||||
() => state.positionHistoryDateTimeTo,
|
|
||||||
(newValue) => {
|
|
||||||
if (newValue != null) {
|
|
||||||
loadNodePositionHistory(state.selectedNodeToShowPositionHistory.node_id);
|
|
||||||
}
|
|
||||||
}, {deep: true}
|
|
||||||
);
|
|
||||||
watch(
|
|
||||||
() => state.positionHistoryDateTimeFrom,
|
|
||||||
(newValue) => {
|
|
||||||
if (newValue != null) {
|
|
||||||
loadNodePositionHistory(state.selectedNodeToShowPositionHistory.node_id);
|
|
||||||
}
|
|
||||||
}, {deep: true}
|
|
||||||
);
|
|
||||||
|
|
||||||
function showNodeOutline(id) {
|
|
||||||
// remove any existing node circle
|
|
||||||
clearNodeOutline();
|
|
||||||
|
|
||||||
// find node marker by id
|
|
||||||
const nodeMarker = state.nodeMarkers[id];
|
|
||||||
if (!nodeMarker) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find node by id
|
|
||||||
const node = findNodeById(id);
|
|
||||||
if (!node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add position precision circle around node
|
|
||||||
if(node.position_precision != null && node.position_precision > 0 && node.position_precision < 32){
|
|
||||||
state.selectedNodeOutlineCircle = L.circle(nodeMarker.getLatLng(), {
|
|
||||||
radius: getPositionPrecisionInMeters(node.position_precision),
|
|
||||||
}).addTo(getMap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.showNodeDetails = function(id) {
|
|
||||||
// find node
|
|
||||||
const node = findNodeById(id);
|
|
||||||
if (!node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state.selectedNode = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.showNodeNeighboursThatHeardUs = function(id) {
|
|
||||||
cleanUpNodeNeighbors();
|
|
||||||
|
|
||||||
// find node
|
|
||||||
const node = findNodeById(id);
|
|
||||||
if (!node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find node marker
|
|
||||||
const nodeMarker = findNodeMarkerById(node.node_id);
|
|
||||||
if (!nodeMarker) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// show overlay
|
|
||||||
state.selectedNodeToShowNeighbours = node;
|
|
||||||
state.selectedNodeToShowNeighboursType = 'theyHeard';
|
|
||||||
|
|
||||||
// find all nodes that have us as a neighbour
|
|
||||||
const neighbourNodeInfos = [];
|
|
||||||
for (const nodeThatMayHaveHeardUs of state.nodes) {
|
|
||||||
// find our node in this nodes neighbours
|
|
||||||
const nodeNeighbours = nodeThatMayHaveHeardUs.neighbours ?? [];
|
|
||||||
const neighbour = nodeNeighbours.find(function(neighbour) {
|
|
||||||
return neighbour.node_id.toString() === node.node_id.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
// we exist as a neighbour
|
|
||||||
if (neighbour) {
|
|
||||||
neighbourNodeInfos.push({
|
|
||||||
node: nodeThatMayHaveHeardUs,
|
|
||||||
neighbour: neighbour,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure we have neighbours to show
|
|
||||||
if (neighbourNodeInfos.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add node neighbours
|
|
||||||
for (const neighbourNodeInfo of neighbourNodeInfos) {
|
|
||||||
|
|
||||||
const neighbourNode = neighbourNodeInfo.node;
|
|
||||||
const neighbour = neighbourNodeInfo.neighbour;
|
|
||||||
|
|
||||||
// fixme: skipping zero snr? saw some crazy long neighbours with zero snr...
|
|
||||||
if (neighbour.snr === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find neighbour node marker
|
|
||||||
const neighbourNodeMarker = findNodeMarkerById(neighbourNode.node_id);
|
|
||||||
if (!neighbourNodeMarker) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate distance in meters between nodes (rounded to 2 decimal places)
|
|
||||||
const distanceInMeters = neighbourNodeMarker.getLatLng().distanceTo(nodeMarker.getLatLng()).toFixed(2);
|
|
||||||
|
|
||||||
// don't show this neighbour connection if further than config allows
|
|
||||||
if (neighboursMaxDistance.value != null && parseFloat(distanceInMeters) > neighboursMaxDistance.value) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add neighbour line to map
|
|
||||||
const line = L.polyline([
|
|
||||||
nodeMarker.getLatLng(), // from us
|
|
||||||
neighbourNodeMarker.getLatLng(), // to neighbour
|
|
||||||
], {
|
|
||||||
color: getColourForSnr(neighbour.snr),
|
|
||||||
opacity: 1,
|
|
||||||
}).arrowheads({
|
|
||||||
size: '10px',
|
|
||||||
fill: true,
|
|
||||||
offsets: {
|
|
||||||
start: '25px',
|
|
||||||
end: '25px',
|
|
||||||
},
|
|
||||||
}).addTo(layerGroups.nodeNeighbors);
|
|
||||||
|
|
||||||
const tooltip = getNeighbourTooltipContent('theyHeard', node, neighbourNode, distanceInMeters, neighbour.snr);
|
|
||||||
line.bindTooltip(tooltip, {
|
|
||||||
sticky: true,
|
|
||||||
opacity: 1,
|
|
||||||
interactive: true,
|
|
||||||
}).bindPopup(tooltip).on('click', function(event) {
|
|
||||||
// close tooltip on click to prevent tooltip and popup showing at same time
|
|
||||||
event.target.closeTooltip();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.showNodeNeighboursThatWeHeard = function(id) {
|
|
||||||
cleanUpNodeNeighbors();
|
|
||||||
|
|
||||||
// find node
|
|
||||||
const node = findNodeById(id);
|
|
||||||
if (!node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find node marker
|
|
||||||
const nodeMarker = findNodeMarkerById(node.node_id);
|
|
||||||
if (!nodeMarker) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// show overlay
|
|
||||||
state.selectedNodeToShowNeighbours = node;
|
|
||||||
state.selectedNodeToShowNeighboursType = 'weHeard';
|
|
||||||
|
|
||||||
// ensure we have neighbours to show
|
|
||||||
const neighbours = node.neighbours ?? [];
|
|
||||||
if (neighbours.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const neighbour of neighbours) {
|
|
||||||
// fixme: skipping zero snr? saw some crazy long neighbours with zero snr...
|
|
||||||
if (neighbour.snr === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// find neighbor node
|
|
||||||
const neighbourNode = findNodeById(neighbour.node_id);
|
|
||||||
if (!neighbourNode) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// find neighbor node marker
|
|
||||||
const neighbourNodeMarker = findNodeMarkerById(neighbour.node_id);
|
|
||||||
if (!neighbourNodeMarker) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// calculate distance in meters between nodes (rounded to 2 decimal places)
|
|
||||||
const distanceInMeters = nodeMarker.getLatLng().distanceTo(neighbourNodeMarker.getLatLng()).toFixed(2);
|
|
||||||
|
|
||||||
if (neighboursMaxDistance.value != null && parseFloat(distanceInMeters) > neighboursMaxDistance.value) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add neighbour line to map
|
|
||||||
const line = L.polyline([
|
|
||||||
neighbourNodeMarker.getLatLng(), // from neighbor
|
|
||||||
nodeMarker.getLatLng(), // to us
|
|
||||||
], {
|
|
||||||
color: getColorForSnr(neighbour.snr),
|
|
||||||
opacity: 1,
|
|
||||||
}).arrowheads({
|
|
||||||
size: '10px',
|
|
||||||
fill: true,
|
|
||||||
offsets: {
|
|
||||||
start: '25px',
|
|
||||||
end: '25px',
|
|
||||||
},
|
|
||||||
}).addTo(layerGroups.nodeNeighbors);
|
|
||||||
|
|
||||||
const tooltip = getNeighbourTooltipContent('weHeard', node, neighbourNode, distanceInMeters, neighbour.snr);
|
|
||||||
line.bindTooltip(tooltip, {
|
|
||||||
sticky: true,
|
|
||||||
opacity: 1,
|
|
||||||
interactive: true,
|
|
||||||
}).bindPopup(tooltip).on('click', function(event) {
|
|
||||||
// close tooltip on click to prevent tooltip and popup showing at same time
|
|
||||||
event.target.closeTooltip();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onNodesUpdated(nodes) {
|
|
||||||
const now = moment();
|
|
||||||
state.nodes = [];
|
|
||||||
for (const node of nodes) {
|
|
||||||
// skip nodes older than configured node max age
|
|
||||||
if (nodesMaxAge.value) {
|
|
||||||
const lastUpdatedAgeInMillis = now.diff(moment(node.updated_at));
|
|
||||||
if (lastUpdatedAgeInMillis > nodesMaxAge.value * 1000) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// skip nodes without position
|
|
||||||
if (!node.latitude || !node.longitude) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip nodes with invalid position
|
|
||||||
if (isNaN(node.latitude) || isNaN(node.longitude)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fix lat long
|
|
||||||
node.latitude = node.latitude / 10000000;
|
|
||||||
node.longitude = node.longitude / 10000000;
|
|
||||||
|
|
||||||
// wrap longitude for shortest path, everything to left of australia should be shown on the right
|
|
||||||
let longitude = parseFloat(node.longitude);
|
|
||||||
if (longitude <= 100) {
|
|
||||||
longitude += 360;
|
|
||||||
}
|
|
||||||
|
|
||||||
// icon based on mqtt connection state
|
|
||||||
let icon = icons.mqttDisconnected;
|
|
||||||
|
|
||||||
// use offline icon for nodes older than configured node offline age
|
|
||||||
if (nodesOfflineAge) {
|
|
||||||
const lastUpdatedAgeInMillis = now.diff(moment(node.updated_at));
|
|
||||||
if (lastUpdatedAgeInMillis > nodesOfflineAge.value * 1000) {
|
|
||||||
icon = icons.offline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine if node was recently heard uplinking packets to mqtt
|
|
||||||
const nodeHasUplinkedToMqttRecently = hasNodeUplinkedToMqttRecently(node);
|
|
||||||
if (nodeHasUplinkedToMqttRecently) {
|
|
||||||
icon = icons.mqttConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create node marker
|
|
||||||
const 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: nodeHasUplinkedToMqttRecently ? 1000 : -1000,
|
|
||||||
}).on('click', function(event) {
|
|
||||||
// close tooltip on click to prevent tooltip and popup showing at same time
|
|
||||||
event.target.closeTooltip();
|
|
||||||
});
|
|
||||||
|
|
||||||
// add marker to node layer groups
|
|
||||||
marker.addTo(layerGroups.nodes);
|
|
||||||
layerGroups.nodesClustered.addLayer(marker);
|
|
||||||
|
|
||||||
// add markers for routers and repeaters to routers layer group
|
|
||||||
if (node.role_name === 'ROUTER'
|
|
||||||
|| node.role_name === 'ROUTER_CLIENT'
|
|
||||||
|| node.role_name === 'ROUTER_LATE'
|
|
||||||
|| node.role_name === 'REPEATER') {
|
|
||||||
layerGroups.nodesRouter.addLayer(marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
// show tooltip on desktop only
|
|
||||||
if (!isMobile()) {
|
|
||||||
marker.bindTooltip(getTooltipContentForNode(node), {
|
|
||||||
interactive: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push node and marker to cache
|
|
||||||
state.nodes.push(node);
|
|
||||||
state.nodeMarkers[node.node_id] = marker;
|
|
||||||
|
|
||||||
// show node info tooltip when clicking node marker
|
|
||||||
marker.on('click', function(event) {
|
|
||||||
// close all other popups and tooltips
|
|
||||||
closeAllTooltips();
|
|
||||||
closeAllPopups();
|
|
||||||
|
|
||||||
// find node
|
|
||||||
const node = findNodeById(event.target.options.tagName);
|
|
||||||
if (!node){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// show position precision outline
|
|
||||||
showNodeOutline(node.node_id);
|
|
||||||
|
|
||||||
// open tooltip for node
|
|
||||||
getMap().openTooltip(getTooltipContentForNode(node), event.target.getLatLng(), {
|
|
||||||
interactive: true, // allow clicking buttons inside tooltip
|
|
||||||
permanent: true, // don't auto dismiss when clicking buttons inside tooltip
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for(const node of nodes) {
|
|
||||||
// find current node
|
|
||||||
const currentNode = findNodeMarkerById(node.node_id);
|
|
||||||
if (!currentNode) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add node neighbours
|
|
||||||
var polylineOffset = 0;
|
|
||||||
const neighbours = node.neighbours ?? [];
|
|
||||||
for( const neighbour of neighbours) {
|
|
||||||
// fixme: skipping zero snr? saw some crazy long neighbours with zero snr...
|
|
||||||
if(neighbour.snr === 0){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const neighbourNode = findNodeById(neighbour.node_id);
|
|
||||||
if (!neighbourNode) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const neighbourNodeMarker = findNodeMarkerById(neighbour.node_id);
|
|
||||||
if (neighbourNodeMarker) {
|
|
||||||
|
|
||||||
// calculate distance in meters between nodes (rounded to 2 decimal places)
|
|
||||||
const distanceInMeters = currentNode.getLatLng().distanceTo(neighbourNodeMarker.getLatLng()).toFixed(2);
|
|
||||||
|
|
||||||
// don't show this neighbour connection if further than config allows
|
|
||||||
if (neighboursMaxDistance.value != null && parseFloat(distanceInMeters) > neighboursMaxDistance.value) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add neighbour line to map
|
|
||||||
const line = L.polyline([
|
|
||||||
currentNode.getLatLng(),
|
|
||||||
neighbourNodeMarker.getLatLng(),
|
|
||||||
], {
|
|
||||||
color: '#2563eb',
|
|
||||||
opacity: 0.75,
|
|
||||||
offset: polylineOffset,
|
|
||||||
}).addTo(layerGroups.neighbors);
|
|
||||||
|
|
||||||
// increase offset so next neighbour does not overlay other neighbours from self
|
|
||||||
polylineOffset += 2;
|
|
||||||
|
|
||||||
const tooltip = getNeighbourTooltipContent('weHeard', node, neighbourNode, distanceInMeters, neighbour.snr);
|
|
||||||
line.bindTooltip(tooltip, {
|
|
||||||
sticky: true,
|
|
||||||
opacity: 1,
|
|
||||||
interactive: true,
|
|
||||||
}).bindPopup(tooltip).on('click', function(event) {
|
|
||||||
// close tooltip on click to prevent tooltip and popup showing at same time
|
|
||||||
event.target.closeTooltip();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onWaypointsUpdated(waypoints) {
|
|
||||||
state.waypoints = [];
|
|
||||||
const now = moment();
|
|
||||||
// add nodes
|
|
||||||
for (const waypoint of waypoints) {
|
|
||||||
// skip waypoints older than configured waypoint max age
|
|
||||||
if (waypointsMaxAge.value) {
|
|
||||||
const lastUpdatedAgeInMillis = now.diff(moment(waypoint.updated_at));
|
|
||||||
if (lastUpdatedAgeInMillis > waypointsMaxAge.value * 1000) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip expired waypoints
|
|
||||||
if (waypoint.expire < Date.now() / 1000) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip waypoints without position
|
|
||||||
if (!waypoint.latitude || !waypoint.longitude) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip nodes with invalid position
|
|
||||||
if (isNaN(waypoint.latitude) || isNaN(waypoint.longitude)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fix lat long
|
|
||||||
waypoint.latitude = waypoint.latitude / 10000000;
|
|
||||||
waypoint.longitude = waypoint.longitude / 10000000;
|
|
||||||
|
|
||||||
// wrap longitude for shortest path, everything to left of australia should be shown on the right
|
|
||||||
let longitude = parseFloat(waypoint.longitude);
|
|
||||||
if (longitude <= 100) {
|
|
||||||
longitude += 360;
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine emoji to show as marker icon
|
|
||||||
const emoji = waypoint.icon === 0 ? 128205 : waypoint.icon;
|
|
||||||
const emojiText = String.fromCodePoint(emoji);
|
|
||||||
|
|
||||||
let tooltip = getTooltipContentForWaypoint(waypoint);
|
|
||||||
|
|
||||||
// create waypoint marker
|
|
||||||
const marker = L.marker([waypoint.latitude, longitude], {
|
|
||||||
icon: L.divIcon({
|
|
||||||
className: 'waypoint-label',
|
|
||||||
iconSize: [26, 26], // increase from 12px to 26px
|
|
||||||
html: emojiText,
|
|
||||||
}),
|
|
||||||
}).bindPopup(tooltip).on('click', function(event) {
|
|
||||||
// close tooltip on click to prevent tooltip and popup showing at same time
|
|
||||||
event.target.closeTooltip();
|
|
||||||
});
|
|
||||||
|
|
||||||
// show tooltip on desktop only
|
|
||||||
if (!isMobile()) {
|
|
||||||
marker.bindTooltip(tooltip, {
|
|
||||||
interactive: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// add marker to waypoints layer groups
|
|
||||||
marker.addTo(layerGroups.waypoints)
|
|
||||||
|
|
||||||
// add to cache
|
|
||||||
state.waypoints.push(waypoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPositionHistoryUpdated(updatedPositionHistories) {
|
|
||||||
let positionHistoryLinesCords = [];
|
|
||||||
// add nodes
|
|
||||||
for (const positionHistory of updatedPositionHistories) {
|
|
||||||
// skip position history without position
|
|
||||||
if (!positionHistory.latitude || !positionHistory.longitude) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find node this position is for
|
|
||||||
const node = findNodeById(positionHistory.node_id);
|
|
||||||
if (!node) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip position history without position
|
|
||||||
if (!positionHistory.latitude || !positionHistory.longitude) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip nodes with invalid position
|
|
||||||
if (isNaN(positionHistory.latitude) || isNaN(positionHistory.longitude)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fix lat long
|
|
||||||
positionHistory.latitude = positionHistory.latitude / 10000000;
|
|
||||||
positionHistory.longitude = positionHistory.longitude / 10000000;
|
|
||||||
|
|
||||||
// wrap longitude for shortest path, everything to left of australia should be shown on the right
|
|
||||||
let longitude = parseFloat(positionHistory.longitude);
|
|
||||||
if (longitude <= 100) {
|
|
||||||
longitude += 360;
|
|
||||||
}
|
|
||||||
positionHistoryLinesCords.push([positionHistory.latitude, longitude]);
|
|
||||||
|
|
||||||
let tooltip = "";
|
|
||||||
if(positionHistory.type === "position"){
|
|
||||||
tooltip += `<b>Position</b>`;
|
|
||||||
} else if(positionHistory.type === "map_report"){
|
|
||||||
tooltip += `<b>Map Report</b>`;
|
|
||||||
}
|
|
||||||
tooltip += `<br/>[${escapeString(node.short_name)}] ${escapeString(node.long_name)}`;
|
|
||||||
tooltip += `<br/>${positionHistory.latitude}, ${positionHistory.longitude}`;
|
|
||||||
tooltip += `<br/>Heard on: ${moment(new Date(positionHistory.created_at)).format("DD/MM/YYYY hh:mm A")}`;
|
|
||||||
|
|
||||||
// add gateway info if available
|
|
||||||
if (positionHistory.gateway_id) {
|
|
||||||
const gatewayNode = findNodeById(positionHistory.gateway_id);
|
|
||||||
const gatewayNodeInfo = gatewayNode ? `[${gatewayNode.short_name}] ${gatewayNode.long_name}` : "???";
|
|
||||||
tooltip += `<br/>Heard by: <a href="javascript:void(0);" onclick="goToNode(${positionHistory.gateway_id})">${gatewayNodeInfo}</a>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create position history marker
|
|
||||||
const marker = L.marker([positionHistory.latitude, longitude],{
|
|
||||||
icon: iconPositionHistory,
|
|
||||||
}).bindTooltip(tooltip).bindPopup(tooltip).on('click', function(event) {
|
|
||||||
// close tooltip on click to prevent tooltip and popup showing at same time
|
|
||||||
event.target.closeTooltip();
|
|
||||||
});
|
|
||||||
|
|
||||||
// add marker to position history layer group
|
|
||||||
marker.addTo(layerGroups.nodePositionHistory);
|
|
||||||
}
|
|
||||||
// show lines between position history markers
|
|
||||||
L.polyline(positionHistoryLinesCords).addTo(layerGroups.nodePositionHistory);
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToRandomNode() {
|
|
||||||
if (state.nodes.length > 0) {
|
|
||||||
const randomNode = state.nodes[Math.floor(Math.random() * state.nodes.length)];
|
|
||||||
if (randomNode) {
|
|
||||||
// go to node
|
|
||||||
if (goToNode(randomNode.node_id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// fallback to showing node details since we can't go to the node
|
|
||||||
window.showNodeDetails(randomNode.node_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showNodePositionHistory(nodeId) {
|
|
||||||
// find node
|
|
||||||
const node = findNodeById(nodeId);
|
|
||||||
if (!node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update ui
|
|
||||||
state.selectedNode = null;
|
|
||||||
state.selectedNodeToShowPositionHistory = node;
|
|
||||||
state.positionHistoryModalExpanded = true;
|
|
||||||
|
|
||||||
// close node info tooltip as position history shows under it
|
|
||||||
closeAllTooltips();
|
|
||||||
|
|
||||||
// reset default time range when opening position history ui
|
|
||||||
// YYYY-MM-DDTHH:mm is the format expected by the datetime-local input type
|
|
||||||
state.positionHistoryDateTimeFrom = moment().subtract(1, "hours").format('YYYY-MM-DDTHH:mm');
|
|
||||||
state.positionHistoryDateTimeTo = moment().format('YYYY-MM-DDTHH:mm');
|
|
||||||
|
|
||||||
// load position history
|
|
||||||
loadNodePositionHistory(nodeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadNodePositionHistory(nodeId) {
|
|
||||||
state.selectedNodePositionHistory = [];
|
|
||||||
axios.get(buildPath(`/api/v1/nodes/${nodeId}/position-history`), {
|
|
||||||
params: {
|
|
||||||
// parse from datetime-local format, and send as unix timestamp in milliseconds
|
|
||||||
time_from: moment(state.positionHistoryDateTimeFrom, "YYYY-MM-DDTHH:mm").format("x"),
|
|
||||||
time_to: moment(state.positionHistoryDateTimeTo, "YYYY-MM-DDTHH:mm").format("x"),
|
|
||||||
},
|
|
||||||
}).then((response) => {
|
|
||||||
state.selectedNodePositionHistory = response.data.position_history;
|
|
||||||
if (state.selectedNodeToShowPositionHistory != null) {
|
|
||||||
clearAllPositionHistory();
|
|
||||||
onPositionHistoryUpdated(response.data.position_history);
|
|
||||||
};
|
|
||||||
}).catch(() => {
|
|
||||||
// do nothing
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function reload(goToNodeId, zoom) {
|
|
||||||
// show loading
|
|
||||||
state.loading = true;
|
|
||||||
// clear previous data
|
|
||||||
clearMap();
|
|
||||||
axios.get(buildPath('/api/v1/nodes')).then(response => {
|
|
||||||
// update nodes
|
|
||||||
onNodesUpdated(response.data.nodes);
|
|
||||||
// hide loading
|
|
||||||
state.loading = false;
|
|
||||||
// fetch waypoints (after awaiting nodes, so we can use nodes cache in waypoint tooltips)
|
|
||||||
axios.get(buildPath('/api/v1/waypoints')).then(response => {
|
|
||||||
onWaypointsUpdated(response.data.waypoints);
|
|
||||||
});
|
|
||||||
// go to node id if provided
|
|
||||||
if (goToNodeId) {
|
|
||||||
// go to node
|
|
||||||
if(goToNode(goToNodeId, false, zoom)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// fallback to showing node details since we can't go to the node
|
|
||||||
window.showNodeDetails(goToNodeId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToNode(id, animate, zoom){
|
|
||||||
// find node
|
|
||||||
const node = findNodeById(id);
|
|
||||||
if (!node) {
|
|
||||||
alert("Could not find node: " + id);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find node marker by id
|
|
||||||
const nodeMarker = findNodeMarkerById(id);
|
|
||||||
if (!nodeMarker) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// close all popups and tooltips
|
|
||||||
closeAllPopups();
|
|
||||||
closeAllTooltips();
|
|
||||||
|
|
||||||
// select node
|
|
||||||
showNodeOutline(id);
|
|
||||||
|
|
||||||
// fly to node marker
|
|
||||||
const shouldAnimate = animate != null ? animate : true;
|
|
||||||
getMap().flyTo(nodeMarker.getLatLng(), parseFloat(zoom || goToNodeZoomLevel.value), {
|
|
||||||
animate: enableMapAnimations.value ? shouldAnimate : false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// open tooltip for node
|
|
||||||
getMap().openTooltip(getTooltipContentForNode(node), nodeMarker.getLatLng(), {
|
|
||||||
interactive: true, // allow clicking buttons inside tooltip
|
|
||||||
permanent: true, // don't auto dismiss when clicking buttons inside tooltip
|
|
||||||
});
|
|
||||||
|
|
||||||
// successfully went to node
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSearchResultNodeClick(node) {
|
|
||||||
// clear search
|
|
||||||
state.searchText = '';
|
|
||||||
|
|
||||||
// hide search
|
|
||||||
state.mobileSearchVisible = false;
|
|
||||||
|
|
||||||
// go to node
|
|
||||||
if (goToNode(node.node_id) ){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fallback to showing node details since we can't go to the node
|
|
||||||
window.showNodeDetails(node.node_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// set map bounds to be a little more than full size to prevent panning off screen
|
|
||||||
const bounds = [
|
|
||||||
[-100, 70], // top left
|
|
||||||
[100, 500], // bottom right
|
|
||||||
];
|
|
||||||
// create map positioned over AU and NZ
|
|
||||||
setMap(L.map(mapEl.value, {
|
|
||||||
maxBounds: bounds,
|
|
||||||
}));
|
|
||||||
// set view
|
|
||||||
getMap().setView([-15, 150], 2);
|
|
||||||
// remove leaflet link
|
|
||||||
getMap().attributionControl.setPrefix('');
|
|
||||||
|
|
||||||
// use tile layer based on config
|
|
||||||
const selectedTileLayer = tileLayers[selectedTileLayerName.value] || tileLayers['OpenStreetMap'];
|
|
||||||
selectedTileLayer.addTo(getMap());
|
|
||||||
|
|
||||||
// handle baselayerchange to update tile layer preference
|
|
||||||
getMap().on('baselayerchange', function(event) {
|
|
||||||
selectedTileLayerName.value = event.name;
|
|
||||||
});
|
|
||||||
|
|
||||||
// create legend
|
|
||||||
const legend = L.control({position: 'bottomleft'});
|
|
||||||
legend.onAdd = function (map) {
|
|
||||||
const div = L.DomUtil.create('div', 'leaflet-control-layers');
|
|
||||||
div.style.backgroundColor = 'white';
|
|
||||||
div.style.padding = '12px';
|
|
||||||
div.innerHTML = `<div style="margin-bottom:6px;"><strong>Legend</strong></div>`
|
|
||||||
+ `<div style="display:flex"><div class="icon-mqtt-connected" style="width:12px;height:12px;margin-right:4px;margin-top:auto;margin-bottom:auto;"></div> MQTT Connected</div>`
|
|
||||||
+ `<div style="display:flex"><div class="icon-mqtt-disconnected" style="width:12px;height:12px;margin-right:4px;margin-top:auto;margin-bottom:auto;"></div> MQTT Disconnected</div>`
|
|
||||||
+ `<div style="display:flex"><div class="icon-offline" style="width:12px;height:12px;margin-right:4px;margin-top:auto;margin-bottom:auto;"></div> Offline Too Long</div>`;
|
|
||||||
return div;
|
|
||||||
};
|
|
||||||
// handle adding/remove legend on map (can't use L.Control as an overlay, so we toggle an empty L.LayerGroup)
|
|
||||||
getMap().on('overlayadd overlayremove', function(event) {
|
|
||||||
if (event.name === 'Legend') {
|
|
||||||
if (event.type === 'overlayadd') {
|
|
||||||
getMap().addControl(legend);
|
|
||||||
} else if(event.type === 'overlayremove') {
|
|
||||||
getMap().removeControl(legend);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// add layers to control ui
|
|
||||||
L.control.groupedLayers(tileLayers, {
|
|
||||||
'Nodes': {
|
|
||||||
'All': layerGroups.nodes,
|
|
||||||
'Routers': layerGroups.nodesRouter,
|
|
||||||
'Clustered': layerGroups.nodesClustered,
|
|
||||||
'None': layerGroups.none,
|
|
||||||
},
|
|
||||||
'Overlays': {
|
|
||||||
'Legend': layerGroups.legend,
|
|
||||||
'Neighbors': layerGroups.neighbors,
|
|
||||||
'Waypoints': layerGroups.waypoints,
|
|
||||||
'Position History': layerGroups.nodePositionHistory,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
// make the "Nodes" group exclusive (use radio inputs instead of checkbox)
|
|
||||||
exclusiveGroups: ['Nodes'],
|
|
||||||
}).addTo(getMap());
|
|
||||||
|
|
||||||
// enable base layers
|
|
||||||
layerGroups.nodesClustered.addTo(getMap());
|
|
||||||
|
|
||||||
// enable overlay layers based on config
|
|
||||||
if (enabledOverlayLayers.value.includes('Legend')) {
|
|
||||||
layerGroups.legend.addTo(getMap());
|
|
||||||
}
|
|
||||||
if (enabledOverlayLayers.value.includes('Neighbors')) {
|
|
||||||
layerGroups.neighbors.addTo(getMap());
|
|
||||||
}
|
|
||||||
if (enabledOverlayLayers.value.includes('Waypoints')) {
|
|
||||||
layerGroups.waypoints.addTo(getMap());
|
|
||||||
}
|
|
||||||
if (enabledOverlayLayers.value.includes('Position History')) {
|
|
||||||
layerGroups.nodePositionHistory.addTo(getMap());
|
|
||||||
}
|
|
||||||
// update config when map overlay is added/removed
|
|
||||||
getMap().on('overlayremove', function(event) {
|
|
||||||
const layerName = event.name;
|
|
||||||
enabledOverlayLayers.value = enabledOverlayLayers.value.filter(function(enabledOverlayLayer) {
|
|
||||||
return enabledOverlayLayer !== layerName;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
getMap().on('overlayadd', function(event) {
|
|
||||||
const layerName = event.name;
|
|
||||||
if (!enabledOverlayLayers.value.includes(layerName)) {
|
|
||||||
enabledOverlayLayers.value.push(layerName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
getMap().on('click', function(event) {
|
|
||||||
// remove outline when map clicked
|
|
||||||
clearNodeOutline();
|
|
||||||
// clear search
|
|
||||||
state.searchText = '';
|
|
||||||
state.mobileSearchVisible = false;
|
|
||||||
// do nothing when clicking inside tooltip
|
|
||||||
const clickedElement = event.originalEvent.target;
|
|
||||||
if (elementOrAnyAncestorHasClass(clickedElement, 'leaflet-tooltip')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
closeAllTooltips();
|
|
||||||
closeAllPopups();
|
|
||||||
});
|
|
||||||
|
|
||||||
// auto update url when lat/lng/zoom changes
|
|
||||||
getMap().on('moveend zoomend', function() {
|
|
||||||
// check if user enabled auto updating position in url
|
|
||||||
if (!autoUpdatePositionInUrl.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get map info
|
|
||||||
const latLng = getMap().getCenter();
|
|
||||||
const zoom = getMap().getZoom();
|
|
||||||
|
|
||||||
// construct new url
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
url.searchParams.set('lat', latLng.lat);
|
|
||||||
url.searchParams.set('lng', latLng.lng);
|
|
||||||
url.searchParams.set('zoom', zoom);
|
|
||||||
|
|
||||||
// update current url
|
|
||||||
if(window.history.replaceState){
|
|
||||||
window.history.replaceState(null, null, url.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// parse url params
|
|
||||||
const queryParams = new URLSearchParams(location.search);
|
|
||||||
const queryNodeId = queryParams.get('node_id');
|
|
||||||
const queryLat = queryParams.get('lat');
|
|
||||||
const queryLng = queryParams.get('lng');
|
|
||||||
const queryZoom = queryParams.get('zoom');
|
|
||||||
|
|
||||||
// go to lat/lng if provided
|
|
||||||
if(queryLat && queryLng){
|
|
||||||
const zoomLevel = queryZoom || goToNodeZoomLevel.value
|
|
||||||
getMap().flyTo([queryLat, queryLng], parseFloat(zoomLevel), {
|
|
||||||
animate: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// reload and go to provided node id
|
|
||||||
reload(queryNodeId, queryZoom);
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (lastSeenAnnouncementId.value !== CURRENT_ANNOUNCEMENT_ID) {
|
|
||||||
state.announcementVisible = true;
|
|
||||||
}
|
|
||||||
if (!isMobile() && hasSeenInfoModal.value === false) {
|
|
||||||
state.infoModalVisible = true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
|
||||||
<div class="flex flex-col h-full">
|
|
||||||
<Announcement />
|
|
||||||
<Header
|
|
||||||
@reload="reload"
|
|
||||||
@random-node="goToRandomNode"
|
|
||||||
@search-click="onSearchResultNodeClick"
|
|
||||||
/>
|
|
||||||
<div id="map" style="width:100%;height:100%;" ref="appMap"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<InfoModal />
|
|
||||||
<HardwareModelList />
|
|
||||||
<Settings />
|
|
||||||
<NodeInfo @show-position-history="showNodePositionHistory"/>
|
|
||||||
<NodeNeighborsModal @dismiss="cleanUpNodeNeighbors" />
|
|
||||||
<NodePositionHistoryModal @dismiss="cleanUpPositionHistory" />
|
|
||||||
<TracerouteInfo @go-to="goToNode" />
|
|
||||||
</template>
|
|
1
lora/.npmrc
Normal file
1
lora/.npmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
@jsr:registry=https://npm.jsr.io
|
60
lora/LoraStream.js
Normal file
60
lora/LoraStream.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { Transform } from 'stream';
|
||||||
|
|
||||||
|
export default class LoraStream extends Transform {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
this.byteBuffer = new Uint8Array([]);
|
||||||
|
this.textDecoder = new TextDecoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform(chunk, encoding, callback) {
|
||||||
|
this.byteBuffer = new Uint8Array([
|
||||||
|
...this.byteBuffer,
|
||||||
|
...chunk,
|
||||||
|
]);
|
||||||
|
let processingExhausted = false;
|
||||||
|
while (this.byteBuffer.length !== 0 && !processingExhausted) {
|
||||||
|
const framingIndex = this.byteBuffer.findIndex((byte) => byte === 0x94);
|
||||||
|
const framingByte2 = this.byteBuffer[framingIndex + 1];
|
||||||
|
if (framingByte2 === 0xc3) {
|
||||||
|
if (this.byteBuffer.subarray(0, framingIndex).length) {
|
||||||
|
this.byteBuffer = this.byteBuffer.subarray(framingIndex);
|
||||||
|
}
|
||||||
|
const msb = this.byteBuffer[2];
|
||||||
|
const lsb = this.byteBuffer[3];
|
||||||
|
if (msb !== undefined && lsb !== undefined && this.byteBuffer.length >= 4 + (msb << 8) + lsb) {
|
||||||
|
const packet = this.byteBuffer.subarray(4, 4 + (msb << 8) + lsb);
|
||||||
|
|
||||||
|
const malformedDetectorIndex = packet.findIndex(
|
||||||
|
(byte) => byte === 0x94,
|
||||||
|
);
|
||||||
|
if (malformedDetectorIndex !== -1 && packet[malformedDetectorIndex + 1] === 0xc3) {
|
||||||
|
// malformed
|
||||||
|
this.byteBuffer = this.byteBuffer.subarray(malformedDetectorIndex);
|
||||||
|
} else {
|
||||||
|
this.byteBuffer = this.byteBuffer.subarray(3 + (msb << 8) + lsb + 1);
|
||||||
|
this.push(packet);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/** Only partioal message in buffer, wait for the rest */
|
||||||
|
processingExhausted = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/** Message not complete, only 1 byte in buffer */
|
||||||
|
processingExhausted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
_flush(callback) {
|
||||||
|
try {
|
||||||
|
if (this._buffer) {
|
||||||
|
this.push(this._buffer.trim());
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
} catch (err) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
lora/MeshtasticStream.js
Normal file
62
lora/MeshtasticStream.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { Transform } from 'stream';
|
||||||
|
import { Mesh, Channel, Config, ModuleConfig } from '@meshtastic/protobufs';
|
||||||
|
import { fromBinary, create } from '@bufbuild/protobuf';
|
||||||
|
|
||||||
|
export default class MeshtasticStream extends Transform {
|
||||||
|
constructor(options) {
|
||||||
|
super({ readableObjectMode: true, ...options });
|
||||||
|
}
|
||||||
|
_transform(chunk, encoding, callback) {
|
||||||
|
const dataPacket = fromBinary(Mesh.FromRadioSchema, chunk);
|
||||||
|
let schema = null;
|
||||||
|
switch(dataPacket.payloadVariant.case) {
|
||||||
|
case 'packet':
|
||||||
|
schema = Mesh.MeshPacketSchema;
|
||||||
|
break;
|
||||||
|
case 'nodeInfo':
|
||||||
|
schema = Mesh.NodeInfoSchema;
|
||||||
|
break;
|
||||||
|
case 'myInfo':
|
||||||
|
schema = Mesh.MyNodeInfoSchema;
|
||||||
|
break;
|
||||||
|
case 'deviceuiConfig':
|
||||||
|
// can't find, come back to this
|
||||||
|
break;
|
||||||
|
case 'config':
|
||||||
|
schema = Config.ConfigSchema
|
||||||
|
break;
|
||||||
|
case 'moduleConfig':
|
||||||
|
schema = ModuleConfig.ModuleConfigSchema
|
||||||
|
break;
|
||||||
|
case 'fileInfo':
|
||||||
|
schema = Mesh.FileInfoSchema
|
||||||
|
break;
|
||||||
|
case 'channel':
|
||||||
|
schema = Channel.ChannelSchema
|
||||||
|
break;
|
||||||
|
case 'metadata':
|
||||||
|
schema = Mesh.DeviceMetadataSchema
|
||||||
|
break;
|
||||||
|
case 'logRecord':
|
||||||
|
schema = Mesh.LogRecordSchema
|
||||||
|
break;
|
||||||
|
case 'configCompleteId':
|
||||||
|
// done sending init data
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (schema !== null) {
|
||||||
|
this.push(create(schema, dataPacket.payloadVariant.value));
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
_flush(callback) {
|
||||||
|
try {
|
||||||
|
if (this._buffer) {
|
||||||
|
this.push(this._buffer.trim());
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
} catch (err) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
107
lora/index.js
Normal file
107
lora/index.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { Mesh, Mqtt, Portnums, Telemetry, Config, Channel } from '@meshtastic/protobufs';
|
||||||
|
import { fromBinary, toBinary, create } from '@bufbuild/protobuf';
|
||||||
|
import { LoraStream } from './LoraStream';
|
||||||
|
import { MeshtasticStream } from './MeshtasticStream';
|
||||||
|
import net from 'net';
|
||||||
|
|
||||||
|
const client = new net.Socket();
|
||||||
|
const IP = '192.168.10.117';
|
||||||
|
const PORT = 4403;
|
||||||
|
|
||||||
|
function sendHello() {
|
||||||
|
const data = create(Mesh.ToRadioSchema, {
|
||||||
|
payloadVariant: {
|
||||||
|
case: 'wantConfigId',
|
||||||
|
value: 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sendToDevice(toBinary(Mesh.ToRadioSchema, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendToDevice(data) {
|
||||||
|
const bufferLength = data.length;
|
||||||
|
const header = new Uint8Array([
|
||||||
|
0x94,
|
||||||
|
0xC3,
|
||||||
|
(bufferLength >> 8) & 0xFF,
|
||||||
|
bufferLength & 0xFF,
|
||||||
|
]);
|
||||||
|
data = new Uint8Array([...header, ...data]);
|
||||||
|
client.write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.connect(PORT, IP, function() {
|
||||||
|
console.log('Connected');
|
||||||
|
sendHello();
|
||||||
|
});
|
||||||
|
|
||||||
|
const meshtasticStream = new MeshtasticStream();
|
||||||
|
client.pipe(new LoraStream()).pipe(meshtasticStream);
|
||||||
|
meshtasticStream.on('data', (data) => {
|
||||||
|
parseMeshtastic(data['$typeName'], data);
|
||||||
|
})
|
||||||
|
|
||||||
|
function parseMeshtastic(typeName, data) {
|
||||||
|
switch(typeName) {
|
||||||
|
case Mesh.MeshPacketSchema.typeName:
|
||||||
|
onMeshPacket(data);
|
||||||
|
break;
|
||||||
|
case Mesh.NodeInfoSchema.typeName:
|
||||||
|
console.log('node info');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMeshPacket(envelope) {
|
||||||
|
const payloadVariant = envelope.payloadVariant.case;
|
||||||
|
|
||||||
|
if (payloadVariant === 'encrypted') {
|
||||||
|
// attempt decryption
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataPacket = envelope.payloadVariant.value;
|
||||||
|
const portNum = dataPacket.portnum;
|
||||||
|
|
||||||
|
if (!portNum) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = null;
|
||||||
|
switch (portNum) {
|
||||||
|
case Portnums.PortNum.POSITION_APP:
|
||||||
|
schema = Mesh.PositionSchema;
|
||||||
|
break;
|
||||||
|
case Portnums.PortNum.TELEMETRY_APP:
|
||||||
|
schema = Telemetry.TelemetrySchema;
|
||||||
|
break;
|
||||||
|
case Portnums.PortNum.TEXT_MESSAGE_APP:
|
||||||
|
// no schema?
|
||||||
|
break;
|
||||||
|
case Portnums.PortNum.WAYPOINT_APP:
|
||||||
|
schema = Mesh.WaypointSchema;
|
||||||
|
break;
|
||||||
|
case Portnums.PortNum.TRACEROUTE_APP:
|
||||||
|
schema = Mesh.RouteDiscoverySchema;
|
||||||
|
break;
|
||||||
|
case Portnums.PortNum.NODEINFO_APP:
|
||||||
|
schema = Mesh.NodeInfoSchema;
|
||||||
|
break;
|
||||||
|
case Portnums.PortNum.NEIGHBORINFO_APP:
|
||||||
|
schema = Mesh.NeighborInfoSchema;
|
||||||
|
break;
|
||||||
|
case Portnums.PortNum.MAP_REPORT_APP:
|
||||||
|
schema = Mqtt.MapReportSchema;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let decodedData = dataPacket.payload;
|
||||||
|
if (schema !== null) {
|
||||||
|
try {
|
||||||
|
decodedData = fromBinary(schema, decodedData);
|
||||||
|
console.log(decodedData);
|
||||||
|
} catch(e) {
|
||||||
|
// ignore errors, likely incomplete data
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
341
lora/package-lock.json
generated
Normal file
341
lora/package-lock.json
generated
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
{
|
||||||
|
"name": "lora",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "lora",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@bufbuild/protobuf": "^2.2.5",
|
||||||
|
"@meshtastic/protobufs": "npm:@jsr/meshtastic__protobufs@^2.6.2",
|
||||||
|
"@prisma/client": "^5.11.0",
|
||||||
|
"command-line-args": "^5.2.1",
|
||||||
|
"command-line-usage": "^7.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prisma": "^5.10.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@bufbuild/protobuf": {
|
||||||
|
"version": "2.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.5.tgz",
|
||||||
|
"integrity": "sha512-/g5EzJifw5GF8aren8wZ/G5oMuPoGeS6MQD3ca8ddcvdXR5UELUfdTZITCGNhNXynY/AYl3Z4plmxdj/tRl/hQ==",
|
||||||
|
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||||
|
},
|
||||||
|
"node_modules/@meshtastic/protobufs": {
|
||||||
|
"name": "@jsr/meshtastic__protobufs",
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://npm.jsr.io/~/11/@jsr/meshtastic__protobufs/2.6.2.tgz",
|
||||||
|
"integrity": "sha512-bIENtFnUEru28GrAeSdiBS9skp0hN/3HZunMbF/IjvUrXOlx2fptKVj3b+pzjOWnLBZxllrByV/W+XDmrxqJ6g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@bufbuild/protobuf": "^2.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/client": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.13"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prisma": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"prisma": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/debug": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/engines": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "5.22.0",
|
||||||
|
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
||||||
|
"@prisma/fetch-engine": "5.22.0",
|
||||||
|
"@prisma/get-platform": "5.22.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/engines-version": {
|
||||||
|
"version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz",
|
||||||
|
"integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/fetch-engine": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "5.22.0",
|
||||||
|
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
||||||
|
"@prisma/get-platform": "5.22.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/get-platform": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "5.22.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/array-back": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chalk": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chalk-template": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^4.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk-template?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/command-line-args": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"array-back": "^3.1.0",
|
||||||
|
"find-replace": "^3.0.0",
|
||||||
|
"lodash.camelcase": "^4.3.0",
|
||||||
|
"typical": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/command-line-usage": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"array-back": "^6.2.2",
|
||||||
|
"chalk-template": "^0.4.0",
|
||||||
|
"table-layout": "^4.1.0",
|
||||||
|
"typical": "^7.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/command-line-usage/node_modules/array-back": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/command-line-usage/node_modules/typical": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/find-replace": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"array-back": "^3.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lodash.camelcase": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/prisma": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==",
|
||||||
|
"devOptional": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/engines": "5.22.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"prisma": "build/index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.13"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/table-layout": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"array-back": "^6.2.2",
|
||||||
|
"wordwrapjs": "^5.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/table-layout/node_modules/array-back": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typical": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wordwrapjs": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.17"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
lora/package.json
Normal file
19
lora/package.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "lora",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@bufbuild/protobuf": "^2.2.5",
|
||||||
|
"@meshtastic/protobufs": "npm:@jsr/meshtastic__protobufs@^2.6.2",
|
||||||
|
"@prisma/client": "^5.11.0",
|
||||||
|
"command-line-args": "^5.2.1",
|
||||||
|
"command-line-usage": "^7.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prisma": "^5.10.2"
|
||||||
|
}
|
||||||
|
}
|
@ -1,39 +0,0 @@
|
|||||||
[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
|
|
@ -1,15 +0,0 @@
|
|||||||
[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
|
|
1
mqtt/.npmrc
Normal file
1
mqtt/.npmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
@jsr:registry=https://npm.jsr.io
|
11
mqtt/Dockerfile
Normal file
11
mqtt/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM node:lts-alpine3.17
|
||||||
|
|
||||||
|
# add project files to /app
|
||||||
|
ADD ./mqtt /app
|
||||||
|
ADD ./common /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# install node dependencies
|
||||||
|
RUN npm install && npx prisma generate
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
1399
mqtt/index.js
Normal file
1399
mqtt/index.js
Normal file
File diff suppressed because it is too large
Load Diff
877
mqtt/package-lock.json
generated
Normal file
877
mqtt/package-lock.json
generated
Normal file
@ -0,0 +1,877 @@
|
|||||||
|
{
|
||||||
|
"name": "mqtt",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "mqtt",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@bufbuild/protobuf": "^2.2.5",
|
||||||
|
"@meshtastic/protobufs": "npm:@jsr/meshtastic__protobufs@^2.6.2",
|
||||||
|
"@prisma/client": "^5.11.0",
|
||||||
|
"command-line-args": "^5.2.1",
|
||||||
|
"command-line-usage": "^7.0.1",
|
||||||
|
"mqtt": "^5.11.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prisma": "^5.10.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
|
||||||
|
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"regenerator-runtime": "^0.14.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@bufbuild/protobuf": {
|
||||||
|
"version": "2.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.5.tgz",
|
||||||
|
"integrity": "sha512-/g5EzJifw5GF8aren8wZ/G5oMuPoGeS6MQD3ca8ddcvdXR5UELUfdTZITCGNhNXynY/AYl3Z4plmxdj/tRl/hQ==",
|
||||||
|
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||||
|
},
|
||||||
|
"node_modules/@meshtastic/protobufs": {
|
||||||
|
"name": "@jsr/meshtastic__protobufs",
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://npm.jsr.io/~/11/@jsr/meshtastic__protobufs/2.6.2.tgz",
|
||||||
|
"integrity": "sha512-bIENtFnUEru28GrAeSdiBS9skp0hN/3HZunMbF/IjvUrXOlx2fptKVj3b+pzjOWnLBZxllrByV/W+XDmrxqJ6g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@bufbuild/protobuf": "^2.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/client": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.13"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prisma": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"prisma": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/debug": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/engines": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "5.22.0",
|
||||||
|
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
||||||
|
"@prisma/fetch-engine": "5.22.0",
|
||||||
|
"@prisma/get-platform": "5.22.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/engines-version": {
|
||||||
|
"version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz",
|
||||||
|
"integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/fetch-engine": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "5.22.0",
|
||||||
|
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
||||||
|
"@prisma/get-platform": "5.22.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/get-platform": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "5.22.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "22.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz",
|
||||||
|
"integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/readable-stream": {
|
||||||
|
"version": "4.0.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.18.tgz",
|
||||||
|
"integrity": "sha512-21jK/1j+Wg+7jVw1xnSwy/2Q1VgVjWuFssbYGTREPUBeZ+rqVFl2udq0IkxzPC0ZhOzVceUbyIACFZKLqKEBlA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"safe-buffer": "~5.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/ws": {
|
||||||
|
"version": "8.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||||
|
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/abort-controller": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"event-target-shim": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/array-back": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/base64-js": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/bl": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bl/-/bl-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-ClDyJGQkc8ZtzdAAbAwBmhMSpwN/sC9HA8jxdYm6nVUbCfZbe2mgza4qh7AuEYyEPB/c4Kznf9s66bnsKMQDjw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/readable-stream": "^4.0.0",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
|
"inherits": "^2.0.4",
|
||||||
|
"readable-stream": "^4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/buffer": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/buffer-from": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/chalk": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chalk-template": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^4.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk-template?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/command-line-args": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"array-back": "^3.1.0",
|
||||||
|
"find-replace": "^3.0.0",
|
||||||
|
"lodash.camelcase": "^4.3.0",
|
||||||
|
"typical": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/command-line-usage": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"array-back": "^6.2.2",
|
||||||
|
"chalk-template": "^0.4.0",
|
||||||
|
"table-layout": "^4.1.0",
|
||||||
|
"typical": "^7.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/command-line-usage/node_modules/array-back": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/command-line-usage/node_modules/typical": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/commist": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/concat-stream": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
|
||||||
|
"engines": [
|
||||||
|
"node >= 6.0"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-from": "^1.0.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"readable-stream": "^3.0.2",
|
||||||
|
"typedarray": "^0.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/concat-stream/node_modules/readable-stream": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/event-target-shim": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/events": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fast-unique-numbers": {
|
||||||
|
"version": "8.0.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-8.0.13.tgz",
|
||||||
|
"integrity": "sha512-7OnTFAVPefgw2eBJ1xj2PGGR9FwYzSUso9decayHgCDX4sJkHLdcsYTytTg+tYv+wKF3U8gJuSBz2jJpQV4u/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.8",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/find-replace": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"array-back": "^3.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/help-me": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/ieee754": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/ip-address": {
|
||||||
|
"version": "9.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
|
||||||
|
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jsbn": "1.1.0",
|
||||||
|
"sprintf-js": "^1.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/js-sdsl": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/js-sdsl"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jsbn": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.camelcase": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lru-cache": {
|
||||||
|
"version": "10.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||||
|
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/minimist": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mqtt": {
|
||||||
|
"version": "5.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.11.0.tgz",
|
||||||
|
"integrity": "sha512-VDqfADTNvohwcY02NgxPb7OojIeDrNQ1q62r/DcM+bnIWY8LBi3nMTvdEaFEp6Bu4ejBIpHjJVthUEgnvGLemA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/readable-stream": "^4.0.18",
|
||||||
|
"@types/ws": "^8.5.14",
|
||||||
|
"commist": "^3.2.0",
|
||||||
|
"concat-stream": "^2.0.0",
|
||||||
|
"debug": "^4.4.0",
|
||||||
|
"help-me": "^5.0.0",
|
||||||
|
"lru-cache": "^10.4.3",
|
||||||
|
"minimist": "^1.2.8",
|
||||||
|
"mqtt-packet": "^9.0.2",
|
||||||
|
"number-allocator": "^1.0.14",
|
||||||
|
"readable-stream": "^4.7.0",
|
||||||
|
"reinterval": "^1.1.0",
|
||||||
|
"rfdc": "^1.4.1",
|
||||||
|
"socks": "^2.8.3",
|
||||||
|
"split2": "^4.2.0",
|
||||||
|
"worker-timers": "^7.1.8",
|
||||||
|
"ws": "^8.18.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"mqtt": "build/bin/mqtt.js",
|
||||||
|
"mqtt_pub": "build/bin/pub.js",
|
||||||
|
"mqtt_sub": "build/bin/sub.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mqtt-packet": {
|
||||||
|
"version": "9.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.2.tgz",
|
||||||
|
"integrity": "sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bl": "^6.0.8",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"process-nextick-args": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/number-allocator": {
|
||||||
|
"version": "1.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz",
|
||||||
|
"integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.3.1",
|
||||||
|
"js-sdsl": "4.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prisma": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==",
|
||||||
|
"devOptional": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/engines": "5.22.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"prisma": "build/index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.13"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/process": {
|
||||||
|
"version": "0.11.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||||
|
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/process-nextick-args": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/readable-stream": {
|
||||||
|
"version": "4.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
|
||||||
|
"integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
|
"events": "^3.3.0",
|
||||||
|
"process": "^0.11.10",
|
||||||
|
"string_decoder": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/reinterval": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/rfdc": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/safe-buffer": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/smart-buffer": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0",
|
||||||
|
"npm": ">= 3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socks": {
|
||||||
|
"version": "2.8.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz",
|
||||||
|
"integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ip-address": "^9.0.5",
|
||||||
|
"smart-buffer": "^4.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0",
|
||||||
|
"npm": ">= 3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/split2": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sprintf-js": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/string_decoder": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "~5.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/string_decoder/node_modules/safe-buffer": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/table-layout": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"array-back": "^6.2.2",
|
||||||
|
"wordwrapjs": "^5.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/table-layout/node_modules/array-back": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
|
"node_modules/typedarray": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/typical": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "6.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/wordwrapjs": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/worker-timers": {
|
||||||
|
"version": "7.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-7.1.8.tgz",
|
||||||
|
"integrity": "sha512-R54psRKYVLuzff7c1OTFcq/4Hue5Vlz4bFtNEIarpSiCYhpifHU3aIQI29S84o1j87ePCYqbmEJPqwBTf+3sfw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.24.5",
|
||||||
|
"tslib": "^2.6.2",
|
||||||
|
"worker-timers-broker": "^6.1.8",
|
||||||
|
"worker-timers-worker": "^7.0.71"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/worker-timers-broker": {
|
||||||
|
"version": "6.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-6.1.8.tgz",
|
||||||
|
"integrity": "sha512-FUCJu9jlK3A8WqLTKXM9E6kAmI/dR1vAJ8dHYLMisLNB/n3GuaFIjJ7pn16ZcD1zCOf7P6H62lWIEBi+yz/zQQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.24.5",
|
||||||
|
"fast-unique-numbers": "^8.0.13",
|
||||||
|
"tslib": "^2.6.2",
|
||||||
|
"worker-timers-worker": "^7.0.71"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/worker-timers-worker": {
|
||||||
|
"version": "7.0.71",
|
||||||
|
"resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-7.0.71.tgz",
|
||||||
|
"integrity": "sha512-ks/5YKwZsto1c2vmljroppOKCivB/ma97g9y77MAAz2TBBjPPgpoOiS1qYQKIgvGTr2QYPT3XhJWIB6Rj2MVPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.24.5",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
|
||||||
|
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
mqtt/package.json
Normal file
20
mqtt/package.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "mqtt",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@bufbuild/protobuf": "^2.2.5",
|
||||||
|
"@meshtastic/protobufs": "npm:@jsr/meshtastic__protobufs@^2.6.2",
|
||||||
|
"@prisma/client": "^5.11.0",
|
||||||
|
"command-line-args": "^5.2.1",
|
||||||
|
"command-line-usage": "^7.0.1",
|
||||||
|
"mqtt": "^5.11.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prisma": "^5.10.2"
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
class NodeIdUtil {
|
export default class NodeIdUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the provided hex id to a numeric id, for example: !FFFFFFFF to 4294967295
|
* Converts the provided hex id to a numeric id, for example: !FFFFFFFF to 4294967295
|
||||||
@ -19,5 +19,3 @@ class NodeIdUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = NodeIdUtil;
|
|
@ -1,4 +1,4 @@
|
|||||||
class PositionUtil {
|
export default class PositionUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obfuscates the provided latitude or longitude down to the provided precision in bits.
|
* Obfuscates the provided latitude or longitude down to the provided precision in bits.
|
||||||
@ -62,5 +62,3 @@ class PositionUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PositionUtil;
|
|
5057
package-lock.json
generated
5057
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
Before Width: | Height: | Size: 270 KiB |
1390
src/mqtt.js
1390
src/mqtt.js
File diff suppressed because it is too large
Load Diff
@ -1,12 +0,0 @@
|
|||||||
*AdminMessage.payload_variant anonymous_oneof:true
|
|
||||||
|
|
||||||
*AdminMessage.set_canned_message_module_messages max_size:201
|
|
||||||
*AdminMessage.get_canned_message_module_messages_response max_size:201
|
|
||||||
*AdminMessage.delete_file_request max_size:201
|
|
||||||
|
|
||||||
*AdminMessage.set_ringtone_message max_size:231
|
|
||||||
*AdminMessage.get_ringtone_response max_size:231
|
|
||||||
|
|
||||||
*HamParameters.call_sign max_size:8
|
|
||||||
*HamParameters.short_name max_size:6
|
|
||||||
*NodeRemoteHardwarePinsResponse.node_remote_hardware_pins max_count:16
|
|
@ -1,364 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package meshtastic;
|
|
||||||
|
|
||||||
import "meshtastic/channel.proto";
|
|
||||||
import "meshtastic/config.proto";
|
|
||||||
import "meshtastic/connection_status.proto";
|
|
||||||
import "meshtastic/deviceonly.proto";
|
|
||||||
import "meshtastic/mesh.proto";
|
|
||||||
import "meshtastic/module_config.proto";
|
|
||||||
|
|
||||||
option csharp_namespace = "Meshtastic.Protobufs";
|
|
||||||
option go_package = "github.com/meshtastic/go/generated";
|
|
||||||
option java_outer_classname = "AdminProtos";
|
|
||||||
option java_package = "com.geeksville.mesh";
|
|
||||||
option swift_prefix = "";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This message is handled by the Admin module and is responsible for all settings/channel read/write operations.
|
|
||||||
* This message is used to do settings operations to both remote AND local nodes.
|
|
||||||
* (Prior to 1.2 these operations were done via special ToRadio operations)
|
|
||||||
*/
|
|
||||||
message AdminMessage {
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
enum ConfigType {
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
DEVICE_CONFIG = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
POSITION_CONFIG = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
POWER_CONFIG = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
NETWORK_CONFIG = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
DISPLAY_CONFIG = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
LORA_CONFIG = 5;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
BLUETOOTH_CONFIG = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
enum ModuleConfigType {
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
MQTT_CONFIG = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
SERIAL_CONFIG = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
EXTNOTIF_CONFIG = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
STOREFORWARD_CONFIG = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
RANGETEST_CONFIG = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
TELEMETRY_CONFIG = 5;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
CANNEDMSG_CONFIG = 6;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
AUDIO_CONFIG = 7;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
REMOTEHARDWARE_CONFIG = 8;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
NEIGHBORINFO_CONFIG = 9;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
AMBIENTLIGHTING_CONFIG = 10;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
DETECTIONSENSOR_CONFIG = 11;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
PAXCOUNTER_CONFIG = 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
oneof payload_variant {
|
|
||||||
/*
|
|
||||||
* Send the specified channel in the response to this message
|
|
||||||
* NOTE: This field is sent with the channel index + 1 (to ensure we never try to send 'zero' - which protobufs treats as not present)
|
|
||||||
*/
|
|
||||||
uint32 get_channel_request = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
Channel get_channel_response = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send the current owner data in the response to this message.
|
|
||||||
*/
|
|
||||||
bool get_owner_request = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
User get_owner_response = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ask for the following config data to be sent
|
|
||||||
*/
|
|
||||||
ConfigType get_config_request = 5;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send the current Config in the response to this message.
|
|
||||||
*/
|
|
||||||
Config get_config_response = 6;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ask for the following config data to be sent
|
|
||||||
*/
|
|
||||||
ModuleConfigType get_module_config_request = 7;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send the current Config in the response to this message.
|
|
||||||
*/
|
|
||||||
ModuleConfig get_module_config_response = 8;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the Canned Message Module messages in the response to this message.
|
|
||||||
*/
|
|
||||||
bool get_canned_message_module_messages_request = 10;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the Canned Message Module messages in the response to this message.
|
|
||||||
*/
|
|
||||||
string get_canned_message_module_messages_response = 11;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Request the node to send device metadata (firmware, protobuf version, etc)
|
|
||||||
*/
|
|
||||||
bool get_device_metadata_request = 12;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Device metadata response
|
|
||||||
*/
|
|
||||||
DeviceMetadata get_device_metadata_response = 13;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the Ringtone in the response to this message.
|
|
||||||
*/
|
|
||||||
bool get_ringtone_request = 14;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the Ringtone in the response to this message.
|
|
||||||
*/
|
|
||||||
string get_ringtone_response = 15;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Request the node to send it's connection status
|
|
||||||
*/
|
|
||||||
bool get_device_connection_status_request = 16;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Device connection status response
|
|
||||||
*/
|
|
||||||
DeviceConnectionStatus get_device_connection_status_response = 17;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Setup a node for licensed amateur (ham) radio operation
|
|
||||||
*/
|
|
||||||
HamParameters set_ham_mode = 18;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the mesh's nodes with their available gpio pins for RemoteHardware module use
|
|
||||||
*/
|
|
||||||
bool get_node_remote_hardware_pins_request = 19;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Respond with the mesh's nodes with their available gpio pins for RemoteHardware module use
|
|
||||||
*/
|
|
||||||
NodeRemoteHardwarePinsResponse get_node_remote_hardware_pins_response = 20;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Enter (UF2) DFU mode
|
|
||||||
* Only implemented on NRF52 currently
|
|
||||||
*/
|
|
||||||
bool enter_dfu_mode_request = 21;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Delete the file by the specified path from the device
|
|
||||||
*/
|
|
||||||
string delete_file_request = 22;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set the owner for this node
|
|
||||||
*/
|
|
||||||
User set_owner = 32;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set channels (using the new API).
|
|
||||||
* A special channel is the "primary channel".
|
|
||||||
* The other records are secondary channels.
|
|
||||||
* Note: only one channel can be marked as primary.
|
|
||||||
* If the client sets a particular channel to be primary, the previous channel will be set to SECONDARY automatically.
|
|
||||||
*/
|
|
||||||
Channel set_channel = 33;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set the current Config
|
|
||||||
*/
|
|
||||||
Config set_config = 34;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set the current Config
|
|
||||||
*/
|
|
||||||
ModuleConfig set_module_config = 35;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set the Canned Message Module messages text.
|
|
||||||
*/
|
|
||||||
string set_canned_message_module_messages = 36;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set the ringtone for ExternalNotification.
|
|
||||||
*/
|
|
||||||
string set_ringtone_message = 37;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Remove the node by the specified node-num from the NodeDB on the device
|
|
||||||
*/
|
|
||||||
uint32 remove_by_nodenum = 38;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Begins an edit transaction for config, module config, owner, and channel settings changes
|
|
||||||
* This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings)
|
|
||||||
*/
|
|
||||||
bool begin_edit_settings = 64;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Commits an open transaction for any edits made to config, module config, owner, and channel settings
|
|
||||||
*/
|
|
||||||
bool commit_edit_settings = 65;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot)
|
|
||||||
* Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth.
|
|
||||||
*/
|
|
||||||
int32 reboot_ota_seconds = 95;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This message is only supported for the simulator Portduino build.
|
|
||||||
* If received the simulator will exit successfully.
|
|
||||||
*/
|
|
||||||
bool exit_simulator = 96;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tell the node to reboot in this many seconds (or <0 to cancel reboot)
|
|
||||||
*/
|
|
||||||
int32 reboot_seconds = 97;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tell the node to shutdown in this many seconds (or <0 to cancel shutdown)
|
|
||||||
*/
|
|
||||||
int32 shutdown_seconds = 98;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tell the node to factory reset, all device settings will be returned to factory defaults.
|
|
||||||
*/
|
|
||||||
int32 factory_reset = 99;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tell the node to reset the nodedb.
|
|
||||||
*/
|
|
||||||
int32 nodedb_reset = 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Parameters for setting up Meshtastic for ameteur radio usage
|
|
||||||
*/
|
|
||||||
message HamParameters {
|
|
||||||
/*
|
|
||||||
* Amateur radio call sign, eg. KD2ABC
|
|
||||||
*/
|
|
||||||
string call_sign = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Transmit power in dBm at the LoRA transceiver, not including any amplification
|
|
||||||
*/
|
|
||||||
int32 tx_power = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The selected frequency of LoRA operation
|
|
||||||
* Please respect your local laws, regulations, and band plans.
|
|
||||||
* Ensure your radio is capable of operating of the selected frequency before setting this.
|
|
||||||
*/
|
|
||||||
float frequency = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Optional short name of user
|
|
||||||
*/
|
|
||||||
string short_name = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Response envelope for node_remote_hardware_pins
|
|
||||||
*/
|
|
||||||
message NodeRemoteHardwarePinsResponse {
|
|
||||||
/*
|
|
||||||
* Nodes and their respective remote hardware GPIO pins
|
|
||||||
*/
|
|
||||||
repeated NodeRemoteHardwarePin node_remote_hardware_pins = 1;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
*ChannelSet.settings max_count:8
|
|
@ -1,31 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package meshtastic;
|
|
||||||
|
|
||||||
import "meshtastic/channel.proto";
|
|
||||||
import "meshtastic/config.proto";
|
|
||||||
|
|
||||||
option csharp_namespace = "Meshtastic.Protobufs";
|
|
||||||
option go_package = "github.com/meshtastic/go/generated";
|
|
||||||
option java_outer_classname = "AppOnlyProtos";
|
|
||||||
option java_package = "com.geeksville.mesh";
|
|
||||||
option swift_prefix = "";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is the most compact possible representation for a set of channels.
|
|
||||||
* It includes only one PRIMARY channel (which must be first) and
|
|
||||||
* any SECONDARY channels.
|
|
||||||
* No DISABLED channels are included.
|
|
||||||
* This abstraction is used only on the the 'app side' of the world (ie python, javascript and android etc) to show a group of Channels as a (long) URL
|
|
||||||
*/
|
|
||||||
message ChannelSet {
|
|
||||||
/*
|
|
||||||
* Channel list with settings
|
|
||||||
*/
|
|
||||||
repeated ChannelSettings settings = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* LoRa config
|
|
||||||
*/
|
|
||||||
Config.LoRaConfig lora_config = 2;
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
*Contact.callsign max_size:120
|
|
||||||
*Contact.device_callsign max_size:120
|
|
||||||
*Status.battery int_size:8
|
|
||||||
*PLI.course int_size:16
|
|
||||||
*GeoChat.message max_size:200
|
|
||||||
*GeoChat.to max_size:120
|
|
@ -1,251 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package meshtastic;
|
|
||||||
|
|
||||||
option csharp_namespace = "Meshtastic.Protobufs";
|
|
||||||
option go_package = "github.com/meshtastic/go/generated";
|
|
||||||
option java_outer_classname = "ATAKProtos";
|
|
||||||
option java_package = "com.geeksville.mesh";
|
|
||||||
option swift_prefix = "";
|
|
||||||
/*
|
|
||||||
* Packets for the official ATAK Plugin
|
|
||||||
*/
|
|
||||||
message TAKPacket
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Are the payloads strings compressed for LoRA transport?
|
|
||||||
*/
|
|
||||||
bool is_compressed = 1;
|
|
||||||
/*
|
|
||||||
* The contact / callsign for ATAK user
|
|
||||||
*/
|
|
||||||
Contact contact = 2;
|
|
||||||
/*
|
|
||||||
* The group for ATAK user
|
|
||||||
*/
|
|
||||||
Group group = 3;
|
|
||||||
/*
|
|
||||||
* The status of the ATAK EUD
|
|
||||||
*/
|
|
||||||
Status status = 4;
|
|
||||||
/*
|
|
||||||
* The payload of the packet
|
|
||||||
*/
|
|
||||||
oneof payload_variant {
|
|
||||||
/*
|
|
||||||
* TAK position report
|
|
||||||
*/
|
|
||||||
PLI pli = 5;
|
|
||||||
/*
|
|
||||||
* ATAK GeoChat message
|
|
||||||
*/
|
|
||||||
GeoChat chat = 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ATAK GeoChat message
|
|
||||||
*/
|
|
||||||
message GeoChat {
|
|
||||||
/*
|
|
||||||
* The text message
|
|
||||||
*/
|
|
||||||
string message = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Uid recipient of the message
|
|
||||||
*/
|
|
||||||
optional string to = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ATAK Group
|
|
||||||
* <__group role='Team Member' name='Cyan'/>
|
|
||||||
*/
|
|
||||||
message Group {
|
|
||||||
/*
|
|
||||||
* Role of the group member
|
|
||||||
*/
|
|
||||||
MemberRole role = 1;
|
|
||||||
/*
|
|
||||||
* Team (color)
|
|
||||||
* Default Cyan
|
|
||||||
*/
|
|
||||||
Team team = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Team {
|
|
||||||
/*
|
|
||||||
* Unspecifed
|
|
||||||
*/
|
|
||||||
Unspecifed_Color = 0;
|
|
||||||
/*
|
|
||||||
* White
|
|
||||||
*/
|
|
||||||
White = 1;
|
|
||||||
/*
|
|
||||||
* Yellow
|
|
||||||
*/
|
|
||||||
Yellow = 2;
|
|
||||||
/*
|
|
||||||
* Orange
|
|
||||||
*/
|
|
||||||
Orange = 3;
|
|
||||||
/*
|
|
||||||
* Magenta
|
|
||||||
*/
|
|
||||||
Magenta = 4;
|
|
||||||
/*
|
|
||||||
* Red
|
|
||||||
*/
|
|
||||||
Red = 5;
|
|
||||||
/*
|
|
||||||
* Maroon
|
|
||||||
*/
|
|
||||||
Maroon = 6;
|
|
||||||
/*
|
|
||||||
* Purple
|
|
||||||
*/
|
|
||||||
Purple = 7;
|
|
||||||
/*
|
|
||||||
* Dark Blue
|
|
||||||
*/
|
|
||||||
Dark_Blue = 8;
|
|
||||||
/*
|
|
||||||
* Blue
|
|
||||||
*/
|
|
||||||
Blue = 9;
|
|
||||||
/*
|
|
||||||
* Cyan
|
|
||||||
*/
|
|
||||||
Cyan = 10;
|
|
||||||
/*
|
|
||||||
* Teal
|
|
||||||
*/
|
|
||||||
Teal = 11;
|
|
||||||
/*
|
|
||||||
* Green
|
|
||||||
*/
|
|
||||||
Green = 12;
|
|
||||||
/*
|
|
||||||
* Dark Green
|
|
||||||
*/
|
|
||||||
Dark_Green = 13;
|
|
||||||
/*
|
|
||||||
* Brown
|
|
||||||
*/
|
|
||||||
Brown = 14;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Role of the group member
|
|
||||||
*/
|
|
||||||
enum MemberRole {
|
|
||||||
/*
|
|
||||||
* Unspecifed
|
|
||||||
*/
|
|
||||||
Unspecifed = 0;
|
|
||||||
/*
|
|
||||||
* Team Member
|
|
||||||
*/
|
|
||||||
TeamMember = 1;
|
|
||||||
/*
|
|
||||||
* Team Lead
|
|
||||||
*/
|
|
||||||
TeamLead = 2;
|
|
||||||
/*
|
|
||||||
* Headquarters
|
|
||||||
*/
|
|
||||||
HQ = 3;
|
|
||||||
/*
|
|
||||||
* Airsoft enthusiast
|
|
||||||
*/
|
|
||||||
Sniper = 4;
|
|
||||||
/*
|
|
||||||
* Medic
|
|
||||||
*/
|
|
||||||
Medic = 5;
|
|
||||||
/*
|
|
||||||
* ForwardObserver
|
|
||||||
*/
|
|
||||||
ForwardObserver = 6;
|
|
||||||
/*
|
|
||||||
* Radio Telephone Operator
|
|
||||||
*/
|
|
||||||
RTO = 7;
|
|
||||||
/*
|
|
||||||
* Doggo
|
|
||||||
*/
|
|
||||||
K9 = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ATAK EUD Status
|
|
||||||
* <status battery='100' />
|
|
||||||
*/
|
|
||||||
message Status {
|
|
||||||
/*
|
|
||||||
* Battery level
|
|
||||||
*/
|
|
||||||
uint32 battery = 1;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* ATAK Contact
|
|
||||||
* <contact endpoint='0.0.0.0:4242:tcp' phone='+12345678' callsign='FALKE'/>
|
|
||||||
*/
|
|
||||||
message Contact {
|
|
||||||
/*
|
|
||||||
* Callsign
|
|
||||||
*/
|
|
||||||
string callsign = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Device callsign
|
|
||||||
*/
|
|
||||||
string device_callsign = 2;
|
|
||||||
/*
|
|
||||||
* IP address of endpoint in integer form (0.0.0.0 default)
|
|
||||||
*/
|
|
||||||
// fixed32 enpoint_address = 3;
|
|
||||||
/*
|
|
||||||
* Port of endpoint (4242 default)
|
|
||||||
*/
|
|
||||||
// uint32 endpoint_port = 4;
|
|
||||||
/*
|
|
||||||
* Phone represented as integer
|
|
||||||
* Terrible practice, but we really need the wire savings
|
|
||||||
*/
|
|
||||||
// uint32 phone = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Position Location Information from ATAK
|
|
||||||
*/
|
|
||||||
message PLI {
|
|
||||||
/*
|
|
||||||
* The new preferred location encoding, multiply by 1e-7 to get degrees
|
|
||||||
* in floating point
|
|
||||||
*/
|
|
||||||
sfixed32 latitude_i = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The new preferred location encoding, multiply by 1e-7 to get degrees
|
|
||||||
* in floating point
|
|
||||||
*/
|
|
||||||
sfixed32 longitude_i = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Altitude (ATAK prefers HAE)
|
|
||||||
*/
|
|
||||||
int32 altitude = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Speed
|
|
||||||
*/
|
|
||||||
uint32 speed = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Course in degrees
|
|
||||||
*/
|
|
||||||
uint32 course = 5;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
*CannedMessageModuleConfig.messages max_size:201
|
|
@ -1,19 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package meshtastic;
|
|
||||||
|
|
||||||
option csharp_namespace = "Meshtastic.Protobufs";
|
|
||||||
option go_package = "github.com/meshtastic/go/generated";
|
|
||||||
option java_outer_classname = "CannedMessageConfigProtos";
|
|
||||||
option java_package = "com.geeksville.mesh";
|
|
||||||
option swift_prefix = "";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Canned message module configuration.
|
|
||||||
*/
|
|
||||||
message CannedMessageModuleConfig {
|
|
||||||
/*
|
|
||||||
* Predefined messages for canned message module separated by '|' characters.
|
|
||||||
*/
|
|
||||||
string messages = 1;
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
*Channel.index int_size:8
|
|
||||||
|
|
||||||
# 256 bit or 128 bit psk key
|
|
||||||
*ChannelSettings.psk max_size:32
|
|
||||||
*ChannelSettings.name max_size:12
|
|
@ -1,150 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package meshtastic;
|
|
||||||
|
|
||||||
option csharp_namespace = "Meshtastic.Protobufs";
|
|
||||||
option go_package = "github.com/meshtastic/go/generated";
|
|
||||||
option java_outer_classname = "ChannelProtos";
|
|
||||||
option java_package = "com.geeksville.mesh";
|
|
||||||
option swift_prefix = "";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This information can be encoded as a QRcode/url so that other users can configure
|
|
||||||
* their radio to join the same channel.
|
|
||||||
* A note about how channel names are shown to users: channelname-X
|
|
||||||
* poundsymbol is a prefix used to indicate this is a channel name (idea from @professr).
|
|
||||||
* Where X is a letter from A-Z (base 26) representing a hash of the PSK for this
|
|
||||||
* channel - so that if the user changes anything about the channel (which does
|
|
||||||
* force a new PSK) this letter will also change. Thus preventing user confusion if
|
|
||||||
* two friends try to type in a channel name of "BobsChan" and then can't talk
|
|
||||||
* because their PSKs will be different.
|
|
||||||
* The PSK is hashed into this letter by "0x41 + [xor all bytes of the psk ] modulo 26"
|
|
||||||
* This also allows the option of someday if people have the PSK off (zero), the
|
|
||||||
* users COULD type in a channel name and be able to talk.
|
|
||||||
* FIXME: Add description of multi-channel support and how primary vs secondary channels are used.
|
|
||||||
* FIXME: explain how apps use channels for security.
|
|
||||||
* explain how remote settings and remote gpio are managed as an example
|
|
||||||
*/
|
|
||||||
message ChannelSettings {
|
|
||||||
/*
|
|
||||||
* Deprecated in favor of LoraConfig.channel_num
|
|
||||||
*/
|
|
||||||
uint32 channel_num = 1 [deprecated = true];
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A simple pre-shared key for now for crypto.
|
|
||||||
* Must be either 0 bytes (no crypto), 16 bytes (AES128), or 32 bytes (AES256).
|
|
||||||
* A special shorthand is used for 1 byte long psks.
|
|
||||||
* These psks should be treated as only minimally secure,
|
|
||||||
* because they are listed in this source code.
|
|
||||||
* Those bytes are mapped using the following scheme:
|
|
||||||
* `0` = No crypto
|
|
||||||
* `1` = The special "default" channel key: {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01}
|
|
||||||
* `2` through 10 = The default channel key, except with 1 through 9 added to the last byte.
|
|
||||||
* Shown to user as simple1 through 10
|
|
||||||
*/
|
|
||||||
bytes psk = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A SHORT name that will be packed into the URL.
|
|
||||||
* Less than 12 bytes.
|
|
||||||
* Something for end users to call the channel
|
|
||||||
* If this is the empty string it is assumed that this channel
|
|
||||||
* is the special (minimally secure) "Default"channel.
|
|
||||||
* In user interfaces it should be rendered as a local language translation of "X".
|
|
||||||
* For channel_num hashing empty string will be treated as "X".
|
|
||||||
* Where "X" is selected based on the English words listed above for ModemPreset
|
|
||||||
*/
|
|
||||||
string name = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Used to construct a globally unique channel ID.
|
|
||||||
* The full globally unique ID will be: "name.id" where ID is shown as base36.
|
|
||||||
* Assuming that the number of meshtastic users is below 20K (true for a long time)
|
|
||||||
* the chance of this 64 bit random number colliding with anyone else is super low.
|
|
||||||
* And the penalty for collision is low as well, it just means that anyone trying to decrypt channel messages might need to
|
|
||||||
* try multiple candidate channels.
|
|
||||||
* Any time a non wire compatible change is made to a channel, this field should be regenerated.
|
|
||||||
* There are a small number of 'special' globally known (and fairly) insecure standard channels.
|
|
||||||
* Those channels do not have a numeric id included in the settings, but instead it is pulled from
|
|
||||||
* a table of well known IDs.
|
|
||||||
* (see Well Known Channels FIXME)
|
|
||||||
*/
|
|
||||||
fixed32 id = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If true, messages on the mesh will be sent to the *public* internet by any gateway ndoe
|
|
||||||
*/
|
|
||||||
bool uplink_enabled = 5;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If true, messages seen on the internet will be forwarded to the local mesh.
|
|
||||||
*/
|
|
||||||
bool downlink_enabled = 6;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Per-channel module settings.
|
|
||||||
*/
|
|
||||||
ModuleSettings module_settings = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This message is specifically for modules to store per-channel configuration data.
|
|
||||||
*/
|
|
||||||
message ModuleSettings {
|
|
||||||
/*
|
|
||||||
* Bits of precision for the location sent in position packets.
|
|
||||||
*/
|
|
||||||
uint32 position_precision = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A pair of a channel number, mode and the (sharable) settings for that channel
|
|
||||||
*/
|
|
||||||
message Channel {
|
|
||||||
/*
|
|
||||||
* How this channel is being used (or not).
|
|
||||||
* Note: this field is an enum to give us options for the future.
|
|
||||||
* In particular, someday we might make a 'SCANNING' option.
|
|
||||||
* SCANNING channels could have different frequencies and the radio would
|
|
||||||
* occasionally check that freq to see if anything is being transmitted.
|
|
||||||
* For devices that have multiple physical radios attached, we could keep multiple PRIMARY/SCANNING channels active at once to allow
|
|
||||||
* cross band routing as needed.
|
|
||||||
* If a device has only a single radio (the common case) only one channel can be PRIMARY at a time
|
|
||||||
* (but any number of SECONDARY channels can't be sent received on that common frequency)
|
|
||||||
*/
|
|
||||||
enum Role {
|
|
||||||
/*
|
|
||||||
* This channel is not in use right now
|
|
||||||
*/
|
|
||||||
DISABLED = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This channel is used to set the frequency for the radio - all other enabled channels must be SECONDARY
|
|
||||||
*/
|
|
||||||
PRIMARY = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Secondary channels are only used for encryption/decryption/authentication purposes.
|
|
||||||
* Their radio settings (freq etc) are ignored, only psk is used.
|
|
||||||
*/
|
|
||||||
SECONDARY = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The index of this channel in the channel table (from 0 to MAX_NUM_CHANNELS-1)
|
|
||||||
* (Someday - not currently implemented) An index of -1 could be used to mean "set by name",
|
|
||||||
* in which case the target node will find and set the channel by settings.name.
|
|
||||||
*/
|
|
||||||
int32 index = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The new settings, or NULL to disable that channel
|
|
||||||
*/
|
|
||||||
ChannelSettings settings = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: REPLACE
|
|
||||||
*/
|
|
||||||
Role role = 3;
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
*DeviceProfile.long_name max_size:40
|
|
||||||
*DeviceProfile.short_name max_size:5
|
|
@ -1,50 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package meshtastic;
|
|
||||||
|
|
||||||
import "meshtastic/localonly.proto";
|
|
||||||
|
|
||||||
option csharp_namespace = "Meshtastic.Protobufs";
|
|
||||||
option go_package = "github.com/meshtastic/go/generated";
|
|
||||||
option java_outer_classname = "ClientOnlyProtos";
|
|
||||||
option java_package = "com.geeksville.mesh";
|
|
||||||
option swift_prefix = "";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This abstraction is used to contain any configuration for provisioning a node on any client.
|
|
||||||
* It is useful for importing and exporting configurations.
|
|
||||||
*/
|
|
||||||
message DeviceProfile {
|
|
||||||
/*
|
|
||||||
* Long name for the node
|
|
||||||
*/
|
|
||||||
optional string long_name = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Short name of the node
|
|
||||||
*/
|
|
||||||
optional string short_name = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The url of the channels from our node
|
|
||||||
*/
|
|
||||||
optional string channel_url = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The Config of the node
|
|
||||||
*/
|
|
||||||
optional LocalConfig config = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The ModuleConfig of the node
|
|
||||||
*/
|
|
||||||
optional LocalModuleConfig module_config = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A heartbeat message is sent by a node to indicate that it is still alive.
|
|
||||||
* This is currently only needed to keep serial connections alive.
|
|
||||||
*/
|
|
||||||
message Heartbeat {
|
|
||||||
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
*NetworkConfig.wifi_ssid max_size:33
|
|
||||||
*NetworkConfig.wifi_psk max_size:65
|
|
||||||
*NetworkConfig.ntp_server max_size:33
|
|
||||||
*NetworkConfig.rsyslog_server max_size:33
|
|
||||||
|
|
||||||
# Max of three ignored nodes for our testing
|
|
||||||
*LoRaConfig.ignore_incoming max_count:3
|
|
||||||
|
|
||||||
*LoRaConfig.tx_power int_size:8
|
|
||||||
*LoRaConfig.bandwidth int_size:16
|
|
||||||
*LoRaConfig.coding_rate int_size:8
|
|
||||||
*LoRaConfig.channel_num int_size:16
|
|
||||||
|
|
||||||
*PowerConfig.device_battery_ina_address int_size:8
|
|
@ -1,986 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package meshtastic;
|
|
||||||
|
|
||||||
option csharp_namespace = "Meshtastic.Protobufs";
|
|
||||||
option go_package = "github.com/meshtastic/go/generated";
|
|
||||||
option java_outer_classname = "ConfigProtos";
|
|
||||||
option java_package = "com.geeksville.mesh";
|
|
||||||
option swift_prefix = "";
|
|
||||||
|
|
||||||
message Config {
|
|
||||||
/*
|
|
||||||
* Configuration
|
|
||||||
*/
|
|
||||||
message DeviceConfig {
|
|
||||||
/*
|
|
||||||
* Defines the device's role on the Mesh network
|
|
||||||
*/
|
|
||||||
enum Role {
|
|
||||||
/*
|
|
||||||
* Description: App connected or stand alone messaging device.
|
|
||||||
* Technical Details: Default Role
|
|
||||||
*/
|
|
||||||
CLIENT = 0;
|
|
||||||
/*
|
|
||||||
* Description: Device that does not forward packets from other devices.
|
|
||||||
*/
|
|
||||||
CLIENT_MUTE = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Description: Infrastructure node for extending network coverage by relaying messages. Visible in Nodes list.
|
|
||||||
* Technical Details: Mesh packets will prefer to be routed over this node. This node will not be used by client apps.
|
|
||||||
* The wifi radio and the oled screen will be put to sleep.
|
|
||||||
* This mode may still potentially have higher power usage due to it's preference in message rebroadcasting on the mesh.
|
|
||||||
*/
|
|
||||||
ROUTER = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Description: Combination of both ROUTER and CLIENT. Not for mobile devices.
|
|
||||||
*/
|
|
||||||
ROUTER_CLIENT = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
|
|
||||||
* Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
|
|
||||||
* or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
|
|
||||||
*/
|
|
||||||
REPEATER = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Description: Broadcasts GPS position packets as priority.
|
|
||||||
* Technical Details: Position Mesh packets will be prioritized higher and sent more frequently by default.
|
|
||||||
* When used in conjunction with power.is_power_saving = true, nodes will wake up,
|
|
||||||
* send position, and then sleep for position.position_broadcast_secs seconds.
|
|
||||||
*/
|
|
||||||
TRACKER = 5;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Description: Broadcasts telemetry packets as priority.
|
|
||||||
* Technical Details: Telemetry Mesh packets will be prioritized higher and sent more frequently by default.
|
|
||||||
* When used in conjunction with power.is_power_saving = true, nodes will wake up,
|
|
||||||
* send environment telemetry, and then sleep for telemetry.environment_update_interval seconds.
|
|
||||||
*/
|
|
||||||
SENSOR = 6;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Description: Optimized for ATAK system communication and reduces routine broadcasts.
|
|
||||||
* Technical Details: Used for nodes dedicated for connection to an ATAK EUD.
|
|
||||||
* Turns off many of the routine broadcasts to favor CoT packet stream
|
|
||||||
* from the Meshtastic ATAK plugin -> IMeshService -> Node
|
|
||||||
*/
|
|
||||||
TAK = 7;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Description: Device that only broadcasts as needed for stealth or power savings.
|
|
||||||
* Technical Details: Used for nodes that "only speak when spoken to"
|
|
||||||
* Turns all of the routine broadcasts but allows for ad-hoc communication
|
|
||||||
* Still rebroadcasts, but with local only rebroadcast mode (known meshes only)
|
|
||||||
* Can be used for clandestine operation or to dramatically reduce airtime / power consumption
|
|
||||||
*/
|
|
||||||
CLIENT_HIDDEN = 8;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Description: Broadcasts location as message to default channel regularly for to assist with device recovery.
|
|
||||||
* Technical Details: Used to automatically send a text message to the mesh
|
|
||||||
* with the current position of the device on a frequent interval:
|
|
||||||
* "I'm lost! Position: lat / long"
|
|
||||||
*/
|
|
||||||
LOST_AND_FOUND = 9;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Description: Enables automatic TAK PLI broadcasts and reduces routine broadcasts.
|
|
||||||
* Technical Details: Turns off many of the routine broadcasts to favor ATAK CoT packet stream
|
|
||||||
* and automatic TAK PLI (position location information) broadcasts.
|
|
||||||
* Uses position module configuration to determine TAK PLI broadcast interval.
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Defines the device's behavior for how messages are rebroadcast
|
|
||||||
*/
|
|
||||||
enum RebroadcastMode {
|
|
||||||
/*
|
|
||||||
* Default behavior.
|
|
||||||
* Rebroadcast any observed message, if it was on our private channel or from another mesh with the same lora params.
|
|
||||||
*/
|
|
||||||
ALL = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Same as behavior as ALL but skips packet decoding and simply rebroadcasts them.
|
|
||||||
* Only available in Repeater role. Setting this on any other roles will result in ALL behavior.
|
|
||||||
*/
|
|
||||||
ALL_SKIP_DECODING = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ignores observed messages from foreign meshes that are open or those which it cannot decrypt.
|
|
||||||
* Only rebroadcasts message on the nodes local primary / secondary channels.
|
|
||||||
*/
|
|
||||||
LOCAL_ONLY = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ignores observed messages from foreign meshes like LOCAL_ONLY,
|
|
||||||
* but takes it step further by also ignoring messages from nodenums not in the node's known list (NodeDB)
|
|
||||||
*/
|
|
||||||
KNOWN_ONLY = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sets the role of node
|
|
||||||
*/
|
|
||||||
Role role = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Disabling this will disable the SerialConsole by not initilizing the StreamAPI
|
|
||||||
*/
|
|
||||||
bool serial_enabled = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* By default we turn off logging as soon as an API client connects (to keep shared serial link quiet).
|
|
||||||
* Set this to true to leave the debug log outputting even when API is active.
|
|
||||||
*/
|
|
||||||
bool debug_log_enabled = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For boards without a hard wired button, this is the pin number that will be used
|
|
||||||
* Boards that have more than one button can swap the function with this one. defaults to BUTTON_PIN if defined.
|
|
||||||
*/
|
|
||||||
uint32 button_gpio = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For boards without a PWM buzzer, this is the pin number that will be used
|
|
||||||
* Defaults to PIN_BUZZER if defined.
|
|
||||||
*/
|
|
||||||
uint32 buzzer_gpio = 5;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sets the role of node
|
|
||||||
*/
|
|
||||||
RebroadcastMode rebroadcast_mode = 6;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send our nodeinfo this often
|
|
||||||
* Defaults to 900 Seconds (15 minutes)
|
|
||||||
*/
|
|
||||||
uint32 node_info_broadcast_secs = 7;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Treat double tap interrupt on supported accelerometers as a button press if set to true
|
|
||||||
*/
|
|
||||||
bool double_tap_as_button_press = 8;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If true, device is considered to be "managed" by a mesh administrator
|
|
||||||
* Clients should then limit available configuration and administrative options inside the user interface
|
|
||||||
*/
|
|
||||||
bool is_managed = 9;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Disables the triple-press of user button to enable or disable GPS
|
|
||||||
*/
|
|
||||||
bool disable_triple_click = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Position Config
|
|
||||||
*/
|
|
||||||
message PositionConfig {
|
|
||||||
/*
|
|
||||||
* Bit field of boolean configuration options, indicating which optional
|
|
||||||
* fields to include when assembling POSITION messages.
|
|
||||||
* Longitude, latitude, altitude, speed, heading, and DOP
|
|
||||||
* are always included (also time if GPS-synced)
|
|
||||||
* NOTE: the more fields are included, the larger the message will be -
|
|
||||||
* leading to longer airtime and a higher risk of packet loss
|
|
||||||
*/
|
|
||||||
enum PositionFlags {
|
|
||||||
/*
|
|
||||||
* Required for compilation
|
|
||||||
*/
|
|
||||||
UNSET = 0x0000;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Include an altitude value (if available)
|
|
||||||
*/
|
|
||||||
ALTITUDE = 0x0001;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Altitude value is MSL
|
|
||||||
*/
|
|
||||||
ALTITUDE_MSL = 0x0002;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Include geoidal separation
|
|
||||||
*/
|
|
||||||
GEOIDAL_SEPARATION = 0x0004;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Include the DOP value ; PDOP used by default, see below
|
|
||||||
*/
|
|
||||||
DOP = 0x0008;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If POS_DOP set, send separate HDOP / VDOP values instead of PDOP
|
|
||||||
*/
|
|
||||||
HVDOP = 0x0010;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Include number of "satellites in view"
|
|
||||||
*/
|
|
||||||
SATINVIEW = 0x0020;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Include a sequence number incremented per packet
|
|
||||||
*/
|
|
||||||
SEQ_NO = 0x0040;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Include positional timestamp (from GPS solution)
|
|
||||||
*/
|
|
||||||
TIMESTAMP = 0x0080;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Include positional heading
|
|
||||||
* Intended for use with vehicle not walking speeds
|
|
||||||
* walking speeds are likely to be error prone like the compass
|
|
||||||
*/
|
|
||||||
HEADING = 0x0100;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Include positional speed
|
|
||||||
* Intended for use with vehicle not walking speeds
|
|
||||||
* walking speeds are likely to be error prone like the compass
|
|
||||||
*/
|
|
||||||
SPEED = 0x0200;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum GpsMode {
|
|
||||||
/*
|
|
||||||
* GPS is present but disabled
|
|
||||||
*/
|
|
||||||
DISABLED = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* GPS is present and enabled
|
|
||||||
*/
|
|
||||||
ENABLED = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* GPS is not present on the device
|
|
||||||
*/
|
|
||||||
NOT_PRESENT = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We should send our position this often (but only if it has changed significantly)
|
|
||||||
* Defaults to 15 minutes
|
|
||||||
*/
|
|
||||||
uint32 position_broadcast_secs = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Adaptive position braoadcast, which is now the default.
|
|
||||||
*/
|
|
||||||
bool position_broadcast_smart_enabled = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If set, this node is at a fixed position.
|
|
||||||
* We will generate GPS position updates at the regular interval, but use whatever the last lat/lon/alt we have for the node.
|
|
||||||
* The lat/lon/alt can be set by an internal GPS or with the help of the app.
|
|
||||||
*/
|
|
||||||
bool fixed_position = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Is GPS enabled for this node?
|
|
||||||
*/
|
|
||||||
bool gps_enabled = 4[deprecated = true];
|
|
||||||
|
|
||||||
/*
|
|
||||||
* How often should we try to get GPS position (in seconds)
|
|
||||||
* or zero for the default of once every 30 seconds
|
|
||||||
* or a very large value (maxint) to update only once at boot.
|
|
||||||
*/
|
|
||||||
uint32 gps_update_interval = 5;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Deprecated in favor of using smart / regular broadcast intervals as implicit attempt time
|
|
||||||
*/
|
|
||||||
uint32 gps_attempt_time = 6 [deprecated = true];
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Bit field of boolean configuration options for POSITION messages
|
|
||||||
* (bitwise OR of PositionFlags)
|
|
||||||
*/
|
|
||||||
uint32 position_flags = 7;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (Re)define GPS_RX_PIN for your board.
|
|
||||||
*/
|
|
||||||
uint32 rx_gpio = 8;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (Re)define GPS_TX_PIN for your board.
|
|
||||||
*/
|
|
||||||
uint32 tx_gpio = 9;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The minimum distance in meters traveled (since the last send) before we can send a position to the mesh if position_broadcast_smart_enabled
|
|
||||||
*/
|
|
||||||
uint32 broadcast_smart_minimum_distance = 10;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The minimum number of seconds (since the last send) before we can send a position to the mesh if position_broadcast_smart_enabled
|
|
||||||
*/
|
|
||||||
uint32 broadcast_smart_minimum_interval_secs = 11;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (Re)define PIN_GPS_EN for your board.
|
|
||||||
*/
|
|
||||||
uint32 gps_en_gpio = 12;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set where GPS is enabled, disabled, or not present
|
|
||||||
*/
|
|
||||||
GpsMode gps_mode = 13;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Power Config\
|
|
||||||
* See [Power Config](/docs/settings/config/power) for additional power config details.
|
|
||||||
*/
|
|
||||||
message PowerConfig {
|
|
||||||
/*
|
|
||||||
* If set, we are powered from a low-current source (i.e. solar), so even if it looks like we have power flowing in
|
|
||||||
* we should try to minimize power consumption as much as possible.
|
|
||||||
* YOU DO NOT NEED TO SET THIS IF YOU'VE set is_router (it is implied in that case).
|
|
||||||
* Advanced Option
|
|
||||||
*/
|
|
||||||
bool is_power_saving = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If non-zero, the device will fully power off this many seconds after external power is removed.
|
|
||||||
*/
|
|
||||||
uint32 on_battery_shutdown_after_secs = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ratio of voltage divider for battery pin eg. 3.20 (R1=100k, R2=220k)
|
|
||||||
* Overrides the ADC_MULTIPLIER defined in variant for battery voltage calculation.
|
|
||||||
* Should be set to floating point value between 2 and 4
|
|
||||||
* Fixes issues on Heltec v2
|
|
||||||
*/
|
|
||||||
float adc_multiplier_override = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Wait Bluetooth Seconds
|
|
||||||
* The number of seconds for to wait before turning off BLE in No Bluetooth states
|
|
||||||
* 0 for default of 1 minute
|
|
||||||
*/
|
|
||||||
uint32 wait_bluetooth_secs = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Super Deep Sleep Seconds
|
|
||||||
* While in Light Sleep if mesh_sds_timeout_secs is exceeded we will lower into super deep sleep
|
|
||||||
* for this value (default 1 year) or a button press
|
|
||||||
* 0 for default of one year
|
|
||||||
*/
|
|
||||||
uint32 sds_secs = 6;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Light Sleep Seconds
|
|
||||||
* In light sleep the CPU is suspended, LoRa radio is on, BLE is off an GPS is on
|
|
||||||
* ESP32 Only
|
|
||||||
* 0 for default of 300
|
|
||||||
*/
|
|
||||||
uint32 ls_secs = 7;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Minimum Wake Seconds
|
|
||||||
* While in light sleep when we receive packets on the LoRa radio we will wake and handle them and stay awake in no BLE mode for this value
|
|
||||||
* 0 for default of 10 seconds
|
|
||||||
*/
|
|
||||||
uint32 min_wake_secs = 8;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* I2C address of INA_2XX to use for reading device battery voltage
|
|
||||||
*/
|
|
||||||
uint32 device_battery_ina_address = 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Network Config
|
|
||||||
*/
|
|
||||||
message NetworkConfig {
|
|
||||||
enum AddressMode {
|
|
||||||
/*
|
|
||||||
* obtain ip address via DHCP
|
|
||||||
*/
|
|
||||||
DHCP = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* use static ip address
|
|
||||||
*/
|
|
||||||
STATIC = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message IpV4Config {
|
|
||||||
/*
|
|
||||||
* Static IP address
|
|
||||||
*/
|
|
||||||
fixed32 ip = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Static gateway address
|
|
||||||
*/
|
|
||||||
fixed32 gateway = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Static subnet mask
|
|
||||||
*/
|
|
||||||
fixed32 subnet = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Static DNS server address
|
|
||||||
*/
|
|
||||||
fixed32 dns = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Enable WiFi (disables Bluetooth)
|
|
||||||
*/
|
|
||||||
bool wifi_enabled = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If set, this node will try to join the specified wifi network and
|
|
||||||
* acquire an address via DHCP
|
|
||||||
*/
|
|
||||||
string wifi_ssid = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If set, will be use to authenticate to the named wifi
|
|
||||||
*/
|
|
||||||
string wifi_psk = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* NTP server to use if WiFi is conneced, defaults to `0.pool.ntp.org`
|
|
||||||
*/
|
|
||||||
string ntp_server = 5;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Enable Ethernet
|
|
||||||
*/
|
|
||||||
bool eth_enabled = 6;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* acquire an address via DHCP or assign static
|
|
||||||
*/
|
|
||||||
AddressMode address_mode = 7;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* struct to keep static address
|
|
||||||
*/
|
|
||||||
IpV4Config ipv4_config = 8;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* rsyslog Server and Port
|
|
||||||
*/
|
|
||||||
string rsyslog_server = 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Display Config
|
|
||||||
*/
|
|
||||||
message DisplayConfig {
|
|
||||||
/*
|
|
||||||
* How the GPS coordinates are displayed on the OLED screen.
|
|
||||||
*/
|
|
||||||
enum GpsCoordinateFormat {
|
|
||||||
/*
|
|
||||||
* GPS coordinates are displayed in the normal decimal degrees format:
|
|
||||||
* DD.DDDDDD DDD.DDDDDD
|
|
||||||
*/
|
|
||||||
DEC = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* GPS coordinates are displayed in the degrees minutes seconds format:
|
|
||||||
* DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
|
|
||||||
*/
|
|
||||||
DMS = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Universal Transverse Mercator format:
|
|
||||||
* ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
|
|
||||||
*/
|
|
||||||
UTM = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Military Grid Reference System format:
|
|
||||||
* ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
|
|
||||||
* E is easting, N is northing
|
|
||||||
*/
|
|
||||||
MGRS = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Open Location Code (aka Plus Codes).
|
|
||||||
*/
|
|
||||||
OLC = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ordnance Survey Grid Reference (the National Grid System of the UK).
|
|
||||||
* Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
|
|
||||||
* E is the easting, N is the northing
|
|
||||||
*/
|
|
||||||
OSGR = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Unit display preference
|
|
||||||
*/
|
|
||||||
enum DisplayUnits {
|
|
||||||
/*
|
|
||||||
* Metric (Default)
|
|
||||||
*/
|
|
||||||
METRIC = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Imperial
|
|
||||||
*/
|
|
||||||
IMPERIAL = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Override OLED outo detect with this if it fails.
|
|
||||||
*/
|
|
||||||
enum OledType {
|
|
||||||
/*
|
|
||||||
* Default / Auto
|
|
||||||
*/
|
|
||||||
OLED_AUTO = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Default / Auto
|
|
||||||
*/
|
|
||||||
OLED_SSD1306 = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Default / Auto
|
|
||||||
*/
|
|
||||||
OLED_SH1106 = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Can not be auto detected but set by proto. Used for 128x128 screens
|
|
||||||
*/
|
|
||||||
OLED_SH1107 = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Number of seconds the screen stays on after pressing the user button or receiving a message
|
|
||||||
* 0 for default of one minute MAXUINT for always on
|
|
||||||
*/
|
|
||||||
uint32 screen_on_secs = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* How the GPS coordinates are formatted on the OLED screen.
|
|
||||||
*/
|
|
||||||
GpsCoordinateFormat gps_format = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Automatically toggles to the next page on the screen like a carousel, based the specified interval in seconds.
|
|
||||||
* Potentially useful for devices without user buttons.
|
|
||||||
*/
|
|
||||||
uint32 auto_screen_carousel_secs = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If this is set, the displayed compass will always point north. if unset, the old behaviour
|
|
||||||
* (top of display is heading direction) is used.
|
|
||||||
*/
|
|
||||||
bool compass_north_top = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Flip screen vertically, for cases that mount the screen upside down
|
|
||||||
*/
|
|
||||||
bool flip_screen = 5;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Perferred display units
|
|
||||||
*/
|
|
||||||
DisplayUnits units = 6;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Override auto-detect in screen
|
|
||||||
*/
|
|
||||||
OledType oled = 7;
|
|
||||||
|
|
||||||
enum DisplayMode {
|
|
||||||
/*
|
|
||||||
* Default. The old style for the 128x64 OLED screen
|
|
||||||
*/
|
|
||||||
DEFAULT = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Rearrange display elements to cater for bicolor OLED displays
|
|
||||||
*/
|
|
||||||
TWOCOLOR = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Same as TwoColor, but with inverted top bar. Not so good for Epaper displays
|
|
||||||
*/
|
|
||||||
INVERTED = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TFT Full Color Displays (not implemented yet)
|
|
||||||
*/
|
|
||||||
COLOR = 3;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Display Mode
|
|
||||||
*/
|
|
||||||
DisplayMode displaymode = 8;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Print first line in pseudo-bold? FALSE is original style, TRUE is bold
|
|
||||||
*/
|
|
||||||
bool heading_bold = 9;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Should we wake the screen up on accelerometer detected motion or tap
|
|
||||||
*/
|
|
||||||
bool wake_on_tap_or_motion = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Lora Config
|
|
||||||
*/
|
|
||||||
message LoRaConfig {
|
|
||||||
enum RegionCode {
|
|
||||||
/*
|
|
||||||
* Region is not set
|
|
||||||
*/
|
|
||||||
UNSET = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* United States
|
|
||||||
*/
|
|
||||||
US = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* European Union 433mhz
|
|
||||||
*/
|
|
||||||
EU_433 = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* European Union 868mhz
|
|
||||||
*/
|
|
||||||
EU_868 = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* China
|
|
||||||
*/
|
|
||||||
CN = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Japan
|
|
||||||
*/
|
|
||||||
JP = 5;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Australia / New Zealand
|
|
||||||
*/
|
|
||||||
ANZ = 6;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Korea
|
|
||||||
*/
|
|
||||||
KR = 7;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Taiwan
|
|
||||||
*/
|
|
||||||
TW = 8;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Russia
|
|
||||||
*/
|
|
||||||
RU = 9;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* India
|
|
||||||
*/
|
|
||||||
IN = 10;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* New Zealand 865mhz
|
|
||||||
*/
|
|
||||||
NZ_865 = 11;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Thailand
|
|
||||||
*/
|
|
||||||
TH = 12;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* WLAN Band
|
|
||||||
*/
|
|
||||||
LORA_24 = 13;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ukraine 433mhz
|
|
||||||
*/
|
|
||||||
UA_433 = 14;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ukraine 868mhz
|
|
||||||
*/
|
|
||||||
UA_868 = 15;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Malaysia 433mhz
|
|
||||||
*/
|
|
||||||
MY_433 = 16;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Malaysia 919mhz
|
|
||||||
*/
|
|
||||||
MY_919 = 17;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Singapore 923mhz
|
|
||||||
*/
|
|
||||||
SG_923 = 18;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Philippines 433mhz
|
|
||||||
*/
|
|
||||||
PH_433 = 19;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Philippines 868mhz
|
|
||||||
*/
|
|
||||||
PH_868 = 20;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Philippines 915mhz
|
|
||||||
*/
|
|
||||||
PH_915 = 21;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Standard predefined channel settings
|
|
||||||
* Note: these mappings must match ModemPreset Choice in the device code.
|
|
||||||
*/
|
|
||||||
enum ModemPreset {
|
|
||||||
/*
|
|
||||||
* Long Range - Fast
|
|
||||||
*/
|
|
||||||
LONG_FAST = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Long Range - Slow
|
|
||||||
*/
|
|
||||||
LONG_SLOW = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Very Long Range - Slow
|
|
||||||
*/
|
|
||||||
VERY_LONG_SLOW = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Medium Range - Slow
|
|
||||||
*/
|
|
||||||
MEDIUM_SLOW = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Medium Range - Fast
|
|
||||||
*/
|
|
||||||
MEDIUM_FAST = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Short Range - Slow
|
|
||||||
*/
|
|
||||||
SHORT_SLOW = 5;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Short Range - Fast
|
|
||||||
*/
|
|
||||||
SHORT_FAST = 6;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Long Range - Moderately Fast
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* When enabled, the `modem_preset` fields will be adhered to, else the `bandwidth`/`spread_factor`/`coding_rate`
|
|
||||||
* will be taked from their respective manually defined fields
|
|
||||||
*/
|
|
||||||
bool use_preset = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Either modem_config or bandwidth/spreading/coding will be specified - NOT BOTH.
|
|
||||||
* As a heuristic: If bandwidth is specified, do not use modem_config.
|
|
||||||
* Because protobufs take ZERO space when the value is zero this works out nicely.
|
|
||||||
* This value is replaced by bandwidth/spread_factor/coding_rate.
|
|
||||||
* If you'd like to experiment with other options add them to MeshRadio.cpp in the device code.
|
|
||||||
*/
|
|
||||||
ModemPreset modem_preset = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Bandwidth in MHz
|
|
||||||
* Certain bandwidth numbers are 'special' and will be converted to the
|
|
||||||
* appropriate floating point value: 31 -> 31.25MHz
|
|
||||||
*/
|
|
||||||
uint32 bandwidth = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A number from 7 to 12.
|
|
||||||
* Indicates number of chirps per symbol as 1<<spread_factor.
|
|
||||||
*/
|
|
||||||
uint32 spread_factor = 4;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The denominator of the coding rate.
|
|
||||||
* ie for 4/5, the value is 5. 4/8 the value is 8.
|
|
||||||
*/
|
|
||||||
uint32 coding_rate = 5;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This parameter is for advanced users with advanced test equipment, we do not recommend most users use it.
|
|
||||||
* A frequency offset that is added to to the calculated band center frequency.
|
|
||||||
* Used to correct for crystal calibration errors.
|
|
||||||
*/
|
|
||||||
float frequency_offset = 6;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The region code for the radio (US, CN, EU433, etc...)
|
|
||||||
*/
|
|
||||||
RegionCode region = 7;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Maximum number of hops. This can't be greater than 7.
|
|
||||||
* Default of 3
|
|
||||||
* Attempting to set a value > 7 results in the default
|
|
||||||
*/
|
|
||||||
uint32 hop_limit = 8;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Disable TX from the LoRa radio. Useful for hot-swapping antennas and other tests.
|
|
||||||
* Defaults to false
|
|
||||||
*/
|
|
||||||
bool tx_enabled = 9;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If zero, then use default max legal continuous power (ie. something that won't
|
|
||||||
* burn out the radio hardware)
|
|
||||||
* In most cases you should use zero here.
|
|
||||||
* Units are in dBm.
|
|
||||||
*/
|
|
||||||
int32 tx_power = 10;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This controls the actual hardware frequency the radio transmits on.
|
|
||||||
* Most users should never need to be exposed to this field/concept.
|
|
||||||
* A channel number between 1 and NUM_CHANNELS (whatever the max is in the current region).
|
|
||||||
* If ZERO then the rule is "use the old channel name hash based
|
|
||||||
* algorithm to derive the channel number")
|
|
||||||
* If using the hash algorithm the channel number will be: hash(channel_name) %
|
|
||||||
* NUM_CHANNELS (Where num channels depends on the regulatory region).
|
|
||||||
*/
|
|
||||||
uint32 channel_num = 11;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If true, duty cycle limits will be exceeded and thus you're possibly not following
|
|
||||||
* the local regulations if you're not a HAM.
|
|
||||||
* Has no effect if the duty cycle of the used region is 100%.
|
|
||||||
*/
|
|
||||||
bool override_duty_cycle = 12;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If true, sets RX boosted gain mode on SX126X based radios
|
|
||||||
*/
|
|
||||||
bool sx126x_rx_boosted_gain = 13;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This parameter is for advanced users and licensed HAM radio operators.
|
|
||||||
* Ignore Channel Calculation and use this frequency instead. The frequency_offset
|
|
||||||
* will still be applied. This will allow you to use out-of-band frequencies.
|
|
||||||
* Please respect your local laws and regulations. If you are a HAM, make sure you
|
|
||||||
* enable HAM mode and turn off encryption.
|
|
||||||
*/
|
|
||||||
float override_frequency = 14;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For testing it is useful sometimes to force a node to never listen to
|
|
||||||
* particular other nodes (simulating radio out of range). All nodenums listed
|
|
||||||
* in ignore_incoming will have packets they send dropped on receive (by router.cpp)
|
|
||||||
*/
|
|
||||||
repeated uint32 ignore_incoming = 103;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If true, the device will not process any packets received via LoRa that passed via MQTT anywhere on the path towards it.
|
|
||||||
*/
|
|
||||||
bool ignore_mqtt = 104;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BluetoothConfig {
|
|
||||||
enum PairingMode {
|
|
||||||
/*
|
|
||||||
* Device generates a random PIN that will be shown on the screen of the device for pairing
|
|
||||||
*/
|
|
||||||
RANDOM_PIN = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Device requires a specified fixed PIN for pairing
|
|
||||||
*/
|
|
||||||
FIXED_PIN = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Device requires no PIN for pairing
|
|
||||||
*/
|
|
||||||
NO_PIN = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Enable Bluetooth on the device
|
|
||||||
*/
|
|
||||||
bool enabled = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Determines the pairing strategy for the device
|
|
||||||
*/
|
|
||||||
PairingMode mode = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Specified PIN for PairingMode.FixedPin
|
|
||||||
*/
|
|
||||||
uint32 fixed_pin = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Payload Variant
|
|
||||||
*/
|
|
||||||
oneof payload_variant {
|
|
||||||
DeviceConfig device = 1;
|
|
||||||
PositionConfig position = 2;
|
|
||||||
PowerConfig power = 3;
|
|
||||||
NetworkConfig network = 4;
|
|
||||||
DisplayConfig display = 5;
|
|
||||||
LoRaConfig lora = 6;
|
|
||||||
BluetoothConfig bluetooth = 7;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
*WifiConnectionStatus.ssid max_size:33
|
|
@ -1,120 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package meshtastic;
|
|
||||||
|
|
||||||
option csharp_namespace = "Meshtastic.Protobufs";
|
|
||||||
option go_package = "github.com/meshtastic/go/generated";
|
|
||||||
option java_outer_classname = "ConnStatusProtos";
|
|
||||||
option java_package = "com.geeksville.mesh";
|
|
||||||
option swift_prefix = "";
|
|
||||||
|
|
||||||
message DeviceConnectionStatus {
|
|
||||||
/*
|
|
||||||
* WiFi Status
|
|
||||||
*/
|
|
||||||
optional WifiConnectionStatus wifi = 1;
|
|
||||||
/*
|
|
||||||
* WiFi Status
|
|
||||||
*/
|
|
||||||
optional EthernetConnectionStatus ethernet = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Bluetooth Status
|
|
||||||
*/
|
|
||||||
optional BluetoothConnectionStatus bluetooth = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Serial Status
|
|
||||||
*/
|
|
||||||
optional SerialConnectionStatus serial = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* WiFi connection status
|
|
||||||
*/
|
|
||||||
message WifiConnectionStatus {
|
|
||||||
/*
|
|
||||||
* Connection status
|
|
||||||
*/
|
|
||||||
NetworkConnectionStatus status = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* WiFi access point SSID
|
|
||||||
*/
|
|
||||||
string ssid = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RSSI of wireless connection
|
|
||||||
*/
|
|
||||||
int32 rssi = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ethernet connection status
|
|
||||||
*/
|
|
||||||
message EthernetConnectionStatus {
|
|
||||||
/*
|
|
||||||
* Connection status
|
|
||||||
*/
|
|
||||||
NetworkConnectionStatus status = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ethernet or WiFi connection status
|
|
||||||
*/
|
|
||||||
message NetworkConnectionStatus {
|
|
||||||
/*
|
|
||||||
* IP address of device
|
|
||||||
*/
|
|
||||||
fixed32 ip_address = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Whether the device has an active connection or not
|
|
||||||
*/
|
|
||||||
bool is_connected = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Whether the device has an active connection to an MQTT broker or not
|
|
||||||
*/
|
|
||||||
bool is_mqtt_connected = 3;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Whether the device is actively remote syslogging or not
|
|
||||||
*/
|
|
||||||
bool is_syslog_connected = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Bluetooth connection status
|
|
||||||
*/
|
|
||||||
message BluetoothConnectionStatus {
|
|
||||||
/*
|
|
||||||
* The pairing PIN for bluetooth
|
|
||||||
*/
|
|
||||||
uint32 pin = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RSSI of bluetooth connection
|
|
||||||
*/
|
|
||||||
int32 rssi = 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Whether the device has an active connection or not
|
|
||||||
*/
|
|
||||||
bool is_connected = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Serial connection status
|
|
||||||
*/
|
|
||||||
message SerialConnectionStatus {
|
|
||||||
/*
|
|
||||||
* Serial baud rate
|
|
||||||
*/
|
|
||||||
uint32 baud = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Whether the device has an active connection or not
|
|
||||||
*/
|
|
||||||
bool is_connected = 2;
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
# options for nanopb
|
|
||||||
# https://jpa.kapsi.fi/nanopb/docs/reference.html#proto-file-options
|
|
||||||
|
|
||||||
# FIXME pick a higher number someday? or do dynamic alloc in nanopb?
|
|
||||||
*DeviceState.node_db_lite max_count:100
|
|
||||||
|
|
||||||
# FIXME - max_count is actually 32 but we save/load this as one long string of preencoded MeshPacket bytes - not a big array in RAM
|
|
||||||
*DeviceState.receive_queue max_count:1
|
|
||||||
|
|
||||||
*ChannelFile.channels max_count:8
|
|
||||||
|
|
||||||
*OEMStore.oem_text max_size:40
|
|
||||||
*OEMStore.oem_icon_bits max_size:2048
|
|
||||||
*OEMStore.oem_aes_key max_size:32
|
|
||||||
|
|
||||||
*DeviceState.node_remote_hardware_pins max_count:12
|
|
||||||
|
|
||||||
*NodeInfoLite.channel int_size:8
|
|
||||||
*NodeInfoLite.hops_away int_size:8
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user