Skip to content

Commit

Permalink
Import old device discovery capability and update.
Browse files Browse the repository at this point in the history
  • Loading branch information
pwood committed Dec 31, 2023
1 parent 6155bdc commit a6589cd
Show file tree
Hide file tree
Showing 6 changed files with 353 additions and 7 deletions.
File renamed without changes.
17 changes: 17 additions & 0 deletions da_events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package zda

import "context"

type eventSender interface {
sendEvent(event interface{})
}

func (g *gateway) sendEvent(event interface{}) {
//TODO implement me
panic("implement me")
}

func (g *gateway) ReadEvent(_ context.Context) (interface{}, error) {
//TODO implement me
panic("implement me")
}
11 changes: 11 additions & 0 deletions da_events_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package zda

import "github.com/stretchr/testify/mock"

type mockEventSender struct {
mock.Mock
}

func (m *mockEventSender) sendEvent(event interface{}) {
m.Called(event)
}
95 changes: 95 additions & 0 deletions device_discovery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package zda

import (
"context"
"github.com/shimmeringbee/da"
"github.com/shimmeringbee/da/capabilities"
"github.com/shimmeringbee/logwrap"
"github.com/shimmeringbee/zigbee"
"time"
)

type deviceDiscovery struct {
gateway da.Gateway
networkJoining zigbee.NetworkJoining
eventSender eventSender

discovering bool
allowTimer *time.Timer
allowExpiresAt time.Time

logger logwrap.Logger
}

func (d *deviceDiscovery) Capability() da.Capability {
return capabilities.DeviceDiscoveryFlag
}

func (d *deviceDiscovery) Name() string {
return capabilities.StandardNames[d.Capability()]
}

func (d *deviceDiscovery) Enable(ctx context.Context, duration time.Duration) error {
d.logger.LogInfo(ctx, "Invoking PermitJoin on Zigbee provider.", logwrap.Datum("Duration", duration))
if err := d.networkJoining.PermitJoin(ctx, true); err != nil {
d.logger.LogError(ctx, "Failed to PermitJoin on Zigbee provider.", logwrap.Err(err))
return err
}

if d.allowTimer != nil {
d.allowTimer.Stop()
}

d.allowExpiresAt = time.Now().Add(duration)
d.allowTimer = time.AfterFunc(duration, func() {
cctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

if err := d.Disable(cctx); err != nil {
d.logger.LogError(cctx, "Automatic timed DenyJoin failed.", logwrap.Err(err))
}
})

d.discovering = true

d.eventSender.sendEvent(capabilities.DeviceDiscoveryEnabled{
Gateway: d.gateway,
Duration: duration,
})
return nil
}

func (d *deviceDiscovery) Disable(ctx context.Context) error {
d.logger.LogInfo(ctx, "Invoking DenyJoin on Zigbee provider.")
if err := d.networkJoining.DenyJoin(ctx); err != nil {
d.logger.LogError(ctx, "Failed to DenyJoin on Zigbee provider.", logwrap.Err(err))
return err
}

d.discovering = false
d.allowTimer = nil
d.allowExpiresAt = time.Time{}

d.eventSender.sendEvent(capabilities.DeviceDiscoveryDisabled{
Gateway: d.gateway,
})
return nil
}

func (d *deviceDiscovery) Status(ctx context.Context) (capabilities.DeviceDiscoveryStatus, error) {
remainingDuration := d.allowExpiresAt.Sub(time.Now())
if remainingDuration < 0 {
remainingDuration = 0
}

return capabilities.DeviceDiscoveryStatus{Discovering: d.discovering, RemainingDuration: remainingDuration}, nil
}

func (d *deviceDiscovery) Stop() {
if d.allowTimer != nil {
d.allowTimer.Stop()
}
}

var _ capabilities.DeviceDiscovery = (*deviceDiscovery)(nil)
var _ da.BasicCapability = (*deviceDiscovery)(nil)
216 changes: 216 additions & 0 deletions device_discovery_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package zda

import (
"context"
"errors"
"github.com/shimmeringbee/da/capabilities"
"github.com/shimmeringbee/logwrap"
"github.com/shimmeringbee/logwrap/impl/discard"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"testing"
"time"
)

type mockNetworkJoining struct {
mock.Mock
}

