From abebc7d8c6ab191c61740c50196b95443c765031 Mon Sep 17 00:00:00 2001 From: cmsassot Date: Wed, 11 Jun 2025 12:00:30 +0200 Subject: [PATCH 01/10] feat(AveragePosition): method to emit average_base_coordinates message --- reach/client/helper.go | 1 + reach/client/operations.go | 14 ++++++++ reach/client/protocol/operations.go | 13 +++++++- reach/client/protocol/testsuite/operations.go | 24 ++++++++++++++ reach/client/protocol/v2/model/action.go | 3 +- reach/client/protocol/v2/operations.go | 32 +++++++++++++++++++ 6 files changed, 85 insertions(+), 2 deletions(-) diff --git a/reach/client/helper.go b/reach/client/helper.go index 2e37c29..28f5167 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 diff --git a/reach/client/operations.go b/reach/client/operations.go index d446bd8..83abd19 100644 --- a/reach/client/operations.go +++ b/reach/client/operations.go @@ -152,4 +152,18 @@ 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 +} var _ protocol.Operations = &Client{} diff --git a/reach/client/protocol/operations.go b/reach/client/protocol/operations.go index 72a5bae..d916e70 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 @@ -8,6 +10,12 @@ type BaseInfo struct { Latitude float64 Longitude float64 Height float64 + Accumulation int +} +type TaskMessage struct { + Name string `json:"name"` + State string `json:"state"` + Payload map[string]interface{} `json:"payload"` } type Operations interface { @@ -41,4 +49,7 @@ 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) } diff --git a/reach/client/protocol/testsuite/operations.go b/reach/client/protocol/testsuite/operations.go index 9e66d75..6a72599 100644 --- a/reach/client/protocol/testsuite/operations.go +++ b/reach/client/protocol/testsuite/operations.go @@ -240,6 +240,30 @@ var testCases = []operationTestCase{ }, }, + { + 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) + }, + }, } func TestOperations(t *testing.T, opsFactory OperationsFactoryFunc) { 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/operations.go b/reach/client/protocol/v2/operations.go index 7a8afb1..0ec7a72 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,35 @@ 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 +} + var _ protocol.Operations = &Operations{} From f56046536471e1a49053056dc5d084ad0fe9d270 Mon Sep 17 00:00:00 2001 From: cmsassot Date: Wed, 11 Jun 2025 12:07:51 +0200 Subject: [PATCH 02/10] feat(CorrectionInput): method to configure the source of correction data --- reach/client/operations.go | 27 +++++++ reach/client/protocol/operations.go | 6 ++ reach/client/protocol/set_base_correction.go | 56 +++++++++++++ reach/client/protocol/testsuite/operations.go | 72 +++++++++++++++++ reach/client/protocol/v2/internal.go | 10 +++ .../protocol/v2/model/corrections_input.go | 81 +++++++++++++++++++ reach/client/protocol/v2/operations.go | 66 +++++++++++++++ 7 files changed, 318 insertions(+) create mode 100644 reach/client/protocol/set_base_correction.go create mode 100644 reach/client/protocol/v2/model/corrections_input.go diff --git a/reach/client/operations.go b/reach/client/operations.go index 83abd19..5e12c31 100644 --- a/reach/client/operations.go +++ b/reach/client/operations.go @@ -166,4 +166,31 @@ func (c *Client) AveragePosition(ctx context.Context) (*protocol.TaskMessage, er 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 +} + +// SetBaseCorrections 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 d916e70..0a029ea 100644 --- a/reach/client/protocol/operations.go +++ b/reach/client/protocol/operations.go @@ -52,4 +52,10 @@ type Operations interface { // 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 6a72599..4490b7f 100644 --- a/reach/client/protocol/testsuite/operations.go +++ b/reach/client/protocol/testsuite/operations.go @@ -264,6 +264,78 @@ var testCases = []operationTestCase{ t.Logf("Task Message : %s", taskmessage) }, }, + { + Name: "SetBaseCorrections", + 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)) + } + }() + opts := []protocol.SetBaseCorrectionsFunc{ + protocol.WithNTRIPAddress("crtk.net"), + protocol.WithNTRIPPort(2101), + protocol.WithNTRIPUsername("centipede"), + protocol.WithNTRIPPassword("centipede"), + protocol.WithNTRIPMountPoint("EPI21"), + protocol.WithSendPositionToBase(true), + } + + if err := ops.SetBaseCorrections(ctx, opts...); err != nil { + t.Errorf("%+v", errors.WithStack(err)) + return + } + t.Logf("BaseCorrection Update") + + }, + }, + { + 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/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/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 0ec7a72..beb920a 100644 --- a/reach/client/protocol/v2/operations.go +++ b/reach/client/protocol/v2/operations.go @@ -296,4 +296,70 @@ func (o *Operations) AveragePosition(ctx context.Context) (*protocol.TaskMessage 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{} From 359a0018c51556aec0624b20dacc4d3874d554f2 Mon Sep 17 00:00:00 2001 From: cmsassot Date: Wed, 11 Jun 2025 12:09:22 +0200 Subject: [PATCH 03/10] feat(test): change base mode for average position --- reach/client/protocol/testsuite/operations.go | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/reach/client/protocol/testsuite/operations.go b/reach/client/protocol/testsuite/operations.go index 4490b7f..f249c12 100644 --- a/reach/client/protocol/testsuite/operations.go +++ b/reach/client/protocol/testsuite/operations.go @@ -264,6 +264,40 @@ var testCases = []operationTestCase{ t.Logf("Task Message : %s", taskmessage) }, }, + { + 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("fix-and-hold"), + } + + if err := ops.SetBase(ctx, opts...); err != nil { + t.Errorf("%+v", errors.WithStack(err)) + } + + }, + }, { Name: "SetBaseCorrections", Run: func(t *testing.T, ops protocol.Operations) { From 3224e1ef0f997fd8ee3df6fc579fa1077b6fdaff Mon Sep 17 00:00:00 2001 From: cmsassot Date: Wed, 11 Jun 2025 12:11:17 +0200 Subject: [PATCH 04/10] feat(modem): method to configure APN/authentication --- reach/client/operations.go | 14 +++++++ reach/client/protocol/modem.go | 41 +++++++++++++++++++ reach/client/protocol/operations.go | 3 ++ reach/client/protocol/testsuite/operations.go | 26 ++++++++++++ reach/client/protocol/v2/internal.go | 10 +++++ reach/client/protocol/v2/model/modem.go | 12 ++++++ reach/client/protocol/v2/operations.go | 27 ++++++++++++ 7 files changed, 133 insertions(+) create mode 100644 reach/client/protocol/modem.go create mode 100644 reach/client/protocol/v2/model/modem.go diff --git a/reach/client/operations.go b/reach/client/operations.go index 5e12c31..0d72f80 100644 --- a/reach/client/operations.go +++ b/reach/client/operations.go @@ -193,4 +193,18 @@ func (c *Client) SetBaseCorrections(ctx context.Context, funcs ...protocol.SetBa } return nil } + +// SetModem implements protocol.Operations. +func (c *Client) SetModem(ctx context.Context, funcs ...protocol.SetModemOptionsFunc) error { + _, ops, err := c.getProtocol(ctx) + if err != nil { + return errors.WithStack(err) + } + + if err := ops.SetModem(ctx, funcs...); err != nil { + return errors.WithStack(err) + } + return nil +} + var _ protocol.Operations = &Client{} diff --git a/reach/client/protocol/modem.go b/reach/client/protocol/modem.go new file mode 100644 index 0000000..a58ca5d --- /dev/null +++ b/reach/client/protocol/modem.go @@ -0,0 +1,41 @@ +package protocol + +type SetModemOptions struct { + Apn *string + Type *string + Username *string + Password *string +} + +type SetModemOptionsFunc func(opts *SetModemOptions) + +func NewSetModemOptions(funcs ...SetModemOptionsFunc) *SetModemOptions { + opts := &SetModemOptions{} + for _, fn := range funcs { + fn(opts) + } + return opts +} + +func WithApn(value string) SetModemOptionsFunc { + return func(opts *SetModemOptions) { + opts.Apn = &value + } +} + +func WithType(value string) SetModemOptionsFunc { + return func(opts *SetModemOptions) { + opts.Type = &value + } +} + +func WithUsername(value string) SetModemOptionsFunc { + return func(opts *SetModemOptions) { + opts.Username = &value + } +} +func WithPassword(value string) SetModemOptionsFunc { + return func(opts *SetModemOptions) { + opts.Password = &value + } +} diff --git a/reach/client/protocol/operations.go b/reach/client/protocol/operations.go index 0a029ea..97f35b1 100644 --- a/reach/client/protocol/operations.go +++ b/reach/client/protocol/operations.go @@ -58,4 +58,7 @@ type Operations interface { //SetBaseCorrections updates the corrections obtaining station SetBaseCorrections(ctx context.Context, funcs ...SetBaseCorrectionsFunc) error + + //SetModem updates mobile data config + SetModem(ctx context.Context, funcs ...SetModemOptionsFunc) error } diff --git a/reach/client/protocol/testsuite/operations.go b/reach/client/protocol/testsuite/operations.go index f249c12..00a105b 100644 --- a/reach/client/protocol/testsuite/operations.go +++ b/reach/client/protocol/testsuite/operations.go @@ -329,6 +329,32 @@ var testCases = []operationTestCase{ }, }, + { + Name: "SetModem", + 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)) + } + }() + opts := []protocol.SetModemOptionsFunc{ + protocol.WithApn("mmsbouygtel.com"), + } + + if err := ops.SetModem(ctx, opts...); err != nil { + t.Errorf("%+v", errors.WithStack(err)) + return + } + t.Logf("Modem Update") + + }, + }, { Name: "GetNTRIPMountPoint", Run: func(t *testing.T, ops protocol.Operations) { diff --git a/reach/client/protocol/v2/internal.go b/reach/client/protocol/v2/internal.go index fa9cb88..cf85c2f 100644 --- a/reach/client/protocol/v2/internal.go +++ b/reach/client/protocol/v2/internal.go @@ -162,3 +162,13 @@ func (o *Operations) GetConfiguration(ctx context.Context) (*model.Configuration return config, nil } + +func (o *Operations) PostModem(ctx context.Context, config *model.ModemAuthentication) (*model.ModemAuthentication, error) { + var updated model.ModemAuthentication + + if err := o.PostJSON("/modem/1/settings", config, &updated); err != nil { + return nil, errors.WithStack(err) + } + + return &updated, nil +} diff --git a/reach/client/protocol/v2/model/modem.go b/reach/client/protocol/v2/model/modem.go new file mode 100644 index 0000000..bfcf7da --- /dev/null +++ b/reach/client/protocol/v2/model/modem.go @@ -0,0 +1,12 @@ +package model + +// type : null, pap_chap, pap, chap +// if type selected, username and password are mandatory +type ModemAuthentication struct { + Authentication struct { + Apn string `json:"apn"` + Type string `json:"type,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + } `json:"authentication"` +} diff --git a/reach/client/protocol/v2/operations.go b/reach/client/protocol/v2/operations.go index beb920a..2fd2c7a 100644 --- a/reach/client/protocol/v2/operations.go +++ b/reach/client/protocol/v2/operations.go @@ -362,4 +362,31 @@ func (o *Operations) SetBaseCorrections(ctx context.Context, funcs ...protocol.S return nil } + +// SetModem implements protocol.Operations. +func (o *Operations) SetModem(ctx context.Context, funcs ...protocol.SetModemOptionsFunc) error { + opts := protocol.NewSetModemOptions(funcs...) + modem := &model.ModemAuthentication{} + if opts.Apn != nil { + modem.Authentication.Apn = *opts.Apn + } + + if opts.Type != nil { + modem.Authentication.Type = *opts.Type + } + + if opts.Password != nil { + modem.Authentication.Password = *opts.Password + } + + if opts.Username != nil { + modem.Authentication.Username = *opts.Username + } + + if _, err := o.PostModem(ctx, modem); err != nil { + return errors.WithStack(err) + } + return nil +} + var _ protocol.Operations = &Operations{} From af3b3b40f37f04e1688640e179adbb7b3f47ccf2 Mon Sep 17 00:00:00 2001 From: cmsassot Date: Wed, 11 Jun 2025 12:13:20 +0200 Subject: [PATCH 05/10] feat(SetBase): new parameter: Accumulation --- reach/client/protocol/set_base.go | 7 +++++++ reach/client/protocol/v2/operations.go | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/reach/client/protocol/set_base.go b/reach/client/protocol/set_base.go index 882780d..a0cd8cf 100644 --- a/reach/client/protocol/set_base.go +++ b/reach/client/protocol/set_base.go @@ -6,6 +6,7 @@ type SetBaseOptions struct { Latitude *float64 Longitude *float64 Height *float64 + Accumulation *int } type SetBaseOptionFunc func(opts *SetBaseOptions) @@ -47,3 +48,9 @@ func WithBaseHeight(value float64) SetBaseOptionFunc { opts.Height = &value } } + +func WithAccumulation(value int) SetBaseOptionFunc { + return func(opts *SetBaseOptions) { + opts.Accumulation = &value + } +} diff --git a/reach/client/protocol/v2/operations.go b/reach/client/protocol/v2/operations.go index 2fd2c7a..e76209e 100644 --- a/reach/client/protocol/v2/operations.go +++ b/reach/client/protocol/v2/operations.go @@ -87,6 +87,10 @@ func (o *Operations) SetBase(ctx context.Context, funcs ...protocol.SetBaseOptio base.Mode = *opts.Mode } + if opts.Accumulation != nil { + base.Accumulation = *opts.Accumulation + } + if opts.Height != nil { base.Coordinates.Height = *opts.Height } @@ -129,6 +133,7 @@ func (o *Operations) GetBaseInfo(ctx context.Context) (*protocol.BaseInfo, error Height: config.BaseMode.BaseCoordinates.Coordinates.Height, Latitude: config.BaseMode.BaseCoordinates.Coordinates.Latitude, Longitude: config.BaseMode.BaseCoordinates.Coordinates.Longitude, + Accumulation: config.BaseMode.BaseCoordinates.Accumulation, } return baseInfo, nil From 5a8f4301cbeef4c66c610cea8187cef72686b5ef Mon Sep 17 00:00:00 2001 From: cmsassot Date: Wed, 11 Jun 2025 12:15:13 +0200 Subject: [PATCH 06/10] feat(cmd): averagePosition, command to run the entire scenario to switch to automatic base --- cmd/average_position/README.md | 21 +++ cmd/average_position/main.go | 288 +++++++++++++++++++++++++++++++++ reach/client/helper.go | 10 ++ 3 files changed, 319 insertions(+) create mode 100644 cmd/average_position/README.md create mode 100644 cmd/average_position/main.go 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..63ddc5b --- /dev/null +++ b/cmd/average_position/main.go @@ -0,0 +1,288 @@ +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"` +} + +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) + /// setModem + if err := updateModem(ctx, client); err != nil { + fmt.Printf("[FATAL] %+v", err) + os.Exit(1) + } + // Récupération de la configuration + config, err := retrieveAndProcessConfig(ctx, client) + if err != nil { + fmt.Printf("[FATAL] %+v", err) + os.Exit(1) + } + + 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 { + 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") +} + +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 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 *model.Configuration, mode string, accumulation int) error { + fmt.Println("Configuration de la base en mode float-and-hold...") + opts := []protocol.SetBaseOptionFunc{ + protocol.WithBaseLatitude(baseConfig.BaseMode.BaseCoordinates.Coordinates.Latitude), + protocol.WithBaseLongitude(baseConfig.BaseMode.BaseCoordinates.Coordinates.Longitude), + protocol.WithBaseHeight(baseConfig.BaseMode.BaseCoordinates.Coordinates.Height), + protocol.WithBaseAntennaOffset(baseConfig.BaseMode.BaseCoordinates.AntennaOffset), + protocol.WithBaseMode(fmt.Sprintf("%s-and-hold", mode)), + protocol.WithAccumulation(accumulation), + } + + 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 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 +} + +func updateModem(ctx context.Context, client *reach.Client) error { + opts := []protocol.SetModemOptionsFunc{ + protocol.WithApn("mmsbouygtel.com"), + } + + if err := client.SetModem(ctx, opts...); err != nil { + return errors.WithStack(err) + } + + fmt.Println("Modem mis à jour") + return nil +} diff --git a/reach/client/helper.go b/reach/client/helper.go index 28f5167..4c8ab12 100644 --- a/reach/client/helper.go +++ b/reach/client/helper.go @@ -53,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 +} From 2c5d1442fe05c3066ef600e0a23dfe292f98f0b3 Mon Sep 17 00:00:00 2001 From: cmsassot Date: Wed, 11 Jun 2025 12:20:19 +0200 Subject: [PATCH 07/10] chore(v1): deprecated --- reach/client/protocol/v1/init.go | 1 + reach/client/protocol/v1/internal.go | 4 +++ .../client/protocol/v1/model/configuration.go | 1 + reach/client/protocol/v1/model/message.go | 8 +++++ reach/client/protocol/v1/operations.go | 32 +++++++++++++++++++ reach/client/protocol/v1/protocol.go | 4 +++ 6 files changed, 50 insertions(+) create mode 100644 reach/client/protocol/v1/model/message.go diff --git a/reach/client/protocol/v1/init.go b/reach/client/protocol/v1/init.go index 522bb43..cb0ac21 100644 --- a/reach/client/protocol/v1/init.go +++ b/reach/client/protocol/v1/init.go @@ -2,6 +2,7 @@ package v1 import "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol" +// Deprecated : is no longer maintained for modules in V1 func init() { protocol.Register(Identifier, func(opts *protocol.ProtocolOptions) (protocol.Protocol, error) { return &Protocol{ diff --git a/reach/client/protocol/v1/internal.go b/reach/client/protocol/v1/internal.go index 888f3a8..18ea0f7 100644 --- a/reach/client/protocol/v1/internal.go +++ b/reach/client/protocol/v1/internal.go @@ -22,12 +22,14 @@ const ( eventSettingsResetToDefault = "settings reset to default" ) +// Deprecated : is no longer maintained for modules in V1 type configurationApplied struct { Configuration *model.Configuration `mapstructure:"configuration,omitempty"` Result string `mapstructure:"result"` Constraints *model.Constraints `mapstructure:"constraints,omitempty"` } +// Deprecated : is no longer maintained for modules in V1 func (o *Operations) ApplyConfiguration(ctx context.Context, config *model.Configuration) (string, *model.Configuration, error) { o.logger.Debug("applying configuration", logger.Attr("configuration", spew.Sdump(config))) @@ -41,6 +43,7 @@ func (o *Operations) ApplyConfiguration(ctx context.Context, config *model.Confi return res.Result, res.Configuration, nil } +// Deprecated : is no longer maintained for modules in V1 func (o *Operations) RequestConfiguration(ctx context.Context) (*model.Configuration, error) { configuration := &model.Configuration{} if err := o.ReqResp(ctx, eventGetConfiguration, nil, eventCurrentConfiguration, configuration); err != nil { @@ -49,6 +52,7 @@ func (o *Operations) RequestConfiguration(ctx context.Context) (*model.Configura return configuration, nil } +// Deprecated : is no longer maintained for modules in V1 // ReqResp emits an event with the given data and waits for a response func (o *Operations) ReqResp(ctx context.Context, requestEvent string, requestData any, diff --git a/reach/client/protocol/v1/model/configuration.go b/reach/client/protocol/v1/model/configuration.go index 2c5779f..fb55449 100644 --- a/reach/client/protocol/v1/model/configuration.go +++ b/reach/client/protocol/v1/model/configuration.go @@ -1,5 +1,6 @@ package model +// Deprecated : is no longer maintained for modules in V1 var ( // On - On = String("on") diff --git a/reach/client/protocol/v1/model/message.go b/reach/client/protocol/v1/model/message.go new file mode 100644 index 0000000..06ffbd5 --- /dev/null +++ b/reach/client/protocol/v1/model/message.go @@ -0,0 +1,8 @@ +package model + +// Deprecated : is no longer maintained for modules in V1 +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..26a596b 100644 --- a/reach/client/protocol/v1/operations.go +++ b/reach/client/protocol/v1/operations.go @@ -13,6 +13,7 @@ import ( "github.com/pkg/errors" ) +// Deprecated : is no longer maintained for modules in V1 type Operations struct { addr string client *socketio.Client @@ -22,6 +23,7 @@ type Operations struct { dial protocol.DialFunc } +// Deprecated : is no longer maintained for modules in V1 // Close implements protocol.Operations. func (o *Operations) Close(ctx context.Context) error { o.mutex.Lock() @@ -36,12 +38,14 @@ func (o *Operations) Close(ctx context.Context) error { return nil } +// Deprecated : is no longer maintained for modules in V1 const ( // EventBrowserConnected is emitted after the initial connection to the // ReachView endpoint eventBrowserConnected = "browser connected" ) +// Deprecated : is no longer maintained for modules in V1 // Connect implements protocol.Operations. func (o *Operations) Connect(ctx context.Context) error { o.mutex.Lock() @@ -80,6 +84,7 @@ func (o *Operations) Connect(ctx context.Context) error { return nil } +// Deprecated : is no longer maintained for modules in V1 // Emit implements protocol.Operations. func (o *Operations) Emit(ctx context.Context, mType string, message any) error { o.mutex.RLock() @@ -96,6 +101,7 @@ func (o *Operations) Emit(ctx context.Context, mType string, message any) error return nil } +// Deprecated : is no longer maintained for modules in V1 // On implements protocol.Operations. func (o *Operations) On(ctx context.Context, event string) (chan any, error) { o.mutex.RLock() @@ -136,6 +142,7 @@ func (o *Operations) On(ctx context.Context, event string) (chan any, error) { return out, nil } +// Deprecated : is no longer maintained for modules in V1 // Alive implements protocol.Operations. func (o *Operations) Alive(ctx context.Context) (bool, error) { o.mutex.RLock() @@ -148,6 +155,7 @@ func (o *Operations) Alive(ctx context.Context) (bool, error) { return o.client.Alive(), nil } +// Deprecated : is no longer maintained for modules in V1 // Configuration implements protocol.Operations. func (o *Operations) Configuration(ctx context.Context) (any, error) { config, err := o.RequestConfiguration(ctx) @@ -162,6 +170,7 @@ const ( eventReboot = "reboot" ) +// Deprecated : is no longer maintained for modules in V1 // Reboot implements protocol.Operations. func (o *Operations) Reboot(ctx context.Context) error { o.mutex.RLock() @@ -212,6 +221,7 @@ const ( configurationApplyFailed = "failed" ) +// Deprecated : is no longer maintained for modules in V1 // SetBase implements protocol.Operations. func (o *Operations) SetBase(ctx context.Context, funcs ...protocol.SetBaseOptionFunc) error { rawConfig, err := o.Configuration(ctx) @@ -297,6 +307,7 @@ func (o *Operations) SetBase(ctx context.Context, funcs ...protocol.SetBaseOptio return nil } +// Deprecated : is no longer maintained for modules in V1 // GetBaseInfo implements protocol.Operations. func (o *Operations) GetBaseInfo(ctx context.Context) (*protocol.BaseInfo, error) { rawConfig, err := o.Configuration(ctx) @@ -353,6 +364,7 @@ type reachViewVersion struct { Stable bool `json:"bool"` } +// Deprecated : is no longer maintained for modules in V1 // Version implements protocol.Operations. func (o *Operations) Version(ctx context.Context) (string, bool, error) { res := &reachViewVersion{} @@ -363,4 +375,24 @@ func (o *Operations) Version(ctx context.Context) (string, bool, error) { return strings.TrimSpace(res.Version), res.Stable, nil } +// Deprecated : is no longer maintained for modules in V1 +func (o *Operations) AveragePosition(ctx context.Context) (*protocol.TaskMessage, 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 +} + +// Deprecated : is no longer maintained for modules in V1 +func (o *Operations) SetBaseCorrections(ctx context.Context, funcs ...protocol.SetBaseCorrectionsFunc) error { + return protocol.ErrUnimplemented +} + +// Deprecated : is no longer maintained for modules in V1 +func (o *Operations) SetModem(ctx context.Context, funcs ...protocol.SetModemOptionsFunc) error { + return protocol.ErrUnimplemented +} + var _ protocol.Operations = &Operations{} diff --git a/reach/client/protocol/v1/protocol.go b/reach/client/protocol/v1/protocol.go index c78dcf0..baf2c59 100644 --- a/reach/client/protocol/v1/protocol.go +++ b/reach/client/protocol/v1/protocol.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" ) +// Deprecated : is no longer maintained for modules in V1 const Identifier protocol.Identifier = "v1" const compatibleVersionConstraint = "^2.24" @@ -19,6 +20,7 @@ type Protocol struct { dial protocol.DialFunc } +// Deprecated : is no longer maintained for modules in V1 // Available implements protocol.Protocol. func (p *Protocol) Available(ctx context.Context, addr string) (bool, error) { ops := p.Operations(addr).(*Operations) @@ -53,11 +55,13 @@ func (p *Protocol) Available(ctx context.Context, addr string) (bool, error) { return true, nil } +// Deprecated : is no longer maintained for modules in V1 // Identifier implements protocol.Protocol. func (p *Protocol) Identifier() protocol.Identifier { return Identifier } +// Deprecated : is no longer maintained for modules in V1 // Operations implements protocol.Protocol. func (p *Protocol) Operations(addr string) protocol.Operations { return &Operations{addr: addr, logger: p.logger, dial: p.dial} From 1fcf74831021cd2fd67631d3a5b119f1ffd13d7e Mon Sep 17 00:00:00 2001 From: cmsassot Date: Thu, 12 Jun 2025 14:43:41 +0200 Subject: [PATCH 08/10] feat(AveragePosition/GetNTRIPMountPoint): change return type, update cmd average_position --- cmd/average_position/README.md | 2 +- cmd/average_position/main.go | 156 ++++++------------ reach/client/operations.go | 14 +- reach/client/protocol/average_postion.go | 12 ++ .../model/corrections_input.go => ntrip.go} | 8 +- reach/client/protocol/operations.go | 12 +- reach/client/protocol/testsuite/operations.go | 63 +++---- .../protocol/testsuite/operations_test.go | 16 ++ reach/client/protocol/v1/operations.go | 6 +- reach/client/protocol/v2/internal.go | 5 +- reach/client/protocol/v2/operations.go | 41 +++-- 11 files changed, 148 insertions(+), 187 deletions(-) create mode 100644 reach/client/protocol/average_postion.go rename reach/client/protocol/{v2/model/corrections_input.go => ntrip.go} (93%) create mode 100644 reach/client/protocol/testsuite/operations_test.go 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, From daadb9b6788b1f6928ef980e4d250c31dbd4f2f8 Mon Sep 17 00:00:00 2001 From: cmsassot Date: Tue, 8 Jul 2025 09:00:13 +0200 Subject: [PATCH 09/10] feat(reachview): GetModemConfiguration func --- cmd/message/main.go | 70 +++++++++++++++++++ reach/client/operations.go | 15 ++++ reach/client/protocol/operations.go | 3 + reach/client/protocol/testsuite/operations.go | 23 ++++++ .../protocol/testsuite/operations_test.go | 16 ----- reach/client/protocol/v1/operations.go | 5 ++ reach/client/protocol/v2/internal.go | 10 +++ reach/client/protocol/v2/model/action.go | 2 +- reach/client/protocol/v2/model/modem.go | 22 ++++++ reach/client/protocol/v2/operations.go | 11 +++ 10 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 cmd/message/main.go delete mode 100644 reach/client/protocol/testsuite/operations_test.go diff --git a/cmd/message/main.go b/cmd/message/main.go new file mode 100644 index 0000000..f3c3860 --- /dev/null +++ b/cmd/message/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "log/slog" + "os" + + reach "forge.cadoles.com/cadoles/go-emlid/reach/client" + "forge.cadoles.com/cadoles/go-emlid/reach/client/logger" + "github.com/pkg/errors" +) + +var ( + host string = "192.168.42.1" + filter string = "task" + rawLogLevel string = "ERROR" +) + +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 type messages by name") +} + +func main() { + flag.Parse() + + ctx := context.Background() + client := reach.NewClient(host) + + logLevel, err := logger.ParseLevel(rawLogLevel) + if err != nil { + fmt.Printf("[FATAL] %+v", errors.WithStack(err)) + os.Exit(1) + } + + slog.SetLogLoggerLevel(logLevel) + + if err := client.Connect(ctx); err != nil { + fmt.Printf("[FATAL] %+v", errors.WithStack(err)) + os.Exit(1) + } + + defer func() { + if err := client.Close(ctx); err != nil { + fmt.Printf("[FATAL] %+v", errors.WithStack(err)) + os.Exit(1) + } + }() + fmt.Println(filter) + messages, err := reach.OnMessageType(ctx, client, filter) + if err != nil { + fmt.Printf("[FATAL] %+v", errors.WithStack(err)) + os.Exit(1) + } + + for b := range messages { + + data, err := json.MarshalIndent(b, "", " ") + if err != nil { + fmt.Printf("[ERROR] %+v", errors.WithStack(err)) + continue + } + + fmt.Println(string(data)) + } +} diff --git a/reach/client/operations.go b/reach/client/operations.go index 86ef123..c98b675 100644 --- a/reach/client/operations.go +++ b/reach/client/operations.go @@ -207,4 +207,19 @@ func (c *Client) SetModem(ctx context.Context, funcs ...protocol.SetModemOptions return nil } +// GetModemConfiguration implements protocol.Operations. +func (c *Client) GetModemConfiguration(ctx context.Context) (any, error) { + _, ops, err := c.getProtocol(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + + config, err := ops.GetModemConfiguration(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + + return config, nil +} + var _ protocol.Operations = &Client{} diff --git a/reach/client/protocol/operations.go b/reach/client/protocol/operations.go index e3ddc49..8c9fe1c 100644 --- a/reach/client/protocol/operations.go +++ b/reach/client/protocol/operations.go @@ -61,4 +61,7 @@ type Operations interface { //SetModem updates mobile data config SetModem(ctx context.Context, funcs ...SetModemOptionsFunc) error + + //GetModemConfiguration mobile data config + GetModemConfiguration(ctx context.Context) (any, error) } diff --git a/reach/client/protocol/testsuite/operations.go b/reach/client/protocol/testsuite/operations.go index 0c56772..d7bd535 100644 --- a/reach/client/protocol/testsuite/operations.go +++ b/reach/client/protocol/testsuite/operations.go @@ -377,6 +377,29 @@ var testCases = []operationTestCase{ t.Logf("Message : %+v", taskMsg) }, }, + { + Name: "GetModemConfiguration", + 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)) + } + }() + config, err := ops.GetModemConfiguration(ctx) + if err != nil { + t.Errorf("%+v", errors.WithStack(err)) + return + } + t.Logf("Modem configuration : %+v", config) + }, + }, } 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 deleted file mode 100644 index 8948c9e..0000000 --- a/reach/client/protocol/testsuite/operations_test.go +++ /dev/null @@ -1,16 +0,0 @@ -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 8aad20c..1db989a 100644 --- a/reach/client/protocol/v1/operations.go +++ b/reach/client/protocol/v1/operations.go @@ -395,4 +395,9 @@ func (o *Operations) SetModem(ctx context.Context, funcs ...protocol.SetModemOpt return protocol.ErrUnimplemented } +// Deprecated : is no longer maintained for modules in V1 +func (o *Operations) GetModemConfiguration(ctx context.Context) (any, error) { + return nil, protocol.ErrUnimplemented +} + var _ protocol.Operations = &Operations{} diff --git a/reach/client/protocol/v2/internal.go b/reach/client/protocol/v2/internal.go index b0027ec..083b404 100644 --- a/reach/client/protocol/v2/internal.go +++ b/reach/client/protocol/v2/internal.go @@ -173,3 +173,13 @@ func (o *Operations) PostModem(ctx context.Context, config *model.ModemAuthentic return &updated, nil } + +func (o *Operations) GetModem(ctx context.Context) (*model.ModemConfiguration, error) { + config := &model.ModemConfiguration{} + + if err := o.GetJSON("/modem/1/info", config); err != nil { + return nil, errors.WithStack(err) + } + + return config, nil +} diff --git a/reach/client/protocol/v2/model/action.go b/reach/client/protocol/v2/model/action.go index 2f0ed35..a16380b 100644 --- a/reach/client/protocol/v2/model/action.go +++ b/reach/client/protocol/v2/model/action.go @@ -2,5 +2,5 @@ package model type Action struct { Name string `json:"name"` - Paylaod map[string]any `json:"payload"` + Paylaod map[string]any `json:"payload,omitempty"` } diff --git a/reach/client/protocol/v2/model/modem.go b/reach/client/protocol/v2/model/modem.go index bfcf7da..b8f72f8 100644 --- a/reach/client/protocol/v2/model/modem.go +++ b/reach/client/protocol/v2/model/modem.go @@ -10,3 +10,25 @@ type ModemAuthentication struct { Password string `json:"password,omitempty"` } `json:"authentication"` } + +type ModemConfiguration struct { + AccessTechnology string `json:"access_technology"` + AllowedModes []string `json:"allowed_modes"` + AvailableAPNs []string `json:"available_apns,omitempty"` + CurrentAPN string `json:"current_apn"` + CurrentMode string `json:"current_mode"` + FailReason *string `json:"fail_reason,omitempty"` + IMEI string `json:"imei"` + InternetAvailable string `json:"internet_available,omitempty"` + LockReason *string `json:"lock_reason,omitempty"` + OperatorName string `json:"operator_name"` + PreferredMode string `json:"preferred_mode"` + RegistrationState string `json:"registration_state"` + RSSI int `json:"rssi"` + State string `json:"state"` + Stats struct { + Since string `json:"since"` + UsageMB string `json:"usage_mb"` + } `json:"stats"` + UnlockRetries int `json:"unlock_retries"` +} diff --git a/reach/client/protocol/v2/operations.go b/reach/client/protocol/v2/operations.go index bd79346..5deeb39 100644 --- a/reach/client/protocol/v2/operations.go +++ b/reach/client/protocol/v2/operations.go @@ -399,4 +399,15 @@ func (o *Operations) SetModem(ctx context.Context, funcs ...protocol.SetModemOpt return nil } +// SetModem implements protocol.Operations. +func (o *Operations) GetModemConfiguration(ctx context.Context) (any, error) { + config, err := o.GetModem(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + + return config, nil + +} + var _ protocol.Operations = &Operations{} From 05f5b3f7710041d195c547232161cc5d1a175e60 Mon Sep 17 00:00:00 2001 From: cmsassot Date: Wed, 16 Jul 2025 15:10:34 +0200 Subject: [PATCH 10/10] feat(AveragePosition): no return value --- reach/client/protocol/v2/operations.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/reach/client/protocol/v2/operations.go b/reach/client/protocol/v2/operations.go index 5deeb39..3522bba 100644 --- a/reach/client/protocol/v2/operations.go +++ b/reach/client/protocol/v2/operations.go @@ -281,17 +281,6 @@ func (o *Operations) AveragePosition(ctx context.Context) (*protocol.TaskMessage 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 { - var taskMsg protocol.TaskMessage[protocol.AveragePositionPayload] - if err := mapstructure.Decode(message, &taskMsg); err != nil { - return nil, errors.WithStack(err) - } - - if taskMsg.State == "completed" { - return &taskMsg, nil - } - } return nil, err }