From 2ce2871da8bc081c2eaa3962fd2d0b63b121e37a Mon Sep 17 00:00:00 2001 From: Roslund Date: Sun, 5 Oct 2025 20:30:12 +0000 Subject: [PATCH] deploy: 14689c6bfab76d23587a7c2fb04e8cf84428e735 --- index.xml | 38 +++--- js/status/device-roles.js | 28 ++++- js/status/firmware-versions.js | 21 +++- js/status/is-ok-to-mqtt-chart.js | 21 +++- js/status/is-unmessagable-chart.js | 23 +++- js/status/max-hops-chart.js | 25 +++- js/status/most-active-nodes.js | 147 ++++++++++++++---------- js/status/nodes-seen.js | 21 +++- js/status/portnum-distribution-chart.js | 24 +++- js/status/position-precision-chart.js | 23 +++- js/status/shared.js | 28 +++++ sitemap.xml | 2 +- status/index.html | 28 ++--- 13 files changed, 316 insertions(+), 113 deletions(-) diff --git a/index.xml b/index.xml index ad2fb54..a82e277 100644 --- a/index.xml +++ b/index.xml @@ -233,6 +233,28 @@ Projektet drivs av entusiaster som vill utforska möjligheterna med decentralise <section class="row td-box td-box--white td-box--height-auto"> <div class="col"> <div class="container"> +<style> +@media (max-width: 576px) { + #channelToolbar { top: 16px !important; } +} +</style> +<div id="channelToolbar" class="d-flex justify-content-end sticky-top" style="top: 84px;"> + <div class="btn-toolbar bg-white shadow-sm rounded-3 py-2 px-2" role="toolbar" aria-label="Channel toolbar"> + <div class="btn-group btn-group-sm me-2" role="group" aria-label="Channel filter"> + <input type="radio" class="btn-check" name="btnchannel-nodes" id="filterChannelAll" autocomplete="off" checked> + <label class="btn btn-outline-primary" for="filterChannelAll">Alla</label> + </div> + <div class="btn-group btn-group-sm me-2" role="group" aria-label="Basic radio toggle button group"> + <input type="radio" class="btn-check" name="btnchannel-nodes" id="filterChannelMediumFast" data-channel="MediumFast" autocomplete="off"> + <label class="btn btn-outline-primary" for="filterChannelMediumFast">MediumFast</label> + <input type="radio" class="btn-check" name="btnchannel-nodes" id="filterChannelLongFast" data-channel="LongFast" autocomplete="off"> + <label class="btn btn-outline-primary" for="filterChannelLongFast">LongFast</label> + </div> + <div data-bs-toggle="tooltip" data-bs-placement="left" title="Filtrera på kanal eller visa alla."> + <span class="badge rounded-pill bg-dark">?</span> + </div> + </div> +</div> <h2 id="antal-enheter">Antal Enheter<a class="td-heading-self-link" href="#antal-enheter" aria-label="Heading self-link"></a></h2> <div class="container my-3 mx-0" style="max-width: 1000px;"> <div class="row text-center px-0"> @@ -242,18 +264,4 @@ Projektet drivs av entusiaster som vill utforska möjligheterna med decentralise </div> <div class="col"> <h2 class="display-4 text-warning fw-bold" id="count-7">–</h2> - <p class="text-muted">Senaste 7 dagarna</p> - </div> - <div class="col"> - <h2 class="display-4 text-success fw-bold" id="count-30">–</h2> - <p class="text-muted">Senaste 30 dagarna</p> - </div> - </div> -</div> -<h2 id="text-meddelanden">Text Meddelanden<a class="td-heading-self-link" href="#text-meddelanden" aria-label="Heading self-link"></a></h2> -<p>Antalet meddelanden per timme senaste 7 dygnen. Grafen visar meddelanden som skickats på LongFast kanalen, men även okrypterade meddelanden mellan noder. De meddelanden som skickas går att se <a href="https://sthlm-mesh.se/messages/">här</a>.</p> -<div class="stats-chart-container" style="min-height:300px;"> - <canvas id="messagesChart"></canvas> -</div> -<h2 id="kanalutnyttjande">Kanalutnyttjande<a class="td-heading-self-link" href="#kanalutnyttjande" aria-label="Heading self-link"></a></h2> -<p>Mätarna visar genomsnittligt kanalutnyttjande för de 10 noder som har högst kanalutnyttjande. Kanalutnyttjande (Channel Utilization) visar hur mycket av radiokanalen som är upptagen. Under 15–20% är allt normalt, över 25% får Telemetri lägre prioritet, vilket kan ge längre tid mellan uppdateringar. Runt 40% stryps positionsuppdateringar. Här i EU gäller dessutom max 10% sändtid per timme som kan pausa sändningar helt.</p> \ No newline at end of file + <p class="text-muted">Senaste 7 dagarna</p> \ No newline at end of file diff --git a/js/status/device-roles.js b/js/status/device-roles.js index 76e103d..c606f7b 100644 --- a/js/status/device-roles.js +++ b/js/status/device-roles.js @@ -1,3 +1,6 @@ +let deviceRolesChartInstance = null; +let selectedChannelIdRoles = null; // null = All + async function deviceRolesChart() { const canvas = document.getElementById('deviceRoles'); const ctx = canvas.getContext('2d'); @@ -14,7 +17,12 @@ async function deviceRolesChart() { // Count role occurrences const roleCounts = {}; - nodes.forEach(node => { + const filteredNodes = Array.isArray(nodes) ? nodes.filter(n => { + if (!selectedChannelIdRoles) return true; // All + return n.channel_id === selectedChannelIdRoles; + }) : []; + + filteredNodes.forEach(node => { const role = node.role_name || "UNKNOWN"; roleCounts[role] = (roleCounts[role] || 0) + 1; }); @@ -36,7 +44,12 @@ async function deviceRolesChart() { return 'rgb(41, 156, 70)'; }); - new Chart(ctx, { + if (deviceRolesChartInstance) { + deviceRolesChartInstance.destroy(); + deviceRolesChartInstance = null; + } + + deviceRolesChartInstance = new Chart(ctx, { type: 'bar', data: { labels: labels, @@ -71,3 +84,14 @@ async function deviceRolesChart() { } deviceRolesChart(); + +// Listen via shared StatusFilter +document.addEventListener('DOMContentLoaded', () => { + if (typeof StatusFilter !== 'undefined' && StatusFilter.subscribe) { + selectedChannelIdRoles = StatusFilter.getChannelId ? StatusFilter.getChannelId() : null; + StatusFilter.subscribe(() => { + selectedChannelIdRoles = StatusFilter.getChannelId ? StatusFilter.getChannelId() : null; + deviceRolesChart(); + }); + } +}); diff --git a/js/status/firmware-versions.js b/js/status/firmware-versions.js index ab70baa..151909a 100644 --- a/js/status/firmware-versions.js +++ b/js/status/firmware-versions.js @@ -10,8 +10,11 @@ async function firmwareVersionGraph() { try { await fetchNodes(); - // Filter out nodes without firmware_version - const nodesWithVersion = nodes.filter(node => node.firmware_version != null); + const channelId = (typeof StatusFilter !== 'undefined' && StatusFilter.getChannelId) ? StatusFilter.getChannelId() : null; + // Filter out nodes without firmware_version and channel match + const nodesWithVersion = nodes + .filter(node => node.firmware_version != null) + .filter(node => !channelId || node.channel_id === channelId); const countsByVersion = {}; const validVersions = ['<2.5.0', '>2.5.0', '>2.6.8']; @@ -39,7 +42,12 @@ async function firmwareVersionGraph() { const chartContainer = document.getElementById('firmwareVersionContainer'); chartContainer.style.height = `${labels.length * 35 + 50}px`; - new Chart(ctx, { + if (window.firmwareVersionChartInstance) { + window.firmwareVersionChartInstance.destroy(); + window.firmwareVersionChartInstance = null; + } + + window.firmwareVersionChartInstance = new Chart(ctx, { type: 'bar', data: { labels: labels, @@ -67,3 +75,10 @@ async function firmwareVersionGraph() { } firmwareVersionGraph(); + +// Re-render on channel change +document.addEventListener('DOMContentLoaded', () => { + if (typeof StatusFilter !== 'undefined' && StatusFilter.subscribe) { + StatusFilter.subscribe(() => firmwareVersionGraph()); + } +}); diff --git a/js/status/is-ok-to-mqtt-chart.js b/js/status/is-ok-to-mqtt-chart.js index 17266aa..cab2ca5 100644 --- a/js/status/is-ok-to-mqtt-chart.js +++ b/js/status/is-ok-to-mqtt-chart.js @@ -11,8 +11,11 @@ async function isOkToMqttGraph() { try { await fetchNodes(); - // Filter nodes to only include those updated within the last 30 days - const recentNodes = nodes.filter(node => new Date(node.updated_at) >= new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)); + const channelId = (typeof StatusFilter !== 'undefined' && StatusFilter.getChannelId) ? StatusFilter.getChannelId() : null; + // Filter nodes to only include those updated within the last 30 days and channel match + const recentNodes = nodes + .filter(node => new Date(node.updated_at) >= new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)) + .filter(node => !channelId || node.channel_id === channelId); let countTrue = 0; let countFalse = 0; @@ -30,7 +33,12 @@ async function isOkToMqttGraph() { ctx.clearRect(0, 0, canvas.width, canvas.height); - new Chart(ctx, { + if (window.isOkToMqttChartInstance) { + window.isOkToMqttChartInstance.destroy(); + window.isOkToMqttChartInstance = null; + } + + window.isOkToMqttChartInstance = new Chart(ctx, { type: 'bar', data: { labels: ['True', 'False'], @@ -77,4 +85,11 @@ async function isOkToMqttGraph() { isOkToMqttGraph(); +// Re-render on channel change +document.addEventListener('DOMContentLoaded', () => { + if (typeof StatusFilter !== 'undefined' && StatusFilter.subscribe) { + StatusFilter.subscribe(() => isOkToMqttGraph()); + } +}); + diff --git a/js/status/is-unmessagable-chart.js b/js/status/is-unmessagable-chart.js index bca79a4..431bb93 100644 --- a/js/status/is-unmessagable-chart.js +++ b/js/status/is-unmessagable-chart.js @@ -11,8 +11,11 @@ async function isUnmessagableGraph() { try { await fetchNodes(); - // Filter nodes to only include those updated within the last 30 days - const recentNodes = nodes.filter(node => new Date(node.updated_at) >= new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)); + const channelId = (typeof StatusFilter !== 'undefined' && StatusFilter.getChannelId) ? StatusFilter.getChannelId() : null; + // Filter nodes to only include those updated within the last 30 days and channel match + const recentNodes = nodes + .filter(node => new Date(node.updated_at) >= new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)) + .filter(node => !channelId || node.channel_id === channelId); let countTrue = 0; let countFalse = 0; @@ -32,7 +35,12 @@ async function isUnmessagableGraph() { ctx.clearRect(0, 0, canvas.width, canvas.height); - new Chart(ctx, { + if (window.isUnmessagableChartInstance) { + window.isUnmessagableChartInstance.destroy(); + window.isUnmessagableChartInstance = null; + } + + window.isUnmessagableChartInstance = new Chart(ctx, { type: 'bar', data: { labels: ['unmessagable', 'messagable'], @@ -81,4 +89,11 @@ async function isUnmessagableGraph() { } } -isUnmessagableGraph(); \ No newline at end of file +isUnmessagableGraph(); + +// Re-render on channel change +document.addEventListener('DOMContentLoaded', () => { + if (typeof StatusFilter !== 'undefined' && StatusFilter.subscribe) { + StatusFilter.subscribe(() => isUnmessagableGraph()); + } +}); \ No newline at end of file diff --git a/js/status/max-hops-chart.js b/js/status/max-hops-chart.js index 8cddf44..9de81b8 100644 --- a/js/status/max-hops-chart.js +++ b/js/status/max-hops-chart.js @@ -12,6 +12,8 @@ function getHopsColor(maxHops) { return 'rgb(200, 200, 200)'; } +let maxHopsChartInstance = null; + async function maxHopsGraph() { const canvas = document.getElementById('maxHopsChart'); const ctx = canvas.getContext('2d'); @@ -29,7 +31,13 @@ async function maxHopsGraph() { const countsByHops = {}; for (let i = 0; i <= 7; i++) countsByHops[i] = 0; - const nodesWithMaxHops = nodes.filter(n => n.max_hops !== null && n.max_hops !== undefined); + const channelId = (typeof StatusFilter !== 'undefined' && StatusFilter.getChannelId) ? StatusFilter.getChannelId() : null; + const filteredNodes = Array.isArray(nodes) ? nodes.filter(n => { + if (!channelId) return true; + return n.channel_id === channelId; + }) : []; + + const nodesWithMaxHops = filteredNodes.filter(n => n.max_hops !== null && n.max_hops !== undefined); for (const node of nodesWithMaxHops) { const val = Number(node.max_hops); @@ -43,7 +51,12 @@ async function maxHopsGraph() { const chartContainer = document.getElementById('maxHopsContainer'); if (chartContainer) chartContainer.style.height = `${labels.length * 35 + 50}px`; - new Chart(ctx, { + if (maxHopsChartInstance) { + maxHopsChartInstance.destroy(); + maxHopsChartInstance = null; + } + + maxHopsChartInstance = new Chart(ctx, { type: 'bar', data: { labels, @@ -75,3 +88,11 @@ async function maxHopsGraph() { maxHopsGraph(); +// Re-render on channel change +document.addEventListener('DOMContentLoaded', () => { + if (typeof StatusFilter !== 'undefined' && StatusFilter.subscribe) { + StatusFilter.subscribe(() => maxHopsGraph()); + } +}); + + diff --git a/js/status/most-active-nodes.js b/js/status/most-active-nodes.js index 062c417..c3fb1f4 100644 --- a/js/status/most-active-nodes.js +++ b/js/status/most-active-nodes.js @@ -1,63 +1,94 @@ -async function mostActiveNodesGraph() { +document.addEventListener('DOMContentLoaded', () => { const canvas = document.getElementById('mostActiveNodes'); + if (!canvas) return; + const ctx = canvas.getContext('2d'); + let chartInstance = null; - // Show "Loading..." message - ctx.font = '16px Arial'; - ctx.fillStyle = 'gray'; - ctx.textAlign = 'center'; - ctx.fillText('Loading data...', canvas.width / 2, canvas.height / 2); - - try { - const response = await fetch('https://map.sthlm-mesh.se/api/v1/stats/most-active-nodes'); - const data = await response.json(); - - const labels = data.map(entry => entry.long_name); - const counts = data.map(entry => entry.count); - - // Adjust chart height based on number of items - const chartContainer = document.getElementById('mostActiveNodesContainer'); - chartContainer.style.height = `${labels.length * 22 + 50}px`; - - const backgroundColors = counts.map(count => { - if (count < 100) return '#7EB26D'; // Green - if (count < 200) return '#EAB839'; // Yellow - return '#BF1B00'; // Red - }); - - - new Chart(ctx, { - type: 'bar', - data: { - labels: labels, - datasets: [{ - label: 'Packages originated', - data: counts, - backgroundColor: backgroundColors, - borderWidth: 1 - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - indexAxis: 'y', // horizontal bars - scales: { - y: { - ticks: { autoSkip: false, font: { size: 12 } } - }, - x: { max: 300 }, - }, - plugins: { - legend: { display: false }, - } - } - }); - } catch (error) { - console.error('Error fetching data:', error); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.fillStyle = 'red'; - ctx.fillText('Error loading data', canvas.width / 2, canvas.height / 2); + function getSelectedChannelId() { + return (typeof StatusFilter !== 'undefined' && StatusFilter.getChannelId) ? StatusFilter.getChannelId() : null; } -} -mostActiveNodesGraph(); + async function renderMostActiveNodes() { + // Show "Loading..." message + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.font = '16px Arial'; + ctx.fillStyle = 'gray'; + ctx.textAlign = 'center'; + ctx.fillText('Loading data...', canvas.width / 2, canvas.height / 2); + + try { + // Build URL with optional channel_id + const baseUrl = 'https://map.sthlm-mesh.se/api/v1/stats/most-active-nodes'; + const channelId = getSelectedChannelId(); + const url = channelId ? `${baseUrl}?channel_id=${encodeURIComponent(channelId)}` : baseUrl; + + const response = await fetch(url); + const data = await response.json(); + + const labels = data.map(entry => entry.long_name); + const counts = data.map(entry => entry.count); + + // Adjust chart height based on number of items + const chartContainer = document.getElementById('mostActiveNodesContainer'); + if (chartContainer) { + chartContainer.style.height = `${labels.length * 22 + 50}px`; + } + + const backgroundColors = counts.map(count => { + if (count < 100) return '#7EB26D'; // Green + if (count < 200) return '#EAB839'; // Yellow + return '#BF1B00'; // Red + }); + + if (chartInstance) { + chartInstance.destroy(); + chartInstance = null; + } + + chartInstance = new Chart(ctx, { + type: 'bar', + data: { + labels: labels, + datasets: [{ + label: 'Packages originated', + data: counts, + backgroundColor: backgroundColors, + borderWidth: 1 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + indexAxis: 'y', + scales: { + y: { + ticks: { autoSkip: false, font: { size: 12 } } + }, + x: { max: 300 }, + }, + plugins: { + legend: { display: false }, + } + } + }); + } catch (error) { + console.error('Error fetching data:', error); + if (chartInstance) { + chartInstance.destroy(); + chartInstance = null; + } + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = 'red'; + ctx.fillText('Error loading data', canvas.width / 2, canvas.height / 2); + } + } + + // Listen via shared StatusFilter + if (typeof StatusFilter !== 'undefined' && StatusFilter.subscribe) { + StatusFilter.subscribe(() => renderMostActiveNodes()); + } + + // Initial render + renderMostActiveNodes(); +}); diff --git a/js/status/nodes-seen.js b/js/status/nodes-seen.js index 0f92484..e9b8b2c 100644 --- a/js/status/nodes-seen.js +++ b/js/status/nodes-seen.js @@ -1,7 +1,15 @@ +let selectedChannelIdNodes = null; // null = All + function countNodesSince(daysAgo) { const now = new Date(); const threshold = new Date(now.getTime() - daysAgo * 24 * 60 * 60 * 1000); - return nodes.filter(node => new Date(node.updated_at) >= threshold).length; + return nodes + .filter(node => new Date(node.updated_at) >= threshold) + .filter(node => { + if (!selectedChannelIdNodes) return true; // All + return node.channel_id === selectedChannelIdNodes; + }) + .length; } async function nodesSeen() { @@ -13,3 +21,14 @@ async function nodesSeen() { } nodesSeen(); + +// Listen for Antal Enheter channel changes (local only) +document.addEventListener('DOMContentLoaded', () => { + if (typeof StatusFilter !== 'undefined' && StatusFilter.subscribe) { + selectedChannelIdNodes = StatusFilter.getChannelId ? StatusFilter.getChannelId() : null; + StatusFilter.subscribe(() => { + selectedChannelIdNodes = StatusFilter.getChannelId ? StatusFilter.getChannelId() : null; + nodesSeen(); + }); + } +}); diff --git a/js/status/portnum-distribution-chart.js b/js/status/portnum-distribution-chart.js index 6d11264..acb7754 100644 --- a/js/status/portnum-distribution-chart.js +++ b/js/status/portnum-distribution-chart.js @@ -1,5 +1,6 @@ let highlightedIndex = -1; let chartInstance; +let selectedChannelIdPort = null; // null = All function filterSuggestions(query) { @@ -97,6 +98,21 @@ document.getElementById('clearFilterBtn').addEventListener('click', () => { portnumDistributionChart(); // Reload full chart }); +// Channel filter listeners (mirrors messages/most-active-nodes design) +if (typeof StatusFilter !== 'undefined' && StatusFilter.subscribe) { + StatusFilter.subscribe(() => { + selectedChannelIdPort = StatusFilter.getChannelId ? StatusFilter.getChannelId() : null; + const currentNodeName = document.getElementById('nodeSearch').value.trim(); + if (currentNodeName) { + const match = (nodes || []).find(n => n.long_name === currentNodeName); + const nodeId = match ? match.node_id : null; + portnumDistributionChart(nodeId); + } else { + portnumDistributionChart(); + } + }); +} + // Main chart function async function portnumDistributionChart(nodeId = null) { const canvas = document.getElementById('portnumDistribution'); @@ -110,9 +126,11 @@ async function portnumDistributionChart(nodeId = null) { try { await fetchNodes(); - const url = nodeId - ? `https://map.sthlm-mesh.se/api/v1/stats/portnum-counts?nodeId=${nodeId}` - : 'https://map.sthlm-mesh.se/api/v1/stats/portnum-counts'; + const base = 'https://map.sthlm-mesh.se/api/v1/stats/portnum-counts'; + const params = new URLSearchParams(); + if (nodeId) params.set('nodeId', nodeId); + if (selectedChannelIdPort) params.set('channel_id', selectedChannelIdPort); + const url = params.toString() ? `${base}?${params.toString()}` : base; const response = await fetch(url); const data = await response.json(); diff --git a/js/status/position-precision-chart.js b/js/status/position-precision-chart.js index 0aa8034..63a4e04 100644 --- a/js/status/position-precision-chart.js +++ b/js/status/position-precision-chart.js @@ -77,6 +77,8 @@ function formatPositionPrecision(positionPrecision) { } +let positionPrecisionChartInstance = null; + async function positionPrecisionGraph() { const canvas = document.getElementById('positionPrecisionChart'); const ctx = canvas.getContext('2d'); @@ -90,10 +92,13 @@ async function positionPrecisionGraph() { try { await fetchNodes(); + const channelId = (typeof StatusFilter !== 'undefined' && StatusFilter.getChannelId) ? StatusFilter.getChannelId() : null; const countsByPrecision = {}; // Filter out nodes without position_precision - const nodesWithPoistion = nodes.filter(node => node.position_precision != null); + const nodesWithPoistion = nodes + .filter(node => node.position_precision != null) + .filter(node => !channelId || node.channel_id === channelId); // Count how many nodes fall into each precision value for (const node of nodesWithPoistion) { @@ -116,7 +121,12 @@ async function positionPrecisionGraph() { const chartContainer = document.getElementById('positionPrecisionContainer'); chartContainer.style.height = `${labels.length * 35 + 50}px`; - new Chart(ctx, { + if (positionPrecisionChartInstance) { + positionPrecisionChartInstance.destroy(); + positionPrecisionChartInstance = null; + } + + positionPrecisionChartInstance = new Chart(ctx, { type: 'bar', data: { labels: labels, @@ -144,4 +154,11 @@ async function positionPrecisionGraph() { } -positionPrecisionGraph(); \ No newline at end of file +positionPrecisionGraph(); + +// Re-render on channel change +document.addEventListener('DOMContentLoaded', () => { + if (typeof StatusFilter !== 'undefined' && StatusFilter.subscribe) { + StatusFilter.subscribe(() => positionPrecisionGraph()); + } +}); \ No newline at end of file diff --git a/js/status/shared.js b/js/status/shared.js index fbedde4..6156db4 100644 --- a/js/status/shared.js +++ b/js/status/shared.js @@ -1,5 +1,20 @@ let nodes = []; let fetchPromise = null; +const StatusFilter = { + _channelId: null, + _subscribers: [], + getChannelId() { + return this._channelId; + }, + subscribe(callback) { + if (typeof callback === 'function') this._subscribers.push(callback); + }, + _notify() { + for (const cb of this._subscribers) { + try { cb(this._channelId); } catch (_) {} + } + } +}; async function fetchNodes() { if (nodes.length > 0) return; @@ -19,3 +34,16 @@ async function fetchNodes() { await fetchPromise; } + +// Initialize global channel filter listeners once +document.addEventListener('DOMContentLoaded', () => { + const radios = document.querySelectorAll("input[name='btnchannel-nodes']"); + if (!radios || radios.length === 0) return; + const checked = document.querySelector("input[name='btnchannel-nodes']:checked"); + StatusFilter._channelId = checked ? (checked.getAttribute('data-channel') || null) : null; + radios.forEach(r => r.addEventListener('change', () => { + const channel = r.getAttribute('data-channel'); + StatusFilter._channelId = channel || null; + StatusFilter._notify(); + })); +}); diff --git a/sitemap.xml b/sitemap.xml index 6f7813f..0abc09f 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -1 +1 @@ -https://sthlm-mesh.se/docs/settings/2025-09-28T18:34:50+02:00https://sthlm-mesh.se/docs/device_role/2025-09-28T18:34:50+02:00https://sthlm-mesh.se/docs/position/2025-09-28T18:34:50+02:00https://sthlm-mesh.se/blog/2025/meetup-1-oktober-sundbyberg/2025-09-24T19:44:30+02:00https://sthlm-mesh.se/blog/2025/%C3%B6verg%C3%A5ng-till-mediumfast-den-27-september/2025-09-21T22:33:53+02:00https://sthlm-mesh.se/blog/2025/meshtastic-p%C3%A5-sec-t/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/blog/2025/meetup-19-augusti-telefonplan/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/blog/2025/meetup-8-april-sundbyberg/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/blog/2024/meetup-4-september-sundbyberg/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/blog/2024/meetup-15-maj-urban-deli/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/blog/2025-08-24T19:47:51+02:00https://sthlm-mesh.se/docs/mqtt/2025-03-31T19:16:34+02:00https://sthlm-mesh.se/blog/meetups/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/docs/neighbor_info/2025-09-28T18:34:50+02:00https://sthlm-mesh.se/blog/past-meetups/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/docs/kartor/2025-03-08T20:11:28+01:00https://sthlm-mesh.se/docs/hardware/2025-04-21T12:00:04+02:00https://sthlm-mesh.se/docs/solar_nodes/2025-04-21T01:54:22+02:00https://sthlm-mesh.se/docs/communities/2025-02-23T20:48:06+01:00https://sthlm-mesh.se/categories/https://sthlm-mesh.se/docs/2025-03-03T22:48:04+01:00https://sthlm-mesh.se/messages/2025-09-28T21:23:33+02:00https://sthlm-mesh.se/about/2025-02-23T18:02:23+01:00https://sthlm-mesh.se/status/2025-09-25T22:18:41+02:00https://sthlm-mesh.se/2025-09-21T21:23:50+02:00https://sthlm-mesh.se/tags/ \ No newline at end of file +https://sthlm-mesh.se/docs/settings/2025-09-28T18:34:50+02:00https://sthlm-mesh.se/docs/device_role/2025-09-28T18:34:50+02:00https://sthlm-mesh.se/docs/position/2025-09-28T18:34:50+02:00https://sthlm-mesh.se/blog/2025/meetup-1-oktober-sundbyberg/2025-09-24T19:44:30+02:00https://sthlm-mesh.se/blog/2025/%C3%B6verg%C3%A5ng-till-mediumfast-den-27-september/2025-09-21T22:33:53+02:00https://sthlm-mesh.se/blog/2025/meshtastic-p%C3%A5-sec-t/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/blog/2025/meetup-19-augusti-telefonplan/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/blog/2025/meetup-8-april-sundbyberg/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/blog/2024/meetup-4-september-sundbyberg/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/blog/2024/meetup-15-maj-urban-deli/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/blog/2025-08-24T19:47:51+02:00https://sthlm-mesh.se/docs/mqtt/2025-03-31T19:16:34+02:00https://sthlm-mesh.se/blog/meetups/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/docs/neighbor_info/2025-09-28T18:34:50+02:00https://sthlm-mesh.se/blog/past-meetups/2025-09-21T20:27:22+02:00https://sthlm-mesh.se/docs/kartor/2025-03-08T20:11:28+01:00https://sthlm-mesh.se/docs/hardware/2025-04-21T12:00:04+02:00https://sthlm-mesh.se/docs/solar_nodes/2025-04-21T01:54:22+02:00https://sthlm-mesh.se/docs/communities/2025-02-23T20:48:06+01:00https://sthlm-mesh.se/categories/https://sthlm-mesh.se/docs/2025-03-03T22:48:04+01:00https://sthlm-mesh.se/messages/2025-09-28T21:23:33+02:00https://sthlm-mesh.se/about/2025-02-23T18:02:23+01:00https://sthlm-mesh.se/status/2025-10-05T22:29:26+02:00https://sthlm-mesh.se/2025-09-21T21:23:50+02:00https://sthlm-mesh.se/tags/ \ No newline at end of file diff --git a/status/index.html b/status/index.html index 9fcfc59..d286b3d 100644 --- a/status/index.html +++ b/status/index.html @@ -1,26 +1,18 @@ Mesh Status | STHLM-MESH -

Antal Enheter

Hörts idag

Senaste 7 dagarna

Senaste 30 dagarna

Text Meddelanden

Antalet meddelanden per timme senaste 7 dygnen. Grafen visar meddelanden som skickats på LongFast kanalen, men även okrypterade meddelanden mellan noder. De meddelanden som skickas går att se här.

Kanalutnyttjande

Mätarna visar genomsnittligt kanalutnyttjande för de 10 noder som har högst kanalutnyttjande. Kanalutnyttjande (Channel Utilization) visar hur mycket av radiokanalen som är upptagen. Under 15–20% är allt normalt, över 25% får Telemetri lägre prioritet, vilket kan ge längre tid mellan uppdateringar. Runt 40% stryps positionsuppdateringar. Här i EU gäller dessutom max 10% sändtid per timme som kan pausa sändningar helt.

LongFast
MediumFast

Skapade paket per nod

Grafen nedan visar vilka noder som har skickat flest paket under det senaste dygnet. +


Antal Enheter

Hörts idag

Senaste 7 dagarna

Senaste 30 dagarna

Text Meddelanden

Antalet meddelanden per timme senaste 7 dygnen. Grafen visar meddelanden som skickats på LongFast kanalen, men även okrypterade meddelanden mellan noder. De meddelanden som skickas går att se här.

Kanalutnyttjande

Mätarna visar genomsnittligt kanalutnyttjande för de 10 noder som har högst kanalutnyttjande. Kanalutnyttjande (Channel Utilization) visar hur mycket av radiokanalen som är upptagen. Under 15–20% är allt normalt, över 25% får Telemetri lägre prioritet, vilket kan ge längre tid mellan uppdateringar. Runt 40% stryps positionsuppdateringar. Här i EU gäller dessutom max 10% sändtid per timme som kan pausa sändningar helt.

LongFast
MediumFast

Skapade paket per nod

Grafen nedan visar vilka noder som har skickat flest paket under det senaste dygnet. Graferna bygger på MQTT-data från ett begränsat antal noder. Eftersom vi har begränsad bandbredd är det viktigt att hålla nere trafiken. En sändningsvolym på max 100 paket per dygn är önskvärd.

Packet typer

Fördelningen av olika pakettyper i nätverket under de senaste dygnet. Diagrammet visar hur nätverket används, inklusive meddelanden, positionstelemetri och andra systempaket. Sökrutan till höger kan användas för att visa antalet paket av respektive typ för en given nod.

Enhetsroll

Antalet enheter av respektive Enhetsroll som synts i meshet de senaste 30 dagarna. I större mesh så som Stockholm bör i princip all noder vara Client eller Client Mute. Enbart ett fåtal Router eller Repeater behövs. Dessa måste vara placerade extremt strategiskt, till exempel högst upp i en skidbacke. Router Client är en avvecklad roll som inte finns längre, men i meshen lever det kvar några noder som inte blivit uppdaterade. För mer info see vår dokumentation.

Position Precision

Grafen visar position precision, eller noggrannhet, på de noder som rapporterat sin position de senaste 30 dagarna. Vi ser gärna att man använder en noggrannhet på ±182m eller noggrannare. Detta är dock inte möjligt att ställa in i iPhone Appen. För mer info se vår dokumentation.

Firmware versioner

Antalet enheter av firmware version som synts i meshet de senaste 30 dagarna. Information om firmware information skickas inte över meshet. Men det går att härleda till viss del utifrån vilken information de paket som skickas innehåller.

Max antal hopp

Visar fördelningen av konfigurerade max-hopp för noder som synts de senaste 30 dagarna. För att värna om meshens stabilitet bör man använda ett så lågt antal max hops som möjlig. Används traceroute funktionen för att se hur många hops det krävs för dig att nå olika noder.

Unmessagable

Visar antalet noder som är markerade som “unmessagable” respektive “messagable” baserat på de senaste 30 dagarnas aktivitet. Inställningen “unmessagable” används för att identifiera oövervakade infrastrukturnoder så att meddelanden inte skickas till noder som aldrig kommer att svara. Noder som inte har denna inställning definierad räknas som “messagable” eftersom denna egenskap infördes i version 2.6.8.

OK to MQTT

Visar antalet noder som har “ok_to_mqtt” aktiverad eller avstängd under de senaste 30 dagarna. Flaggan är en begäran om att paket inte ska vidarebefordras till MQTT-servrar och karttjänster som sthlm-mesh. Begäran behöver dock inte respekteras. För att undvika exponering på karttjänster bör man använda krypterade kanaler istället för att dela information publikt. -Äldre firmware skickar inte denna flagga och behandlas som falskt värde.

Batteri

Visar genomsnittlig batterinivå av de noder som rapporterat batteri nivå och inte har fast strömförsörjning.

Hårdvarumodeller

Antalet enheter av respektive hårdvarutyp som synts i meshet de senaste 30 dagarna.

OM

Graferna baseras på data från map.sthlm-mesh.se, där data samlas in från ett par noder i Stockholmsområdet. Data hämtas från API’t, som vi dessutom har utökat med fler funktioner för att möjliggöra mer analys. Visualisering sker genom biblioteket chartjs.org.

OM

Graferna baseras på data från map.sthlm-mesh.se, där data samlas in från ett par noder i Stockholmsområdet. Data hämtas från API’t, som vi dessutom har utökat med fler funktioner för att möjliggöra mer analys. Visualisering sker genom biblioteket chartjs.org.

\ No newline at end of file