Skip to content

fix(telemetry): collect air-quality particle counts (protobuf.js snake_case quirk)#3483

Merged
Yeraze merged 1 commit into
mainfrom
fix/air-quality-particle-telemetry
Jun 15, 2026
Merged

fix(telemetry): collect air-quality particle counts (protobuf.js snake_case quirk)#3483
Yeraze merged 1 commit into
mainfrom
fix/air-quality-particle-telemetry

Conversation

@Yeraze

@Yeraze Yeraze commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Summary

A user hooked up an air-quality sensor; the PM/CO₂ values graphed but the particle counts never rendered. Root cause is a protobuf.js field-naming quirk in the serial/direct telemetry ingestion path — the data was dropped before it ever reached the database.

Root cause (empirically verified)

protobuf.js's camelCase only collapses an underscore followed by a letter, not a digit. Decoding the bundled telemetry.proto:

pm10_standard    → pm10Standard     ✓
co2_temperature  → co2Temperature   ✓
particles_03um   → particles_03um   ✗  (stays snake_case!)

processTelemetryMessageProtobuf read the particle bins as aqMetrics.particles03um (camelCase) → undefinedsaveTelemetryMetrics filters out undefined → nothing stored → nothing to graph. The PM and CO₂ fields were unaffected because their underscores precede letters, which is why only the particle data went missing.

The same quirk affects EnvironmentMetrics.rainfall_1h / rainfall_24h, fixed alongside.

Fix

Read the snake_case form the decoder actually produces, with the camelCase as a fallback (so a future protobuf.js/keepCase change can't re-break it). The stored telemetryType stays the canonical camelCase the UI graphs/colors already key on (particles03um, etc.) — so no frontend change is needed; labels, colors and integer-formatting for these types already exist.

Note: the MQTT ingestion path was already correct — its own snakeToCamel (telemetryKeys.ts) includes digits — so only the direct/serial path needed fixing.

Testing

  • New meshtasticManager.airQualityParticles.test.ts: feeds payloads shaped exactly as protobuf.js decodes them (snake_case digit fields) and asserts all six particle bins + rainfall_1h/24h are stored under canonical types, including zero-valued readings. Would fail on the unfixed code.
  • Full Vitest suite green: 6450 passed, 0 failures. tsc --noEmit clean.

🤖 Generated with Claude Code

…e_case quirk)

Particle counts from an air-quality sensor (particles_03um … particles_100um)
were never stored or graphed. protobuf.js only camelCases an underscore that
precedes a *letter*, so these underscore-before-digit fields stay snake_case on
the decoded message. The serial/direct ingestion path read them as
`aqMetrics.particles03um` (camelCase) → undefined → saveTelemetryMetrics dropped
them. PM/CO2 fields were fine because their underscores precede letters.

- Read the snake_case form the decoder produces, camelCase as a fallback, for
  the six particle bins and for EnvironmentMetrics rainfall_1h / rainfall_24h
  (same quirk). Stored telemetryType stays canonical camelCase for the UI.
- Regression test feeds payloads shaped exactly as protobuf.js decodes them.

Empirically verified against the bundled telemetry.proto: particles_03um decodes
to `particles_03um`, while pm10_standard → pm10Standard and co2_temperature →
co2Temperature. The MQTT ingestion path was already correct (its snakeToCamel
handles digits).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review Analysis

I've completed a thorough review of this PR. Here's my comprehensive feedback:

Todo List:

  • Read and analyze CHANGELOG.md changes
  • Review the new test file for coverage adequacy
  • Analyze meshtasticManager.ts changes for the core fix
  • Assess code quality and best practices
  • Check for potential bugs or edge cases
  • Evaluate performance implications
  • Review security considerations
  • Provide comprehensive feedback

Overall Assessment: Excellent Quality

This is a high-quality, well-researched fix with comprehensive testing. I recommend approval.

🎯 Strengths

Problem Analysis & Root Cause

  • Excellent debugging: The protobuf.js camelCase quirk (underscore+digit vs underscore+letter) is perfectly diagnosed
  • Empirical verification: Problem was properly isolated to the serial/direct path vs MQTT path
  • Clear scope: Only affects 8 specific fields (particles_*um + rainfall_*h)

Code Quality

  • Minimal, surgical fix: Only touches the exact lines needed
  • Future-proof pattern: field1 ?? field2 fallback handles potential decoder changes
  • Consistent implementation: Same pattern applied to both air quality particles and rainfall fields
  • Clear comments: Explains the protobuf.js quirk inline for future maintainers

Test Coverage

  • Excellent regression protection: Tests feed exact protobuf.js decoder output format
  • Edge case coverage: Tests zero values (important for particle counts), missing fields, and future decoder changes
  • Clear test structure: Helper functions storedValue() and storedTypes() make assertions readable
  • Real-world data: Uses actual decoded message shapes

🔍 Technical Analysis

