From 854052bc84203524799d4d6bb0009050172ea809 Mon Sep 17 00:00:00 2001 From: William Petit Date: Wed, 26 Sep 2018 15:56:06 +0200 Subject: [PATCH] Example: simple cli for automatic configuration of rover/base modules --- emlid/client.go | 2 +- emlid/reachview/configuration.go | 40 +++- emlid/reachview/configuration_model.go | 227 +++++++++++++-------- emlid/reachview/configuration_test.go | 56 +++++- emlid/reachview/rtk.go | 10 + emlid/reachview/{test.go => util_test.go} | 0 example/reachview/README.md | 16 ++ example/reachview/main.go | 228 ++++++++++++++++++++++ go.mod | 2 +- 9 files changed, 493 insertions(+), 88 deletions(-) create mode 100644 emlid/reachview/rtk.go rename emlid/reachview/{test.go => util_test.go} (100%) create mode 100644 example/reachview/README.md create mode 100644 example/reachview/main.go diff --git a/emlid/client.go b/emlid/client.go index 0b6325c..78d81e9 100644 --- a/emlid/client.go +++ b/emlid/client.go @@ -121,7 +121,7 @@ func (c *Client) ReqResp(ctx context.Context, }() err = c.conn.On(responseEvent, func(_ *gosocketio.Channel, data interface{}) { - err = mapstructure.Decode(data, res) + err = mapstructure.WeakDecode(data, res) once.Do(done) }) if err != nil { diff --git a/emlid/reachview/configuration.go b/emlid/reachview/configuration.go index eae57b8..c554289 100644 --- a/emlid/reachview/configuration.go +++ b/emlid/reachview/configuration.go @@ -1,15 +1,51 @@ package reachview +import "context" + const ( eventGetConfiguration = "get configuration" eventCurrentConfiguration = "current configuration" + eventApplyConfiguration = "apply configuration" + eventConfigurationApplied = "configuration applied" + eventResetConfiguration = "reset configuration to default" ) // Configuration fetches and return the current configuration of the ReachRS module -func (r *Client) Configuration() (*Configuration, error) { +func (r *Client) Configuration(ctx context.Context) (*Configuration, error) { configuration := &Configuration{} - if err := r.ReqResp(eventGetConfiguration, nil, eventCurrentConfiguration, configuration); err != nil { + if err := r.ReqResp(ctx, eventGetConfiguration, nil, eventCurrentConfiguration, configuration); err != nil { return nil, err } return configuration, nil } + +type configurationApplied struct { + Configuration *Configuration `mapstructure:"configuration,omitempty"` + Result string `mapstructure:"result"` + Constraints *Constraints `mapstructure:"Constraints,omitempty"` +} + +const ( + // ConfigurationApplySuccess - + ConfigurationApplySuccess = "success" + // ConfigurationApplyFailed - + ConfigurationApplyFailed = "failed" +) + +// ApplyConfiguration applies the given configuration +func (r *Client) ApplyConfiguration(ctx context.Context, config *Configuration) (string, *Configuration, error) { + res := &configurationApplied{} + if err := r.ReqResp(ctx, eventApplyConfiguration, config, eventConfigurationApplied, res); err != nil { + return ConfigurationApplyFailed, nil, err + } + return res.Result, res.Configuration, nil +} + +// ResetConfiguration resets the module configuration to factory defaults +func (r *Client) ResetConfiguration(ctx context.Context) (string, *Configuration, error) { + res := &configurationApplied{} + if err := r.ReqResp(ctx, eventResetConfiguration, nil, eventConfigurationApplied, res); err != nil { + return ConfigurationApplyFailed, nil, err + } + return res.Result, res.Configuration, nil +} diff --git a/emlid/reachview/configuration_model.go b/emlid/reachview/configuration_model.go index b6650c7..0ff9ae2 100644 --- a/emlid/reachview/configuration_model.go +++ b/emlid/reachview/configuration_model.go @@ -1,59 +1,124 @@ package reachview +var ( + // On - + On = String("on") + // Off - + Off = String("off") + // True - + True = Bool(true) + // False - + False = Bool(false) + // GPSARModeFixAndHold - + GPSARModeFixAndHold = String("fix-and-hold") + // GPSARModeContinuous - + GPSARModeContinuous = String("continuous") + // PositionningModeKinematic - + PositionningModeKinematic = String("kinematic") + // PositionningModeSingle - + PositionningModeSingle = String("single") + // PositionningModeStatic - + PositionningModeStatic = String("static") + // IOFormatRTCM3 - + IOFormatRTCM3 = String("rtcm3") + // IOTypeLoRa - + IOTypeLoRa = String("lora") + // BaseCoordinatesModeManual - + BaseCoordinatesModeManual = String("manual") + // BaseCoordinatesModeAverageFix - + BaseCoordinatesModeAverageFix = String("fix-and-hold") + // BaseCoordinatesModeAverageSingle - + BaseCoordinatesModeAverageSingle = String("single-and-hold") + // BaseCoordinatesModeAverageFloat - + BaseCoordinatesModeAverageFloat = String("float-and-hold") + // BaseCoordinatesFormatLLH - + BaseCoordinatesFormatLLH = String("llh") + // BaseCoordinatesFormatXYZ - + BaseCoordinatesFormatXYZ = String("xyz") +) + +// String returns an string pointer +// This is a helper to constructs partials configations objects +// for the ApplyConfiguration() method +func String(v string) *string { + return &v +} + +// Int returns an int pointer +// This is a helper to constructs partials configations objects +// for the ApplyConfiguration() method +func Int(v int) *int { + return &v +} + +// Bool returns a bool pointer +// This is a helper to constructs partials configations objects +// for the ApplyConfiguration() method +func Bool(v bool) *bool { + return &v +} + +// Float returns a float64 pointer +// This is a helper to constructs partials configations objects +// for the ApplyConfiguration() method +func Float(v float64) *float64 { + return &v +} + // Configuration - type Configuration struct { - RTKSettings *RTKSettings `mapstructure:"rtk settings,omitempty"` - CorrectionInput *CorrectionInput `mapstructure:"correction input,omitempty"` - PositionOutput *PositionOutput `mapstructure:"position output,omitempty"` - BaseMode *BaseMode `mapstructure:"base mode,omitempty"` - Logging *Logging `mapstructure:"logging,omitempty"` - Bluetooth *Bluetooth `mapstructure:"bluetooth,omitempty"` - LoRa *LoRa `mapstructure:"lora,omitempty"` - Constraints *Constraints `mapstructure:"constraints,omitempty"` + RTKSettings *RTKSettings `mapstructure:"rtk settings,omitempty" json:"rtk settings,omitempty"` + CorrectionInput *CorrectionInput `mapstructure:"correction input,omitempty" json:"correction input,omitempty"` + PositionOutput *PositionOutput `mapstructure:"position output,omitempty" json:"position output,omitempty"` + BaseMode *BaseMode `mapstructure:"base mode,omitempty" json:"base mode,omitempty"` + Logging *Logging `mapstructure:"logging,omitempty" json:"logging,omitempty"` + Bluetooth *Bluetooth `mapstructure:"bluetooth,omitempty" json:"bluetooth,omitempty"` + LoRa *LoRa `mapstructure:"lora,omitempty" json:"lora,omitempty"` + Constraints *Constraints `mapstructure:"constraints,omitempty" json:"constraints,omitempty"` } // RTKSettings - type RTKSettings struct { - GLONASSARMode *string `mapstructure:"glonass ar mode,omitempty"` - UpdateRate *string `mapstructure:"update rate,omitempty"` - ElevationMaskAngle *string `mapstructure:"elevation mask angle,omitempty"` - MaxHorizontalAcceleration *string `mapstructure:"max horizontal acceleration,omitempty"` - SNRMask *string `mapstructure:"snr mask,omitempty"` - GPSARMode *string `mapstructure:"gps ar mode,omitempty"` - PositionningMode *string `mapstructure:"positioning mode,omitempty"` - PositioningSystems *PositionningSystems `mapstructure:"positioning systems,omitempty"` - MaxVerticalAcceleration *string `mapstructure:"max vertical acceleration,omitempty"` + GLONASSARMode *string `mapstructure:"glonass ar mode,omitempty" json:"glonass ar mode,omitempty"` + UpdateRate *string `mapstructure:"update rate,omitempty" json:"update rate,omitempty"` + ElevationMaskAngle *string `mapstructure:"elevation mask angle,omitempty" json:"elevation mask angle,omitempty"` + MaxHorizontalAcceleration *string `mapstructure:"max horizontal acceleration,omitempty" json:"max horizontal acceleration,omitempty"` + SNRMask *string `mapstructure:"snr mask,omitempty" json:"snr mask,omitempty"` + GPSARMode *string `mapstructure:"gps ar mode,omitempty" json:"gps ar mode,omitempty"` + PositionningMode *string `mapstructure:"positioning mode,omitempty" json:"positioning mode,omitempty"` + PositioningSystems *PositionningSystems `mapstructure:"positioning systems,omitempty" json:"positioning systems,omitempty"` + MaxVerticalAcceleration *string `mapstructure:"max vertical acceleration,omitempty" json:"max vertical acceleration,omitempty"` } // PositionningSystems - type PositionningSystems struct { - GLONASS *bool `mapstructure:"glonass,omitempty"` - SBAS *bool `mapstructure:"sbas,omitempty"` - QZS *bool `mapstructure:"qzs,omitempty"` - QZSS *bool `mapstructure:"qzss,omitempty"` - Compass *bool `mapstructure:"compass,omitempty"` - Galileo *bool `mapstructure:"galileo,omitempty"` - GPS *bool `mapstructure:"gps,omitempty"` + GLONASS *bool `mapstructure:"glonass,omitempty" json:"glonass,omitempty"` + SBAS *bool `mapstructure:"sbas,omitempty" json:"sbas,omitempty"` + QZS *bool `mapstructure:"qzs,omitempty" json:"qzs,omitempty"` + QZSS *bool `mapstructure:"qzss,omitempty" json:"qzss,omitempty"` + Compass *bool `mapstructure:"compass,omitempty" json:"compass,omitempty"` + Galileo *bool `mapstructure:"galileo,omitempty" json:"galileo,omitempty"` + GPS *bool `mapstructure:"gps,omitempty" json:"gps,omitempty"` } // CorrectionInput - type CorrectionInput struct { - Input2 *Input2 `mapstructure:"input2,omitempty"` - Input3 *Input3 `mapstructure:"input3,omitempty"` + Input2 *Input2 `mapstructure:"input2,omitempty" json:"input2,omitempty"` + Input3 *Input3 `mapstructure:"input3,omitempty" json:"input3,omitempty"` } // Input - type Input struct { - Path *string `mapstructure:"path,omitempty"` - Type *string `mapstructure:"type,omitempty"` - Enabled *bool `mapstructure:"enabled,omitempty"` - Format *string `mapstructure:"format,omitempty"` + Path *string `mapstructure:"path,omitempty" json:"path,omitempty"` + Type *string `mapstructure:"type,omitempty" json:"type,omitempty"` + Enabled *bool `mapstructure:"enabled,omitempty" json:"enabled,omitempty"` + Format *string `mapstructure:"format,omitempty" json:"format,omitempty"` } // Input2 - type Input2 struct { Input `mapstructure:",squash"` - SendPositionToBase *string `mapstructure:"send position to base,omitempty"` + SendPositionToBase *string `mapstructure:"send position to base,omitempty" json:"send position to base,omitempty"` } // Input3 - @@ -63,116 +128,116 @@ type Input3 struct { // PositionOutput - type PositionOutput struct { - Output1 *Output `mapstructure:"output1,omitempty"` - Output2 *Output `mapstructure:"output2,omitempty"` - Output3 *Output `mapstructure:"output3,omitempty"` - Output4 *Output `mapstructure:"output4,omitempty"` + Output1 *Output `mapstructure:"output1,omitempty" json:"output1,omitempty"` + Output2 *Output `mapstructure:"output2,omitempty" json:"output2,omitempty"` + Output3 *Output `mapstructure:"output3,omitempty" json:"output3,omitempty"` + Output4 *Output `mapstructure:"output4,omitempty" json:"output4,omitempty"` } // Output - type Output struct { - Path *string `mapstructure:"path,omitempty"` - Type *string `mapstructure:"type,omitempty"` - Enabled *bool `mapstructure:"enabled,omitempty"` - Format *string `mapstructure:"format,omitempty"` + Path *string `mapstructure:"path,omitempty" json:"path,omitempty"` + Type *string `mapstructure:"type,omitempty" json:"type,omitempty"` + Enabled *bool `mapstructure:"enabled,omitempty" json:"enabled,omitempty"` + Format *string `mapstructure:"format,omitempty" json:"format,omitempty"` } // BaseMode - type BaseMode struct { - Output *Output `mapstructure:"output,omitempty"` - BaseCoordinates *BaseCoordinates `mapstructure:"base coordinates,omitempty"` - RTCM3Messages *RTCM3Messages `mapstructure:"rtcm3 messages,omitempty"` + Output *Output `mapstructure:"output,omitempty" json:"output,omitempty"` + BaseCoordinates *BaseCoordinates `mapstructure:"base coordinates,omitempty" json:"base coordinates,omitempty"` + RTCM3Messages *RTCM3Messages `mapstructure:"rtcm3 messages,omitempty" json:"rtcm3 messages,omitempty"` } // BaseCoordinates - type BaseCoordinates struct { - Format *string `mapstructure:"format,omitempty"` - AntennaOffset *AntennaOffset `mapstructure:"antenna offset,omitempty"` - Accumulation *string `mapstructure:"accumulation,omitempty"` - Coordinates *[]string `mapstructure:"coordinates,omitempty"` - Mode *string `mapstructure:"mode,omitempty"` + Format *string `mapstructure:"format,omitempty" json:"format,omitempty"` + AntennaOffset *AntennaOffset `mapstructure:"antenna offset,omitempty" json:"antenna offset,omitempty"` + Accumulation *string `mapstructure:"accumulation,omitempty" json:"accumulation,omitempty"` + Coordinates []*string `mapstructure:"coordinates,omitempty" json:"coordinates,omitempty"` + Mode *string `mapstructure:"mode,omitempty" json:"mode,omitempty"` } // AntennaOffset - type AntennaOffset struct { - East *string `mapstructure:"east,omitempty"` - North *string `mapstructure:"north,omitempty"` - Up *string `mapstructure:"up,omitempty"` + East *string `mapstructure:"east,omitempty" json:"east,omitempty"` + North *string `mapstructure:"north,omitempty" json:"north,omitempty"` + Up *string `mapstructure:"up,omitempty" json:"up,omitempty"` } // RTCM3Messages - type RTCM3Messages struct { // GPS L1 code and phase and ambiguities and carrier-to-noise ratio - Type1002 *RTCMMessageType `mapstructure:"1002,omitemtpy"` + Type1002 *RTCMMessageType `mapstructure:"1002,omitemtpy" json:"1002,omitemtpy"` // Station coordinates XYZ for antenna reference point and antenna height. - Type1006 *RTCMMessageType `mapstructure:"1006,omitemtpy"` + Type1006 *RTCMMessageType `mapstructure:"1006,omitemtpy" json:"1006,omitemtpy"` // Antenna serial number. - Type1008 *RTCMMessageType `mapstructure:"1008,omitemtpy"` + Type1008 *RTCMMessageType `mapstructure:"1008,omitemtpy" json:"1008,omitemtpy"` // GLONASS L1 code and phase and ambiguities and carrier-to-noise ratio. - Type1010 *RTCMMessageType `mapstructure:"1010,omitemtpy"` + Type1010 *RTCMMessageType `mapstructure:"1010,omitemtpy" json:"1010,omitemtpy"` // GPS ephemeris. - Type1019 *RTCMMessageType `mapstructure:"1019,omitemtpy"` + Type1019 *RTCMMessageType `mapstructure:"1019,omitemtpy" json:"1019,omitemtpy"` // GLONASS ephemeris. - Type1020 *RTCMMessageType `mapstructure:"1020,omitemtpy"` + Type1020 *RTCMMessageType `mapstructure:"1020,omitemtpy" json:"1020,omitemtpy"` // The type 7 Multiple Signal Message format for Europe’s Galileo system - Type1097 *RTCMMessageType `mapstructure:"1097,omitemtpy"` + Type1097 *RTCMMessageType `mapstructure:"1097,omitemtpy" json:"1097,omitemtpy"` // Full SBAS pseudo-ranges, carrier phases, Doppler and signal strength (high resolution) - Type1107 *RTCMMessageType `mapstructure:"1107,omitemtpy"` + Type1107 *RTCMMessageType `mapstructure:"1107,omitemtpy" json:"1107,omitemtpy"` // Full QZSS pseudo-ranges, carrier phases, Doppler and signal strength (high resolution) - Type1117 *RTCMMessageType `mapstructure:"1117,omitemtpy"` + Type1117 *RTCMMessageType `mapstructure:"1117,omitemtpy" json:"1117,omitemtpy"` // Full BeiDou pseudo-ranges, carrier phases, Doppler and signal strength (high resolution) - Type1127 *RTCMMessageType `mapstructure:"1127,omitemtpy"` + Type1127 *RTCMMessageType `mapstructure:"1127,omitemtpy" json:"1127,omitemtpy"` } // RTCMMessageType - type RTCMMessageType struct { - Frequency string `mapstructure:"frequency"` - Enabled bool `mapstructure:"enabled"` + Frequency *string `mapstructure:"frequency,omitempty" json:"frequency,omitempty"` + Enabled *bool `mapstructure:"enabled,omitempty" json:"enabled,omitempty"` } // Logging - type Logging struct { - Correction *LoggingService `mapstructure:"correction,omitempty"` - Interval int `mapstructure:"interval"` - Solution *LoggingService `mapstructure:"solution,omitempty"` - Raw *LoggingService `mapstructure:"raw,omitempty"` - Base *LoggingService `mapstructure:"base,omitempty"` - Overwrite *bool `mapstructure:"overwrite,omitempty"` + Correction *LoggingService `mapstructure:"correction,omitempty" json:"correction,omitempty"` + Interval *int `mapstructure:"interval,omitempty" json:"interval,omitempty"` + Solution *LoggingService `mapstructure:"solution,omitempty" json:"solution,omitempty"` + Raw *LoggingService `mapstructure:"raw,omitempty" json:"raw,omitempty"` + Base *LoggingService `mapstructure:"base,omitempty" json:"base,omitempty"` + Overwrite *bool `mapstructure:"overwrite,omitempty" json:"overwrite,omitempty"` } // LoggingService - type LoggingService struct { - Started *bool `mapstructure:"started,omitempty"` - Version *string `mapstructure:"version,omitempty"` - Format *string `mapstructure:"format,omitempty"` + Started *bool `mapstructure:"started,omitempty" json:"started,omitempty"` + Version *string `mapstructure:"version,omitempty" json:"version,omitempty"` + Format *string `mapstructure:"format,omitempty" json:"format,omitempty"` } // Bluetooth - type Bluetooth struct { - Enabled *bool `mapstructure:"enabled,omitempty"` - Discoverable *bool `mapstructure:"discoverable,omitempty"` - Pin *int `mapstructure:"pin,omitempty"` + Enabled *bool `mapstructure:"enabled,omitempty" json:"enabled,omitempty"` + Discoverable *bool `mapstructure:"discoverable,omitempty" json:"discoverable,omitempty"` + Pin *int `mapstructure:"pin,omitempty" json:"pin,omitempty"` } // LoRa - type LoRa struct { - AirRate *string `mapstructure:"air rate,omitempty"` - Frequency *float64 `mapstructure:"frequency,omitempty"` - OutputPower *string `mapstructure:"output power,omitempty"` + AirRate *string `mapstructure:"air rate,omitempty" json:"air rate,omitempty"` + Frequency *float64 `mapstructure:"frequency,omitempty" json:"frequency,omitempty"` + OutputPower *string `mapstructure:"output power,omitempty" json:"output power,omitempty"` } // Constraints - type Constraints struct { - LoRa *LoRaConstraints `mapstructure:"lora,omitempty"` + LoRa *LoRaConstraints `mapstructure:"lora,omitempty" json:"lora,omitempty"` } // LoRaConstraints - type LoRaConstraints struct { - Frequency *LoRaFrequencyRange `mapstructure:"frequency,omitempty"` + Frequency *LoRaFrequencyRange `mapstructure:"frequency,omitempty" json:"frequency,omitempty"` } // LoRaFrequencyRange - type LoRaFrequencyRange struct { - Min *int `mapstructure:"min,omitempty"` - Max *int `mapstructure:"max,omitempty"` + Min *int `mapstructure:"min,omitempty" json:"min,omitempty"` + Max *int `mapstructure:"max,omitempty" json:"max,omitempty"` } diff --git a/emlid/reachview/configuration_test.go b/emlid/reachview/configuration_test.go index bea31dd..bb7406b 100644 --- a/emlid/reachview/configuration_test.go +++ b/emlid/reachview/configuration_test.go @@ -1,10 +1,11 @@ package reachview import ( + "context" "testing" + "time" "forge.cadoles.com/Pyxis/orion/emlid" - "github.com/davecgh/go-spew/spew" ) func TestReachViewConfiguration(t *testing.T) { @@ -21,7 +22,10 @@ func TestReachViewConfiguration(t *testing.T) { t.Fatal(err) } - config, err := client.Configuration() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + config, err := client.Configuration(ctx) if err != nil { t.Error(err) } @@ -34,7 +38,53 @@ func TestReachViewConfiguration(t *testing.T) { t.Fatal("config.RTKSettings should not be nil") } - spew.Dump(config) + defer client.Close() + +} + +func TestReachViewApplyConfiguration(t *testing.T) { + + if !*runReachViewIntegrationTests { + t.Skip("To run this test, use: go test -reachview-integration") + } + + client := NewClient( + emlid.WithStandardLogger(), + emlid.WithEndpoint(*reachHost, 80), + ) + if err := client.Connect(); err != nil { + t.Fatal(err) + } + + ctx, configurationCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer configurationCancel() + + config, err := client.Configuration(ctx) + if err != nil { + t.Error(err) + } + + config.RTKSettings.PositionningMode = String("single") + + ctx, applyConfCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer applyConfCancel() + + result, config, err := client.ApplyConfiguration(ctx, config) + if err != nil { + t.Error(err) + } + + if config == nil { + t.Fatal("config should not be nil") + } + + if config.RTKSettings == nil { + t.Fatal("config.RTKSettings should not be nil") + } + + if g, e := result, ConfigurationApplySuccess; g != e { + t.Errorf("result: got '%s', expected '%s'", g, e) + } defer client.Close() diff --git a/emlid/reachview/rtk.go b/emlid/reachview/rtk.go new file mode 100644 index 0000000..bb0df87 --- /dev/null +++ b/emlid/reachview/rtk.go @@ -0,0 +1,10 @@ +package reachview + +const ( + eventRestartRTKLib = "restart rtklib" +) + +// RestartRTKLib asks the ReachRS module to restart the RTKlib +func (c *Client) RestartRTKLib() error { + return c.Emit(eventRestartRTKLib, nil) +} diff --git a/emlid/reachview/test.go b/emlid/reachview/util_test.go similarity index 100% rename from emlid/reachview/test.go rename to emlid/reachview/util_test.go diff --git a/example/reachview/README.md b/example/reachview/README.md new file mode 100644 index 0000000..3ae3a72 --- /dev/null +++ b/example/reachview/README.md @@ -0,0 +1,16 @@ +# Example: ReachView + +A simple example of a ReachView client that can: + +- Configure a ReachRS module as "base" or "rover" + +## Usage + +1. Boot your ReachRS module in "ReachView" mode + +2. Launch the example: + ```shell + go run example/reachview/main.go \ + -mode 'rover|base'\ + -host '