fix: protocol v1

This commit is contained in:
wpetit 2024-08-02 12:57:07 +02:00
parent 8f89ed7e77
commit b976bde363
23 changed files with 392 additions and 85 deletions

View File

@ -5,18 +5,22 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"log/slog"
"os" "os"
reach "forge.cadoles.com/cadoles/go-emlid/reach/client" reach "forge.cadoles.com/cadoles/go-emlid/reach/client"
"forge.cadoles.com/cadoles/go-emlid/reach/client/logger"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
var ( var (
host string = "192.168.42.1" host string = "192.168.42.1"
filter string = "" filter string = ""
rawLogLevel string = "ERROR"
) )
func init() { func init() {
flag.StringVar(&rawLogLevel, "log-level", rawLogLevel, "log level")
flag.StringVar(&host, "host", host, "the reachrs module host") flag.StringVar(&host, "host", host, "the reachrs module host")
flag.StringVar(&filter, "filter", filter, "filter the broadcast messages by name") flag.StringVar(&filter, "filter", filter, "filter the broadcast messages by name")
} }
@ -27,6 +31,14 @@ func main() {
ctx := context.Background() ctx := context.Background()
client := reach.NewClient(host) 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 { if err := client.Connect(ctx); err != nil {
fmt.Printf("[FATAL] %+v", errors.WithStack(err)) fmt.Printf("[FATAL] %+v", errors.WithStack(err))
os.Exit(1) os.Exit(1)

View File

@ -5,9 +5,11 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"log/slog"
"os" "os"
reach "forge.cadoles.com/cadoles/go-emlid/reach/client" 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"
v1 "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v1" v1 "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v1"
v2 "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v2" v2 "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v2"
@ -18,9 +20,11 @@ var (
host string = "192.168.42.1" host string = "192.168.42.1"
preferredProtocol string = string(v2.Identifier) preferredProtocol string = string(v2.Identifier)
fallbackProtocol string = string(v1.Identifier) fallbackProtocol string = string(v1.Identifier)
rawLogLevel string = "ERROR"
) )
func init() { func init() {
flag.StringVar(&rawLogLevel, "log-level", rawLogLevel, "log level")
flag.StringVar(&host, "host", host, "the reachrs module host") flag.StringVar(&host, "host", host, "the reachrs module host")
flag.StringVar(&preferredProtocol, "preferred-protocol", preferredProtocol, "preferred-protocol") flag.StringVar(&preferredProtocol, "preferred-protocol", preferredProtocol, "preferred-protocol")
flag.StringVar(&fallbackProtocol, "fallback-protocol", fallbackProtocol, "fallback-protocol") flag.StringVar(&fallbackProtocol, "fallback-protocol", fallbackProtocol, "fallback-protocol")
@ -29,6 +33,14 @@ func init() {
func main() { func main() {
flag.Parse() 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() ctx := context.Background()
client := reach.NewClient( client := reach.NewClient(
host, host,

35
cmd/discover/main.go Normal file
View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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) { func (c *Client) getProtocol(ctx context.Context) (protocol.Identifier, protocol.Operations, error) {
c.getProtocolOnce.Do(func() { 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 { if err != nil {
c.getProtocolOnceErr = errors.WithStack(err) c.getProtocolOnceErr = errors.WithStack(err)
return return

View File

@ -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
}

View File

@ -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)
}

View File

@ -1,6 +1,10 @@
package client package client
import ( import (
"log/slog"
"time"
"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 "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v1" v1 "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v1"
v2 "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v2" v2 "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v2"
@ -10,6 +14,8 @@ type Options struct {
Protocols *protocol.Registry Protocols *protocol.Registry
PreferredProtocol protocol.Identifier PreferredProtocol protocol.Identifier
FallbackProtocol protocol.Identifier FallbackProtocol protocol.Identifier
AvailableTimeout time.Duration
Logger logger.Logger
} }
type OptionFunc func(opts *Options) type OptionFunc func(opts *Options)
@ -19,6 +25,8 @@ func NewOptions(funcs ...OptionFunc) *Options {
PreferredProtocol: v2.Identifier, PreferredProtocol: v2.Identifier,
FallbackProtocol: v1.Identifier, FallbackProtocol: v1.Identifier,
Protocols: protocol.DefaultRegistry(), Protocols: protocol.DefaultRegistry(),
AvailableTimeout: 5 * time.Second,
Logger: slog.Default(),
} }
for _, fn := range funcs { for _, fn := range funcs {
@ -45,3 +53,15 @@ func WithProtocols(protocols *protocol.Registry) OptionFunc {
opts.Protocols = protocols 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
}
}

View File

@ -2,6 +2,9 @@ package protocol
import ( import (
"context" "context"
"log/slog"
"forge.cadoles.com/cadoles/go-emlid/reach/client/logger"
) )
type Identifier string type Identifier string
@ -11,3 +14,29 @@ type Protocol interface {
Available(ctx context.Context, addr string) (bool, error) Available(ctx context.Context, addr string) (bool, error)
Operations(addr string) Operations 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
}
}

View File

@ -2,27 +2,39 @@ package protocol
import ( import (
"context" "context"
"time"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type Registry struct { type Registry struct {
protocols map[Identifier]Protocol protocols map[Identifier]ProtocolFactory
} }
func (r *Registry) Register(protocol Protocol) { func (r *Registry) Register(identifier Identifier, factory ProtocolFactory) {
r.protocols[protocol.Identifier()] = protocol 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) availables := make([]Protocol, 0)
protocolOpts := NewProtocolOptions(funcs...)
for _, proto := range r.protocols { for _, factory := range r.protocols {
available, err := proto.Available(ctx, addr) proto, err := factory(protocolOpts)
if err != nil { if err != nil {
return nil, errors.WithStack(err) 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 { if !available {
continue continue
} }
@ -35,7 +47,7 @@ func (r *Registry) Availables(ctx context.Context, addr string) ([]Protocol, err
func NewRegistry() *Registry { func NewRegistry() *Registry {
return &Registry{ return &Registry{
protocols: make(map[Identifier]Protocol), protocols: make(map[Identifier]ProtocolFactory),
} }
} }
@ -45,10 +57,10 @@ func DefaultRegistry() *Registry {
return defaultRegistry return defaultRegistry
} }
func Register(protocol Protocol) { func Register(identifier Identifier, factory ProtocolFactory) {
defaultRegistry.Register(protocol) defaultRegistry.Register(identifier, factory)
} }
func Availables(ctx context.Context, addr string) ([]Protocol, error) { func Availables(ctx context.Context, addr string, timeout time.Duration) ([]Protocol, error) {
return defaultRegistry.Availables(ctx, addr) return defaultRegistry.Availables(ctx, addr, timeout)
} }

View File

@ -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{}

View File

@ -2,7 +2,9 @@ package testsuite
import ( import (
"context" "context"
"math/rand"
"os" "os"
"strconv"
"testing" "testing"
"time" "time"
@ -52,6 +54,17 @@ var testCases = []operationTestCase{
Run: func(t *testing.T, ops protocol.Operations) { Run: func(t *testing.T, ops protocol.Operations) {
ctx := context.Background() 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) version, stable, err := ops.Version(ctx)
if err != nil { if err != nil {
t.Errorf("%+v", errors.WithStack(err)) t.Errorf("%+v", errors.WithStack(err))
@ -104,6 +117,17 @@ var testCases = []operationTestCase{
Run: func(t *testing.T, ops protocol.Operations) { Run: func(t *testing.T, ops protocol.Operations) {
ctx := context.Background() 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) config, err := ops.Configuration(ctx)
if err != nil { if err != nil {
t.Errorf("%+v", errors.WithStack(err)) t.Errorf("%+v", errors.WithStack(err))
@ -118,13 +142,32 @@ var testCases = []operationTestCase{
Run: func(t *testing.T, ops protocol.Operations) { Run: func(t *testing.T, ops protocol.Operations) {
ctx := context.Background() 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{ opts := []protocol.SetBaseOptionFunc{
protocol.WithBaseLatitude(47.72769), protocol.WithBaseLatitude(latitude),
protocol.WithBaseLongitude(4.72783), protocol.WithBaseLongitude(longitude),
protocol.WithBaseHeight(101), protocol.WithBaseHeight(height),
protocol.WithBaseAntennaOffset(antennaOffset),
protocol.WithBaseMode("manual"), 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 { if err := ops.SetBase(ctx, opts...); err != nil {
t.Errorf("%+v", errors.WithStack(err)) t.Errorf("%+v", errors.WithStack(err))
return return
@ -132,10 +175,26 @@ var testCases = []operationTestCase{
}, },
}, },
{ {
Name: "Reboot", Name: "Reboot",
Run: func(t *testing.T, ops protocol.Operations) { 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() ctx := context.Background()
if err := ops.Connect(ctx); err != nil { if err := ops.Connect(ctx); err != nil {

View File

@ -3,5 +3,9 @@ package v1
import "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol" import "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol"
func init() { func init() {
protocol.Register(&Protocol{}) protocol.Register(Identifier, func(opts *protocol.ProtocolOptions) (protocol.Protocol, error) {
return &Protocol{
logger: opts.Logger,
}, nil
})
} }

View File

@ -4,9 +4,11 @@ import (
"context" "context"
"sync" "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"
"forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v1/model" "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v1/model"
"forge.cadoles.com/cadoles/go-emlid/reach/client/socketio" "forge.cadoles.com/cadoles/go-emlid/reach/client/socketio"
"github.com/davecgh/go-spew/spew"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/pkg/errors" "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) { 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{} res := &configurationApplied{}
if err := o.ReqResp(ctx, eventApplyConfiguration, config, eventConfigurationApplied, res); err != nil { if err := o.ReqResp(ctx, eventApplyConfiguration, config, eventConfigurationApplied, res); err != nil {
return configurationApplyFailed, nil, err return configurationApplyFailed, nil, err
} }
o.logger.Debug("apply configuration response", logger.Attr("response", res))
return res.Result, res.Configuration, nil return res.Result, res.Configuration, nil
} }

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"sync" "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"
"forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v1/model" "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v1/model"
"forge.cadoles.com/cadoles/go-emlid/reach/client/socketio" "forge.cadoles.com/cadoles/go-emlid/reach/client/socketio"
@ -16,6 +17,7 @@ type Operations struct {
addr string addr string
client *socketio.Client client *socketio.Client
mutex sync.RWMutex mutex sync.RWMutex
logger logger.Logger
} }
// Close implements protocol.Operations. // Close implements protocol.Operations.
@ -54,6 +56,8 @@ func (o *Operations) Connect(ctx context.Context) error {
client := socketio.NewClient(endpoint) client := socketio.NewClient(endpoint)
o.logger.Debug("connecting", logger.Attr("endpoint", endpoint))
if err := client.Connect(); err != nil { if err := client.Connect(); err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
@ -67,6 +71,8 @@ func (o *Operations) Connect(ctx context.Context) error {
return errors.WithStack(err) return errors.WithStack(err)
} }
o.logger.Debug("connected")
o.client = client o.client = client
return nil return nil
@ -206,29 +212,58 @@ const (
// SetBase implements protocol.Operations. // SetBase implements protocol.Operations.
func (o *Operations) SetBase(ctx context.Context, funcs ...protocol.SetBaseOptionFunc) error { 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...) opts := protocol.NewSetBaseOptions(funcs...)
var lat string var lat string
if opts.Latitude != nil { if opts.Latitude != nil {
lat = strconv.FormatFloat(*opts.Latitude, 'f', -1, 64) 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 var lon string
if opts.Longitude != nil { if opts.Longitude != nil {
lon = strconv.FormatFloat(*opts.Longitude, 'f', -1, 64) 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 var alt string
if opts.Height != nil { if opts.Height != nil {
alt = strconv.FormatFloat(*opts.Height, 'f', -1, 64) 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 var antennaHeight string
if opts.AntennaOffset != nil { if opts.AntennaOffset != nil {
antennaHeight = strconv.FormatFloat(*opts.AntennaOffset, 'f', -1, 64) 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{ BaseMode: &model.BaseMode{
BaseCoordinates: &model.BaseCoordinates{ BaseCoordinates: &model.BaseCoordinates{
Accumulation: model.String("1"), 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 { if err != nil {
return errors.New("configuration update failed") return errors.New("configuration update failed")
} }

View File

@ -10,7 +10,9 @@ import (
) )
func TestProtocolV1Operations(t *testing.T) { func TestProtocolV1Operations(t *testing.T) {
proto := &Protocol{} proto := &Protocol{
logger: testsuite.NewLogger(t),
}
factory := func(addr string) (protocol.Operations, error) { factory := func(addr string) (protocol.Operations, error) {
ctx := context.Background() ctx := context.Background()

View File

@ -2,17 +2,53 @@ package v1
import ( import (
"context" "context"
"log/slog"
"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"
"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
) )
const Identifier protocol.Identifier = "v1" const Identifier protocol.Identifier = "v1"
const compatibleVersionConstraint = "^2.24"
type Protocol struct { type Protocol struct {
logger logger.Logger
} }
// Available implements protocol.Protocol. // Available implements protocol.Protocol.
func (p *Protocol) Available(ctx context.Context, addr string) (bool, error) { 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 return true, nil
} }
@ -23,7 +59,7 @@ func (p *Protocol) Identifier() protocol.Identifier {
// Operations implements protocol.Protocol. // Operations implements protocol.Protocol.
func (p *Protocol) Operations(addr string) protocol.Operations { func (p *Protocol) Operations(addr string) protocol.Operations {
return &Operations{addr: addr} return &Operations{addr: addr, logger: p.logger}
} }
var _ protocol.Protocol = &Protocol{} var _ protocol.Protocol = &Protocol{}

View File

@ -3,5 +3,9 @@ package v2
import "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol" import "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol"
func init() { func init() {
protocol.Register(&Protocol{}) protocol.Register(Identifier, func(opts *protocol.ProtocolOptions) (protocol.Protocol, error) {
return &Protocol{
logger: opts.Logger,
}, nil
})
} }

View File

@ -18,7 +18,7 @@ type Configuration struct {
Lora struct { Lora struct {
AirRate float64 `json:"air_rate,omitempty"` AirRate float64 `json:"air_rate,omitempty"`
Frequency int `json:"frequency,omitempty"` Frequency int `json:"frequency,omitempty"`
OutputPower int `json:"output_power,omitempty"` OutputPower float64 `json:"output_power,omitempty"`
} `json:"lora,omitempty"` } `json:"lora,omitempty"`
Ntripcaster struct { Ntripcaster struct {
MountPoint string `json:"mount_point,omitempty"` MountPoint string `json:"mount_point,omitempty"`

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"sync" "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"
"forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v2/model" "forge.cadoles.com/cadoles/go-emlid/reach/client/protocol/v2/model"
@ -15,6 +16,7 @@ type Operations struct {
addr string addr string
client *socketio.Client client *socketio.Client
mutex sync.RWMutex mutex sync.RWMutex
logger logger.Logger
} }
// Reboot implements protocol.Operations. // Reboot implements protocol.Operations.

View File

@ -10,7 +10,9 @@ import (
) )
func TestProtocolV2Operations(t *testing.T) { func TestProtocolV2Operations(t *testing.T) {
proto := &Protocol{} proto := &Protocol{
logger: testsuite.NewLogger(t),
}
factory := func(addr string) (protocol.Operations, error) { factory := func(addr string) (protocol.Operations, error) {
ctx := context.Background() ctx := context.Background()

View File

@ -3,6 +3,7 @@ package v2
import ( import (
"context" "context"
"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"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -13,6 +14,7 @@ const Identifier protocol.Identifier = "v2"
const compatibleVersionConstraint = ">= 32" const compatibleVersionConstraint = ">= 32"
type Protocol struct { type Protocol struct {
logger logger.Logger
} }
// Available implements protocol.Protocol. // Available implements protocol.Protocol.
@ -35,7 +37,7 @@ func (p *Protocol) Available(ctx context.Context, addr string) (bool, error) {
} }
if !versionConstraint.Check(version) { 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 return true, nil
@ -48,7 +50,7 @@ func (p *Protocol) Identifier() protocol.Identifier {
// Operations implements protocol.Protocol. // Operations implements protocol.Protocol.
func (p *Protocol) Operations(addr string) protocol.Operations { func (p *Protocol) Operations(addr string) protocol.Operations {
return &Operations{addr: addr} return &Operations{addr: addr, logger: p.logger}
} }
var _ protocol.Protocol = &Protocol{} var _ protocol.Protocol = &Protocol{}

View File

@ -3,9 +3,9 @@ package discovery
import ( import (
"context" "context"
"net" "net"
"sync"
"github.com/grandcat/zeroconf" "github.com/grandcat/zeroconf"
"github.com/pkg/errors"
) )
// Service is a ReachRS service discovered via MDNS-SD // 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 // Discover tries to discover ReachRS services on the local network via mDNS-SD
func Discover(ctx context.Context) ([]Service, error) { 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() resolver, err := zeroconf.NewResolver()
if err != nil { if err != nil {
return nil, err return nil, errors.WithStack(err)
} }
services := make([]Service, 0)
entries := make(chan *zeroconf.ServiceEntry) entries := make(chan *zeroconf.ServiceEntry)
go func() { go func() {
defer close(out)
for e := range entries { for e := range entries {
var addr *net.IP var addr *net.IP
if len(e.AddrIPv4) > 0 { if len(e.AddrIPv4) > 0 {
@ -40,17 +55,14 @@ func Discover(ctx context.Context) ([]Service, error) {
AddrV4: addr, AddrV4: addr,
Port: e.Port, Port: e.Port,
} }
services = append(services, srv) out <- srv
} }
wg.Done()
}() }()
if err = resolver.Browse(ctx, "_reach._tcp", ".local", entries); err != nil { if err = resolver.Browse(ctx, "_reach._tcp", ".local", entries); err != nil {
return nil, err return nil, err
} }
wg.Wait() return out, nil
return services, nil
} }