From ecf5fb46a0045eb880fe82d2cc289fce3d67ab8b Mon Sep 17 00:00:00 2001 From: cmsassot Date: Tue, 27 May 2025 16:22:36 +0200 Subject: [PATCH] wip --- .env | 0 cmd/average_position/README.md | 21 ++++ cmd/average_position/main.go | 87 +++++++-------- reach/client/operations.go | 11 +- reach/client/protocol/operations.go | 11 +- reach/client/protocol/testsuite/operations.go | 100 ++++++++++++++++++ .../protocol/testsuite/operations_test.go | 16 +++ reach/client/protocol/v1/model/message.go | 7 ++ reach/client/protocol/v1/operations.go | 23 +++- reach/client/protocol/v2/model/action.go | 4 +- reach/client/protocol/v2/operations.go | 23 +++- 11 files changed, 246 insertions(+), 57 deletions(-) create mode 100644 .env create mode 100644 cmd/average_position/README.md create mode 100644 reach/client/protocol/testsuite/operations_test.go create mode 100644 reach/client/protocol/v1/model/message.go 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 index b7e7119..65d0399 100644 --- a/cmd/average_position/main.go +++ b/cmd/average_position/main.go @@ -65,7 +65,7 @@ func main() { } baseConfig := extractBaseConfig(config) - fmt.Printf("Configuration base actuelle: lat=%v, lon=%v, height=%v, offset=%v\n\n NTRIPSettings: \n address:%s\n port:%d \n username: %s\n password:%s\n sendPositionToBase:%t\n", + 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 @@ -75,7 +75,7 @@ func main() { } //Configuration de la base - if err := setBaseToSingleAndHold(ctx, client, baseConfig); err != nil { + if err := setBaseToModeAndHold(ctx, client, baseConfig, "float"); err != nil { fmt.Printf("[FATAL] %+v", err) os.Exit(1) } @@ -135,7 +135,7 @@ func extractBaseConfig(config *model.Configuration) BaseConfig { } func setupNTRIPCorrections(ctx context.Context, client *reach.Client, config *model.Configuration) error { - fmt.Println("Configuration des corrections NTRIP...") + fmt.Println("\nConfiguration des corrections NTRIP...") // Recherche de points de montage if err := client.GetNTRIPMountPoint(ctx); err != nil { @@ -184,6 +184,8 @@ func handleNTRIPResponse(ctx context.Context, client *reach.Client, config *mode 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", @@ -215,66 +217,67 @@ func updateNTRIPMountPoint(ctx context.Context, client *reach.Client, config *mo return nil } -func setBaseToSingleAndHold(ctx context.Context, client *reach.Client, baseConfig BaseConfig) error { - fmt.Println("Configuration de la base en mode single-and-hold...") - +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("single-and-hold"), + 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 single-and-hold") + 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 du moyennage de position...") + fmt.Println("Démarrage de la moyenne des position...") - if err := client.AveragePosition(ctx); err != nil { - return errors.WithStack(err) - } - - return waitForAverageCompletion(ctx, client) -} - -func waitForAverageCompletion(ctx context.Context, client *reach.Client) error { - broadcasts, err := reach.OnMessageType(ctx, client, "task_status") + messageTask, err := client.AveragePosition(ctx) if err != nil { return errors.WithStack(err) } + logTaskProgress(messageTask) + return handleAverageCompletion(ctx, client, messageTask) - 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 reach.Broadcast) error { +// 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) @@ -283,7 +286,7 @@ func logTaskProgress(message reach.Broadcast) error { return nil } -func handleAverageCompletion(ctx context.Context, client *reach.Client, message reach.Broadcast) error { +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) diff --git a/reach/client/operations.go b/reach/client/operations.go index 1e8b8d7..95db661 100644 --- a/reach/client/operations.go +++ b/reach/client/operations.go @@ -153,17 +153,18 @@ func (c *Client) Reboot(ctx context.Context) error { } // AveragePosition implements protocol.Operations. -func (c *Client) AveragePosition(ctx context.Context) error { +func (c *Client) AveragePosition(ctx context.Context) (*protocol.TaskMessage, error) { _, ops, err := c.getProtocol(ctx) if err != nil { - return errors.WithStack(err) + return nil, errors.WithStack(err) } - if err := ops.AveragePosition(ctx); err != nil { - return errors.WithStack(err) + taskMsg, err := ops.AveragePosition(ctx) + if err != nil { + return nil, errors.WithStack(err) } - return nil + return taskMsg, err } // GetNTRIPMountPoint implements protocol.Operations. diff --git a/reach/client/protocol/operations.go b/reach/client/protocol/operations.go index 33edc62..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 @@ -43,7 +50,7 @@ type Operations interface { Reboot(ctx context.Context) error // AveragePosition gathers data and computes the average position - AveragePosition(ctx context.Context) error + AveragePosition(ctx context.Context) (*TaskMessage, error) //GetNTRIPMountPoint retrieves availables mount point GetNTRIPMountPoint(ctx context.Context) error 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 45afc8e..308e68b 100644 --- a/reach/client/protocol/v1/operations.go +++ b/reach/client/protocol/v1/operations.go @@ -364,7 +364,7 @@ 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) error { +func (o *Operations) AveragePosition(ctx context.Context) (*protocol.TaskMessage, error) { var err error go func() { @@ -373,10 +373,27 @@ func (o *Operations) AveragePosition(ctx context.Context) error { }() if err = o.client.Emit("task", map[string]string{"name": "average_base_coordinates"}); err != nil { - return err + 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 err + return nil, err } // TODO À VOIR POUR LES VERSION 1 diff --git a/reach/client/protocol/v2/model/action.go b/reach/client/protocol/v2/model/action.go index 4c72142..2f0ed35 100644 --- a/reach/client/protocol/v2/model/action.go +++ b/reach/client/protocol/v2/model/action.go @@ -1,6 +1,6 @@ package model type Action struct { - Name string `json:"name"` - Paylaod map[string]interface{} `json:"payload"` + Name string `json:"name"` + Paylaod map[string]any `json:"payload"` } diff --git a/reach/client/protocol/v2/operations.go b/reach/client/protocol/v2/operations.go index 6a76fbb..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,7 +265,7 @@ func (o *Operations) On(ctx context.Context, event string) (chan any, error) { return out, nil } -func (o *Operations) AveragePosition(ctx context.Context) error { +func (o *Operations) AveragePosition(ctx context.Context) (*protocol.TaskMessage, error) { var err error go func() { @@ -273,10 +274,26 @@ func (o *Operations) AveragePosition(ctx context.Context) error { }() if err = o.client.Emit("task", &model.Action{Name: "average_base_coordinates"}); err != nil { - return err + 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 + } - return err + 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.