Skip to content

port(upstream#2208): rolling 1 h observer TX airtime and duty-cycle warning #479

Closed

Description

@dborup

Context

Port the observer TX/RX airtime over the last hour, with a duty-cycle warning, to our fork. This is an evaluated port, not a blind cherry-pick.

Source repository: Kpa-clawbot/CoreScope
Pull request number: 2208 (upstream issue 661)
PR head commit SHA: c6685c61
State: merged 2026-10-14

Why it matters for us. In the firmware, Dispatcher.cpp:26 computes duty_cycle = 1/(1 + airtime_factor), and simple_repeater defaults to airtime_factor = 1.0 (MyMesh.cpp:891). A repeater therefore allows itself 50% TX airtime per hour and does not enforce the EU 868 MHz g3 sub-band limit of 10%. Our Danish deployment (869.5 MHz) is subject to that limit.

Our fork already has

  • observer_metrics.tx_air_secs / rx_air_secs (ingestor);
  • GET /api/observers/metrics/summary (GetMetricsSummary, cmd/server/db.go);
  • a per-sample tx_airtime_pct / rx_airtime_pct in the RF Health charts (public/analytics.js).

Work

  • Add tx_airtime_pct_1h / rx_airtime_pct_1h per observer to the summary.
    • The value is the share over the last hour, computed from the cumulative counters.
    • A counter that goes down marks a reboot; only the stretch after the last reset counts.
    • Return null when the span is shorter than the minimum or the newest sample is stale. Use the merged upstream constants: a minimum span of 30 min and a maximum age of 15 min.
    • Clamp the value to 0..100 and round it to 2 decimals.
  • Add duty_cycle_warn_pct, set from the new config key dutyCycleWarnPct (default 10). Add it to config.example.json.
  • Add a sortable numeric TX 1h column to the observer list and a TX 1h stat on the RF Health tiles. Both use one shared helper and a warning style based on CSS variables.
  • Reconcile with our existing per-sample tx_airtime_pct. The UI labels must make clear which value is the rolling 1 h share and which is the per-interval value. Do not change the meaning of existing fields.
  • Use a named Go struct for the summary response. No new map[string]interface{}, and remove one if the handler currently uses one.
  • Perf: one extra bulk query per summary request, on the existing timestamp index. The observer list makes one extra bulk call with a client TTL, not one per observer.
  • Tests, adapted to our layout with root-level test-*.js:
    • Go unit tests: steady state, a reboot mid-hour, a single sample, a span under the minimum, a stale newest sample, nil counters, clamping and rounding;
    • summary integration;
    • frontend helper;
    • E2E for the column, the warning state and sorting.
  • Customizer. Note in the PR that the threshold should be exposed in the customizer later (AGENTS.md rule 8). Do not build that here.

Acceptance criteria

The values match the upstream semantics on our data. The warning appears at or above the threshold in both themes. Existing RF Health values are unchanged.

Activity

  1. dborup commented on Oct 15, 2026

    @dborup
    OwnerAuthor

    Fixed by #481 (merged as 4fce0d40e57a2e7b903e9b1bd6b332ed229b8fde).Fixed by #481: port of upstream 2208, rolling 1 h observer TX/RX airtime with a configurable duty-cycle warning (default 10%, EU g3) (APPROVE with nits, CI green).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions