diff --git a/backend/routers/slm_dashboard.py b/backend/routers/slm_dashboard.py
index f3d3fcd..d35746c 100644
--- a/backend/routers/slm_dashboard.py
+++ b/backend/routers/slm_dashboard.py
@@ -94,6 +94,7 @@ async def get_slm_units(
# Legacy default from the roster field; refined from SLMM's cached status below.
unit.is_recent = bool(unit.slm_last_check and unit.slm_last_check > one_hour_ago)
unit.measurement_state = None
+ unit.cache_last_seen = None # SLMM cache last_seen (real monitoring freshness)
if include_measurement:
# SLMM's /roster carries each unit's CACHED status (last_seen,
@@ -124,6 +125,7 @@ async def get_slm_units(
try:
ls = datetime.fromisoformat(last_seen.replace("Z", ""))
unit.is_recent = ls > recent_cutoff
+ unit.cache_last_seen = ls # the real freshness the monitor updates
except Exception:
pass
diff --git a/templates/partials/slm_device_list.html b/templates/partials/slm_device_list.html
index 810bd0f..56e47a7 100644
--- a/templates/partials/slm_device_list.html
+++ b/templates/partials/slm_device_list.html
@@ -49,7 +49,7 @@
Retired
{% elif not unit.deployed %}
Benched
- {% elif unit.measurement_state == "Start" %}
+ {% elif unit.measurement_state in ["Start", "Measure"] %}
Measuring
{% elif unit.is_recent %}
Active
@@ -57,7 +57,9 @@
Idle
{% endif %}
- {% if unit.slm_last_check %}
+ {% if unit.cache_last_seen %}
+ Last check: {{ unit.cache_last_seen|local_datetime }}
+ {% elif unit.slm_last_check %}
Last check: {{ unit.slm_last_check|local_datetime }}
{% else %}
No recent check-in
diff --git a/templates/sound_level_meters.html b/templates/sound_level_meters.html
index 360ddbd..123e1d8 100644
--- a/templates/sound_level_meters.html
+++ b/templates/sound_level_meters.html
@@ -168,9 +168,18 @@ window.selectedUnitId = null;
window.dashboardChartData = {
timestamps: [],
lp: [],
- leq: []
+ leq: [],
+ ln1: [],
+ ln2: []
};
+// Parse a metric to a number, or null (so a missing/"-.-" percentile leaves a gap
+// in the line instead of dropping it to 0).
+function numOrNull(v) {
+ const f = parseFloat(v);
+ return isNaN(f) ? null : f;
+}
+
// Initialize Chart.js
function initializeDashboardChart() {
if (typeof Chart === 'undefined') {
@@ -212,6 +221,26 @@ function initializeDashboardChart() {
tension: 0.3,
borderWidth: 2,
pointRadius: 0
+ },
+ {
+ label: 'L1',
+ data: [],
+ borderColor: 'rgb(168, 85, 247)',
+ backgroundColor: 'rgba(168, 85, 247, 0.1)',
+ tension: 0.3,
+ borderWidth: 2,
+ pointRadius: 0,
+ spanGaps: true
+ },
+ {
+ label: 'L10',
+ data: [],
+ borderColor: 'rgb(249, 115, 22)',
+ backgroundColor: 'rgba(249, 115, 22, 0.1)',
+ tension: 0.3,
+ borderWidth: 2,
+ pointRadius: 0,
+ spanGaps: true
}
]
},
@@ -263,11 +292,10 @@ function showLiveChart(unitId) {
}
// Reset data for the newly-selected unit (clears any prior unit's line)
- window.dashboardChartData = { timestamps: [], lp: [], leq: [] };
+ window.dashboardChartData = { timestamps: [], lp: [], leq: [], ln1: [], ln2: [] };
if (window.dashboardChart) {
window.dashboardChart.data.labels = [];
- window.dashboardChart.data.datasets[0].data = [];
- window.dashboardChart.data.datasets[1].data = [];
+ window.dashboardChart.data.datasets.forEach(ds => ds.data = []);
window.dashboardChart.update('none');
}
@@ -366,22 +394,30 @@ function updateDashboardMetrics(data) {
}
function updateDashboardChart(data) {
+ const cd = window.dashboardChartData;
const now = new Date();
- window.dashboardChartData.timestamps.push(now.toLocaleTimeString());
- window.dashboardChartData.lp.push(parseFloat(data.lp || 0));
- window.dashboardChartData.leq.push(parseFloat(data.leq || 0));
+ cd.timestamps.push(now.toLocaleTimeString());
+ cd.lp.push(numOrNull(data.lp));
+ cd.leq.push(numOrNull(data.leq));
+ // /monitor (DOD) frames carry ln1/ln2; a DRD frame would omit them -> null gap.
+ cd.ln1.push(numOrNull(data.ln1));
+ cd.ln2.push(numOrNull(data.ln2));
// Keep a generous window (backfill seeds up to ~120 points from the 2h trail).
- if (window.dashboardChartData.timestamps.length > 600) {
- window.dashboardChartData.timestamps.shift();
- window.dashboardChartData.lp.shift();
- window.dashboardChartData.leq.shift();
+ if (cd.timestamps.length > 600) {
+ cd.timestamps.shift();
+ cd.lp.shift();
+ cd.leq.shift();
+ cd.ln1.shift();
+ cd.ln2.shift();
}
if (window.dashboardChart) {
- window.dashboardChart.data.labels = window.dashboardChartData.timestamps;
- window.dashboardChart.data.datasets[0].data = window.dashboardChartData.lp;
- window.dashboardChart.data.datasets[1].data = window.dashboardChartData.leq;
+ window.dashboardChart.data.labels = cd.timestamps;
+ window.dashboardChart.data.datasets[0].data = cd.lp;
+ window.dashboardChart.data.datasets[1].data = cd.leq;
+ window.dashboardChart.data.datasets[2].data = cd.ln1;
+ window.dashboardChart.data.datasets[3].data = cd.ln2;
window.dashboardChart.update('none');
}
}
@@ -417,13 +453,17 @@ async function backfillDashboardChart(unitId) {
// Trail timestamps are naive UTC; append 'Z' to render in local time
// consistently with the live frames (which use local Date.now()).
cd.timestamps.push(row.timestamp ? new Date(row.timestamp + 'Z').toLocaleTimeString() : '');
- cd.lp.push(parseFloat(row.lp || 0));
- cd.leq.push(parseFloat(row.leq || 0));
+ cd.lp.push(numOrNull(row.lp));
+ cd.leq.push(numOrNull(row.leq));
+ cd.ln1.push(numOrNull(row.ln1));
+ cd.ln2.push(numOrNull(row.ln2));
}
if (window.dashboardChart) {
window.dashboardChart.data.labels = cd.timestamps;
window.dashboardChart.data.datasets[0].data = cd.lp;
window.dashboardChart.data.datasets[1].data = cd.leq;
+ window.dashboardChart.data.datasets[2].data = cd.ln1;
+ window.dashboardChart.data.datasets[3].data = cd.ln2;
window.dashboardChart.update('none');
}
} catch (e) {