Performance: ✅ No Concerns

  • Nullish coalescing (??) is very fast - negligible performance impact
  • Only adds 8 additional property lookups per telemetry message
  • No unnecessary object creation or complex operations

Security: ✅ No Issues

  • No external input validation concerns - data comes from trusted protobuf decoder
  • No SQL injection risk - values pass through existing telemetry validation pipeline
  • No authorization bypass - maintains existing permission model

Compatibility: ✅ Backward/Forward Compatible

  • Maintains existing camelCase storage format for UI consistency
  • MQTT ingestion path unchanged (already working correctly)
  • Graceful fallback for future decoder changes

💡 Architecture Validation

Integration Points Verified:

  • Storage layer: Canonical telemetryTypes maintained (particles03um etc)
  • UI layer: No frontend changes needed - labels/colors/formatting already exist
  • Database: Values still filtered through existing saveTelemetryMetrics pipeline
  • Multi-path: MQTT vs serial paths properly differentiated

Error Handling: ✅ Robust

  • Undefined values still filtered out by existing logic
  • No new crash vectors introduced
  • Maintains existing telemetry data validation

📋 Code Review Checklist

Aspect Status Notes
Bug Fix Accuracy Addresses exact root cause
Test Coverage Comprehensive regression tests
Documentation CHANGELOG entry is excellent
Code Style Consistent with codebase patterns
Performance Minimal overhead
Security No new attack vectors
Backward Compatibility No breaking changes

🧪 Testing Quality

The test file demonstrates excellent testing practices:

  • Tests the actual problem (snake_case input → camelCase storage)
  • Covers zero-value preservation (critical for scientific data)
  • Includes negative test case (missing fields don't create spurious data)
  • Tests forward compatibility (future camelCase decoder support)
  • Well-documented test intentions

📚 Documentation Excellence

  • CHANGELOG: Provides clear user-facing explanation of the bug and fix
  • Code comments: Explain the protobuf.js quirk for future developers
  • Test comments: Document the exact problem being prevented

🏁 Recommendation: APPROVE & MERGE

This PR demonstrates best practices in:

  • Problem diagnosis and verification
  • Minimal, targeted fixes
  • Comprehensive test coverage
  • Clear documentation
  • Future-proofing

No changes required - ready to merge.


@Yeraze Yeraze merged commit 1a94d53 into main Jun 15, 2026
19 checks passed
@Yeraze Yeraze deleted the fix/air-quality-particle-telemetry branch June 15, 2026 17:06
@Yeraze Yeraze mentioned this pull request Jun 15, 2026
Yeraze added a commit that referenced this pull request Jun 15, 2026
Bump version to 4.10.4 across package.json, package-lock.json,
helm/meshmonitor/Chart.yaml, desktop/package.json, and
desktop/src-tauri/tauri.conf.json.

Promote CHANGELOG [Unreleased] to [4.10.4] and add the user-facing fixes
merged since 4.10.3: timed-events cross-source firing (#3479), module
config save for Traffic Management/Status Message (#3464), Auto Favorites
firmware-support cache (#3482), MeshCore Share Contact error surfacing
(#3481), and the Firefox-Android delivered-icon CSS fix (#3477). The
air-quality particle-count fix (#3483) was already in [Unreleased].

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Yeraze added a commit that referenced this pull request Jun 17, 2026
#3506) (#3514)

* fix(telemetry): unify serial ingest onto shared digit-aware normalizer (#3506)

The serial telemetry path (processTelemetryMessageProtobuf) read each
device/environment/airQuality/power metric by a hand-maintained camelCase
property name, with per-field `?? particles_NNum` / `?? rainfall_Nh`
fallbacks bolted on to survive the protobuf.js underscore-before-digit quirk
(#3483). Any new underscore-before-digit field added upstream would silently
drop again until someone remembered to add another fallback.

Replace the four static metric lists with buildCanonicalMetrics(group, decoded),
which iterates the fields the decoder actually produced and resolves each leaf
through the shared canonicalTelemetryType/canonicalTelemetryUnit map (the same
digit-aware snakeToCamel the MQTT path uses). New `_<digit>` fields are now
picked up automatically once their unit lands in CANONICAL_TELEMETRY_UNITS — no
per-field fallback to maintain.

Verified against the real decoder shape: Object.entries yields only wire-present
own fields (unset scalars are null and excluded), genuine 0 readings are own
properties and preserved, and protobuf.js repeated fields (own `[]`) plus
untracked leaves are skipped via the typeof-number + known-unit guards.

Behavior-preserving for the current protobuf; adds coverage for the env leaf
renames, device/power channels, genuine-0, and the repeated/unknown-field skips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test+docs: NaN/Infinity guard test + dual-path comment (review #3514)

- Add a test pinning the Number.isFinite guard (NaN/Infinity excluded, finite
  values still stored).
- Comment the deviceMetrics nodeData copies vs telemetry-history rows so the
  apparent duplication reads clearly.

Follow-up for the unconverted localStats/host/trafficManagement branches tracked
in #3515.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant