mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-16 00:49:57 +00:00
Hourly activity read raw rows only 3h back, so a longer rollup backlog left hours drawn as zero. The raw tail now starts at the newest complete rollup hour, capped at the 24h raw holdback, and the response reports rolledUntil and rawFrom so clients can show anything still uncovered.
124 lines
6.7 KiB
Go
124 lines
6.7 KiB
Go
// Copyright 2026 Beacon Contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// ObserverSummary is the minimal observer representation used in list responses.
|
|
type ObserverSummary struct {
|
|
ID uuid.UUID `json:"id"`
|
|
DisplayName *string `json:"displayName,omitempty"` // friendly name from /status messages
|
|
ObserverType *string `json:"observerType,omitempty"` // e.g. "meshcoretomqtt", "meshcoreha"
|
|
IATA string `json:"iata"` // most recently heard IATA
|
|
Status string `json:"status"` // "online" or "offline" derived from last_status_at
|
|
Radio *string `json:"radio,omitempty"` // friendly radio param string: freqMhz,BwKhz,SF e.g. "910.525,62.5,7"
|
|
Scopes []string `json:"scopes,omitempty"` // list of observer forwarded scopes matched to config
|
|
}
|
|
|
|
// ObserverBroker represents a single MQTT broker an observer has been seen on,
|
|
// including timestamps for diagnosing partial outages — e.g. distinguishing
|
|
// "observer is down" from "one broker stopped delivering for this observer".
|
|
type ObserverBroker struct {
|
|
Name string `json:"name"` // broker name e.g. "mqtt1"
|
|
LastSeenAt int64 `json:"lastSeenAt"` // epoch ms, last time observer was seen on this broker
|
|
LastPacketAt int64 `json:"lastPacketAt"` // epoch ms, last packet received via this broker; 0 if none
|
|
}
|
|
|
|
// Observer is the full observer representation including radio config,
|
|
// telemetry, broker memberships and raw status metadata.
|
|
type Observer struct {
|
|
ObserverSummary
|
|
PublicKey string `json:"publicKey"` // hex-encoded public key
|
|
SoftwareVersion *string `json:"softwareVersion,omitempty"`
|
|
HardwareModel *string `json:"hardwareModel,omitempty"`
|
|
FirmwareVersion *string `json:"firmwareVersion,omitempty"`
|
|
FirmwareBuild *string `json:"firmwareBuild,omitempty"`
|
|
RadioFreqMHz *float32 `json:"radioFreqMhz,omitempty"` // MHz e.g. 910.525
|
|
RadioSF *int16 `json:"radioSf,omitempty"` // LoRa spreading factor
|
|
RadioBWKHz *float32 `json:"radioBwKhz,omitempty"` // bandwidth in kHz
|
|
RadioCR *int16 `json:"radioCr,omitempty"` // coding rate denominator
|
|
BatteryLevel *float32 `json:"batteryLevel,omitempty"` // volts, nil if mains powered
|
|
UptimeSeconds *int64 `json:"uptimeSeconds,omitempty"`
|
|
StatusMetadata json.RawMessage `json:"statusMetadata,omitempty" swaggertype:"object"` // raw /status JSON payload
|
|
LastStatusAt *int64 `json:"lastStatusAt,omitempty"` // epoch ms
|
|
FirstSeen int64 `json:"firstSeen"` // epoch ms
|
|
LastSeen int64 `json:"lastSeen"` // epoch ms
|
|
ObservationCount int64 `json:"observationCount"` // legacy cumulative presence counter; includes non-packet events
|
|
Brokers []ObserverBroker `json:"brokers"` // broker names this observer has been seen on
|
|
}
|
|
|
|
// ObserverTelemetryPoint is a single telemetry snapshot for an observer.
|
|
// Airtime is radio seconds: cumulative since boot on 1h points, per-bucket delta on 6h/24h.
|
|
type ObserverTelemetryPoint struct {
|
|
T int64 `json:"t"` // epoch ms
|
|
BatteryMV *int32 `json:"batteryMv,omitempty"`
|
|
AirtimeTxSecs *float32 `json:"airtimeTxSecs,omitempty"`
|
|
AirtimeRxSecs *float32 `json:"airtimeRxSecs,omitempty"`
|
|
NoiseFloorDB *float32 `json:"noiseFloorDb,omitempty"`
|
|
UptimeSeconds *int64 `json:"uptimeSeconds,omitempty"`
|
|
QueueLength *int32 `json:"queueLength,omitempty"`
|
|
ReceiveErrors *int32 `json:"receiveErrors,omitempty"`
|
|
}
|
|
|
|
// ObserverTelemetry is the full telemetry response for an observer.
|
|
// Range and interval reflect the query parameters used.
|
|
type ObserverTelemetry struct {
|
|
Range string `json:"range"`
|
|
Interval string `json:"interval"`
|
|
Points []ObserverTelemetryPoint `json:"points"`
|
|
}
|
|
|
|
// ObserverActivityRadio echoes the observer's current radio params plus the preamble the airtime math assumes.
|
|
type ObserverActivityRadio struct {
|
|
FreqMHz *float32 `json:"freqMhz"`
|
|
SF int16 `json:"sf"`
|
|
BWKHz float32 `json:"bwKhz"`
|
|
CR int16 `json:"cr"`
|
|
PreambleSymbols int `json:"preambleSymbols"`
|
|
}
|
|
|
|
// ObserverActivityPoint is one bucket of what an observer heard.
|
|
type ObserverActivityPoint struct {
|
|
T int64 `json:"t"` // bucket start, epoch ms
|
|
Observations int64 `json:"observations"`
|
|
AirtimeMs *float32 `json:"airtimeMs"`
|
|
SNRAvg *float32 `json:"snrAvg"`
|
|
SNRMin *float32 `json:"snrMin"`
|
|
RSSIAvg *float32 `json:"rssiAvg"`
|
|
}
|
|
|
|
// ObserverActivitySummary describes stored packet records, never MQTT presence events.
|
|
// Freshness and lastCompleteHour are measured at generatedAt, even with an explicit until;
|
|
// only recordedPackets follows the selected activity window.
|
|
type ObserverActivitySummary struct {
|
|
RecordedPackets int64 `json:"recordedPackets"` // stored observations within windowStart/windowEnd
|
|
LastCompleteHour int64 `json:"lastCompleteHour"`
|
|
LastCompleteHourStart int64 `json:"lastCompleteHourStart"`
|
|
LastCompleteHourEnd int64 `json:"lastCompleteHourEnd"`
|
|
LatestRecordedAt *int64 `json:"latestRecordedAt"`
|
|
}
|
|
|
|
// ObserverActivity is the per-observer heard-activity response.
|
|
type ObserverActivity struct {
|
|
WindowStart int64 `json:"windowStart"` // inclusive complete-bucket start, epoch ms
|
|
WindowEnd int64 `json:"windowEnd"` // exclusive end, epoch ms; live requests include the current partial bucket
|
|
GeneratedAt int64 `json:"generatedAt"` // response computation time, not proof of continuous coverage
|
|
Source string `json:"source"` // raw or hourly; missing records do not prove an outage
|
|
Summary *ObserverActivitySummary `json:"summary,omitempty"`
|
|
// Hourly only. Buckets before rolledUntil come from rollups and from rawFrom on from raw rows;
|
|
// when rawFrom is later, the hours between are uncovered, not quiet.
|
|
RolledUntil *int64 `json:"rolledUntil,omitempty"` // end of the newest complete rollup hour, epoch ms
|
|
RawFrom *int64 `json:"rawFrom,omitempty"` // start of the raw tail, epoch ms
|
|
|
|
Range string `json:"range"`
|
|
Interval string `json:"interval"`
|
|
Radio *ObserverActivityRadio `json:"radio"`
|
|
PayloadTypes []PayloadBreakdownItem `json:"payloadTypes"`
|
|
Points []ObserverActivityPoint `json:"points"`
|
|
}
|