func (m *mockNetworkJoining) PermitJoin(ctx context.Context, allRouters bool) error {
args := m.Called(ctx, allRouters)
return args.Error(0)
}

func (m *mockNetworkJoining) DenyJoin(ctx context.Context) error {
args := m.Called(ctx)
return args.Error(0)
}

func TestZigbeeDeviceDiscovery_Enable(t *testing.T) {
t.Run("calling enable on device which is self causes AllowJoin of zigbee provider", func(t *testing.T) {
mockEventSender := mockEventSender{}
mockEventSender.On("sendEvent", mock.IsType(capabilities.DeviceDiscoveryEnabled{}))

mockNetworkJoining := mockNetworkJoining{}
mockNetworkJoining.On("PermitJoin", mock.Anything, true).Return(nil)

zdd := deviceDiscovery{
eventSender: &mockEventSender,
networkJoining: &mockNetworkJoining,
logger: logwrap.New(discard.Discard()),
}
defer zdd.Stop()

err := zdd.Enable(context.Background(), 500*time.Millisecond)
assert.NoError(t, err)

status, err := zdd.Status(context.Background())
assert.NoError(t, err)
assert.True(t, status.Discovering)

mockEventSender.AssertExpectations(t)
mockNetworkJoining.AssertExpectations(t)
})

t.Run("calling enable on device which is self causes AllowJoin of zigbee provider, and forwards an error", func(t *testing.T) {
mockEventSender := mockEventSender{}

expectedError := errors.New("error")
mockNetworkJoining := mockNetworkJoining{}
mockNetworkJoining.On("PermitJoin", mock.Anything, true).Return(expectedError)

zdd := deviceDiscovery{
eventSender: &mockEventSender,
networkJoining: &mockNetworkJoining,
logger: logwrap.New(discard.Discard()),
}
defer zdd.Stop()

err := zdd.Enable(context.Background(), 500*time.Millisecond)
assert.Error(t, err)
assert.Equal(t, expectedError, err)

status, err := zdd.Status(context.Background())
assert.NoError(t, err)
assert.False(t, status.Discovering)

mockEventSender.AssertExpectations(t)
mockNetworkJoining.AssertExpectations(t)
})
}

func TestZigbeeDeviceDiscovery_Disable(t *testing.T) {
t.Run("calling disable on device which is self causes DenyJoin of zigbee provider", func(t *testing.T) {
mockEventSender := mockEventSender{}
mockEventSender.On("sendEvent", mock.IsType(capabilities.DeviceDiscoveryDisabled{}))

mockNetworkJoining := mockNetworkJoining{}
mockNetworkJoining.On("DenyJoin", mock.Anything).Return(nil)

zdd := deviceDiscovery{
eventSender: &mockEventSender,
networkJoining: &mockNetworkJoining,
logger: logwrap.New(discard.Discard()),
}
defer zdd.Stop()

zdd.discovering = true

err := zdd.Disable(context.Background())
assert.NoError(t, err)

status, err := zdd.Status(context.Background())
assert.NoError(t, err)
assert.False(t, status.Discovering)

mockEventSender.AssertExpectations(t)
mockNetworkJoining.AssertExpectations(t)
})

t.Run("calling disable on device which is self causes DenyJoin of zigbee provider, and forwards an error", func(t *testing.T) {
expectedError := errors.New("deny join failure")

mockEventSender := mockEventSender{}

mockNetworkJoining := mockNetworkJoining{}
mockNetworkJoining.On("DenyJoin", mock.Anything).Return(expectedError)

zdd := deviceDiscovery{
eventSender: &mockEventSender,
networkJoining: &mockNetworkJoining,
logger: logwrap.New(discard.Discard()),
}
defer zdd.Stop()

zdd.discovering = true

err := zdd.Disable(context.Background())

assert.Error(t, err)
assert.Equal(t, expectedError, err)

status, err := zdd.Status(context.Background())
assert.NoError(t, err)
assert.True(t, status.Discovering)

mockEventSender.AssertExpectations(t)
mockNetworkJoining.AssertExpectations(t)
})
}

