package hub import "encoding/json" // Telemetry holds the latest flight-controller / battery values for a device. // Pointers distinguish "not yet reported" (nil) from a genuine zero value. type Telemetry struct { SatelliteCount *int `json:"satelliteCount,omitempty"` IsFlying *bool `json:"isFlying,omitempty"` FlightMode *string `json:"flightMode,omitempty"` Altitude *float64 `json:"altitude,omitempty"` Latitude *float64 `json:"latitude,omitempty"` Longitude *float64 `json:"longitude,omitempty"` // Phone's own GPS (reported by the Fly App), distinct from the drone's fix // above — used as a location fallback for the Web App's auto bounding box. PhoneLatitude *float64 `json:"phoneLatitude,omitempty"` PhoneLongitude *float64 `json:"phoneLongitude,omitempty"` VelocityX *float64 `json:"velocityX,omitempty"` VelocityY *float64 `json:"velocityY,omitempty"` VelocityZ *float64 `json:"velocityZ,omitempty"` BatteryPercent *int `json:"batteryPercent,omitempty"` } // DeviceState is the server's aggregated view of one app/drone. type DeviceState struct { DeviceID string `json:"deviceId"` Online bool `json:"online"` // app's websocket is connected to the server Connected bool `json:"connected"` // a drone is connected to the app Model string `json:"model"` Registration string `json:"registration"` Telemetry Telemetry `json:"telemetry"` LastSeenMs int64 `json:"lastSeenMs"` } // TrackPoint is one sample of the drone's GPS track (for the map trail). type TrackPoint struct { Lat float64 `json:"lat"` Lng float64 `json:"lng"` Alt float64 `json:"alt"` TS int64 `json:"ts"` } // ServerToUI is the message a dashboard receives over /ws/ui. type ServerToUI struct { Type string `json:"type"` // "snapshot" | "update" | "removed" Device *DeviceState `json:"device,omitempty"` Devices []*DeviceState `json:"devices,omitempty"` DeviceID string `json:"deviceId,omitempty"` // for "removed" Event map[string]any `json:"event,omitempty"` // the raw device event that triggered this TS int64 `json:"ts"` } // Command is what the server pushes down to a device over /ws/device. type Command struct { Type string `json:"type"` // always "command" Command string `json:"command"` Payload map[string]any `json:"payload,omitempty"` TS int64 `json:"ts"` } // toFloat coerces a JSON-decoded value into a float64. func toFloat(v any) (float64, bool) { switch n := v.(type) { case float64: return n, true case float32: return float64(n), true case int: return float64(n), true case int64: return float64(n), true case json.Number: f, err := n.Float64() return f, err == nil } return 0, false } // toInt coerces a JSON-decoded value into an int. func toInt(v any) (int, bool) { if f, ok := toFloat(v); ok { return int(f), true } return 0, false } // applyTelemetry copies any present telemetry fields from a raw event map. func applyTelemetry(t *Telemetry, raw map[string]any) { if v, ok := toInt(raw["satelliteCount"]); ok { t.SatelliteCount = &v } if v, ok := raw["isFlying"].(bool); ok { t.IsFlying = &v } if v, ok := raw["flightMode"].(string); ok { t.FlightMode = &v } if v, ok := toFloat(raw["altitude"]); ok { t.Altitude = &v } if v, ok := toFloat(raw["latitude"]); ok { t.Latitude = &v } if v, ok := toFloat(raw["longitude"]); ok { t.Longitude = &v } if v, ok := toFloat(raw["phoneLatitude"]); ok { t.PhoneLatitude = &v } if v, ok := toFloat(raw["phoneLongitude"]); ok { t.PhoneLongitude = &v } if v, ok := toFloat(raw["velocityX"]); ok { t.VelocityX = &v } if v, ok := toFloat(raw["velocityY"]); ok { t.VelocityY = &v } if v, ok := toFloat(raw["velocityZ"]); ok { t.VelocityZ = &v } }