diff --git a/cmd/average_position/README.md b/cmd/average_position/README.md index 0fe74f2..549b549 100644 --- a/cmd/average_position/README.md +++ b/cmd/average_position/README.md @@ -11,7 +11,7 @@ Utilitaire permettant de faire le scénario de paramétrage de la base automatiq ## Utilisation ```shell -go run ./cmd/average_position -host +go run ./cmd/average_position -host -baseAccuracy ``` Où: diff --git a/cmd/average_position/main.go b/cmd/average_position/main.go index 63ddc5b..5e9db11 100644 --- a/cmd/average_position/main.go +++ b/cmd/average_position/main.go @@ -2,12 +2,10 @@ 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" @@ -18,24 +16,15 @@ import ( ) var ( - host string = "192.168.42.1" - rawLogLevel string = "ERROR" + host string = "192.168.42.1" + rawLogLevel string = "ERROR" + baseAccuracy string = "fix" ) 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"` + flag.StringVar(&baseAccuracy, "baseAccuracy", baseAccuracy, "precision required for average position") } func main() { @@ -64,25 +53,17 @@ func main() { fmt.Printf("Configuration actuelle de la base :\n lat=%v\n lon=%v\n height=%v\n antenna_offset=%v\n accumulation=%v\n\n NTRIPSettings: \n address:%s\n port:%d \n username: %s\n password:%s\n sendPositionToBase:%t\n", config.BaseMode.BaseCoordinates.Coordinates.Latitude, config.BaseMode.BaseCoordinates.Coordinates.Longitude, config.BaseMode.BaseCoordinates.Coordinates.Height, config.BaseMode.BaseCoordinates.AntennaOffset, config.BaseMode.BaseCoordinates.Accumulation, 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 { + //NTRIP Correction + processNTRIPCorrection(ctx, client, config) + + //Base configuration + if err := setBaseToModeAndHold(ctx, client, config, baseAccuracy, 30); err != nil { fmt.Printf("[FATAL] %+v", err) os.Exit(1) } - //Configuration de la base - if err := setBaseToModeAndHold(ctx, client, config, "float", 30); 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") + // AveragePosition + processAveragePosition(ctx, client) } func initializeClient(ctx context.Context) (*reach.Client, error) { @@ -121,58 +102,32 @@ func retrieveAndProcessConfig(ctx context.Context, client *reach.Client) (*model return &config, nil } -func setupNTRIPCorrections(ctx context.Context, client *reach.Client, config *model.Configuration) error { +func processNTRIPCorrection(ctx context.Context, client *reach.Client, config *model.Configuration) { + // Configuration des corrections NTRIP + ntripMsg, err := setupNTRIPCorrections(ctx, client) + if err != nil { + fmt.Printf("[FATAL] %+v", err) + os.Exit(1) + } + displayAvailableMountPoint(ntripMsg) + updateNTRIPMountPoint(ctx, client, config) +} + +func setupNTRIPCorrections(ctx context.Context, client *reach.Client) (*protocol.TaskMessage[protocol.NTRIPPayload], 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") + ntripMsg, err := client.GetNTRIPMountPoint(ctx) 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() - } + return nil, errors.WithStack(err) } + return ntripMsg, nil } -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) { +func displayAvailableMountPoint(response *protocol.TaskMessage[protocol.NTRIPPayload]) { 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", @@ -205,7 +160,8 @@ func updateNTRIPMountPoint(ctx context.Context, client *reach.Client, config *mo } func setBaseToModeAndHold(ctx context.Context, client *reach.Client, baseConfig *model.Configuration, mode string, accumulation int) error { - fmt.Println("Configuration de la base en mode float-and-hold...") + fmt.Printf("Configuration de la base en mode %s-and-hold...\n", baseAccuracy) + opts := []protocol.SetBaseOptionFunc{ protocol.WithBaseLatitude(baseConfig.BaseMode.BaseCoordinates.Coordinates.Latitude), protocol.WithBaseLongitude(baseConfig.BaseMode.BaseCoordinates.Coordinates.Longitude), @@ -219,50 +175,46 @@ func setBaseToModeAndHold(ctx context.Context, client *reach.Client, baseConfig return errors.WithStack(err) } - fmt.Println("Base configurée en mode float-and-hold") + fmt.Printf("Base configurée en mode %s-and-hold\n", baseAccuracy) return nil } -func averagePositionAndSave(ctx context.Context, client *reach.Client) error { - fmt.Println("Démarrage de la moyenne des position...") +func processAveragePosition(ctx context.Context, client *reach.Client) { + // Collecte des données de positions + messageTask, err := averagePosition(ctx, client) + if err != nil { + fmt.Printf("[FATAL] %+v", err) + os.Exit(1) + } + displayAveragePositionMessage(messageTask) + saveAveragedPosition(ctx, client, messageTask) + fmt.Println("Configuration terminée avec succès") +} + +func averagePosition(ctx context.Context, client *reach.Client) (*protocol.TaskMessage[protocol.AveragePositionPayload], error) { + fmt.Println("Démarrage de la moyenne des position...") messageTask, err := client.AveragePosition(ctx) if err != nil { - return errors.WithStack(err) + return nil, errors.WithStack(err) } - logTaskProgress(messageTask) - return handleAverageCompletion(ctx, client, messageTask) + return messageTask, nil } -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 +func displayAveragePositionMessage(message *protocol.TaskMessage[protocol.AveragePositionPayload]) error { fmt.Printf("Position moyennée: lat=%g, lon=%g, altitude=%g\n", - coords.Latitude, coords.Longitude, coords.Height) + message.Payload.Coordinates.Latitude, message.Payload.Coordinates.Longitude, message.Payload.Coordinates.Height) + return nil - return saveAveragedPosition(ctx, client, payload) } -func saveAveragedPosition(ctx context.Context, client *reach.Client, payload Payload) error { +func saveAveragedPosition(ctx context.Context, client *reach.Client, message *protocol.TaskMessage[protocol.AveragePositionPayload]) error { opts := []protocol.SetBaseOptionFunc{ - protocol.WithBaseLatitude(payload.Coordinates.Latitude), - protocol.WithBaseLongitude(payload.Coordinates.Longitude), - protocol.WithBaseHeight(payload.Coordinates.Height), - protocol.WithBaseAntennaOffset(payload.AntennaOffset), + protocol.WithBaseLatitude(message.Payload.Coordinates.Latitude), + protocol.WithBaseLongitude(message.Payload.Coordinates.Longitude), + protocol.WithBaseHeight(message.Payload.Coordinates.Height), + protocol.WithBaseAntennaOffset(message.Payload.AntennaOffset), protocol.WithBaseMode("manual"), } diff --git a/reach/client/operations.go b/reach/client/operations.go index 0d72f80..86ef123 100644 --- a/reach/client/operations.go +++ b/reach/client/operations.go @@ -153,7 +153,7 @@ func (c *Client) Reboot(ctx context.Context) error { } // AveragePosition implements protocol.Operations. -func (c *Client) AveragePosition(ctx context.Context) (*protocol.TaskMessage, error) { +func (c *Client) AveragePosition(ctx context.Context) (*protocol.TaskMessage[protocol.AveragePositionPayload], error) { _, ops, err := c.getProtocol(ctx) if err != nil { return nil, errors.WithStack(err) @@ -168,17 +168,17 @@ func (c *Client) AveragePosition(ctx context.Context) (*protocol.TaskMessage, er } // GetNTRIPMountPoint implements protocol.Operations. -func (c *Client) GetNTRIPMountPoint(ctx context.Context) error { +func (c *Client) GetNTRIPMountPoint(ctx context.Context) (*protocol.TaskMessage[protocol.NTRIPPayload], error) { _, ops, err := c.getProtocol(ctx) if err != nil { - return errors.WithStack(err) + return nil, errors.WithStack(err) } - if err := ops.GetNTRIPMountPoint(ctx); err != nil { - return errors.WithStack(err) + ntripMsg, err := ops.GetNTRIPMountPoint(ctx) + if err != nil { + return nil, errors.WithStack(err) } - - return nil + return ntripMsg, nil } // SetBaseCorrections implements protocol.Operations. diff --git a/reach/client/protocol/average_postion.go b/reach/client/protocol/average_postion.go new file mode 100644 index 0000000..5d6699f --- /dev/null +++ b/reach/client/protocol/average_postion.go @@ -0,0 +1,12 @@ +package protocol + +type Coordinates struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Height float64 `json:"height"` +} + +type AveragePositionPayload struct { + Coordinates Coordinates `json:"coordinates"` + AntennaOffset float64 `json:"antenna_offset"` +} diff --git a/reach/client/protocol/v2/model/corrections_input.go b/reach/client/protocol/ntrip.go similarity index 93% rename from reach/client/protocol/v2/model/corrections_input.go rename to reach/client/protocol/ntrip.go index 4f81281..68872f8 100644 --- a/reach/client/protocol/v2/model/corrections_input.go +++ b/reach/client/protocol/ntrip.go @@ -1,4 +1,4 @@ -package model +package protocol type IOConfig struct { IOType string `json:"io_type"` @@ -18,12 +18,6 @@ type NTRIPCliConfig struct { 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"` diff --git a/reach/client/protocol/operations.go b/reach/client/protocol/operations.go index 97f35b1..e3ddc49 100644 --- a/reach/client/protocol/operations.go +++ b/reach/client/protocol/operations.go @@ -12,10 +12,10 @@ type BaseInfo struct { Height float64 Accumulation int } -type TaskMessage struct { - Name string `json:"name"` - State string `json:"state"` - Payload map[string]interface{} `json:"payload"` +type TaskMessage[T any] struct { + Name string `json:"name"` + State string `json:"state"` + Payload T `json:"payload"` } type Operations interface { @@ -51,10 +51,10 @@ type Operations interface { Reboot(ctx context.Context) error // AveragePosition gathers data and computes the average position - AveragePosition(ctx context.Context) (*TaskMessage, error) + AveragePosition(ctx context.Context) (*TaskMessage[AveragePositionPayload], error) //GetNTRIPMountPoint retrieves availables mount point - GetNTRIPMountPoint(ctx context.Context) error + GetNTRIPMountPoint(ctx context.Context) (*TaskMessage[NTRIPPayload], error) //SetBaseCorrections updates the corrections obtaining station SetBaseCorrections(ctx context.Context, funcs ...SetBaseCorrectionsFunc) error diff --git a/reach/client/protocol/testsuite/operations.go b/reach/client/protocol/testsuite/operations.go index 00a105b..0c56772 100644 --- a/reach/client/protocol/testsuite/operations.go +++ b/reach/client/protocol/testsuite/operations.go @@ -237,31 +237,6 @@ var testCases = []operationTestCase{ t.Errorf("%+v", errors.WithStack(err)) return } - - }, - }, - { - 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 - } - t.Logf("Task Message : %s", taskmessage) }, }, { @@ -356,7 +331,7 @@ var testCases = []operationTestCase{ }, }, { - Name: "GetNTRIPMountPoint", + Name: "AveragePosition", Run: func(t *testing.T, ops protocol.Operations) { ctx := context.Background() @@ -371,29 +346,35 @@ var testCases = []operationTestCase{ } }() - 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") + taskmessage, err := ops.AveragePosition(ctx) if err != nil { t.Errorf("%+v", errors.WithStack(err)) return } + t.Logf("Task Message : %+v", taskmessage) + }, + }, + { + Name: "GetNTRIPMountPoint", + Run: func(t *testing.T, ops protocol.Operations) { + ctx := context.Background() - count := 0 - - for m := range messages { - t.Logf("new message: %s", spew.Sdump(m)) - count++ + if err := ops.Connect(ctx); err != nil { + t.Errorf("%+v", errors.WithStack(err)) + return } - if e, g := 1, count; g < e { - t.Errorf("expected total messages > %d, got %d", e, g) + defer func() { + if err := ops.Close(ctx); err != nil { + t.Errorf("%+v", errors.WithStack(err)) + } + }() + taskMsg, err := ops.GetNTRIPMountPoint(ctx) + if err != nil { + t.Errorf("%+v", errors.WithStack(err)) + return } + t.Logf("Message : %+v", taskMsg) }, }, } 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/operations.go b/reach/client/protocol/v1/operations.go index 26a596b..8aad20c 100644 --- a/reach/client/protocol/v1/operations.go +++ b/reach/client/protocol/v1/operations.go @@ -376,13 +376,13 @@ func (o *Operations) Version(ctx context.Context) (string, bool, error) { } // Deprecated : is no longer maintained for modules in V1 -func (o *Operations) AveragePosition(ctx context.Context) (*protocol.TaskMessage, error) { +func (o *Operations) AveragePosition(ctx context.Context) (*protocol.TaskMessage[protocol.AveragePositionPayload], error) { return nil, protocol.ErrUnimplemented } // Deprecated : is no longer maintained for modules in V1 -func (o *Operations) GetNTRIPMountPoint(ctx context.Context) error { - return protocol.ErrUnimplemented +func (o *Operations) GetNTRIPMountPoint(ctx context.Context) (*protocol.TaskMessage[protocol.NTRIPPayload], error) { + return nil, protocol.ErrUnimplemented } // Deprecated : is no longer maintained for modules in V1 diff --git a/reach/client/protocol/v2/internal.go b/reach/client/protocol/v2/internal.go index cf85c2f..b0027ec 100644 --- a/reach/client/protocol/v2/internal.go +++ b/reach/client/protocol/v2/internal.go @@ -8,6 +8,7 @@ import ( "io" "net/http" + "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol" "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v2/model" "github.com/pkg/errors" ) @@ -126,8 +127,8 @@ 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 +func (o *Operations) PostBaseCorrection(ctx context.Context, base *protocol.IOConfig) (*protocol.IOConfig, error) { + var updated protocol.IOConfig if err := o.PostJSON("/configuration/correction_input/base_corrections", base, &updated); err != nil { return nil, errors.WithStack(err) diff --git a/reach/client/protocol/v2/operations.go b/reach/client/protocol/v2/operations.go index e76209e..bd79346 100644 --- a/reach/client/protocol/v2/operations.go +++ b/reach/client/protocol/v2/operations.go @@ -2,7 +2,6 @@ package v2 import ( "context" - "encoding/json" "net/http" "sync" @@ -11,6 +10,7 @@ import ( "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v2/model" "forge.cadoles.com/cadoles/go-emlid/reach/client/socketio" + "github.com/mitchellh/mapstructure" "github.com/pkg/errors" ) @@ -270,7 +270,7 @@ 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) { +func (o *Operations) AveragePosition(ctx context.Context) (*protocol.TaskMessage[protocol.AveragePositionPayload], error) { var err error go func() { @@ -283,15 +283,9 @@ func (o *Operations) AveragePosition(ctx context.Context) (*protocol.TaskMessage } 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 + var taskMsg protocol.TaskMessage[protocol.AveragePositionPayload] + if err := mapstructure.Decode(message, &taskMsg); err != nil { + return nil, errors.WithStack(err) } if taskMsg.State == "completed" { @@ -302,12 +296,12 @@ func (o *Operations) AveragePosition(ctx context.Context) (*protocol.TaskMessage } // GetNTRIPMountPoint implements protocol.Operations. -func (o *Operations) GetNTRIPMountPoint(ctx context.Context) error { +func (o *Operations) GetNTRIPMountPoint(ctx context.Context) (*protocol.TaskMessage[protocol.NTRIPPayload], error) { var err error config, err := o.GetConfiguration(ctx) if err != nil { - return errors.WithStack(err) + return nil, errors.WithStack(err) } go func() { @@ -321,10 +315,21 @@ func (o *Operations) GetNTRIPMountPoint(ctx context.Context) error { } if err = o.client.Emit("task", &model.Action{Name: "get_ntrip_mountpoints", Paylaod: payload}); err != nil { - return err + return nil, errors.WithStack(err) } - return err + ch, err := o.On(ctx, "task_status") + for message := range ch { + var taskMsg protocol.TaskMessage[protocol.NTRIPPayload] + if err := mapstructure.Decode(message, &taskMsg); err != nil { + return nil, errors.WithStack(err) + } + + if taskMsg.State == "completed" { + return &taskMsg, nil + } + } + return nil, errors.WithStack(err) } // SetBaseCorrections implements protocol.Operations. @@ -346,11 +351,11 @@ func (o *Operations) SetBaseCorrections(ctx context.Context, funcs ...protocol.S return errors.New("NTRIP mount point is required") } - config := &model.IOConfig{ + config := &protocol.IOConfig{ // todo parametrage du type IOType: "ntripcli", - Settings: model.IOConfigSettings{ - NTRIPCli: model.NTRIPCliConfig{ + Settings: protocol.IOConfigSettings{ + NTRIPCli: protocol.NTRIPCliConfig{ Address: *opts.Address, Port: *opts.Port, Username: *opts.Username,