func TestZigbeeDeviceDiscovery_DurationBehaviour(t *testing.T) {
t.Run("when an allows duration expires then a disable instruction is sent", func(t *testing.T) {
mockEventSender := mockEventSender{}
mockEventSender.On("sendEvent", mock.Anything).Return(nil).Twice()

mockNetworkJoining := mockNetworkJoining{}
mockNetworkJoining.On("PermitJoin", mock.Anything, true).Return(nil)
mockNetworkJoining.On("DenyJoin", mock.Anything).Return(nil)

zdd := deviceDiscovery{
eventSender: &mockEventSender,
networkJoining: &mockNetworkJoining,
logger: logwrap.New(discard.Discard()),
}

defer zdd.Stop()

err := zdd.Enable(context.Background(), 100*time.Millisecond)
assert.NoError(t, err)

status, err := zdd.Status(context.Background())
assert.NoError(t, err)
assert.True(t, status.Discovering)

time.Sleep(150 * time.Millisecond)

status, err = zdd.Status(context.Background())
assert.NoError(t, err)
assert.False(t, status.Discovering)

mockEventSender.AssertExpectations(t)
mockNetworkJoining.AssertExpectations(t)
})

t.Run("second allows extend the duration of the first", func(t *testing.T) {
mockEventSender := mockEventSender{}
mockEventSender.On("sendEvent", mock.Anything).Return(nil).Twice()

mockNetworkJoining := mockNetworkJoining{}
mockNetworkJoining.On("PermitJoin", mock.Anything, true).Return(nil)
mockNetworkJoining.On("DenyJoin", mock.Anything).Return(nil).Maybe()

zdd := deviceDiscovery{
eventSender: &mockEventSender,
networkJoining: &mockNetworkJoining,
logger: logwrap.New(discard.Discard()),
}

defer zdd.Stop()

err := zdd.Enable(context.Background(), 50*time.Millisecond)
assert.NoError(t, err)

status, err := zdd.Status(context.Background())
assert.NoError(t, err)
assert.True(t, status.Discovering)
assert.Greater(t, int64(status.RemainingDuration), int64(45*time.Millisecond))

err = zdd.Enable(context.Background(), 200*time.Millisecond)
assert.NoError(t, err)

status, err = zdd.Status(context.Background())
assert.NoError(t, err)
assert.True(t, status.Discovering)
assert.Greater(t, int64(status.RemainingDuration), int64(145*time.Millisecond))

time.Sleep(150 * time.Millisecond)

status, err = zdd.Status(context.Background())
assert.NoError(t, err)
assert.True(t, status.Discovering)

mockEventSender.AssertExpectations(t)
mockNetworkJoining.AssertExpectations(t)
})
}
21 changes: 14 additions & 7 deletions gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,6 @@ type gateway struct {
ruleExecutor ruleExecutor
}

func (g *gateway) ReadEvent(_ context.Context) (interface{}, error) {
//TODO implement me
panic("implement me")
}

func (g *gateway) Capabilities() []da.Capability {
//TODO implement me
panic("implement me")
Expand Down Expand Up @@ -87,6 +82,12 @@ func (g *gateway) Start(ctx context.Context) error {
g.selfDevice = gatewayDevice{
gateway: g,
identifier: adapterNode.IEEEAddress,
dd: &deviceDiscovery{
gateway: g,
networkJoining: g.provider,
eventSender: g,
logger: g.logger,
},
}

g.logger.LogInfo(g.ctx, "Adapter coordinator IEEE address.", logwrap.Datum("IEEEAddress", g.selfDevice.Identifier().String()))
Expand All @@ -103,6 +104,7 @@ func (g *gateway) Start(ctx context.Context) error {

func (g *gateway) Stop(_ context.Context) error {
g.logger.LogInfo(g.ctx, "Stopping ZDA.")
g.selfDevice.dd.Stop()
g.ctxCancel()
return nil
}
Expand All @@ -112,6 +114,7 @@ var _ da.Gateway = (*gateway)(nil)
type gatewayDevice struct {
gateway da.Gateway
identifier da.Identifier
dd *deviceDiscovery
}

func (g gatewayDevice) Gateway() da.Gateway {
Expand All @@ -127,8 +130,12 @@ func (g gatewayDevice) Capabilities() []da.Capability {
}

func (g gatewayDevice) Capability(capability da.Capability) da.BasicCapability {
//TODO implement me
panic("implement me")
switch capability {
case capabilities.DeviceDiscoveryFlag:
return g.dd
default:
return nil
}
}

var _ da.Device = (*gatewayDevice)(nil)

0 comments on commit a6589cd

Please sign in to comment.