Skip to content

Commit

Permalink
Refactor ISY99x binding, continued ...
Browse files Browse the repository at this point in the history
Bug fixes
  • Loading branch information
hspaay committed Feb 18, 2024
1 parent 4c82d8a commit 0624944
Show file tree
Hide file tree
Showing 15 changed files with 417 additions and 295 deletions.
7 changes: 5 additions & 2 deletions bindings/isy99x/Isy99x_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func TestMain(m *testing.M) {

os.Exit(result)
}

func TestStartStop(t *testing.T) {
os.Remove(nodesFile)

Expand All @@ -72,9 +73,11 @@ func TestStartStop(t *testing.T) {
err = svc.Start(hc)

err = svc.IsyGW.ReadIsyThings()
assert.NoError(t, err)
require.NoError(t, err)

time.Sleep(time.Second)
devices := svc.IsyGW.GetIsyThings()
assert.True(t, len(devices) > 5, "Expected 6 ISY nodes. Got fewer.")

svc.Stop()
time.Sleep(time.Millisecond)
Expand All @@ -91,7 +94,7 @@ func TestBadAddress(t *testing.T) {
badConfig.IsyAddress = "localhost"
svc := service.NewIsyBinding(&badConfig)
err = svc.Start(hc)

assert.NoError(t, err)
err = svc.IsyGW.ReadIsyThings()
assert.Error(t, err)
time.Sleep(time.Millisecond * 100)
Expand Down
1 change: 1 addition & 0 deletions bindings/isy99x/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

// Start the ISY99x protocol binding
func main() {

env := plugin.GetAppEnvironment("", true)
cfg := config.NewIsy99xConfig()
_ = env.LoadConfig(&cfg)
Expand Down
2 changes: 1 addition & 1 deletion bindings/isy99x/config/Isy99xConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type Isy99xConfig struct {

func NewIsy99xConfig() *Isy99xConfig {
cfg := &Isy99xConfig{
IsyAddress: "127.0.0.1",
IsyAddress: "", // use auto config
LoginName: "",
Password: "",
LogLevel: "warn",
Expand Down
48 changes: 32 additions & 16 deletions bindings/isy99x/service/IsyBinding.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ func (svc *IsyBinding) handleActionRequest(tv *things.ThingValue) (reply []byte,
slog.String("name", tv.Name),
slog.String("senderID", tv.SenderID))

if !svc.ic.IsConnected() {
return nil, fmt.Errorf("No connection with the gateway")
}
isyThing := svc.IsyGW.GetIsyThing(tv.ThingID)
if isyThing == nil {
err = fmt.Errorf("handleActionRequest: thing '%s' not found", tv.ThingID)
Expand All @@ -72,6 +75,17 @@ func (svc *IsyBinding) handleConfigRequest(tv *things.ThingValue) (err error) {
slog.String("name", tv.Name),
slog.String("senderID", tv.SenderID))

// configuring the binding doesn't require a connection with the gateway
if tv.ThingID == svc.thingID {
err = svc.HandleBindingConfig(tv)
return err
}

if !svc.ic.IsConnected() {
return fmt.Errorf("No connection with the gateway")
}

// pass request to the Thing
isyThing := svc.IsyGW.GetIsyThing(tv.ThingID)
if isyThing == nil {
err = fmt.Errorf("handleActionRequest: thing '%s' not found", tv.ThingID)
Expand All @@ -82,7 +96,10 @@ func (svc *IsyBinding) handleConfigRequest(tv *things.ThingValue) (err error) {
return err
}

// Start the ISY99x protocol binding
// Start the ISY99x protocol binding.
// Connection to the gateway will be made during the heartbeat.
// If no connection can be made the heartbeat will retry periodically until stopped.
//
// This publishes a TD for this binding, starts a background polling heartbeat.
func (svc *IsyBinding) Start(hc *hubclient.HubClient) (err error) {
slog.Warn("Starting Isy99x binding")
Expand All @@ -93,27 +110,26 @@ func (svc *IsyBinding) Start(hc *hubclient.HubClient) (err error) {
}
svc.prodMap, err = LoadProductMapCSV("")

// 'IsyThings' use the 'isy connection' to talk to the gateway
//// 'IsyThings' use the 'isy connection' to talk to the gateway
svc.ic = NewIsyConnection()
err = svc.ic.Connect(svc.config.IsyAddress, svc.config.LoginName, svc.config.Password)
if err != nil {
// gateway not found
return err
}
// The binding manages the gateway instance while the gateway instance manages
// the nodes connected to the gateway device.
svc.IsyGW = NewIsyGateway(svc.prodMap)
svc.IsyGW.Init(svc.ic, svc.ic.GetID(), InsteonProduct{}, "")
_ = svc.ic.Connect(svc.config.IsyAddress, svc.config.LoginName, svc.config.Password)
svc.IsyGW.Init(svc.ic)

//err = svc.ic.Connect(svc.config.IsyAddress, svc.config.LoginName, svc.config.Password)
//if err != nil {
// // gateway not found
// return err
//}
//// The binding manages the gateway instance while the gateway instance manages
//// the nodes connected to the gateway device.
//svc.IsyGW = NewIsyGateway(svc.prodMap)
//// need a connection to get the device ID
//svc.IsyGW.Init(svc.ic, svc.ic.GetID(), InsteonProduct{}, "")

// subscribe to action requests
svc.hc.SetActionHandler(svc.handleActionRequest)
if err != nil {
return err
}
svc.hc.SetConfigHandler(svc.handleConfigRequest)
if err != nil {
return err
}

// last, start polling heartbeat
svc.stopHeartbeatFn = svc.startHeartbeat()
Expand Down
46 changes: 43 additions & 3 deletions bindings/isy99x/service/IsyBindingThing.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package service

import (
"fmt"
"github.com/hiveot/hub/lib/things"
"github.com/hiveot/hub/lib/vocab"
)
Expand All @@ -12,14 +13,25 @@ import (
func (svc *IsyBinding) GetTD() *things.TD {
td := things.NewTD(svc.thingID, "ISY99x binding", vocab.DeviceTypeBinding)

// binding attributes
prop := td.AddProperty("connectionStatus", "",
"Connection Status", vocab.WoTDataTypeString)
prop.Description = "Whether the Binding has a connection to an ISY gateway"
prop = td.AddProperty(vocab.VocabManufacturer, vocab.VocabManufacturer,
"Manufacturer", vocab.WoTDataTypeString)
prop.Description = "Developer of the binding"

// TODO: persist configuration
//binding config
prop := td.AddProperty(vocab.VocabPollInterval, vocab.VocabPollInterval,
"Gateway data polling interval", vocab.WoTDataTypeInteger)
prop = td.AddProperty(vocab.VocabPollInterval, vocab.VocabPollInterval,
"Poll Interval", vocab.WoTDataTypeInteger)
prop.Description = "Interval the binding polls the gateway for data value updates."
prop.Unit = vocab.UnitNameSecond
prop.ReadOnly = false

prop = td.AddProperty(vocab.VocabGatewayAddress, vocab.VocabGatewayAddress,
"OWServer gateway IP address; empty to auto discover.", vocab.WoTDataTypeString)
"Gateway Network Address", vocab.WoTDataTypeString)
prop.Description = "ISY 99x gateway IP address; empty to auto discover."
prop.ReadOnly = false

// gateway events
Expand All @@ -30,6 +42,34 @@ func (svc *IsyBinding) GetTD() *things.TD {
return td
}

// GetPropValues returns the property values to publish
func (svc *IsyBinding) GetPropValues(onlyChanges bool) map[string]string {
props := make(map[string]string)
props[vocab.VocabPollInterval] = fmt.Sprintf("%d", svc.config.PollInterval)
props[vocab.VocabGatewayAddress] = svc.config.IsyAddress
props[vocab.VocabManufacturer] = "Hive Of Things"
connStatus := "disconnnected"
if svc.ic.IsConnected() {
connStatus = "connected"
}
props["connectionStatus"] = connStatus
return props
}

// HandleBindingConfig configures the binding.
func (svc *IsyBinding) HandleBindingConfig(tv *things.ThingValue) error {
err := fmt.Errorf("unknown configuration request '%s' from '%s'", tv.Name, tv.SenderID)
switch tv.Name {
case vocab.VocabGatewayAddress:
svc.config.IsyAddress = string(tv.Data)
err = svc.ic.Connect(svc.config.IsyAddress, svc.config.LoginName, svc.config.Password)
if err == nil {
svc.IsyGW.Init(svc.ic)
}
}
return err
}

//// GetProps returns the property values of the binding Thing
//func (svc *IsyBinding) GetProps(onlyChanges bool) map[string]string {
// props := make(map[string]string)
Expand Down
34 changes: 27 additions & 7 deletions bindings/isy99x/service/IsyConnection.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"os"
"path"
"strings"
"sync"
"sync/atomic"
"time"
)

Expand Down Expand Up @@ -54,34 +56,47 @@ type IsyConnection struct {
password string // Basic Auth password
//simulation map[string]string // map used when in simulation

id string
isConnected atomic.Bool

// the ISY connect gateway device config, if isConnected is true
isyConfig ISYConfiguration

// mutex to protect reconnection
mux sync.RWMutex
}

// Connect to the gateway and read its make and model
// Connect to the gateway and read its ID
func (ic *IsyConnection) Connect(
gwAddr string, login string, password string) (err error) {

if gwAddr == "" {
gwAddr, err = ic.Discover(time.Second * 3)
if err != nil {
ic.isConnected.Store(false)
return err
}
}
ic.mux.Lock()
defer ic.mux.Unlock()

ic.address = gwAddr
ic.login = login
ic.password = password

// get the ISY gateway ID and test if it is reachable
cfg := ISYConfiguration{}
err = ic.SendRequest("GET", "/rest/config", &cfg)
ic.id = cfg.App
//ic.id = cfg.Product.ID
err = ic.SendRequest("GET", "/rest/config", &ic.isyConfig)
if err != nil {
// connection failed
ic.isConnected.Store(false)
}
ic.isConnected.Store(true)

// TODO: websocket connect
return err
}

func (ic *IsyConnection) Disconnect() {
ic.isConnected.Store(false)
// TODO: websocket disconnect
}

Expand Down Expand Up @@ -113,7 +128,12 @@ func (ic *IsyConnection) Discover(timeout time.Duration) (addr string, err error

// GetID returns the ISY Gateway ID
func (ic *IsyConnection) GetID() string {
return ic.id
// Use the 'app' field as the gateway ID
return ic.isyConfig.App
}

func (ic *IsyConnection) IsConnected() bool {
return ic.isConnected.Load()
}

// ReadNodes reads the ISY Node list
Expand Down
Loading

0 comments on commit 0624944

Please sign in to comment.