117 lines
3.2 KiB
Go
117 lines
3.2 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"git.piksar.eu/piotr/groundstation/internal/osd"
|
|
"git.piksar.eu/piotr/groundstation/internal/protocol"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// DisplayConfig configures the independent kiosk canvas process.
|
|
type DisplayConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Command string `yaml:"command"`
|
|
User string `yaml:"user"`
|
|
Display string `yaml:"display"`
|
|
Xauthority string `yaml:"xauthority"`
|
|
}
|
|
|
|
// OSDConfig configures the supervisor's flight OSD snapshot publication.
|
|
type OSDConfig struct {
|
|
// StatePath is the snapshot state file the display process reads.
|
|
StatePath string `yaml:"state_path"`
|
|
// ArmIndex and ArmedThreshold derive the arm state from the final RC
|
|
// channels: armed while that channel stays above the threshold.
|
|
ArmIndex int `yaml:"arm_index"`
|
|
ArmedThreshold int16 `yaml:"armed_threshold"`
|
|
// VtxTableGen and VtxTableHash are the operator-selected VTX power
|
|
// table identifiers. Zero means no table is selected yet; a static
|
|
// selection is operator intent and never applied VTX state.
|
|
VtxTableGen uint32 `yaml:"vtx_table_gen"`
|
|
VtxTableHash uint64 `yaml:"vtx_table_hash"`
|
|
}
|
|
|
|
type Config struct {
|
|
Display DisplayConfig `yaml:"display"`
|
|
Serial struct {
|
|
Path string `yaml:"path"`
|
|
} `yaml:"serial"`
|
|
Video struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
AuxIndex int `yaml:"aux_index"`
|
|
Threshold int16 `yaml:"threshold"`
|
|
Command string `yaml:"command"`
|
|
} `yaml:"video"`
|
|
OSD OSDConfig `yaml:"osd"`
|
|
Logging struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Path string `yaml:"path"`
|
|
} `yaml:"logging"`
|
|
}
|
|
|
|
func Default() Config {
|
|
var c Config
|
|
c.Display = DefaultDisplay()
|
|
c.Serial.Path = "/dev/ttyS0"
|
|
c.Video.Enabled = true
|
|
c.Video.AuxIndex = 5 // AUX2, zero-based
|
|
c.Video.Threshold = -512
|
|
c.Video.Command = "fpv-video"
|
|
c.OSD = DefaultOSD()
|
|
c.Logging.Enabled = true
|
|
return c
|
|
}
|
|
|
|
// DefaultDisplay starts the kiosk canvas before video on the desktop X
|
|
// server owned by the unprivileged display user.
|
|
func DefaultDisplay() DisplayConfig {
|
|
return DisplayConfig{
|
|
Enabled: true,
|
|
Command: "/usr/local/bin/groundstation-display",
|
|
User: "orangepi",
|
|
Display: ":0",
|
|
Xauthority: "/home/orangepi/.Xauthority",
|
|
}
|
|
}
|
|
|
|
// DefaultOSD publishes snapshots to the default tmpfs path and derives the
|
|
// arm state from CH5/AUX1, armed above the midpoint.
|
|
func DefaultOSD() OSDConfig {
|
|
return OSDConfig{
|
|
StatePath: osd.DefaultStatePath,
|
|
ArmIndex: 4,
|
|
ArmedThreshold: 0,
|
|
}
|
|
}
|
|
|
|
func Load(path string) (Config, error) {
|
|
config := Default()
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return config, config.validate()
|
|
}
|
|
return config, err
|
|
}
|
|
if err := yaml.Unmarshal(data, &config); err != nil {
|
|
return config, err
|
|
}
|
|
return config, config.validate()
|
|
}
|
|
|
|
// validate rejects channel indexes outside the final RC channel range: an
|
|
// out-of-range index would panic on the first decoded frame.
|
|
func (c Config) validate() error {
|
|
for name, index := range map[string]int{
|
|
"video.aux_index": c.Video.AuxIndex,
|
|
"osd.arm_index": c.OSD.ArmIndex,
|
|
} {
|
|
if index < 0 || index >= protocol.Channels {
|
|
return fmt.Errorf("%s %d outside the RC channel range 0..%d", name, index, protocol.Channels-1)
|
|
}
|
|
}
|
|
return nil
|
|
}
|