From fb0a41a3bd74a0e94cd145767c72f07ef1e07acd Mon Sep 17 00:00:00 2001 From: Florian Thienel Date: Sun, 10 Dec 2023 11:34:26 +0100 Subject: [PATCH 1/9] read and manage the connection configurations --- pkg/hamdeck/config.go | 32 +++++++++++++++++++++++++++++- pkg/hamdeck/connections_test.go | 35 +++++++++++++++++++++++++++++++++ pkg/hamdeck/hamdeck.go | 9 +++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 pkg/hamdeck/connections_test.go diff --git a/pkg/hamdeck/config.go b/pkg/hamdeck/config.go index ad4330c..a77b757 100644 --- a/pkg/hamdeck/config.go +++ b/pkg/hamdeck/config.go @@ -18,6 +18,7 @@ const ( ConfigButtons = "buttons" ConfigType = "type" ConfigIndex = "index" + ConfigConnections = "connections" ) func (d *HamDeck) ReadConfig(r io.Reader) error { @@ -40,12 +41,21 @@ func (d *HamDeck) ReadConfig(r io.Reader) error { effectiveConfiguration := findEffectiveConfiguration(configuration) d.buttonsPerFactory = make([]int, len(d.factories)) + d.connections = make(map[string]ConnectionConfig) d.pages = make(map[string]Page) + + connections, ok := (effectiveConfiguration[ConfigConnections]).(map[string]any) + if ok { + err = d.loadConnections(connections) + } + if err != nil { + return err + } + d.startPageID, ok = effectiveConfiguration[ConfigStartPageID].(string) if !ok { d.startPageID = legacyPageID } - pages, ok := effectiveConfiguration[ConfigPages].(map[string]any) if ok { err = d.loadPages(pages) @@ -57,6 +67,8 @@ func (d *HamDeck) ReadConfig(r io.Reader) error { buttons, ok := effectiveConfiguration[ConfigButtons].([]any) if ok { err = d.loadLegacyPage(buttons) + } else { + d.loadEmptyLegacyPage() } if err != nil { return err @@ -78,6 +90,18 @@ func findEffectiveConfiguration(configuration map[string]any) map[string]any { return subconfiguration } +func (d *HamDeck) loadConnections(configuration map[string]any) error { + for id, config := range configuration { + connection, ok := config.(map[string]any) + if !ok { + log.Printf("%s is not a valid connection configuration", id) + continue + } + d.connections[id] = ConnectionConfig(connection) + } + return nil +} + func (d *HamDeck) loadPages(configuration map[string]any) error { for id, rawPage := range configuration { pageConfiguration, ok := rawPage.(map[string]any) @@ -123,6 +147,12 @@ func (d *HamDeck) loadLegacyPage(configuration []any) error { return nil } +func (d *HamDeck) loadEmptyLegacyPage() { + d.pages[legacyPageID] = Page{ + buttons: make([]Button, len(d.buttons)), + } +} + func (d *HamDeck) loadButtons(configuration []any) ([]Button, error) { result := make([]Button, len(d.buttons)) for i, rawButtonConfig := range configuration { diff --git a/pkg/hamdeck/connections_test.go b/pkg/hamdeck/connections_test.go new file mode 100644 index 0000000..be48077 --- /dev/null +++ b/pkg/hamdeck/connections_test.go @@ -0,0 +1,35 @@ +package hamdeck + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReadConfig_Connections(t *testing.T) { + runWithConfigString(t, `{ + "connections": { + "testsdrA": { + "type": "test", + "some_config": "some_value" + }, + "testsdrB": { + "type": "test", + "some_config": "some_other_value" + } + } +}`, func(t *testing.T, deck *HamDeck, device *testDevice, _ chan struct{}) { + assert.Equal(t, 2, len(deck.connections)) + + configA, ok := deck.GetConnection("testsdrA") + assert.True(t, ok) + assert.Equal(t, "some_value", configA["some_config"]) + + configB, ok := deck.GetConnection("testsdrB") + assert.True(t, ok) + assert.Equal(t, "some_other_value", configB["some_config"]) + + _, ok = deck.GetConnection("undefined") + assert.False(t, ok) + }) +} diff --git a/pkg/hamdeck/hamdeck.go b/pkg/hamdeck/hamdeck.go index 7478def..e63949e 100644 --- a/pkg/hamdeck/hamdeck.go +++ b/pkg/hamdeck/hamdeck.go @@ -84,6 +84,8 @@ type ButtonFactory interface { CreateButton(config map[string]interface{}) Button } +type ConnectionConfig map[string]any + const legacyPageID = "" type HamDeck struct { @@ -98,6 +100,8 @@ type HamDeck struct { startPageID string pages map[string]Page + + connections map[string]ConnectionConfig } type Page struct { @@ -128,6 +132,11 @@ func (d *HamDeck) RegisterFactory(factory ButtonFactory) { d.factories = append(d.factories, factory) } +func (d *HamDeck) GetConnection(name string) (ConnectionConfig, bool) { + connection, found := d.connections[name] + return connection, found +} + func (d *HamDeck) RedrawAll(redrawImages bool) { d.drawLock.Lock() defer d.drawLock.Unlock() From d73284777646d81005eeb063dbcad3eac1820b58 Mon Sep 17 00:00:00 2001 From: Florian Thienel Date: Sun, 10 Dec 2023 12:48:12 +0100 Subject: [PATCH 2/9] fix test failure --- pkg/hamdeck/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/hamdeck/config.go b/pkg/hamdeck/config.go index a77b757..d78e765 100644 --- a/pkg/hamdeck/config.go +++ b/pkg/hamdeck/config.go @@ -67,7 +67,7 @@ func (d *HamDeck) ReadConfig(r io.Reader) error { buttons, ok := effectiveConfiguration[ConfigButtons].([]any) if ok { err = d.loadLegacyPage(buttons) - } else { + } else if len(d.pages) == 0 { d.loadEmptyLegacyPage() } if err != nil { From 958d1dcc4e63e1af655a84808136b59300c34b0b Mon Sep 17 00:00:00 2001 From: Florian Thienel Date: Sun, 10 Dec 2023 12:59:19 +0100 Subject: [PATCH 3/9] combine the name and the connection type as unique key --- pkg/hamdeck/config.go | 13 +++++++++---- pkg/hamdeck/connections_test.go | 6 +++--- pkg/hamdeck/hamdeck.go | 11 ++++++++--- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/pkg/hamdeck/config.go b/pkg/hamdeck/config.go index d78e765..2048fd0 100644 --- a/pkg/hamdeck/config.go +++ b/pkg/hamdeck/config.go @@ -41,7 +41,7 @@ func (d *HamDeck) ReadConfig(r io.Reader) error { effectiveConfiguration := findEffectiveConfiguration(configuration) d.buttonsPerFactory = make([]int, len(d.factories)) - d.connections = make(map[string]ConnectionConfig) + d.connections = make(map[connectionKey]ConnectionConfig) d.pages = make(map[string]Page) connections, ok := (effectiveConfiguration[ConfigConnections]).(map[string]any) @@ -91,13 +91,18 @@ func findEffectiveConfiguration(configuration map[string]any) map[string]any { } func (d *HamDeck) loadConnections(configuration map[string]any) error { - for id, config := range configuration { + for name, config := range configuration { connection, ok := config.(map[string]any) if !ok { - log.Printf("%s is not a valid connection configuration", id) + log.Printf("%s is not a valid connection configuration", name) continue } - d.connections[id] = ConnectionConfig(connection) + connectionType, ok := ToString(connection[ConfigType]) + if !ok { + log.Printf("connection %s needs a type", name) + continue + } + d.connections[connectionKey{name, connectionType}] = ConnectionConfig(connection) } return nil } diff --git a/pkg/hamdeck/connections_test.go b/pkg/hamdeck/connections_test.go index be48077..41c3663 100644 --- a/pkg/hamdeck/connections_test.go +++ b/pkg/hamdeck/connections_test.go @@ -21,15 +21,15 @@ func TestReadConfig_Connections(t *testing.T) { }`, func(t *testing.T, deck *HamDeck, device *testDevice, _ chan struct{}) { assert.Equal(t, 2, len(deck.connections)) - configA, ok := deck.GetConnection("testsdrA") + configA, ok := deck.GetConnection("testsdrA", "test") assert.True(t, ok) assert.Equal(t, "some_value", configA["some_config"]) - configB, ok := deck.GetConnection("testsdrB") + configB, ok := deck.GetConnection("testsdrB", "test") assert.True(t, ok) assert.Equal(t, "some_other_value", configB["some_config"]) - _, ok = deck.GetConnection("undefined") + _, ok = deck.GetConnection("testsdrA", "undefined") assert.False(t, ok) }) } diff --git a/pkg/hamdeck/hamdeck.go b/pkg/hamdeck/hamdeck.go index e63949e..21fb501 100644 --- a/pkg/hamdeck/hamdeck.go +++ b/pkg/hamdeck/hamdeck.go @@ -84,6 +84,11 @@ type ButtonFactory interface { CreateButton(config map[string]interface{}) Button } +type connectionKey struct { + name string + connectionType string +} + type ConnectionConfig map[string]any const legacyPageID = "" @@ -101,7 +106,7 @@ type HamDeck struct { startPageID string pages map[string]Page - connections map[string]ConnectionConfig + connections map[connectionKey]ConnectionConfig } type Page struct { @@ -132,8 +137,8 @@ func (d *HamDeck) RegisterFactory(factory ButtonFactory) { d.factories = append(d.factories, factory) } -func (d *HamDeck) GetConnection(name string) (ConnectionConfig, bool) { - connection, found := d.connections[name] +func (d *HamDeck) GetConnection(name string, connectionType string) (ConnectionConfig, bool) { + connection, found := d.connections[connectionKey{name, connectionType}] return connection, found } From 2df34bebd3ba4381887ebd6607f66fae91bf6c26 Mon Sep 17 00:00:00 2001 From: Florian Thienel Date: Sun, 10 Dec 2023 13:45:55 +0100 Subject: [PATCH 4/9] provide a helper to manage multiple connections and create them on demand --- pkg/hamdeck/config.go | 3 ++- pkg/hamdeck/connections_test.go | 40 ++++++++++++++++++++++++++++ pkg/hamdeck/hamdeck.go | 47 +++++++++++++++++++++++++++++++-- 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/pkg/hamdeck/config.go b/pkg/hamdeck/config.go index 2048fd0..a3ef500 100644 --- a/pkg/hamdeck/config.go +++ b/pkg/hamdeck/config.go @@ -13,12 +13,13 @@ import ( const ( ConfigDefaultFilename = "hamdeck.json" ConfigMainKey = "hamdeck" + ConfigConnections = "connections" ConfigStartPageID = "start_page" ConfigPages = "pages" ConfigButtons = "buttons" ConfigType = "type" ConfigIndex = "index" - ConfigConnections = "connections" + ConfigConnection = "connection" ) func (d *HamDeck) ReadConfig(r io.Reader) error { diff --git a/pkg/hamdeck/connections_test.go b/pkg/hamdeck/connections_test.go index 41c3663..a547144 100644 --- a/pkg/hamdeck/connections_test.go +++ b/pkg/hamdeck/connections_test.go @@ -33,3 +33,43 @@ func TestReadConfig_Connections(t *testing.T) { assert.False(t, ok) }) } + +func TestConnectionManager(t *testing.T) { + provider := &testConnectionProvider{ + name: "blah", + config: ConnectionConfig{ + "type": "test", + "some_config": "some_value", + }, + } + manager := NewConnectionManager[*testConnection]("test", provider, provider.CreateConnection) + + connection, err := manager.Get("blah") + assert.NoError(t, err) + assert.Equal(t, "some_value", connection.config["some_config"]) + + _, err = manager.Get("undefined") + assert.Error(t, err) +} + +type testConnection struct { + config ConnectionConfig +} + +type testConnectionProvider struct { + name string + config ConnectionConfig +} + +func (p *testConnectionProvider) GetConnection(name string, connectionType string) (ConnectionConfig, bool) { + if name != p.name || connectionType != "test" { + return ConnectionConfig{}, false + } + return p.config, true +} + +func (p *testConnectionProvider) CreateConnection(config ConnectionConfig) (*testConnection, error) { + return &testConnection{ + config: config, + }, nil +} diff --git a/pkg/hamdeck/hamdeck.go b/pkg/hamdeck/hamdeck.go index 21fb501..6dd29d3 100644 --- a/pkg/hamdeck/hamdeck.go +++ b/pkg/hamdeck/hamdeck.go @@ -89,8 +89,6 @@ type connectionKey struct { connectionType string } -type ConnectionConfig map[string]any - const legacyPageID = "" type HamDeck struct { @@ -314,3 +312,48 @@ func (h *LongpressHandler) Released() { } h.timer.Stop() } + +type ConnectionConfig map[string]any + +type ConnectionConfigProvider interface { + GetConnection(string, string) (ConnectionConfig, bool) +} + +type ConnectionFactory[T any] func(ConnectionConfig) (T, error) + +type ConnectionManager[T any] struct { + connectionType string + provider ConnectionConfigProvider + factory ConnectionFactory[T] + connections map[string]T +} + +func NewConnectionManager[T any](connectionType string, provider ConnectionConfigProvider, factory ConnectionFactory[T]) *ConnectionManager[T] { + return &ConnectionManager[T]{ + connectionType: connectionType, + provider: provider, + factory: factory, + connections: make(map[string]T), + } +} + +func (m *ConnectionManager[T]) Get(name string) (T, error) { + connection, ok := m.connections[name] + if ok { + return connection, nil + } + + config, ok := m.provider.GetConnection(name, m.connectionType) + if !ok { + return connection, fmt.Errorf("no %s connection defined with name %s", m.connectionType, name) + } + + connection, err := m.factory(config) + if err != nil { + return connection, err + } + + m.connections[name] = connection + + return connection, nil +} From 8804cead91d2a46d6331865846d7d347d67f656a Mon Sep 17 00:00:00 2001 From: Florian Thienel Date: Sun, 10 Dec 2023 14:27:45 +0100 Subject: [PATCH 5/9] allow mutliple hamlib connections --- cmd/root.go | 4 +- pkg/hamdeck/connections_test.go | 2 +- pkg/hamdeck/hamdeck.go | 38 +++++++++++-- pkg/hamlib/factory.go | 98 ++++++++++++++++++++++++++++----- 4 files changed, 118 insertions(+), 24 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 2968a21..6382462 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -96,9 +96,7 @@ func run(cmd *cobra.Command, args []string) { deck := hamdeck.New(device) deck.RegisterFactory(hamdeck.NewButtonFactory(deck)) deck.RegisterFactory(pulse.NewButtonFactory()) - if rootFlags.hamlibAddress != "" { - deck.RegisterFactory(hamlib.NewButtonFactory(rootFlags.hamlibAddress)) - } + deck.RegisterFactory(hamlib.NewButtonFactory(deck, rootFlags.hamlibAddress)) if rootFlags.tciAddress != "" { deck.RegisterFactory(tci.NewButtonFactory(rootFlags.tciAddress)) } diff --git a/pkg/hamdeck/connections_test.go b/pkg/hamdeck/connections_test.go index a547144..8b39bb0 100644 --- a/pkg/hamdeck/connections_test.go +++ b/pkg/hamdeck/connections_test.go @@ -68,7 +68,7 @@ func (p *testConnectionProvider) GetConnection(name string, connectionType strin return p.config, true } -func (p *testConnectionProvider) CreateConnection(config ConnectionConfig) (*testConnection, error) { +func (p *testConnectionProvider) CreateConnection(_ string, config ConnectionConfig) (*testConnection, error) { return &testConnection{ config: config, }, nil diff --git a/pkg/hamdeck/hamdeck.go b/pkg/hamdeck/hamdeck.go index 6dd29d3..47b8352 100644 --- a/pkg/hamdeck/hamdeck.go +++ b/pkg/hamdeck/hamdeck.go @@ -313,19 +313,23 @@ func (h *LongpressHandler) Released() { h.timer.Stop() } +const LegacyConnectionName = "" + type ConnectionConfig map[string]any type ConnectionConfigProvider interface { GetConnection(string, string) (ConnectionConfig, bool) } -type ConnectionFactory[T any] func(ConnectionConfig) (T, error) +type ConnectionFactory[T any] func(string, ConnectionConfig) (T, error) type ConnectionManager[T any] struct { - connectionType string - provider ConnectionConfigProvider - factory ConnectionFactory[T] - connections map[string]T + connectionType string + provider ConnectionConfigProvider + factory ConnectionFactory[T] + connections map[string]T + hasLegacy bool + legacyConnection T } func NewConnectionManager[T any](connectionType string, provider ConnectionConfigProvider, factory ConnectionFactory[T]) *ConnectionManager[T] { @@ -337,7 +341,20 @@ func NewConnectionManager[T any](connectionType string, provider ConnectionConfi } } +func (m *ConnectionManager[T]) SetLegacy(legacyConnection T) { + m.hasLegacy = true + m.legacyConnection = legacyConnection +} + func (m *ConnectionManager[T]) Get(name string) (T, error) { + var connection T + if name == LegacyConnectionName { + if !m.hasLegacy { + return connection, fmt.Errorf("no legacy %s connection defined", m.connectionType) + } + return m.legacyConnection, nil + } + connection, ok := m.connections[name] if ok { return connection, nil @@ -348,7 +365,7 @@ func (m *ConnectionManager[T]) Get(name string) (T, error) { return connection, fmt.Errorf("no %s connection defined with name %s", m.connectionType, name) } - connection, err := m.factory(config) + connection, err := m.factory(name, config) if err != nil { return connection, err } @@ -357,3 +374,12 @@ func (m *ConnectionManager[T]) Get(name string) (T, error) { return connection, nil } + +func (m *ConnectionManager[T]) ForEach(f func(T)) { + for _, connection := range m.connections { + f(connection) + } + if m.hasLegacy { + f(m.legacyConnection) + } +} diff --git a/pkg/hamlib/factory.go b/pkg/hamlib/factory.go index 3ecfc52..fe86cc2 100644 --- a/pkg/hamlib/factory.go +++ b/pkg/hamlib/factory.go @@ -1,6 +1,7 @@ package hamlib import ( + "fmt" "log" "github.com/ftl/rigproxy/pkg/client" @@ -9,6 +10,7 @@ import ( ) const ( + ConfigAddress = "address" ConfigCommand = "command" ConfigArgs = "args" ConfigMode = "mode" @@ -24,6 +26,7 @@ const ( ) const ( + ConnectionType = "hamlib" SetModeButtonType = "hamlib.SetMode" ToggleModeButtonType = "hamlib.ToggleMode" SetButtonType = "hamlib.Set" @@ -33,21 +36,39 @@ const ( SetVFOButtonType = "hamlib.SetVFO" ) -func NewButtonFactory(address string) *Factory { - client := NewClient(address) - client.KeepOpen() +func NewButtonFactory(provider hamdeck.ConnectionConfigProvider, legacyAddress string) *Factory { + result := &Factory{} + result.connections = hamdeck.NewConnectionManager(ConnectionType, provider, result.createHamlibClient) - return &Factory{ - client: client, + if legacyAddress != "" { + client := NewClient(legacyAddress) + client.KeepOpen() + result.connections.SetLegacy(client) } + + return result } type Factory struct { - client *HamlibClient + connections *hamdeck.ConnectionManager[*HamlibClient] +} + +func (f *Factory) createHamlibClient(name string, config hamdeck.ConnectionConfig) (*HamlibClient, error) { + address, ok := hamdeck.ToString(config[ConfigAddress]) + if !ok { + return nil, fmt.Errorf("no address defined for hamlib connection %s", name) + } + + client := NewClient(address) + client.KeepOpen() + + return client, nil } func (f *Factory) Close() { - f.client.Close() + f.connections.ForEach(func(client *HamlibClient) { + client.Close() + }) } func (f *Factory) CreateButton(config map[string]interface{}) hamdeck.Button { @@ -79,7 +100,14 @@ func (f *Factory) createSetModeButton(config map[string]interface{}) hamdeck.But return nil } - return NewSetModeButton(f.client, client.Mode(mode), label) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + hamlibClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create hamlib.SetMode button: %v", err) + return nil + } + + return NewSetModeButton(hamlibClient, client.Mode(mode), label) } func (f *Factory) createToggleModeButton(config map[string]interface{}) hamdeck.Button { @@ -92,7 +120,14 @@ func (f *Factory) createToggleModeButton(config map[string]interface{}) hamdeck. return nil } - return NewToggleModeButton(f.client, client.Mode(mode1), label1, client.Mode(mode2), label2) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + hamlibClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create hamlib.ToggleMode button: %v", err) + return nil + } + + return NewToggleModeButton(hamlibClient, client.Mode(mode1), label1, client.Mode(mode2), label2) } func (f *Factory) createSetButton(config map[string]interface{}) hamdeck.Button { @@ -104,7 +139,14 @@ func (f *Factory) createSetButton(config map[string]interface{}) hamdeck.Button return nil } - return NewSetButton(f.client, label, command, args...) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + hamlibClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create hamlib.Set button: %v", err) + return nil + } + + return NewSetButton(hamlibClient, label, command, args...) } func (f *Factory) createSwitchToBandButton(config map[string]interface{}) hamdeck.Button { @@ -116,7 +158,14 @@ func (f *Factory) createSwitchToBandButton(config map[string]interface{}) hamdec return nil } - return NewSwitchToBandButton(f.client, label, band, useUpDown) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + hamlibClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create hamlib.SwitchToBand button: %v", err) + return nil + } + + return NewSwitchToBandButton(hamlibClient, label, band, useUpDown) } func (f *Factory) createSetPowerLevelButton(config map[string]interface{}) hamdeck.Button { @@ -127,13 +176,27 @@ func (f *Factory) createSetPowerLevelButton(config map[string]interface{}) hamde return nil } - return NewSetPowerLevelButton(f.client, label, value) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + hamlibClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create hamlib.SetPowerLevel button: %v", err) + return nil + } + + return NewSetPowerLevelButton(hamlibClient, label, value) } func (f *Factory) createMOXButton(config map[string]interface{}) hamdeck.Button { label, _ := hamdeck.ToString(config[ConfigLabel]) - return NewMOXButton(f.client, label) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + hamlibClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create hamlib.MOX button: %v", err) + return nil + } + + return NewMOXButton(hamlibClient, label) } func (f *Factory) createSetVFOButton(config map[string]interface{}) hamdeck.Button { @@ -144,5 +207,12 @@ func (f *Factory) createSetVFOButton(config map[string]interface{}) hamdeck.Butt return nil } - return NewSetVFOButton(f.client, label, client.VFO(vfo)) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + hamlibClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create hamlib.SetVFO button: %v", err) + return nil + } + + return NewSetVFOButton(hamlibClient, label, client.VFO(vfo)) } From 00c9f4b5b858763f2647fbee97747677c32f230e Mon Sep 17 00:00:00 2001 From: Florian Thienel Date: Sun, 10 Dec 2023 14:48:32 +0100 Subject: [PATCH 6/9] configure two hamlib connections in the example --- example_conf.json | 140 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 109 insertions(+), 31 deletions(-) diff --git a/example_conf.json b/example_conf.json index 4ccf6d0..efcbb00 100644 --- a/example_conf.json +++ b/example_conf.json @@ -34,114 +34,182 @@ { "type": "hamlib.SwitchToBand", "index": 0, - "band": "80m" + "band": "80m", + "connection": "hamlib1" }, { "type": "hamlib.SwitchToBand", "index": 1, - "band": "40m" + "band": "40m", + "connection": "hamlib1" }, { "type": "hamlib.SwitchToBand", "index": 2, - "band": "30m" + "band": "30m", + "connection": "hamlib1" }, { "type": "hamlib.SwitchToBand", "index": 3, - "band": "20m" + "band": "20m", + "connection": "hamlib1" }, { "type": "hamlib.SwitchToBand", "index": 4, - "band": "17m" + "band": "17m", + "connection": "hamlib1" }, { "type": "hamlib.SwitchToBand", "index": 5, - "band": "15m" + "band": "15m", + "connection": "hamlib1" }, { "type": "hamlib.SwitchToBand", "index": 6, - "band": "12m" + "band": "12m", + "connection": "hamlib1" }, { "type": "hamlib.SwitchToBand", "index": 7, - "band": "10m" + "band": "10m", + "connection": "hamlib1" }, { - "type": "hamlib.SetMode", + "type": "hamlib.SwitchToBand", "index": 8, - "mode": "CW" + "band": "80m", + "connection": "hamlib2" }, { - "type": "hamlib.SetMode", + "type": "hamlib.SwitchToBand", "index": 9, + "band": "40m", + "connection": "hamlib2" + }, + { + "type": "hamlib.SwitchToBand", + "index": 10, + "band": "30m", + "connection": "hamlib2" + }, + { + "type": "hamlib.SwitchToBand", + "index": 11, + "band": "20m", + "connection": "hamlib2" + }, + { + "type": "hamlib.SwitchToBand", + "index": 12, + "band": "17m", + "connection": "hamlib2" + }, + { + "type": "hamlib.SwitchToBand", + "index": 13, + "band": "15m", + "connection": "hamlib2" + }, + { + "type": "hamlib.SwitchToBand", + "index": 14, + "band": "12m", + "connection": "hamlib2" + }, + { + "type": "hamlib.SwitchToBand", + "index": 15, + "band": "10m", + "connection": "hamlib2" + }, + { + "type": "hamlib.SetMode", + "index": 16, + "mode": "CW", + "connection": "hamlib1" + }, + { + "type": "hamlib.SetMode", + "index": 17, "mode": "PKTUSB", - "label": "Data" + "label": "Data", + "connection": "hamlib1" }, { "type": "hamlib.ToggleMode", - "index": 10, + "index": 18, "mode1": "LSB", - "mode2": "USB" + "mode2": "USB", + "connection": "hamlib1" }, { "type": "hamlib.Set", - "index": 11, + "index": 19, "label": "Bandâ–¾", "command": "vfo_op", - "args": ["BAND_DOWN"] + "args": ["BAND_DOWN"], + "connection": "hamlib1" }, { "type": "hamlib.Set", - "index": 12, + "index": 20, "label": "Bandâ–´", "command": "vfo_op", - "args": ["BAND_UP"] + "args": ["BAND_UP"], + "connection": "hamlib1" }, { "type": "hamlib.SetPowerLevel", - "index": 13, + "index": 21, "label": "10W", - "value": 0.1 + "value": 0.1, + "connection": "hamlib1" }, { "type": "hamlib.SetPowerLevel", - "index": 14, + "index": 22, "label": "50W", - "value": 0.5 + "value": 0.5, + "connection": "hamlib1" }, { "type": "hamlib.SetPowerLevel", - "index": 15, + "index": 23, "label": "100W", - "value": 1.0 + "value": 1.0, + "connection": "hamlib1" }, { "type": "hamlib.SetVFO", - "index": 16, + "index": 24, "label": "VFO A", - "vfo": "VFOA" + "vfo": "VFOA", + "connection": "hamlib1" }, { "type": "hamlib.SetVFO", - "index": 17, + "index": 25, "label": "VFO B", - "vfo": "VFOB" + "vfo": "VFOB", + "connection": "hamlib1" }, { "type": "hamlib.Set", - "index": 18, + "index": 26, "label": "A=B", "command": "vfo_op", - "args": ["CPY"] + "args": ["CPY"], + "connection": "hamlib1" }, { "type": "hamlib.MOX", - "index": 20 + "index": 27, + "connection": "hamlib1" }, { "type": "hamdeck.Page", @@ -271,5 +339,15 @@ } ] } + }, + "connections": { + "hamlib1": { + "type": "hamlib", + "address": "localhost:4533" + }, + "hamlib2": { + "type": "hamlib", + "address": "localhost:4534" + } } } From d69c5aa05ac37cdc6721262f070f0029d11f058e Mon Sep 17 00:00:00 2001 From: Florian Thienel Date: Sun, 10 Dec 2023 16:21:54 +0100 Subject: [PATCH 7/9] bump rigproxy --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2a3afb2..7461522 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/eclipse/paho.mqtt.golang v1.4.3 github.com/fogleman/gg v1.3.0 github.com/ftl/hamradio v0.2.7 - github.com/ftl/rigproxy v0.2.4 + github.com/ftl/rigproxy v0.2.5 github.com/ftl/tci v0.3.2 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/jfreymuth/pulse v0.1.0 diff --git a/go.sum b/go.sum index c32c5d8..58cad62 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/ftl/hamradio v0.2.7 h1:AXg5JI6oPaSvO/YfM7kwAweVP49EF/zaEBiY+cxYDMI= github.com/ftl/hamradio v0.2.7/go.mod h1:BvA+ni3sOKmrIJpLt6f2sYK9vc3VfihZm4x0h8kzOPw= -github.com/ftl/rigproxy v0.2.4 h1:8g9zfYX049Wd0fa1J6WujQ4wMPiAquXO286SVKNf57k= -github.com/ftl/rigproxy v0.2.4/go.mod h1:PrBUiqLwu/6zL44+uOz4lgmOfnis4FIvJDhxDNXoi60= +github.com/ftl/rigproxy v0.2.5 h1:q5b734nvJia/yNzrVOudsMkPJ58QwNwW2FD9r2QxepA= +github.com/ftl/rigproxy v0.2.5/go.mod h1:PrBUiqLwu/6zL44+uOz4lgmOfnis4FIvJDhxDNXoi60= github.com/ftl/tci v0.3.2 h1:1Qdgprldiv7/DQvuK96OHMVqb+SDunqbxTHDcWsE5Tk= github.com/ftl/tci v0.3.2/go.mod h1:3B8x8FI/kBbUwbWnz725tTiiiNfJpIL9cQ67TnjW3aU= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= From 9b5873726baf17f6b04467c6d992f2f0cc98468d Mon Sep 17 00:00:00 2001 From: Florian Thienel Date: Sun, 10 Dec 2023 16:35:15 +0100 Subject: [PATCH 8/9] allow multiple tci connections --- cmd/root.go | 4 +- pkg/tci/factory.go | 133 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 113 insertions(+), 24 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 6382462..ea1b3a7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -97,9 +97,7 @@ func run(cmd *cobra.Command, args []string) { deck.RegisterFactory(hamdeck.NewButtonFactory(deck)) deck.RegisterFactory(pulse.NewButtonFactory()) deck.RegisterFactory(hamlib.NewButtonFactory(deck, rootFlags.hamlibAddress)) - if rootFlags.tciAddress != "" { - deck.RegisterFactory(tci.NewButtonFactory(rootFlags.tciAddress)) - } + deck.RegisterFactory(tci.NewButtonFactory(deck, rootFlags.tciAddress)) if rootFlags.mqttAddress != "" { deck.RegisterFactory(mqtt.NewButtonFactory(rootFlags.mqttAddress, rootFlags.mqttUsername, rootFlags.mqttPassword)) } diff --git a/pkg/tci/factory.go b/pkg/tci/factory.go index 3a54866..56f1a53 100644 --- a/pkg/tci/factory.go +++ b/pkg/tci/factory.go @@ -13,6 +13,7 @@ import ( ) const ( + ConfigAddress = "address" ConfigCommand = "command" ConfigArgs = "args" ConfigMode = "mode" @@ -30,6 +31,7 @@ const ( ) const ( + ConnectionType = "tci" SetModeButtonType = "tci.SetMode" ToggleModeButtonType = "tci.ToggleMode" SetFilterButtonType = "tci.SetFilter" @@ -42,28 +44,46 @@ const ( SwitchToBandButtonType = "tci.SwitchToBand" ) -func NewButtonFactory(address string) *Factory { - host, err := parseTCPAddr(address) - if err != nil { - return &Factory{} +func NewButtonFactory(provider hamdeck.ConnectionConfigProvider, legacyAddress string) *Factory { + result := &Factory{} + result.connections = hamdeck.NewConnectionManager(ConnectionType, provider, result.createTCIClient) + + if legacyAddress != "" { + host, err := parseTCPAddr(legacyAddress) + if err == nil { + result.connections.SetLegacy(NewClient(host)) + } } - return &Factory{client: NewClient(host)} + return result } type Factory struct { - client *Client + connections *hamdeck.ConnectionManager[*Client] +} + +func (f *Factory) createTCIClient(name string, config hamdeck.ConnectionConfig) (*Client, error) { + address, ok := hamdeck.ToString(config[ConfigAddress]) + if !ok { + return nil, fmt.Errorf("no address defined for tci connection %s", name) + } + + host, err := parseTCPAddr(address) + if err != nil { + return nil, err + } + client := NewClient(host) + + return client, nil } func (f *Factory) Close() { - f.client.Disconnect() + f.connections.ForEach(func(client *Client) { + client.Disconnect() + }) } func (f *Factory) CreateButton(config map[string]interface{}) hamdeck.Button { - if f.client == nil { - return nil - } - switch config[hamdeck.ConfigType] { case SetModeButtonType: return f.createSetModeButton(config) @@ -101,7 +121,14 @@ func (f *Factory) createSetModeButton(config map[string]interface{}) hamdeck.But return nil } - return NewSetModeButton(f.client, client.Mode(mode), label) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + tciClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create tci.SetMode button: %v", err) + return nil + } + + return NewSetModeButton(tciClient, client.Mode(mode), label) } func (f *Factory) createToggleModeButton(config map[string]interface{}) hamdeck.Button { @@ -118,7 +145,14 @@ func (f *Factory) createToggleModeButton(config map[string]interface{}) hamdeck. return nil } - return NewToggleModeButton(f.client, client.Mode(strings.ToLower(mode1)), label1, client.Mode(strings.ToLower(mode2)), label2) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + tciClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create tci.ToggleMode button: %v", err) + return nil + } + + return NewToggleModeButton(tciClient, client.Mode(strings.ToLower(mode1)), label1, client.Mode(strings.ToLower(mode2)), label2) } func (f *Factory) createSetFilterButton(config map[string]interface{}) hamdeck.Button { @@ -143,25 +177,54 @@ func (f *Factory) createSetFilterButton(config map[string]interface{}) hamdeck.B if !haveIcon { icon = "filter" } - return NewSetFilterButton(f.client, bottomFrequency, topFrequency, client.Mode(mode), label, icon) + + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + tciClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create tci.SetFilter button: %v", err) + return nil + } + + return NewSetFilterButton(tciClient, bottomFrequency, topFrequency, client.Mode(mode), label, icon) } func (f *Factory) createMOXButton(config map[string]interface{}) hamdeck.Button { label, _ := hamdeck.ToString(config[ConfigLabel]) - return NewMOXButton(f.client, label) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + tciClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create tci.MOX button: %v", err) + return nil + } + + return NewMOXButton(tciClient, label) } func (f *Factory) createTuneButton(config map[string]interface{}) hamdeck.Button { label, _ := hamdeck.ToString(config[ConfigLabel]) - return NewTuneButton(f.client, label) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + tciClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create tci.Tune button: %v", err) + return nil + } + + return NewTuneButton(tciClient, label) } func (f *Factory) createMuteButton(config map[string]interface{}) hamdeck.Button { label, _ := hamdeck.ToString(config[ConfigLabel]) - return NewMuteButton(f.client, label) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + tciClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create tci.Mute button: %v", err) + return nil + } + + return NewMuteButton(tciClient, label) } func (f *Factory) createSetDriveButton(config map[string]interface{}) hamdeck.Button { @@ -172,7 +235,14 @@ func (f *Factory) createSetDriveButton(config map[string]interface{}) hamdeck.Bu return nil } - return NewSetDriveButton(f.client, label, value) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + tciClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create tci.SetDrive button: %v", err) + return nil + } + + return NewSetDriveButton(tciClient, label, value) } func (f *Factory) createIncrementDriveButton(config map[string]interface{}) hamdeck.Button { @@ -183,7 +253,14 @@ func (f *Factory) createIncrementDriveButton(config map[string]interface{}) hamd return nil } - return NewIncrementDriveButton(f.client, label, increment) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + tciClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create tci.IncrementDrive button: %v", err) + return nil + } + + return NewIncrementDriveButton(tciClient, label, increment) } func (f *Factory) createIncrementVolumeButton(config map[string]interface{}) hamdeck.Button { @@ -194,7 +271,14 @@ func (f *Factory) createIncrementVolumeButton(config map[string]interface{}) ham return nil } - return NewIncrementVolumeButton(f.client, label, increment) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + tciClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create tci.IncrementVolume button: %v", err) + return nil + } + + return NewIncrementVolumeButton(tciClient, label, increment) } func (f *Factory) createSwitchToBandButton(config map[string]interface{}) hamdeck.Button { @@ -205,7 +289,14 @@ func (f *Factory) createSwitchToBandButton(config map[string]interface{}) hamdec return nil } - return NewSwitchToBandButton(f.client, label, band) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + tciClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create tci.SwitchToBand button: %v", err) + return nil + } + + return NewSwitchToBandButton(tciClient, label, band) } func parseTCPAddr(arg string) (*net.TCPAddr, error) { From 6acc4e1eea6b11293d6df4ee02b048611ecd44b7 Mon Sep 17 00:00:00 2001 From: Florian Thienel Date: Sun, 10 Dec 2023 16:43:08 +0100 Subject: [PATCH 9/9] allow multiple mqtt connections --- cmd/root.go | 4 +--- pkg/mqtt/factory.go | 53 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index ea1b3a7..d46601a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -98,9 +98,7 @@ func run(cmd *cobra.Command, args []string) { deck.RegisterFactory(pulse.NewButtonFactory()) deck.RegisterFactory(hamlib.NewButtonFactory(deck, rootFlags.hamlibAddress)) deck.RegisterFactory(tci.NewButtonFactory(deck, rootFlags.tciAddress)) - if rootFlags.mqttAddress != "" { - deck.RegisterFactory(mqtt.NewButtonFactory(rootFlags.mqttAddress, rootFlags.mqttUsername, rootFlags.mqttPassword)) - } + deck.RegisterFactory(mqtt.NewButtonFactory(deck, rootFlags.mqttAddress, rootFlags.mqttUsername, rootFlags.mqttPassword)) err = configureHamDeck(deck, rootFlags.configFile) if err != nil { diff --git a/pkg/mqtt/factory.go b/pkg/mqtt/factory.go index 7368564..8fb81b0 100644 --- a/pkg/mqtt/factory.go +++ b/pkg/mqtt/factory.go @@ -1,6 +1,7 @@ package mqtt import ( + "fmt" "log" "strings" @@ -8,6 +9,9 @@ import ( ) const ( + ConfigAddress = "address" + ConfigUsername = "username" + ConfigPassword = "password" ConfigLabel = "label" ConfigPath = "path" ConfigInputTopic = "inputTopic" @@ -18,24 +22,43 @@ const ( ) const ( + ConnectionType = "mqtt" TuneButtonType = "mqtt.AT100Tune" SwitchButtonType = "mqtt.Switch" ) -func NewButtonFactory(address string, username string, password string) *Factory { - client := NewClient(address, username, password) +func NewButtonFactory(provider hamdeck.ConnectionConfigProvider, legacyAddress string, username string, password string) *Factory { + result := &Factory{} + result.connections = hamdeck.NewConnectionManager(ConnectionType, provider, result.createMQTTClient) - return &Factory{ - client: client, + if legacyAddress != "" { + result.connections.SetLegacy(NewClient(legacyAddress, username, password)) } + + return result } type Factory struct { - client *Client + connections *hamdeck.ConnectionManager[*Client] +} + +func (f *Factory) createMQTTClient(name string, config hamdeck.ConnectionConfig) (*Client, error) { + address, ok := hamdeck.ToString(config[ConfigAddress]) + if !ok { + return nil, fmt.Errorf("no address defined for mqtt connection %s", name) + } + username, _ := hamdeck.ToString(config[ConfigUsername]) + password, _ := hamdeck.ToString(config[ConfigPassword]) + + client := NewClient(address, username, password) + + return client, nil } func (f *Factory) Close() { - f.client.Disconnect() + f.connections.ForEach(func(client *Client) { + client.Disconnect() + }) } func (f *Factory) CreateButton(config map[string]interface{}) hamdeck.Button { @@ -58,7 +81,14 @@ func (f *Factory) createTuneButton(config map[string]interface{}) hamdeck.Button return nil } - return NewTuneButton(f.client, label, path) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + mqttClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create mqtt.ATU100Tune button: %v", err) + return nil + } + + return NewTuneButton(mqttClient, label, path) } func (f *Factory) createSwitchButton(config map[string]interface{}) hamdeck.Button { @@ -74,5 +104,12 @@ func (f *Factory) createSwitchButton(config map[string]interface{}) hamdeck.Butt return nil } - return NewSwitchButton(f.client, label, inputTopic, outputTopic, onPayload, offPayload, SwitchMode(strings.TrimSpace(strings.ToUpper(mode)))) + connection, _ := hamdeck.ToString(config[hamdeck.ConfigConnection]) + mqttClient, err := f.connections.Get(connection) + if err != nil { + log.Printf("Cannot create mqtt.Switch button: %v", err) + return nil + } + + return NewSwitchButton(mqttClient, label, inputTopic, outputTopic, onPayload, offPayload, SwitchMode(strings.TrimSpace(strings.ToUpper(mode)))) }