diff --git a/.env b/.env new file mode 100644 index 0000000..e69de29 diff --git a/cmd/average_position/README.md b/cmd/average_position/README.md new file mode 100644 index 0000000..0fe74f2 --- /dev/null +++ b/cmd/average_position/README.md @@ -0,0 +1,21 @@ +# `configuration` + +Utilitaire permettant de faire le scénario de paramétrage de la base automatique : + +- Récupération de la configuration +- Configuration de la base pour avoir des corrections NTRIP +- Changement de mode de la balise (passage de `manuel` à `fix-and-hold`) +- Collecte des positions pour obtenir la moyenne +- Configuration de la base avec les nouvelles coordonées + +## Utilisation + +```shell +go run ./cmd/average_position -host +``` + +Où: + +- `` est l'adresse IP du module Reach sur le réseau (par défaut `192.168.42.1`). + +> **Info** Utiliser le flag `-h` pour voir les autres options disponibles. diff --git a/cmd/average_position/main.go b/cmd/average_position/main.go new file mode 100644 index 0000000..65d0399 --- /dev/null +++ b/cmd/average_position/main.go @@ -0,0 +1,317 @@ +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "log/slog" + "os" + "time" + + reach "forge.cadoles.com/cadoles/go-emlid/reach/client" + "forge.cadoles.com/cadoles/go-emlid/reach/client/logger" + "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol" + "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v2/model" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" +) + +var ( + host string = "192.168.42.1" + rawLogLevel string = "ERROR" +) + +func init() { + flag.StringVar(&rawLogLevel, "log-level", rawLogLevel, "log level") + flag.StringVar(&host, "host", host, "the reachrs module host") +} + +type Coordinates struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Height float64 `json:"height"` +} + +type Payload struct { + Coordinates Coordinates `json:"coordinates"` + AntennaOffset float64 `json:"antenna_offset"` +} + +type BaseConfig struct { + Latitude float64 + Longitude float64 + Height float64 + AntennaOffset float64 +} + +func main() { + flag.Parse() + + ctx := context.Background() + + client, err := initializeClient(ctx) + if err != nil { + fmt.Printf("[FATAL] %+v", err) + os.Exit(1) + } + defer closeClient(ctx, client) + + // Récupération de la configuration + config, err := retrieveAndProcessConfig(ctx, client) + if err != nil { + fmt.Printf("[FATAL] %+v", err) + os.Exit(1) + } + + baseConfig := extractBaseConfig(config) + fmt.Printf("Configuration actuelle de la base :\n lat=%v\n lon=%v\n height=%v\n antenna_offset=%v\n\n NTRIPSettings: \n address:%s\n port:%d \n username: %s\n password:%s\n sendPositionToBase:%t\n", + baseConfig.Latitude, baseConfig.Longitude, baseConfig.Height, baseConfig.AntennaOffset, config.CorrectionInput.BaseCorrections.Settings.Ntripcli.Address, config.CorrectionInput.BaseCorrections.Settings.Ntripcli.Port, config.CorrectionInput.BaseCorrections.Settings.Ntripcli.Username, config.CorrectionInput.BaseCorrections.Settings.Ntripcli.Password, config.CorrectionInput.BaseCorrections.Settings.Ntripcli.SendPositionToBase) + + // Configuration des corrections NTRIP + if err := setupNTRIPCorrections(ctx, client, config); err != nil { + fmt.Printf("[FATAL] %+v", err) + os.Exit(1) + } + + //Configuration de la base + if err := setBaseToModeAndHold(ctx, client, baseConfig, "float"); err != nil { + fmt.Printf("[FATAL] %+v", err) + os.Exit(1) + } + + // Collecte des données de positions + if err := averagePositionAndSave(ctx, client); err != nil { + fmt.Printf("[FATAL] %+v", err) + os.Exit(1) + } + + fmt.Println("Configuration terminée avec succès") +} + +func initializeClient(ctx context.Context) (*reach.Client, error) { + logLevel, err := logger.ParseLevel(rawLogLevel) + if err != nil { + return nil, errors.WithStack(err) + } + + slog.SetLogLoggerLevel(logLevel) + + client := reach.NewClient(host) + if err := client.Connect(ctx); err != nil { + return nil, errors.WithStack(err) + } + + return client, nil +} + +func closeClient(ctx context.Context, client *reach.Client) { + if err := client.Close(ctx); err != nil { + fmt.Printf("[ERROR] Erreur lors de la fermeture: %+v", errors.WithStack(err)) + } +} + +func retrieveAndProcessConfig(ctx context.Context, client *reach.Client) (*model.Configuration, error) { + configData, err := client.Configuration(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + + var config model.Configuration + if err := mapstructure.Decode(configData, &config); err != nil { + return nil, errors.WithStack(err) + } + + return &config, nil +} + +func extractBaseConfig(config *model.Configuration) BaseConfig { + return BaseConfig{ + Latitude: config.BaseMode.BaseCoordinates.Coordinates.Latitude, + Longitude: config.BaseMode.BaseCoordinates.Coordinates.Longitude, + Height: config.BaseMode.BaseCoordinates.Coordinates.Height, + AntennaOffset: config.BaseMode.BaseCoordinates.AntennaOffset, + } +} + +func setupNTRIPCorrections(ctx context.Context, client *reach.Client, config *model.Configuration) error { + fmt.Println("\nConfiguration des corrections NTRIP...") + + // Recherche de points de montage + if err := client.GetNTRIPMountPoint(ctx); err != nil { + return errors.WithStack(err) + } + + return processNTRIPStreams(ctx, client, config) +} + +func processNTRIPStreams(ctx context.Context, client *reach.Client, config *model.Configuration) error { + messages, err := reach.OnMessageType(ctx, client, "task_status") + if err != nil { + return errors.WithStack(err) + } + + timeout := time.NewTimer(30 * time.Second) + defer timeout.Stop() + + for { + select { + case b := <-messages: + if b.State == "completed" { + return handleNTRIPResponse(ctx, client, config, b) + } + + case <-timeout.C: + return errors.New("timeout lors de la récupération des streams NTRIP") + + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func handleNTRIPResponse(ctx context.Context, client *reach.Client, config *model.Configuration, message reach.Broadcast) error { + var response model.NTRIPResponse + if err := mapstructure.Decode(message, &response); err != nil { + return errors.WithStack(err) + } + + displayAvailableStreams(response) + + return updateNTRIPMountPoint(ctx, client, config) +} + +func displayAvailableStreams(response model.NTRIPResponse) { + streams := response.Payload.Str + fmt.Printf("=== %d Streams disponibles ===\n", len(streams)) + fmt.Printf("=== Base < 50 km disponibles ===\n") + + for i, stream := range streams { + if stream.Distance < 50 { + fmt.Printf("%d. %-15s | %s (%s) | %.1fkm\n", + i+1, + stream.Mountpoint, + stream.ID, + stream.Country, + stream.Distance) + } + } +} + +func updateNTRIPMountPoint(ctx context.Context, client *reach.Client, config *model.Configuration) error { + opts := []protocol.SetBaseCorrectionsFunc{ + protocol.WithNTRIPAddress(config.CorrectionInput.BaseCorrections.Settings.Ntripcli.Address), + protocol.WithNTRIPPort(config.CorrectionInput.BaseCorrections.Settings.Ntripcli.Port), + protocol.WithNTRIPUsername(config.CorrectionInput.BaseCorrections.Settings.Ntripcli.Username), + protocol.WithNTRIPPassword(config.CorrectionInput.BaseCorrections.Settings.Ntripcli.Password), + // todo modification du point de montage ici + protocol.WithNTRIPMountPoint("EPI21"), + protocol.WithSendPositionToBase(true), + } + + if err := client.SetBaseCorrections(ctx, opts...); err != nil { + return errors.WithStack(err) + } + + fmt.Println("MountPoint NTRIP mis à jour") + return nil +} + +func setBaseToModeAndHold(ctx context.Context, client *reach.Client, baseConfig BaseConfig, mode string) error { + fmt.Println("Configuration de la base en mode float-and-hold...") + opts := []protocol.SetBaseOptionFunc{ + protocol.WithBaseLatitude(baseConfig.Latitude), + protocol.WithBaseLongitude(baseConfig.Longitude), + protocol.WithBaseHeight(baseConfig.Height), + protocol.WithBaseAntennaOffset(baseConfig.AntennaOffset), + protocol.WithBaseMode(fmt.Sprintf("%s-and-hold", mode)), + } + + if err := client.SetBase(ctx, opts...); err != nil { + return errors.WithStack(err) + } + + fmt.Println("Base configurée en mode float-and-hold") + return nil +} + +func averagePositionAndSave(ctx context.Context, client *reach.Client) error { + fmt.Println("Démarrage de la moyenne des position...") + + messageTask, err := client.AveragePosition(ctx) + if err != nil { + return errors.WithStack(err) + } + logTaskProgress(messageTask) + return handleAverageCompletion(ctx, client, messageTask) + +} + +// func waitForAverageCompletion(ctx context.Context, client *reach.Client) error { +// broadcasts, err := reach.OnMessageType(ctx, client, "task_status") +// if err != nil { +// return errors.WithStack(err) +// } + +// timeout := time.NewTimer(5 * time.Minute) +// defer timeout.Stop() + +// for { +// select { +// case b := <-broadcasts: +// if err := logTaskProgress(b); err != nil { +// fmt.Printf("[WARNING] %+v", err) +// continue +// } + +// if b.State == "completed" { +// return handleAverageCompletion(ctx, client, b) +// } + +// case <-timeout.C: +// return errors.New("timeout lors du moyennage de position") + +// case <-ctx.Done(): +// return ctx.Err() +// } +// } +// } + +func logTaskProgress(message *protocol.TaskMessage) error { + data, err := json.MarshalIndent(message, "", " ") + if err != nil { + return errors.WithStack(err) + } + fmt.Println(string(data)) + return nil +} + +func handleAverageCompletion(ctx context.Context, client *reach.Client, message *protocol.TaskMessage) error { + var payload Payload + if err := mapstructure.Decode(message.Payload, &payload); err != nil { + return errors.WithStack(err) + } + + coords := payload.Coordinates + fmt.Printf("Position moyennée: lat=%g, lon=%g, altitude=%g\n", + coords.Latitude, coords.Longitude, coords.Height) + + return saveAveragedPosition(ctx, client, payload) +} + +func saveAveragedPosition(ctx context.Context, client *reach.Client, payload Payload) error { + opts := []protocol.SetBaseOptionFunc{ + protocol.WithBaseLatitude(payload.Coordinates.Latitude), + protocol.WithBaseLongitude(payload.Coordinates.Longitude), + protocol.WithBaseHeight(payload.Coordinates.Height), + protocol.WithBaseAntennaOffset(payload.AntennaOffset), + protocol.WithBaseMode("manual"), + } + + if err := client.SetBase(ctx, opts...); err != nil { + return errors.WithStack(err) + } + + fmt.Println("Position sauvegardée en configuration") + return nil +} diff --git a/cmd/broadcast/main.go b/cmd/broadcast/main.go index e0e9015..699bd6a 100644 --- a/cmd/broadcast/main.go +++ b/cmd/broadcast/main.go @@ -17,12 +17,14 @@ var ( host string = "192.168.42.1" filter string = "" rawLogLevel string = "ERROR" + messageType string = "broadcast" ) func init() { flag.StringVar(&rawLogLevel, "log-level", rawLogLevel, "log level") flag.StringVar(&host, "host", host, "the reachrs module host") - flag.StringVar(&filter, "filter", filter, "filter the broadcast messages by name") + flag.StringVar(&filter, "filter", filter, "filter the socket messages by name") + flag.StringVar(&messageType, "messageType", messageType, "socket messages by name") } func main() { @@ -51,7 +53,7 @@ func main() { } }() - broadcasts, err := reach.OnBroadcast(ctx, client) + broadcasts, err := reach.OnMessageType(ctx, client, messageType) if err != nil { fmt.Printf("[FATAL] %+v", errors.WithStack(err)) os.Exit(1) diff --git a/reach/client/helper.go b/reach/client/helper.go index 2e37c29..4c8ab12 100644 --- a/reach/client/helper.go +++ b/reach/client/helper.go @@ -41,6 +41,7 @@ func OnMessage[T any](ctx context.Context, client *Client, mType string) (chan T type Broadcast struct { Name string `mapstructure:"name" json:"name"` Payload any `mapstructure:"payload" json:"payload"` + State string `mapstructure:"state" json:"state"` } // OnBroadcast listens for ReachView "broadcast" messages @@ -52,3 +53,13 @@ func OnBroadcast(ctx context.Context, client *Client) (chan Broadcast, error) { return ch, nil } + +// OnMessageType listens for ReachView type of messages +func OnMessageType(ctx context.Context, client *Client, messageType string) (chan Broadcast, error) { + ch, err := OnMessage[Broadcast](ctx, client, messageType) + if err != nil { + return nil, errors.WithStack(err) + } + + return ch, nil +} diff --git a/reach/client/operations.go b/reach/client/operations.go index d446bd8..95db661 100644 --- a/reach/client/operations.go +++ b/reach/client/operations.go @@ -152,4 +152,46 @@ func (c *Client) Reboot(ctx context.Context) error { return nil } +// AveragePosition implements protocol.Operations. +func (c *Client) AveragePosition(ctx context.Context) (*protocol.TaskMessage, error) { + _, ops, err := c.getProtocol(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + + taskMsg, err := ops.AveragePosition(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + + return taskMsg, err +} + +// GetNTRIPMountPoint implements protocol.Operations. +func (c *Client) GetNTRIPMountPoint(ctx context.Context) error { + _, ops, err := c.getProtocol(ctx) + if err != nil { + return errors.WithStack(err) + } + + if err := ops.GetNTRIPMountPoint(ctx); err != nil { + return errors.WithStack(err) + } + + return nil +} + +// GetNTRIPMountPoint implements protocol.Operations. +func (c *Client) SetBaseCorrections(ctx context.Context, funcs ...protocol.SetBaseCorrectionsFunc) error { + _, ops, err := c.getProtocol(ctx) + if err != nil { + return errors.WithStack(err) + } + + if err := ops.SetBaseCorrections(ctx, funcs...); err != nil { + return errors.WithStack(err) + } + return nil +} + var _ protocol.Operations = &Client{} diff --git a/reach/client/protocol/operations.go b/reach/client/protocol/operations.go index 72a5bae..655d488 100644 --- a/reach/client/protocol/operations.go +++ b/reach/client/protocol/operations.go @@ -1,6 +1,8 @@ package protocol -import "context" +import ( + "context" +) type BaseInfo struct { Mode string @@ -9,6 +11,11 @@ type BaseInfo struct { Longitude float64 Height float64 } +type TaskMessage struct { + Name string `json:"name"` + State string `json:"state"` + Payload map[string]interface{} `json:"payload"` +} type Operations interface { // Connect initiates a new connection to the ReachView service @@ -41,4 +48,13 @@ type Operations interface { // Reboot restarts the module Reboot(ctx context.Context) error + + // AveragePosition gathers data and computes the average position + AveragePosition(ctx context.Context) (*TaskMessage, error) + + //GetNTRIPMountPoint retrieves availables mount point + GetNTRIPMountPoint(ctx context.Context) error + + //SetBaseCorrections updates the corrections obtaining station + SetBaseCorrections(ctx context.Context, funcs ...SetBaseCorrectionsFunc) error } diff --git a/reach/client/protocol/set_base_correction.go b/reach/client/protocol/set_base_correction.go new file mode 100644 index 0000000..f8cfcac --- /dev/null +++ b/reach/client/protocol/set_base_correction.go @@ -0,0 +1,56 @@ +package protocol + +type SetBaseCorrectionsOptions struct { + Address *string + Port *int + Username *string + Password *string + MountPoint *string + SendPositionToBase *bool +} + +type SetBaseCorrectionsFunc func(opts *SetBaseCorrectionsOptions) + +func NewSetBaseCorrectionsOptions(funcs ...SetBaseCorrectionsFunc) *SetBaseCorrectionsOptions { + opts := &SetBaseCorrectionsOptions{} + for _, fn := range funcs { + fn(opts) + } + return opts +} + +func WithNTRIPAddress(value string) SetBaseCorrectionsFunc { + return func(opts *SetBaseCorrectionsOptions) { + opts.Address = &value + } +} + +func WithNTRIPPort(value int) SetBaseCorrectionsFunc { + return func(opts *SetBaseCorrectionsOptions) { + opts.Port = &value + } +} + +func WithNTRIPUsername(value string) SetBaseCorrectionsFunc { + return func(opts *SetBaseCorrectionsOptions) { + opts.Username = &value + } +} + +func WithNTRIPPassword(value string) SetBaseCorrectionsFunc { + return func(opts *SetBaseCorrectionsOptions) { + opts.Password = &value + } +} + +func WithNTRIPMountPoint(value string) SetBaseCorrectionsFunc { + return func(opts *SetBaseCorrectionsOptions) { + opts.MountPoint = &value + } +} + +func WithSendPositionToBase(value bool) SetBaseCorrectionsFunc { + return func(opts *SetBaseCorrectionsOptions) { + opts.SendPositionToBase = &value + } +} diff --git a/reach/client/protocol/testsuite/operations.go b/reach/client/protocol/testsuite/operations.go index 9e66d75..3314177 100644 --- a/reach/client/protocol/testsuite/operations.go +++ b/reach/client/protocol/testsuite/operations.go @@ -2,6 +2,7 @@ package testsuite import ( "context" + "fmt" "math" "math/rand" "os" @@ -240,6 +241,105 @@ var testCases = []operationTestCase{ }, }, + { + Name: "SetBaseModeAveragePosition", + Run: func(t *testing.T, ops protocol.Operations) { + ctx := context.Background() + + if err := ops.Connect(ctx); err != nil { + t.Errorf("%+v", errors.WithStack(err)) + return + } + + defer func() { + if err := ops.Close(ctx); err != nil { + t.Errorf("%+v", errors.WithStack(err)) + } + }() + latitude := -90 + rand.Float64()*180 + longitude := -180 + rand.Float64()*360 + height := rand.Float64() * 1000 + antennaOffset := toFixed(rand.Float64()*2, 3) + + opts := []protocol.SetBaseOptionFunc{ + protocol.WithBaseLatitude(latitude), + protocol.WithBaseLongitude(longitude), + protocol.WithBaseHeight(height), + protocol.WithBaseAntennaOffset(antennaOffset), + protocol.WithBaseMode(fmt.Sprintf("%s-and-hold", "fix")), + } + + if err := ops.SetBase(ctx, opts...); err != nil { + t.Errorf("%+v", errors.WithStack(err)) + } + + }, + }, + { + Name: "AveragePosition", + Run: func(t *testing.T, ops protocol.Operations) { + ctx := context.Background() + + if err := ops.Connect(ctx); err != nil { + t.Errorf("%+v", errors.WithStack(err)) + return + } + + defer func() { + if err := ops.Close(ctx); err != nil { + t.Errorf("%+v", errors.WithStack(err)) + } + }() + + taskmessage, err := ops.AveragePosition(ctx) + if err != nil { + t.Errorf("%+v", errors.WithStack(err)) + return + } + fmt.Println(taskmessage) + }, + }, + { + Name: "GetNTRIPMountPoint", + Run: func(t *testing.T, ops protocol.Operations) { + ctx := context.Background() + + if err := ops.Connect(ctx); err != nil { + t.Errorf("%+v", errors.WithStack(err)) + return + } + + defer func() { + if err := ops.Close(ctx); err != nil { + t.Errorf("%+v", errors.WithStack(err)) + } + }() + + if err := ops.GetNTRIPMountPoint(ctx); err != nil { + t.Errorf("%+v", errors.WithStack(err)) + return + } + broadcastCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + messages, err := ops.On(broadcastCtx, "task_status") + if err != nil { + t.Errorf("%+v", errors.WithStack(err)) + return + } + + count := 0 + + for m := range messages { + t.Logf("new message: %s", spew.Sdump(m)) + count++ + } + + if e, g := 1, count; g < e { + t.Errorf("expected total messages > %d, got %d", e, g) + } + }, + }, } func TestOperations(t *testing.T, opsFactory OperationsFactoryFunc) { diff --git a/reach/client/protocol/testsuite/operations_test.go b/reach/client/protocol/testsuite/operations_test.go new file mode 100644 index 0000000..8948c9e --- /dev/null +++ b/reach/client/protocol/testsuite/operations_test.go @@ -0,0 +1,16 @@ +package testsuite + +import ( + "testing" + + "forge.cadoles.com/cadoles/go-emlid/reach/client" + "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol" +) + +func TestReachOperations(t *testing.T) { + opsFactory := func(addr string) (protocol.Operations, error) { + return client.NewClient(addr), nil + } + + TestOperations(t, opsFactory) +} diff --git a/reach/client/protocol/v1/model/message.go b/reach/client/protocol/v1/model/message.go new file mode 100644 index 0000000..13185f8 --- /dev/null +++ b/reach/client/protocol/v1/model/message.go @@ -0,0 +1,7 @@ +package model + +type TaskMessage struct { + Name string `json:"name"` + State string `json:"state"` + Payload map[string]interface{} `json:"payload"` +} diff --git a/reach/client/protocol/v1/operations.go b/reach/client/protocol/v1/operations.go index 6c1ea16..308e68b 100644 --- a/reach/client/protocol/v1/operations.go +++ b/reach/client/protocol/v1/operations.go @@ -2,6 +2,7 @@ package v1 import ( "context" + "encoding/json" "strconv" "strings" "sync" @@ -363,4 +364,75 @@ func (o *Operations) Version(ctx context.Context) (string, bool, error) { return strings.TrimSpace(res.Version), res.Stable, nil } +func (o *Operations) AveragePosition(ctx context.Context) (*protocol.TaskMessage, error) { + var err error + + go func() { + <-ctx.Done() + err = ctx.Err() + }() + + if err = o.client.Emit("task", map[string]string{"name": "average_base_coordinates"}); err != nil { + return nil, err + } + ch, err := o.On(ctx, "task_status") + for message := range ch { + // Convertir vers notre struct + jsonData, err := json.Marshal(message) + if err != nil { + continue + } + + var taskMsg protocol.TaskMessage + if err := json.Unmarshal(jsonData, &taskMsg); err != nil { + continue + } + + if taskMsg.State == "completed" { + return &taskMsg, nil + } + } + + return nil, err +} + +// TODO À VOIR POUR LES VERSION 1 +func (o *Operations) GetNTRIPMountPoint(ctx context.Context) error { + var err error + + go func() { + <-ctx.Done() + err = ctx.Err() + }() + payloadJSON, err := json.Marshal(map[string]interface{}{ + "address": "crtk.net", + "port": 2101, + }) + if err = o.client.Emit("task", map[string]string{"name": "get_ntrip_mountpoints", "payload": string(payloadJSON)}); err != nil { + return err + } + + return err +} + +// todo +func (o *Operations) SetBaseCorrections(ctx context.Context, funcs ...protocol.SetBaseCorrectionsFunc) error { + var err error + + go func() { + <-ctx.Done() + err = ctx.Err() + }() + // todo + payloadJSON, err := json.Marshal(map[string]interface{}{ + "address": "crtk.net", + "port": 2101, + }) + if err = o.client.Emit("task", map[string]string{"name": "get_ntrip_mountpoints", "payload": string(payloadJSON)}); err != nil { + return err + } + + return err +} + var _ protocol.Operations = &Operations{} diff --git a/reach/client/protocol/v2/internal.go b/reach/client/protocol/v2/internal.go index 4a3aa4e..fa9cb88 100644 --- a/reach/client/protocol/v2/internal.go +++ b/reach/client/protocol/v2/internal.go @@ -126,6 +126,16 @@ func (o *Operations) PostDevice(ctx context.Context, device *model.Configuration return &updated, nil } +func (o *Operations) PostBaseCorrection(ctx context.Context, base *model.IOConfig) (*model.IOConfig, error) { + var updated model.IOConfig + + if err := o.PostJSON("/configuration/correction_input/base_corrections", base, &updated); err != nil { + return nil, errors.WithStack(err) + } + + return &updated, nil +} + func (o *Operations) GetUpdater(ctx context.Context) (*model.Updater, error) { updater := &model.Updater{} if err := o.GetJSON("/updater", updater); err != nil { diff --git a/reach/client/protocol/v2/model/action.go b/reach/client/protocol/v2/model/action.go index 57096f6..2f0ed35 100644 --- a/reach/client/protocol/v2/model/action.go +++ b/reach/client/protocol/v2/model/action.go @@ -1,5 +1,6 @@ package model type Action struct { - Name string `json:"name"` + Name string `json:"name"` + Paylaod map[string]any `json:"payload"` } diff --git a/reach/client/protocol/v2/model/corrections_input.go b/reach/client/protocol/v2/model/corrections_input.go new file mode 100644 index 0000000..4f81281 --- /dev/null +++ b/reach/client/protocol/v2/model/corrections_input.go @@ -0,0 +1,81 @@ +package model + +type IOConfig struct { + IOType string `json:"io_type"` + Settings IOConfigSettings `json:"settings"` +} + +type IOConfigSettings struct { + NTRIPCli NTRIPCliConfig `json:"ntripcli"` +} + +type NTRIPCliConfig struct { + Address string `json:"address"` + Port int `json:"port"` + Username string `json:"username"` + Password string `json:"password"` + MountPoint string `json:"mount_point"` + SendPositionToBase bool `json:"send_position_to_base"` +} + +type NTRIPResponse struct { + Name string `json:"name"` + Payload NTRIPPayload `json:"payload"` + State string `json:"state"` +} + +type NTRIPPayload struct { + CAS []CasterInfo `json:"cas"` + Net []NetworkInfo `json:"net"` + Str []StreamInfo `json:"str"` +} + +type CasterInfo struct { + Country string `json:"country"` + Distance float64 `json:"distance"` + FallbackHost string `json:"fallback_host"` + FallbackPort string `json:"fallback_port"` + Host string `json:"host"` + ID string `json:"id"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + NMEA string `json:"nmea"` + Operator string `json:"operator"` + OtherDetails *string `json:"other_details"` + Port string `json:"port"` + Site string `json:"site"` +} + +type NetworkInfo struct { + Authentication string `json:"authentication"` + Distance *float64 `json:"distance"` + Fee string `json:"fee"` + ID string `json:"id"` + Operator string `json:"operator"` + OtherDetails string `json:"other_details"` + WebNet string `json:"web_net"` + WebReg string `json:"web_reg"` + WebStr string `json:"web_str"` +} + +type StreamInfo struct { + Authentication string `json:"authentication"` + Bitrate string `json:"bitrate"` + Carrier string `json:"carrier"` + ComprEncryp string `json:"compr_encryp"` + Country string `json:"country"` + Distance float64 `json:"distance"` + Fee string `json:"fee"` + Format string `json:"format"` + FormatDetails string `json:"format_details"` + Generator string `json:"generator"` + ID string `json:"id"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Mountpoint string `json:"mountpoint"` + NavSystem string `json:"nav_system"` + Network string `json:"network"` + NMEA string `json:"nmea"` + OtherDetails string `json:"other_details"` + Solution string `json:"solution"` +} diff --git a/reach/client/protocol/v2/operations.go b/reach/client/protocol/v2/operations.go index 7a8afb1..e4f4e20 100644 --- a/reach/client/protocol/v2/operations.go +++ b/reach/client/protocol/v2/operations.go @@ -2,6 +2,7 @@ package v2 import ( "context" + "encoding/json" "net/http" "sync" @@ -264,4 +265,102 @@ func (o *Operations) On(ctx context.Context, event string) (chan any, error) { return out, nil } +func (o *Operations) AveragePosition(ctx context.Context) (*protocol.TaskMessage, error) { + var err error + + go func() { + <-ctx.Done() + err = ctx.Err() + }() + + if err = o.client.Emit("task", &model.Action{Name: "average_base_coordinates"}); err != nil { + return nil, err + } + ch, err := o.On(ctx, "task_status") + for message := range ch { + // Convertir vers notre struct + jsonData, err := json.Marshal(message) + if err != nil { + continue + } + + var taskMsg protocol.TaskMessage + if err := json.Unmarshal(jsonData, &taskMsg); err != nil { + continue + } + + if taskMsg.State == "completed" { + return &taskMsg, nil + } + } + return nil, err +} + +// GetNTRIPMountPoint implements protocol.Operations. +func (o *Operations) GetNTRIPMountPoint(ctx context.Context) error { + var err error + + config, err := o.GetConfiguration(ctx) + if err != nil { + return errors.WithStack(err) + } + + go func() { + <-ctx.Done() + err = ctx.Err() + }() + + payload := map[string]any{ + "address": config.CorrectionInput.BaseCorrections.Settings.Ntripcli.Address, + "port": config.CorrectionInput.BaseCorrections.Settings.Ntripcli.Port, + } + + if err = o.client.Emit("task", &model.Action{Name: "get_ntrip_mountpoints", Paylaod: payload}); err != nil { + return err + } + + return err +} + +// SetBaseCorrections implements protocol.Operations. +func (o *Operations) SetBaseCorrections(ctx context.Context, funcs ...protocol.SetBaseCorrectionsFunc) error { + opts := protocol.NewSetBaseCorrectionsOptions(funcs...) + if opts.Address == nil { + return errors.New("NTRIP address is required") + } + if opts.Port == nil { + return errors.New("NTRIP port is required") + } + if opts.Username == nil { + return errors.New("NTRIP username is required") + } + if opts.Password == nil { + return errors.New("NTRIP password is required") + } + if opts.MountPoint == nil { + return errors.New("NTRIP mount point is required") + } + + config := &model.IOConfig{ + // todo parametrage du type ???? + IOType: "ntripcli", + Settings: model.IOConfigSettings{ + NTRIPCli: model.NTRIPCliConfig{ + Address: *opts.Address, + Port: *opts.Port, + Username: *opts.Username, + Password: *opts.Password, + MountPoint: *opts.MountPoint, + SendPositionToBase: opts.SendPositionToBase != nil && *opts.SendPositionToBase, + }, + }, + } + + if _, err := o.PostBaseCorrection(ctx, config); err != nil { + return errors.WithStack(err) + } + + return nil +} + var _ protocol.Operations = &Operations{}