fix: protocol v1
This commit is contained in:
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
37
reach/client/protocol/testsuite/logger.go
Normal file
37
reach/client/protocol/testsuite/logger.go
Normal 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{}
|
@ -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 {
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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{}
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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"`
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
@ -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{}
|
||||
|
Reference in New Issue
Block a user