fix(meshcore): preserve LPP channel byte in remote telemetry rows#3141
Conversation
) Cayenne-LPP responses can carry several records of the same `type` under different `channel` bytes (e.g. battery on ch1 = main, ch2 = solar, ch3 = aux). `recordToTelemetryRows` was reading only `record.type` and dropping `record.channel`, so all four readings collapsed onto the same `telemetryType` (e.g. `mc_battery_volts`). The frontend renders one chart per telemetryType, so only the LPP channel-1 reading was visible and channels 2/3/4 effectively disappeared. Fix: embed `_ch<channel>` into the telemetry type string. Channel-1 becomes `mc_battery_volts_ch1`, channel-2 becomes `mc_battery_volts_ch2`, etc. — each gets its own chart and time series. Notable: we deliberately do NOT write `record.channel` into the row's `channel` column. That column is for the *mesh* channel the packet rode on (used by `maskTelemetryByChannel` in nodeEnhancer.ts for per-channel permission filtering). LPP's `channel` is a within-packet sensor-instance discriminator — an entirely different concept. Mixing them would cause per-channel permission filters to deny access to LPP channel 2/3/4 readings on private mesh channels. Existing recordToTelemetryRows tests updated to expect the new suffix. Added two regression tests: one that feeds four ch1..ch4 records and asserts distinct telemetry types, and one that asserts the row's `channel` column is NOT populated from the LPP byte. Reported by @HougeDK in #3139. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @Yeraze's task —— View job Code Review CompleteI've thoroughly reviewed this PR and the implementation looks excellent. Here's my assessment: Review Tasks:
✅ Overall Assessment: LGTM - High Quality ImplementationThis is a well-designed fix that properly addresses the Cayenne-LPP channel collision issue while maintaining architectural integrity. 🎯 Code Quality & Best PracticesExcellent:
Implementation at const channelSuffix = `_ch${record.channel}`;
const baseType = `${MC_TELEMETRY_PREFIX}${naming.type}${channelSuffix}`;🔧 Technical CorrectnessKey architectural decision (lines 174-180): The code correctly does NOT write
This prevents permission system conflicts where LPP channels 2/3/4 would be incorrectly denied access on private mesh channels. 🧪 Test Coverage - ComprehensiveNew regression test (lines 154-170): Excellent coverage of the core issue: it('preserves the LPP channel byte in the telemetry type', () => {
const recs: MeshCoreTelemetryRecord[] = [
{ channel: 1, type: 116, value: 4.10 },
{ channel: 2, type: 116, value: 12.50 },
// ... channels 3,4
];
// Expects: mc_battery_volts_ch1, mc_battery_volts_ch2, etc.
});Negative test (lines 172-179): Verifies the architectural decision about channel separation: it('does not write the LPP channel into the row\'s mesh-channel column', () => {
// Ensures channel isolation between LPP and mesh concepts
});🚨 Security & PerformanceSecurity: ✅ No concerns - the change is purely cosmetic string formatting 🐛 Potential Issues: None Found
📝 Minor Suggestions
🎯 ResultsThe implementation correctly transforms: Each LPP channel now generates distinct time series and charts in the frontend. Recommendation: ✅ Approve and merge |
…oard (#3139) (#3142) Builds out the missing destination for "favorited" MeshCore telemetry charts. The favorite-star toggle was already present in MeshCore views (DM contact panels), but there was no MeshCore-side surface that read `telemetryFavorites` back. Reported by @HougeDK in #3139. ## What changed 1. **Dashboard becomes source-agnostic** via a new `DashboardDataSource` abstraction (`src/components/Dashboard/dataSources.ts`). Every Meshtastic-specific assumption now goes through the data-source adapter: - Nodes endpoint (`/api/nodes` vs `/api/sources/:id/meshcore/nodes`) - Node-key extraction (`user.id` vs `publicKey`) - Device-role labelling (`getDeviceRoleName` vs MeshCore advType map) - Solar-overlay default-on type list - Whether the "Add Widget" button is shown (MeshCore: false, since `nodeStatus` / `traceroute` / `hopDistribution` widgets all assume Meshtastic data shapes and protocol concepts) Two adapters ship: `meshtasticDashboardSource` (the default, exactly preserves prior behaviour byte-for-byte) and `meshcoreDashboardSource` (the new one). The Dashboard component accepts the adapter as an optional prop with the Meshtastic default, so the existing App.tsx mount point is unchanged. 2. **New `MeshCoreTelemetryView`** mounts the same `<Dashboard>` with `meshcoreDashboardSource`. Adds a "📊 Telemetry" entry to `MeshCoreSubToolbar` between Direct Messages and Node Info. 3. **`mc_*` label support in `TELEMETRY_LABELS`** — adds entries for every LPP type name from `LPP_TYPE_NAMES` and every `mc_status_*` field from `STATUS_FIELD_MAP`. The label resolver also recognises the `_ch<N>` suffix from #3141 and renders e.g. `mc_battery_volts_ch2` as `Battery (ch2)` without enumerating every type×channel combo. ## Test plan - [x] `npx tsc --noEmit` clean - [x] Full Vitest suite: 5411 / 5411 passing (5395 before + 16 new) - [x] New `dataSources.test.ts` covers the MeshCore adapter shape mapping, role labels, key extraction, and per-source defaults - [x] New `TelemetryChart.test.ts` covers the channel-suffix label resolution - [ ] Manual smoke: open a MeshCore source, navigate to the Telemetry tab, verify favorited charts render and the "Add Widget" button is hidden ## Notable design decisions - **No data-shape forking inside Dashboard internals.** The adapter normalises both sources into the existing `NodeInfo` shape at fetch time, so the hooks and grid don't have to know which kind of node they're holding. Keeps the diff small. - **Custom widgets are gated, not duplicated.** Rather than build a separate `MeshCoreDashboard` component, we hide the four Meshtastic-only widget types via `showCustomWidgets: false` on the MeshCore adapter. If/when MeshCore grows equivalent widgets (e.g. MeshCore-flavoured hop distribution), they get added to the same flag-gated AddWidgetModal flow. Closes part 2 of #3139. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ish (#3145) Version bump and CHANGELOG entry for 4.6.5. Highlights: - #3143 MQTT Bridge mirror dashboard (read-only Open button on bridge cards; suppresses every TX surface; MqttBridgeManager now exposes the Meshtastic-shaped methods the consolidated /api/poll + /api/connection endpoints expect). - #3142 MeshCore Telemetry dashboard tab via source-agnostic Dashboard. - #3141 MeshCore: preserve LPP channel byte in remote telemetry rows. - #3140 DM: skip PKI flag when keyMismatchDetected. - #3138 docs reorganization. - #3137 unified: merge nodes across sources so labels show in the unified Nodes view. - #3136 mqtt: allow standalone mqtt_bridge as client-proxy target. CLAUDE.md version line updated to 4.6.5. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Fixes part 1 of #3139 — MeshCore telemetry channels 2/3/4 not appearing.
Cayenne-LPP responses can carry several records of the same
typeunder differentchannelbytes (e.g. battery on ch1 = main, ch2 = solar, ch3 = aux).recordToTelemetryRowsinsrc/server/services/meshcoreRemoteTelemetryScheduler.tswas reading onlyrecord.typeand droppingrecord.channel, so all four readings collapsed onto the sametelemetryType(e.g.mc_battery_volts). The frontend renders one chart per telemetryType, so only the channel-1 reading surfaced.Fix
Embed
_ch<channel>into the telemetry type string:mc_battery_volts_ch1mc_battery_volts_ch2mc_battery_volts_ch3mc_battery_volts_ch4Each LPP channel now produces its own chart and time series.
Subtle bit
We deliberately do not write
record.channelinto the row'schannelcolumn. That column is for the mesh channel the packet rode on (read bymaskTelemetryByChannelinsrc/server/utils/nodeEnhancer.tsfor per-channel permission filtering). LPP'schannelis a within-packet sensor-instance discriminator — an entirely different concept. Mixing the two would cause per-channel permission filters to deny access to LPP channel-2/3/4 readings on private mesh channels.This is called out as an inline comment in the function and asserted by a regression test.
Test plan
npm run test— full Vitest suite (5390 tests) passesmeshcoreRemoteTelemetryScheduler.test.ts— allrecordToTelemetryRowstests pass with new assertionschannelcolumn is not populated from the LPP bytemc_*_ch1,mc_*_ch2, ... type names in the telemetry tableNote on display labels
PR follow-up (separate) will add
TELEMETRY_LABELSentries for themc_*_ch<N>types so the Telemetry tab and the (still-to-be-built) MeshCore Telemetry Dashboard render friendly names. With this PR alone the raw machine names will show — but at least all four channels' data is captured and queryable, which was the regression.Fixes part 1 of #3139.
🤖 Generated with Claude Code