mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-09-01 16:48:19 +00:00
110 lines
5.2 KiB
Go
110 lines
5.2 KiB
Go
// Copyright 2026 Beacon Contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package api
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/meshcore-go/meshcore-go"
|
|
)
|
|
|
|
// NodeNeighbor represents a neighboring node relationship observed in a given IATA.
|
|
type NodeNeighbor struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name *string `json:"name,omitempty"`
|
|
PublicKey string `json:"publicKey"`
|
|
NodeType int16 `json:"nodeType"`
|
|
NodeTypeName string `json:"nodeTypeName"`
|
|
Latitude *float64 `json:"lat,omitempty"`
|
|
Longitude *float64 `json:"lng,omitempty"`
|
|
IATA string `json:"iata"`
|
|
ObservationCount int64 `json:"observationCount"`
|
|
FirstSeen int64 `json:"firstSeen"` // epoch ms
|
|
LastSeen int64 `json:"lastSeen"` // epoch ms
|
|
SNR *float32 `json:"snr,omitempty"`
|
|
}
|
|
|
|
// NodeIATA represents a single IATA code and the last time the node was heard there.
|
|
type NodeIATA struct {
|
|
IATA string `json:"iata"`
|
|
LastHeard int64 `json:"lastHeard"` // epoch ms
|
|
}
|
|
|
|
// NodeSummary is the minimal node representation used in list responses.
|
|
type NodeSummary struct {
|
|
ID uuid.UUID `json:"id"`
|
|
PublicKey string `json:"publicKey"` // hex-encoded Ed25519 public key
|
|
NodeType int16 `json:"nodeType"` // 1=companion, 2=repeater, 3=room_server, 4=sensor
|
|
NodeTypeName string `json:"nodeTypeName"`
|
|
Name *string `json:"name,omitempty"`
|
|
IsObserver bool `json:"isObserver"` // true if this node is also a known observer
|
|
ObserverID *uuid.UUID `json:"observerId,omitempty"` // UUID of the associated observer row, if any
|
|
Latitude *float64 `json:"lat,omitempty"` // decimal degrees, from advert AppData
|
|
Longitude *float64 `json:"lng,omitempty"` // decimal degrees, from advert AppData
|
|
Radio *string `json:"radio,omitempty"` // shorthand: "freqMhz,bwKhz,sf" e.g. "910.525,62.5,7"
|
|
IATAs []NodeIATA `json:"iatas"` // IATAs where this node has been heard, with last heard timestamps
|
|
DefaultScope *string `json:"defaultScope,omitempty"` // most recently matched transport scope name e.g. "#bc"
|
|
KnownNeighborCount int64 `json:"knownNeighborCount"`
|
|
NeighborIDs []uuid.UUID `json:"neighborIds,omitempty"` // only populated when the list request opts in; see ?neighbors=true
|
|
}
|
|
|
|
// Node is the full node representation including firmware capability flags,
|
|
// location source, and timing metadata.
|
|
type Node struct {
|
|
NodeSummary
|
|
LocationSource *string `json:"locationSource,omitempty"` // "advert" or "manual"
|
|
LastAdvertAt *int64 `json:"lastAdvertAt,omitempty"` // epoch ms, nil if no advert received
|
|
SupportsMultibytePaths bool `json:"supportsMultibytePaths"` // firmware >= 1.14.0; detected via path hash size
|
|
SupportsMultibyteTraces bool `json:"supportsMultibyteTraces"` // firmware >= 1.11.0; detected via trace hash size
|
|
MinFirmwareVersion *string `json:"minFirmwareVersion,omitempty"` // derived from capability flags
|
|
FirstSeen int64 `json:"firstSeen"` // epoch ms
|
|
LastSeen int64 `json:"lastSeen"` // epoch ms
|
|
Metadata any `json:"metadata,omitempty"` // raw JSONB metadata
|
|
Neighbors []NodeNeighbor `json:"neighbors"`
|
|
// Clock drift, repeaters/room servers only (nodeType 2/3); omitted entirely for other
|
|
// node types or when no qualifying advert has been measured yet. Device minus server
|
|
// time, in seconds, from the advert's self-reported timestamp: +ve = device ahead.
|
|
// clockCheckedAt is the server receive time of the advert this was measured from (same
|
|
// moment as lastAdvertAt). clockOutOfSync is |clockDriftSeconds| exceeding a configured
|
|
// threshold (default 5m) -- see internal/config.ResolvedConfig.ClockDriftThreshold.
|
|
ClockDriftSeconds *int `json:"clockDriftSeconds,omitempty"`
|
|
ClockOutOfSync *bool `json:"clockOutOfSync,omitempty"`
|
|
ClockCheckedAt *int64 `json:"clockCheckedAt,omitempty"`
|
|
}
|
|
|
|
// NodeTypeName returns a human-readable name for a node type integer.
|
|
// NOTE: truncation is fine here until there are at least over 200 types of node
|
|
func NodeTypeName(t int16) string {
|
|
switch byte(t) {
|
|
case meshcore.AdvertTypeChat:
|
|
return "companion"
|
|
case meshcore.AdvertTypeRepeater:
|
|
return "repeater"
|
|
case meshcore.AdvertTypeRoom:
|
|
return "room_server"
|
|
case meshcore.AdvertTypeSensor:
|
|
return "sensor"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
// NodeTypeFromString returns the integer node type for a given name.
|
|
// Returns 0 (no filter) if the string is empty or unrecognized.
|
|
func NodeTypeFromString(s string) int16 {
|
|
switch strings.ToLower(s) {
|
|
case "companion", "chat":
|
|
return int16(meshcore.AdvertTypeChat)
|
|
case "repeater":
|
|
return int16(meshcore.AdvertTypeRepeater)
|
|
case "room_server", "roomserver", "room-server", "room":
|
|
return int16(meshcore.AdvertTypeRoom)
|
|
case "sensor":
|
|
return int16(meshcore.AdvertTypeSensor)
|
|
default:
|
|
return 0
|
|
}
|
|
}
|