diff --git a/cmd/average_position/README.md b/cmd/average_position/README.md new file mode 100644 index 0000000..549b549 --- /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 -baseAccuracy +``` + +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..5e9db11 --- /dev/null +++ b/cmd/average_position/main.go @@ -0,0 +1,240 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log/slog" + "os" + + 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" + baseAccuracy string = "fix" +) + +func init() { + flag.StringVar(&rawLogLevel, "log-level", rawLogLevel, "log level") + flag.StringVar(&host, "host", host, "the reachrs module host") + flag.StringVar(&baseAccuracy, "baseAccuracy", baseAccuracy, "precision required for average position") +} + +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) + + //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) + } + + // AveragePosition + processAveragePosition(ctx, client) +} + +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 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 + ntripMsg, err := client.GetNTRIPMountPoint(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + return ntripMsg, nil +} + +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", + 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.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), + 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.Printf("Base configurée en mode %s-and-hold\n", baseAccuracy) + return nil +} + +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 nil, errors.WithStack(err) + } + return messageTask, nil + +} + +func displayAveragePositionMessage(message *protocol.TaskMessage[protocol.AveragePositionPayload]) error { + fmt.Printf("Position moyennée: lat=%g, lon=%g, altitude=%g\n", + message.Payload.Coordinates.Latitude, message.Payload.Coordinates.Longitude, message.Payload.Coordinates.Height) + return nil + +} + +func saveAveragedPosition(ctx context.Context, client *reach.Client, message *protocol.TaskMessage[protocol.AveragePositionPayload]) error { + opts := []protocol.SetBaseOptionFunc{ + 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"), + } + + 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/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/helper.go b/reach/client/helper.go index 2e37c29..4c8ab12 100644 --- a/reach/client/helper.go +++ b/reach/client/helper.go @@ -41,6 +41,7 @@ func OnMessage[T any](ctx context.Context, client *Client, mType string) (chan T type Broadcast struct { Name string `mapstructure:"name" json:"name"` Payload any `mapstructure:"payload" json:"payload"` + State string `mapstructure:"state" json:"state"` } // OnBroadcast listens for ReachView "broadcast" messages @@ -52,3 +53,13 @@ func OnBroadcast(ctx context.Context, client *Client) (chan Broadcast, error) { return ch, nil } + +// OnMessageType listens for ReachView type of messages +func OnMessageType(ctx context.Context, client *Client, messageType string) (chan Broadcast, error) { + ch, err := OnMessage[Broadcast](ctx, client, messageType) + if err != nil { + return nil, errors.WithStack(err) + } + + return ch, nil +} diff --git a/reach/client/operations.go b/reach/client/operations.go index d446bd8..c98b675 100644 --- a/reach/client/operations.go +++ b/reach/client/operations.go @@ -152,4 +152,74 @@ func (c *Client) Reboot(ctx context.Context) error { return nil } +// AveragePosition implements protocol.Operations. +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) + } + + taskMsg, err := ops.AveragePosition(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + + return taskMsg, err +} + +// GetNTRIPMountPoint implements protocol.Operations. +func (c *Client) GetNTRIPMountPoint(ctx context.Context) (*protocol.TaskMessage[protocol.NTRIPPayload], error) { + _, ops, err := c.getProtocol(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + + ntripMsg, err := ops.GetNTRIPMountPoint(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + return ntripMsg, 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 +} + +// 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 +} + +// 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/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/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/ntrip.go b/reach/client/protocol/ntrip.go new file mode 100644 index 0000000..68872f8 --- /dev/null +++ b/reach/client/protocol/ntrip.go @@ -0,0 +1,75 @@ +package protocol + +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 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/operations.go b/reach/client/protocol/operations.go index 72a5bae..8c9fe1c 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[T any] struct { + Name string `json:"name"` + State string `json:"state"` + Payload T `json:"payload"` } type Operations interface { @@ -41,4 +49,19 @@ 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[AveragePositionPayload], error) + + //GetNTRIPMountPoint retrieves availables mount point + GetNTRIPMountPoint(ctx context.Context) (*TaskMessage[NTRIPPayload], error) + + //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 + + //GetModemConfiguration mobile data config + GetModemConfiguration(ctx context.Context) (any, error) } 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/set_base_correction.go b/reach/client/protocol/set_base_correction.go new file mode 100644 index 0000000..f8cfcac --- /dev/null +++ b/reach/client/protocol/set_base_correction.go @@ -0,0 +1,56 @@ +package protocol + +type SetBaseCorrectionsOptions struct { + Address *string + Port *int + Username *string + Password *string + MountPoint *string + SendPositionToBase *bool +} + +type SetBaseCorrectionsFunc func(opts *SetBaseCorrectionsOptions) + +func NewSetBaseCorrectionsOptions(funcs ...SetBaseCorrectionsFunc) *SetBaseCorrectionsOptions { + opts := &SetBaseCorrectionsOptions{} + for _, fn := range funcs { + fn(opts) + } + return opts +} + +func WithNTRIPAddress(value string) SetBaseCorrectionsFunc { + return func(opts *SetBaseCorrectionsOptions) { + opts.Address = &value + } +} + +func WithNTRIPPort(value int) SetBaseCorrectionsFunc { + return func(opts *SetBaseCorrectionsOptions) { + opts.Port = &value + } +} + +func WithNTRIPUsername(value string) SetBaseCorrectionsFunc { + return func(opts *SetBaseCorrectionsOptions) { + opts.Username = &value + } +} + +func WithNTRIPPassword(value string) SetBaseCorrectionsFunc { + return func(opts *SetBaseCorrectionsOptions) { + opts.Password = &value + } +} + +func WithNTRIPMountPoint(value string) SetBaseCorrectionsFunc { + return func(opts *SetBaseCorrectionsOptions) { + opts.MountPoint = &value + } +} + +func WithSendPositionToBase(value bool) SetBaseCorrectionsFunc { + return func(opts *SetBaseCorrectionsOptions) { + opts.SendPositionToBase = &value + } +} diff --git a/reach/client/protocol/testsuite/operations.go b/reach/client/protocol/testsuite/operations.go index 9e66d75..d7bd535 100644 --- a/reach/client/protocol/testsuite/operations.go +++ b/reach/client/protocol/testsuite/operations.go @@ -237,7 +237,167 @@ var testCases = []operationTestCase{ t.Errorf("%+v", errors.WithStack(err)) return } + }, + }, + { + 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) { + 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: "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: "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 : %+v", 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)) + } + }() + taskMsg, err := ops.GetNTRIPMountPoint(ctx) + if err != nil { + t.Errorf("%+v", errors.WithStack(err)) + return + } + 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) }, }, } 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..1db989a 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,29 @@ 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[protocol.AveragePositionPayload], error) { + return nil, protocol.ErrUnimplemented +} + +// Deprecated : is no longer maintained for modules in V1 +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 +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 +} + +// 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/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} diff --git a/reach/client/protocol/v2/internal.go b/reach/client/protocol/v2/internal.go index 4a3aa4e..083b404 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,6 +127,16 @@ func (o *Operations) PostDevice(ctx context.Context, device *model.Configuration return &updated, nil } +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) + } + + return &updated, nil +} + func (o *Operations) GetUpdater(ctx context.Context) (*model.Updater, error) { updater := &model.Updater{} if err := o.GetJSON("/updater", updater); err != nil { @@ -152,3 +163,23 @@ 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 +} + +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 57096f6..a16380b 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,omitempty"` } diff --git a/reach/client/protocol/v2/model/modem.go b/reach/client/protocol/v2/model/modem.go new file mode 100644 index 0000000..b8f72f8 --- /dev/null +++ b/reach/client/protocol/v2/model/modem.go @@ -0,0 +1,34 @@ +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"` +} + +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 7a8afb1..3522bba 100644 --- a/reach/client/protocol/v2/operations.go +++ b/reach/client/protocol/v2/operations.go @@ -10,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" ) @@ -86,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 } @@ -128,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 @@ -264,4 +270,133 @@ func (o *Operations) On(ctx context.Context, event string) (chan any, error) { return out, nil } +func (o *Operations) AveragePosition(ctx context.Context) (*protocol.TaskMessage[protocol.AveragePositionPayload], 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 + } + return nil, err +} + +// GetNTRIPMountPoint implements protocol.Operations. +func (o *Operations) GetNTRIPMountPoint(ctx context.Context) (*protocol.TaskMessage[protocol.NTRIPPayload], error) { + var err error + + config, err := o.GetConfiguration(ctx) + if err != nil { + return nil, 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 nil, errors.WithStack(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. +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 := &protocol.IOConfig{ + // todo parametrage du type + IOType: "ntripcli", + Settings: protocol.IOConfigSettings{ + NTRIPCli: protocol.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 +} + +// 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 +} + +// 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{}