From b976bde3635c395ee93b87140e1b4555566222ea Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 2 Aug 2024 12:57:07 +0200 Subject: [PATCH] fix: protocol v1 --- cmd/broadcast/main.go | 16 ++++- cmd/configuration/main.go | 12 ++++ cmd/discover/main.go | 35 ++++++++++ example/discovery_test.go | 46 ------------- reach/client/client.go | 2 +- reach/client/logger/level.go | 19 ++++++ reach/client/logger/logger.go | 12 ++++ reach/client/options.go | 20 ++++++ reach/client/protocol/protocol.go | 29 ++++++++ reach/client/protocol/registry.go | 34 +++++++--- reach/client/protocol/testsuite/logger.go | 37 ++++++++++ reach/client/protocol/testsuite/operations.go | 67 +++++++++++++++++-- reach/client/protocol/v1/init.go | 6 +- reach/client/protocol/v1/internal.go | 7 ++ reach/client/protocol/v1/operations.go | 39 ++++++++++- reach/client/protocol/v1/operations_test.go | 4 +- reach/client/protocol/v1/protocol.go | 38 ++++++++++- reach/client/protocol/v2/init.go | 6 +- .../client/protocol/v2/model/configuration.go | 2 +- reach/client/protocol/v2/operations.go | 2 + reach/client/protocol/v2/operations_test.go | 4 +- reach/client/protocol/v2/protocol.go | 6 +- reach/discovery/discovery.go | 34 +++++++--- 23 files changed, 392 insertions(+), 85 deletions(-) create mode 100644 cmd/discover/main.go delete mode 100644 example/discovery_test.go create mode 100644 reach/client/logger/level.go create mode 100644 reach/client/logger/logger.go create mode 100644 reach/client/protocol/testsuite/logger.go diff --git a/cmd/broadcast/main.go b/cmd/broadcast/main.go index e81b365..e0e9015 100644 --- a/cmd/broadcast/main.go +++ b/cmd/broadcast/main.go @@ -5,18 +5,22 @@ import ( "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 = "" + host string = "192.168.42.1" + filter string = "" + 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 broadcast messages by name") } @@ -27,6 +31,14 @@ func main() { 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) diff --git a/cmd/configuration/main.go b/cmd/configuration/main.go index 8f2f705..9bfbf01 100644 --- a/cmd/configuration/main.go +++ b/cmd/configuration/main.go @@ -5,9 +5,11 @@ import ( "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" "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol" v1 "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v1" v2 "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v2" @@ -18,9 +20,11 @@ var ( host string = "192.168.42.1" preferredProtocol string = string(v2.Identifier) fallbackProtocol string = string(v1.Identifier) + rawLogLevel string = "ERROR" ) func init() { + flag.StringVar(&rawLogLevel, "log-level", rawLogLevel, "log level") flag.StringVar(&host, "host", host, "the reachrs module host") flag.StringVar(&preferredProtocol, "preferred-protocol", preferredProtocol, "preferred-protocol") flag.StringVar(&fallbackProtocol, "fallback-protocol", fallbackProtocol, "fallback-protocol") @@ -29,6 +33,14 @@ func init() { func main() { flag.Parse() + logLevel, err := logger.ParseLevel(rawLogLevel) + if err != nil { + fmt.Printf("[FATAL] %+v", errors.WithStack(err)) + os.Exit(1) + } + + slog.SetLogLoggerLevel(logLevel) + ctx := context.Background() client := reach.NewClient( host, diff --git a/cmd/discover/main.go b/cmd/discover/main.go new file mode 100644 index 0000000..3a31ce5 --- /dev/null +++ b/cmd/discover/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + + "forge.cadoles.com/cadoles/go-emlid/reach/discovery" + "github.com/pkg/errors" +) + +func main() { + services, err := discovery.Watch(context.Background()) + if err != nil { + fmt.Printf("[FATAL] %+v", errors.WithStack(err)) + os.Exit(1) + } + + for srv := range services { + data, err := json.MarshalIndent(struct { + Addr string `json:"addr"` + Name string `json:"name"` + }{ + Name: srv.Name, + Addr: fmt.Sprintf("%s:%d", srv.AddrV4.String(), srv.Port), + }, "", " ") + if err != nil { + fmt.Printf("[FATAL] %+v", errors.WithStack(err)) + os.Exit(1) + } + + fmt.Printf("%s\n", data) + } +} diff --git a/example/discovery_test.go b/example/discovery_test.go deleted file mode 100644 index 30c19aa..0000000 --- a/example/discovery_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package example_test - -import ( - "context" - "fmt" - "os" - "testing" - "time" - - "forge.cadoles.com/cadoles/go-emlid/reach/discovery" - "github.com/pkg/errors" -) - -// Exemple d'utilisation du package "discovery" pour identifier -// les modules Reach présents sur le réseau -func ExampleDiscovery() { - timeout := 10 * time.Second - - // Création d'un context avec un timeout de 10 secondes - // pour limiter le temps de recherche des modules - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - // Recherche des modules annoncés sur le réseau - // La recherche durerera 10 secondes, conformément au - // timeout associé au context.Context - services, err := discovery.Discover(ctx) - if err != nil { - fmt.Printf("could not discover reach modules: %+v\n", errors.WithStack(err)) - os.Exit(1) - } - - // Affichage des différents services Reach identifiés - // sur le réseau - for idx, srv := range services { - fmt.Printf("Module #%d: %s:%d\n", idx, srv.AddrV4, srv.Port) - } -} - -func TestExampleDiscovery(t *testing.T) { - if !testing.Verbose() { - return - } - - ExampleDiscovery() -} diff --git a/reach/client/client.go b/reach/client/client.go index 8f8cc6a..33af6d3 100644 --- a/reach/client/client.go +++ b/reach/client/client.go @@ -30,7 +30,7 @@ func (c *Client) Protocol(ctx context.Context) (protocol.Identifier, protocol.Op func (c *Client) getProtocol(ctx context.Context) (protocol.Identifier, protocol.Operations, error) { c.getProtocolOnce.Do(func() { - availables, err := c.opts.Protocols.Availables(ctx, c.addr) + availables, err := c.opts.Protocols.Availables(ctx, c.addr, c.opts.AvailableTimeout, protocol.WithProtocolLogger(c.opts.Logger)) if err != nil { c.getProtocolOnceErr = errors.WithStack(err) return diff --git a/reach/client/logger/level.go b/reach/client/logger/level.go new file mode 100644 index 0000000..0794f84 --- /dev/null +++ b/reach/client/logger/level.go @@ -0,0 +1,19 @@ +package logger + +import ( + "log/slog" + + "github.com/pkg/errors" +) + +type Level = slog.Level + +func ParseLevel(s string) (Level, error) { + var level Level + + if err := level.UnmarshalText([]byte(s)); err != nil { + return level, errors.WithStack(err) + } + + return level, nil +} diff --git a/reach/client/logger/logger.go b/reach/client/logger/logger.go new file mode 100644 index 0000000..5fd5cc5 --- /dev/null +++ b/reach/client/logger/logger.go @@ -0,0 +1,12 @@ +package logger + +import "log/slog" + +var Attr = slog.Any + +type Logger interface { + Debug(msg string, args ...any) + Info(msg string, args ...any) + Warn(msg string, args ...any) + Error(msg string, args ...any) +} diff --git a/reach/client/options.go b/reach/client/options.go index 946ebea..6d46539 100644 --- a/reach/client/options.go +++ b/reach/client/options.go @@ -1,6 +1,10 @@ package client import ( + "log/slog" + "time" + + "forge.cadoles.com/cadoles/go-emlid/reach/client/logger" "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol" v1 "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v1" v2 "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v2" @@ -10,6 +14,8 @@ type Options struct { Protocols *protocol.Registry PreferredProtocol protocol.Identifier FallbackProtocol protocol.Identifier + AvailableTimeout time.Duration + Logger logger.Logger } type OptionFunc func(opts *Options) @@ -19,6 +25,8 @@ func NewOptions(funcs ...OptionFunc) *Options { PreferredProtocol: v2.Identifier, FallbackProtocol: v1.Identifier, Protocols: protocol.DefaultRegistry(), + AvailableTimeout: 5 * time.Second, + Logger: slog.Default(), } for _, fn := range funcs { @@ -45,3 +53,15 @@ func WithProtocols(protocols *protocol.Registry) OptionFunc { opts.Protocols = protocols } } + +func WithLogger(logger logger.Logger) OptionFunc { + return func(opts *Options) { + opts.Logger = logger + } +} + +func WithAvailableTimeout(timeout time.Duration) OptionFunc { + return func(opts *Options) { + opts.AvailableTimeout = timeout + } +} diff --git a/reach/client/protocol/protocol.go b/reach/client/protocol/protocol.go index 14319b5..20f8aa1 100644 --- a/reach/client/protocol/protocol.go +++ b/reach/client/protocol/protocol.go @@ -2,6 +2,9 @@ package protocol import ( "context" + "log/slog" + + "forge.cadoles.com/cadoles/go-emlid/reach/client/logger" ) type Identifier string @@ -11,3 +14,29 @@ type Protocol interface { Available(ctx context.Context, addr string) (bool, error) Operations(addr string) Operations } + +type ProtocolOptions struct { + Logger logger.Logger +} + +type ProtocolFactory func(opts *ProtocolOptions) (Protocol, error) + +type ProtocolOptionFunc func(opts *ProtocolOptions) + +func NewProtocolOptions(funcs ...ProtocolOptionFunc) *ProtocolOptions { + opts := &ProtocolOptions{ + Logger: slog.Default(), + } + + for _, fn := range funcs { + fn(opts) + } + + return opts +} + +func WithProtocolLogger(logger logger.Logger) ProtocolOptionFunc { + return func(opts *ProtocolOptions) { + opts.Logger = logger + } +} diff --git a/reach/client/protocol/registry.go b/reach/client/protocol/registry.go index e550b4f..7edab13 100644 --- a/reach/client/protocol/registry.go +++ b/reach/client/protocol/registry.go @@ -2,27 +2,39 @@ package protocol import ( "context" + "time" "github.com/pkg/errors" ) type Registry struct { - protocols map[Identifier]Protocol + protocols map[Identifier]ProtocolFactory } -func (r *Registry) Register(protocol Protocol) { - r.protocols[protocol.Identifier()] = protocol +func (r *Registry) Register(identifier Identifier, factory ProtocolFactory) { + r.protocols[identifier] = factory } -func (r *Registry) Availables(ctx context.Context, addr string) ([]Protocol, error) { +func (r *Registry) Availables(ctx context.Context, addr string, timeout time.Duration, funcs ...ProtocolOptionFunc) ([]Protocol, error) { availables := make([]Protocol, 0) + protocolOpts := NewProtocolOptions(funcs...) - for _, proto := range r.protocols { - available, err := proto.Available(ctx, addr) + for _, factory := range r.protocols { + proto, err := factory(protocolOpts) if err != nil { return nil, errors.WithStack(err) } + timeoutCtx, cancel := context.WithTimeout(ctx, timeout) + + available, err := proto.Available(timeoutCtx, addr) + if err != nil { + cancel() + return nil, errors.WithStack(err) + } + + cancel() + if !available { continue } @@ -35,7 +47,7 @@ func (r *Registry) Availables(ctx context.Context, addr string) ([]Protocol, err func NewRegistry() *Registry { return &Registry{ - protocols: make(map[Identifier]Protocol), + protocols: make(map[Identifier]ProtocolFactory), } } @@ -45,10 +57,10 @@ func DefaultRegistry() *Registry { return defaultRegistry } -func Register(protocol Protocol) { - defaultRegistry.Register(protocol) +func Register(identifier Identifier, factory ProtocolFactory) { + defaultRegistry.Register(identifier, factory) } -func Availables(ctx context.Context, addr string) ([]Protocol, error) { - return defaultRegistry.Availables(ctx, addr) +func Availables(ctx context.Context, addr string, timeout time.Duration) ([]Protocol, error) { + return defaultRegistry.Availables(ctx, addr, timeout) } diff --git a/reach/client/protocol/testsuite/logger.go b/reach/client/protocol/testsuite/logger.go new file mode 100644 index 0000000..7255bdf --- /dev/null +++ b/reach/client/protocol/testsuite/logger.go @@ -0,0 +1,37 @@ +package testsuite + +import ( + "testing" + + "forge.cadoles.com/cadoles/go-emlid/reach/client/logger" +) + +type Logger struct { + t *testing.T +} + +// Debug implements logger.Logger. +func (l *Logger) Debug(msg string, args ...any) { + l.t.Logf(msg, args...) +} + +// Error implements logger.Logger. +func (l *Logger) Error(msg string, args ...any) { + l.t.Logf(msg, args...) +} + +// Info implements logger.Logger. +func (l *Logger) Info(msg string, args ...any) { + l.t.Logf(msg, args...) +} + +// Warn implements logger.Logger. +func (l *Logger) Warn(msg string, args ...any) { + l.t.Logf(msg, args...) +} + +func NewLogger(t *testing.T) *Logger { + return &Logger{t} +} + +var _ logger.Logger = &Logger{} diff --git a/reach/client/protocol/testsuite/operations.go b/reach/client/protocol/testsuite/operations.go index e26646c..69baf98 100644 --- a/reach/client/protocol/testsuite/operations.go +++ b/reach/client/protocol/testsuite/operations.go @@ -2,7 +2,9 @@ package testsuite import ( "context" + "math/rand" "os" + "strconv" "testing" "time" @@ -52,6 +54,17 @@ var testCases = []operationTestCase{ 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)) + } + }() + version, stable, err := ops.Version(ctx) if err != nil { t.Errorf("%+v", errors.WithStack(err)) @@ -104,6 +117,17 @@ var testCases = []operationTestCase{ 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.Configuration(ctx) if err != nil { t.Errorf("%+v", errors.WithStack(err)) @@ -118,13 +142,32 @@ var testCases = []operationTestCase{ 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 := rand.Float64() * 2 + opts := []protocol.SetBaseOptionFunc{ - protocol.WithBaseLatitude(47.72769), - protocol.WithBaseLongitude(4.72783), - protocol.WithBaseHeight(101), + protocol.WithBaseLatitude(latitude), + protocol.WithBaseLongitude(longitude), + protocol.WithBaseHeight(height), + protocol.WithBaseAntennaOffset(antennaOffset), protocol.WithBaseMode("manual"), } + t.Logf("setting base (latitude: %v, longitude: %v, height: %v, antennaOffset: %v", latitude, longitude, height, antennaOffset) + if err := ops.SetBase(ctx, opts...); err != nil { t.Errorf("%+v", errors.WithStack(err)) return @@ -132,10 +175,26 @@ var testCases = []operationTestCase{ }, }, - { Name: "Reboot", Run: func(t *testing.T, ops protocol.Operations) { + const doRebootEnvVar = "DO_REBOOT" + rawDoReboot := os.Getenv(doRebootEnvVar) + if rawDoReboot == "" { + rawDoReboot = "false" + } + + doReboot, err := strconv.ParseBool(rawDoReboot) + if err != nil { + t.Fatalf("could not parse %s environment variable: %+v", doRebootEnvVar, errors.WithStack(err)) + return + } + + if !doReboot { + t.Skipf("Reboot test case disabled. To enable, set environment variable %s=true", doRebootEnvVar) + return + } + ctx := context.Background() if err := ops.Connect(ctx); err != nil { diff --git a/reach/client/protocol/v1/init.go b/reach/client/protocol/v1/init.go index 1af4e54..c2f1488 100644 --- a/reach/client/protocol/v1/init.go +++ b/reach/client/protocol/v1/init.go @@ -3,5 +3,9 @@ package v1 import "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol" func init() { - protocol.Register(&Protocol{}) + protocol.Register(Identifier, func(opts *protocol.ProtocolOptions) (protocol.Protocol, error) { + return &Protocol{ + logger: opts.Logger, + }, nil + }) } diff --git a/reach/client/protocol/v1/internal.go b/reach/client/protocol/v1/internal.go index 6f1aa05..888f3a8 100644 --- a/reach/client/protocol/v1/internal.go +++ b/reach/client/protocol/v1/internal.go @@ -4,9 +4,11 @@ import ( "context" "sync" + "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/v1/model" "forge.cadoles.com/cadoles/go-emlid/reach/client/socketio" + "github.com/davecgh/go-spew/spew" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" ) @@ -27,10 +29,15 @@ type configurationApplied struct { } 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))) + res := &configurationApplied{} if err := o.ReqResp(ctx, eventApplyConfiguration, config, eventConfigurationApplied, res); err != nil { return configurationApplyFailed, nil, err } + + o.logger.Debug("apply configuration response", logger.Attr("response", res)) + return res.Result, res.Configuration, nil } diff --git a/reach/client/protocol/v1/operations.go b/reach/client/protocol/v1/operations.go index 9245100..7face3e 100644 --- a/reach/client/protocol/v1/operations.go +++ b/reach/client/protocol/v1/operations.go @@ -6,6 +6,7 @@ import ( "strings" "sync" + "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/v1/model" "forge.cadoles.com/cadoles/go-emlid/reach/client/socketio" @@ -16,6 +17,7 @@ type Operations struct { addr string client *socketio.Client mutex sync.RWMutex + logger logger.Logger } // Close implements protocol.Operations. @@ -54,6 +56,8 @@ func (o *Operations) Connect(ctx context.Context) error { client := socketio.NewClient(endpoint) + o.logger.Debug("connecting", logger.Attr("endpoint", endpoint)) + if err := client.Connect(); err != nil { return errors.WithStack(err) } @@ -67,6 +71,8 @@ func (o *Operations) Connect(ctx context.Context) error { return errors.WithStack(err) } + o.logger.Debug("connected") + o.client = client return nil @@ -206,29 +212,58 @@ const ( // SetBase implements protocol.Operations. func (o *Operations) SetBase(ctx context.Context, funcs ...protocol.SetBaseOptionFunc) error { + rawConfig, err := o.Configuration(ctx) + if err != nil { + return errors.WithStack(err) + } + + config := rawConfig.(*model.Configuration) + baseMode := config.BaseMode + + var baseCoordinates *model.BaseCoordinates + if baseMode != nil && baseMode.BaseCoordinates != nil { + baseCoordinates = baseMode.BaseCoordinates + } + opts := protocol.NewSetBaseOptions(funcs...) var lat string if opts.Latitude != nil { lat = strconv.FormatFloat(*opts.Latitude, 'f', -1, 64) + } else if baseCoordinates != nil && baseCoordinates.Coordinates != nil && len(baseCoordinates.Coordinates) > 1 { + lat = *baseCoordinates.Coordinates[0] + } else { + lat = "0" } var lon string if opts.Longitude != nil { lon = strconv.FormatFloat(*opts.Longitude, 'f', -1, 64) + } else if baseCoordinates != nil && baseCoordinates.Coordinates != nil && len(baseCoordinates.Coordinates) > 1 { + lon = *baseCoordinates.Coordinates[1] + } else { + lon = "0" } var alt string if opts.Height != nil { alt = strconv.FormatFloat(*opts.Height, 'f', -1, 64) + } else if baseCoordinates != nil && baseCoordinates.Coordinates != nil && len(baseCoordinates.Coordinates) > 1 { + alt = *baseCoordinates.Coordinates[2] + } else { + alt = "0" } var antennaHeight string if opts.AntennaOffset != nil { antennaHeight = strconv.FormatFloat(*opts.AntennaOffset, 'f', -1, 64) + } else if baseCoordinates != nil && baseCoordinates.AntennaOffset != nil && baseCoordinates.AntennaOffset.Up != nil { + antennaHeight = *baseCoordinates.AntennaOffset.Up + } else { + antennaHeight = "0" } - config := &model.Configuration{ + newConfig := &model.Configuration{ BaseMode: &model.BaseMode{ BaseCoordinates: &model.BaseCoordinates{ Accumulation: model.String("1"), @@ -248,7 +283,7 @@ func (o *Operations) SetBase(ctx context.Context, funcs ...protocol.SetBaseOptio }, } - result, _, err := o.ApplyConfiguration(ctx, config) + result, _, err := o.ApplyConfiguration(ctx, newConfig) if err != nil { return errors.New("configuration update failed") } diff --git a/reach/client/protocol/v1/operations_test.go b/reach/client/protocol/v1/operations_test.go index 7e49ef2..4df7ec4 100644 --- a/reach/client/protocol/v1/operations_test.go +++ b/reach/client/protocol/v1/operations_test.go @@ -10,7 +10,9 @@ import ( ) func TestProtocolV1Operations(t *testing.T) { - proto := &Protocol{} + proto := &Protocol{ + logger: testsuite.NewLogger(t), + } factory := func(addr string) (protocol.Operations, error) { ctx := context.Background() diff --git a/reach/client/protocol/v1/protocol.go b/reach/client/protocol/v1/protocol.go index 4fb4eeb..683c303 100644 --- a/reach/client/protocol/v1/protocol.go +++ b/reach/client/protocol/v1/protocol.go @@ -2,17 +2,53 @@ package v1 import ( "context" + "log/slog" + "forge.cadoles.com/cadoles/go-emlid/reach/client/logger" "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol" + "github.com/Masterminds/semver/v3" + "github.com/pkg/errors" ) const Identifier protocol.Identifier = "v1" +const compatibleVersionConstraint = "^2.24" + type Protocol struct { + logger logger.Logger } // Available implements protocol.Protocol. func (p *Protocol) Available(ctx context.Context, addr string) (bool, error) { + ops := p.Operations(addr).(*Operations) + + if err := ops.Connect(ctx); err != nil { + return false, nil + } + + defer ops.Close(ctx) + + rawVersion, _, err := ops.Version(ctx) + if err != nil { + return false, nil + } + + versionConstraint, err := semver.NewConstraint(compatibleVersionConstraint) + if err != nil { + return false, errors.WithStack(err) + } + + version, err := semver.NewVersion(rawVersion) + if err != nil { + return false, errors.WithStack(err) + } + + p.logger.Debug("checking version", slog.Any("version", rawVersion), slog.Any("constraint", compatibleVersionConstraint)) + + if !versionConstraint.Check(version) { + return false, nil + } + return true, nil } @@ -23,7 +59,7 @@ func (p *Protocol) Identifier() protocol.Identifier { // Operations implements protocol.Protocol. func (p *Protocol) Operations(addr string) protocol.Operations { - return &Operations{addr: addr} + return &Operations{addr: addr, logger: p.logger} } var _ protocol.Protocol = &Protocol{} diff --git a/reach/client/protocol/v2/init.go b/reach/client/protocol/v2/init.go index 637c7bc..0569767 100644 --- a/reach/client/protocol/v2/init.go +++ b/reach/client/protocol/v2/init.go @@ -3,5 +3,9 @@ package v2 import "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol" func init() { - protocol.Register(&Protocol{}) + protocol.Register(Identifier, func(opts *protocol.ProtocolOptions) (protocol.Protocol, error) { + return &Protocol{ + logger: opts.Logger, + }, nil + }) } diff --git a/reach/client/protocol/v2/model/configuration.go b/reach/client/protocol/v2/model/configuration.go index 869c9d4..7e555ca 100644 --- a/reach/client/protocol/v2/model/configuration.go +++ b/reach/client/protocol/v2/model/configuration.go @@ -18,7 +18,7 @@ type Configuration struct { Lora struct { AirRate float64 `json:"air_rate,omitempty"` Frequency int `json:"frequency,omitempty"` - OutputPower int `json:"output_power,omitempty"` + OutputPower float64 `json:"output_power,omitempty"` } `json:"lora,omitempty"` Ntripcaster struct { MountPoint string `json:"mount_point,omitempty"` diff --git a/reach/client/protocol/v2/operations.go b/reach/client/protocol/v2/operations.go index 788a2fc..38cb9a2 100644 --- a/reach/client/protocol/v2/operations.go +++ b/reach/client/protocol/v2/operations.go @@ -4,6 +4,7 @@ import ( "context" "sync" + "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" @@ -15,6 +16,7 @@ type Operations struct { addr string client *socketio.Client mutex sync.RWMutex + logger logger.Logger } // Reboot implements protocol.Operations. diff --git a/reach/client/protocol/v2/operations_test.go b/reach/client/protocol/v2/operations_test.go index d274788..7a918cb 100644 --- a/reach/client/protocol/v2/operations_test.go +++ b/reach/client/protocol/v2/operations_test.go @@ -10,7 +10,9 @@ import ( ) func TestProtocolV2Operations(t *testing.T) { - proto := &Protocol{} + proto := &Protocol{ + logger: testsuite.NewLogger(t), + } factory := func(addr string) (protocol.Operations, error) { ctx := context.Background() diff --git a/reach/client/protocol/v2/protocol.go b/reach/client/protocol/v2/protocol.go index 2f891c1..033c7c2 100644 --- a/reach/client/protocol/v2/protocol.go +++ b/reach/client/protocol/v2/protocol.go @@ -3,6 +3,7 @@ package v2 import ( "context" + "forge.cadoles.com/cadoles/go-emlid/reach/client/logger" "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol" "github.com/Masterminds/semver/v3" "github.com/pkg/errors" @@ -13,6 +14,7 @@ const Identifier protocol.Identifier = "v2" const compatibleVersionConstraint = ">= 32" type Protocol struct { + logger logger.Logger } // Available implements protocol.Protocol. @@ -35,7 +37,7 @@ func (p *Protocol) Available(ctx context.Context, addr string) (bool, error) { } if !versionConstraint.Check(version) { - return false, errors.Errorf("reachview version '%s' does not match constraint '%s'", info.Reachview.Version, compatibleVersionConstraint) + return false, nil } return true, nil @@ -48,7 +50,7 @@ func (p *Protocol) Identifier() protocol.Identifier { // Operations implements protocol.Protocol. func (p *Protocol) Operations(addr string) protocol.Operations { - return &Operations{addr: addr} + return &Operations{addr: addr, logger: p.logger} } var _ protocol.Protocol = &Protocol{} diff --git a/reach/discovery/discovery.go b/reach/discovery/discovery.go index 05564cc..7f2a723 100644 --- a/reach/discovery/discovery.go +++ b/reach/discovery/discovery.go @@ -3,9 +3,9 @@ package discovery import ( "context" "net" - "sync" "github.com/grandcat/zeroconf" + "github.com/pkg/errors" ) // Service is a ReachRS service discovered via MDNS-SD @@ -17,19 +17,34 @@ type Service struct { // Discover tries to discover ReachRS services on the local network via mDNS-SD func Discover(ctx context.Context) ([]Service, error) { - var wg sync.WaitGroup + services := make([]Service, 0) - wg.Add(1) + watch, err := Watch(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + + for srv := range watch { + services = append(services, srv) + } + + return services, nil +} + +// Watch watches ReachRS services on the local network via mDNS-SD +func Watch(ctx context.Context) (chan Service, error) { + out := make(chan Service, 0) resolver, err := zeroconf.NewResolver() if err != nil { - return nil, err + return nil, errors.WithStack(err) } - services := make([]Service, 0) entries := make(chan *zeroconf.ServiceEntry) go func() { + defer close(out) + for e := range entries { var addr *net.IP if len(e.AddrIPv4) > 0 { @@ -40,17 +55,14 @@ func Discover(ctx context.Context) ([]Service, error) { AddrV4: addr, Port: e.Port, } - services = append(services, srv) + out <- srv } - wg.Done() + }() if err = resolver.Browse(ctx, "_reach._tcp", ".local", entries); err != nil { return nil, err } - wg.Wait() - - return services, nil - + return out, nil }