diff --git a/go.mod b/go.mod index d07b249..e4e6ce9 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,11 @@ module github.com/shimmeringbee/zstack go 1.14 require ( - github.com/shimmeringbee/bytecodec v0.0.0-20201107142444-94bb5c0baaee + github.com/shimmeringbee/bytecodec v0.0.0-20210113154719-e97672fe9012 github.com/shimmeringbee/logwrap v0.0.0-20201104114416-23aeb26f66f1 github.com/shimmeringbee/retry v0.0.0-20201009192801-17b4f327c3e1 - github.com/shimmeringbee/unpi v0.0.0-20210111165207-f0210c6942fc - github.com/shimmeringbee/zigbee v0.0.0-20201027194100-4e53cafc0f7a - github.com/stretchr/testify v1.6.1 + github.com/shimmeringbee/unpi v0.0.0-20210111171128-10074b5edd8f + github.com/shimmeringbee/zigbee v0.0.0-20210113204558-b0b1b8711592 + github.com/stretchr/testify v1.7.0 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 89a332a..f803f4e 100644 --- a/go.sum +++ b/go.sum @@ -6,18 +6,21 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/shimmeringbee/bytecodec v0.0.0-20200216120857-49d677293817/go.mod h1:J/gvzi9IgGBHP1cBn++bqJ4tchSbgS10N2lmGMlqD3M= -github.com/shimmeringbee/bytecodec v0.0.0-20201107142444-94bb5c0baaee h1:LGPf3nQB0b+k/zxaSPqwcW1Zd3cBGcEqREwqT7CWmw8= -github.com/shimmeringbee/bytecodec v0.0.0-20201107142444-94bb5c0baaee/go.mod h1:WYnxfxTJ45UQ+xeAuuTSIalcEepgP8Rb7T/OhCaDdgo= +github.com/shimmeringbee/bytecodec v0.0.0-20210111165458-877359ca1003/go.mod h1:iqI5PkiqY+Xq6Hu22TNhepAY00iJCfk9jiXKBUrMSQQ= +github.com/shimmeringbee/bytecodec v0.0.0-20210113154719-e97672fe9012 h1:evpXaVtlNYHk8kU5DzFZIVe2X2ZRe9cgAtzHd1ktjjU= +github.com/shimmeringbee/bytecodec v0.0.0-20210113154719-e97672fe9012/go.mod h1:WYnxfxTJ45UQ+xeAuuTSIalcEepgP8Rb7T/OhCaDdgo= github.com/shimmeringbee/logwrap v0.0.0-20201104114416-23aeb26f66f1 h1:HWCH7L4EyiipHROSw0iyPG0Nv9YUZHkYBGjo3BHCnko= github.com/shimmeringbee/logwrap v0.0.0-20201104114416-23aeb26f66f1/go.mod h1:NBAcZCUl6aFOGnWTs8m67EUAmWFZXRhoRQf5nknY8W0= github.com/shimmeringbee/retry v0.0.0-20201009192801-17b4f327c3e1 h1:XHZWCYRj+2lAwQhchW+SGbSeF/gTpZwROonbyT9u9A4= github.com/shimmeringbee/retry v0.0.0-20201009192801-17b4f327c3e1/go.mod h1:FFeFkkqdD9vdDFr2la9PkSTQr6qgU9aBiGU3QKn8ZKY= -github.com/shimmeringbee/unpi v0.0.0-20201216190504-55241dd5fdcf h1:i6HBAOrD/mx8KMyM+THks3+w/qOr6zmuTWo2/fG91vs= -github.com/shimmeringbee/unpi v0.0.0-20201216190504-55241dd5fdcf/go.mod h1:iAt5R5HT+VC7B9U77uBmN5Z6+DJo4U0z6ag68NH2mMw= github.com/shimmeringbee/unpi v0.0.0-20210111165207-f0210c6942fc h1:NqnGsfB3x2yJHTGmCh05ns6P0fRt+0llHpXxiR5w9Us= github.com/shimmeringbee/unpi v0.0.0-20210111165207-f0210c6942fc/go.mod h1:iAt5R5HT+VC7B9U77uBmN5Z6+DJo4U0z6ag68NH2mMw= -github.com/shimmeringbee/zigbee v0.0.0-20201027194100-4e53cafc0f7a h1:PNZqpjc7ouHQ1MKqerPNNeLOcTsXWZ0ZF9avU8LG9Es= -github.com/shimmeringbee/zigbee v0.0.0-20201027194100-4e53cafc0f7a/go.mod h1:GMA6rVpzvUK16cZwi8uW11JUTx8xUGOk5DbkXYWvm/8= +github.com/shimmeringbee/unpi v0.0.0-20210111171128-10074b5edd8f h1:rLLdpe771s3ImhjNl+Bop1FOjtDcwVcEDYGBXjnHOGM= +github.com/shimmeringbee/unpi v0.0.0-20210111171128-10074b5edd8f/go.mod h1:hOrncW6hd26Z18eayp99i7hNKj0aHtUx1SxXT49aEsk= +github.com/shimmeringbee/zigbee v0.0.0-20210113202023-651b51432b13 h1:Zlu+99+PDDvkWAJwy2SvtCb+NKuVRAJ7fVBZ9J3gm6o= +github.com/shimmeringbee/zigbee v0.0.0-20210113202023-651b51432b13/go.mod h1:GMA6rVpzvUK16cZwi8uW11JUTx8xUGOk5DbkXYWvm/8= +github.com/shimmeringbee/zigbee v0.0.0-20210113204558-b0b1b8711592 h1:iD4w6TAkFqNoFk2WSUnjjD5kuFL4pf7ZnRsyykSKVIA= +github.com/shimmeringbee/zigbee v0.0.0-20210113204558-b0b1b8711592/go.mod h1:GMA6rVpzvUK16cZwi8uW11JUTx8xUGOk5DbkXYWvm/8= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -31,6 +34,8 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/messages.go b/messages.go index 28fd710..2592680 100644 --- a/messages.go +++ b/messages.go @@ -77,6 +77,10 @@ func registerMessages(l *Library) { l.Add(SREQ, ZDO, ZDOMgmtPermitJoinRequestID, ZDOMgmtPermitJoinRequest{}) l.Add(SRSP, ZDO, ZDOMgmtPermitJoinRequestReplyID, ZDOMgmtPermitJoinRequestReply{}) + + l.Add(SREQ, ZDO, ZdoMgmtLeaveReqID, ZdoMgmtLeaveReq{}) + l.Add(SRSP, ZDO, ZdoMgmtLeaveReqReplyID, ZdoMgmtLeaveReqReply{}) + l.Add(AREQ, ZDO, ZdoMgmtLeaveRspID, ZdoMgmtLeaveRsp{}) } type ZStackStatus uint8 diff --git a/messages_test.go b/messages_test.go index b55c99c..25805ce 100644 --- a/messages_test.go +++ b/messages_test.go @@ -723,4 +723,46 @@ func Test_registerMessages(t *testing.T) { assert.True(t, found) assert.Equal(t, reflect.TypeOf(ZDOMgmtPermitJoinRequestReply{}), ty) }) + + t.Run("ZdoMgmtLeaveReq", func(t *testing.T) { + identity, found := ml.GetByObject(&ZdoMgmtLeaveReq{}) + + assert.True(t, found) + assert.Equal(t, SREQ, identity.MessageType) + assert.Equal(t, ZDO, identity.Subsystem) + assert.Equal(t, uint8(0x34), identity.CommandID) + + ty, found := ml.GetByIdentifier(SREQ, ZDO, 0x34) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(ZdoMgmtLeaveReq{}), ty) + }) + + t.Run("ZdoMgmtLeaveReqReply", func(t *testing.T) { + identity, found := ml.GetByObject(&ZdoMgmtLeaveReqReply{}) + + assert.True(t, found) + assert.Equal(t, SRSP, identity.MessageType) + assert.Equal(t, ZDO, identity.Subsystem) + assert.Equal(t, uint8(0x34), identity.CommandID) + + ty, found := ml.GetByIdentifier(SRSP, ZDO, 0x34) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(ZdoMgmtLeaveReqReply{}), ty) + }) + + t.Run("ZdoMgmtLeaveRsp", func(t *testing.T) { + identity, found := ml.GetByObject(&ZdoMgmtLeaveRsp{}) + + assert.True(t, found) + assert.Equal(t, AREQ, identity.MessageType) + assert.Equal(t, ZDO, identity.Subsystem) + assert.Equal(t, uint8(0xb4), identity.CommandID) + + ty, found := ml.GetByIdentifier(AREQ, ZDO, 0xb4) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(ZdoMgmtLeaveRsp{}), ty) + }) } diff --git a/node_remove.go b/node_remove.go new file mode 100644 index 0000000..1ffcadf --- /dev/null +++ b/node_remove.go @@ -0,0 +1,54 @@ +package zstack + +import ( + "context" + "github.com/shimmeringbee/zigbee" +) + +func (z *ZStack) RemoveNode(ctx context.Context, nodeAddress zigbee.IEEEAddress) error { + networkAddress, err := z.ResolveNodeNWKAddress(ctx, nodeAddress) + + if err != nil { + return nil + } + + request := ZdoMgmtLeaveReq{ + NetworkAddress: networkAddress, + IEEEAddress: nodeAddress, + RemoveChildren: false, + } + + _, err = z.nodeRequest(ctx, &request, &ZdoMgmtLeaveReqReply{}, &ZdoMgmtLeaveRsp{}, func(i interface{}) bool { + msg := i.(*ZdoMgmtLeaveRsp) + return msg.SourceAddress == networkAddress + }) + + return err +} + +type ZdoMgmtLeaveReq struct { + NetworkAddress zigbee.NetworkAddress + IEEEAddress zigbee.IEEEAddress + RemoveChildren bool +} + +const ZdoMgmtLeaveReqID uint8 = 0x34 + +type ZdoMgmtLeaveReqReply GenericZStackStatus + +func (r ZdoMgmtLeaveReqReply) WasSuccessful() bool { + return r.Status == ZSuccess +} + +const ZdoMgmtLeaveReqReplyID uint8 = 0x34 + +type ZdoMgmtLeaveRsp struct { + SourceAddress zigbee.NetworkAddress + Status ZStackStatus +} + +func (r ZdoMgmtLeaveRsp) WasSuccessful() bool { + return r.Status == ZSuccess +} + +const ZdoMgmtLeaveRspID uint8 = 0xb4 diff --git a/node_remove_test.go b/node_remove_test.go new file mode 100644 index 0000000..a92d417 --- /dev/null +++ b/node_remove_test.go @@ -0,0 +1,112 @@ +package zstack + +import ( + "context" + "github.com/shimmeringbee/bytecodec" + . "github.com/shimmeringbee/unpi" + unpiTest "github.com/shimmeringbee/unpi/testing" + "github.com/shimmeringbee/zigbee" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestZStack_RemoveNode(t *testing.T) { + t.Run("returns an success on query, response for requested network address is received", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + + unpiMock := unpiTest.NewMockAdapter() + zstack := New(unpiMock, NewNodeTable()) + defer unpiMock.Stop() + + call := unpiMock.On(SREQ, ZDO, ZdoMgmtLeaveReqReplyID).Return(Frame{ + MessageType: SRSP, + Subsystem: ZDO, + CommandID: ZdoMgmtLeaveReqReplyID, + Payload: []byte{0x00}, + }) + + go func() { + time.Sleep(10 * time.Millisecond) + unpiMock.InjectOutgoing(Frame{ + MessageType: AREQ, + Subsystem: ZDO, + CommandID: ZdoMgmtLeaveRspID, + Payload: []byte{0x00, 0x40, 0x00}, + }) + }() + + zstack.nodeTable.addOrUpdate(zigbee.IEEEAddress(1), zigbee.NetworkAddress(0x4000)) + + err := zstack.RemoveNode(ctx, zigbee.IEEEAddress(1)) + assert.NoError(t, err) + + leaveReq := ZdoMgmtLeaveReq{} + bytecodec.Unmarshal(call.CapturedCalls[0].Frame.Payload, &leaveReq) + + assert.Equal(t, zigbee.IEEEAddress(1), leaveReq.IEEEAddress) + assert.Equal(t, zigbee.NetworkAddress(0x4000), leaveReq.NetworkAddress) + assert.False(t, leaveReq.RemoveChildren) + + unpiMock.AssertCalls(t) + }) +} + +func Test_RemoveMessages(t *testing.T) { + t.Run("verify ZdoMgmtLeaveReq marshals", func(t *testing.T) { + req := ZdoMgmtLeaveReq{ + NetworkAddress: 0x1234, + IEEEAddress: zigbee.IEEEAddress(0x8899aabbccddeeff), + RemoveChildren: true, + } + + data, err := bytecodec.Marshal(req) + + assert.NoError(t, err) + assert.Equal(t, []byte{0x34, 0x12, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x01}, data) + }) + + t.Run("verify ZdoMgmtLeaveReqReply marshals", func(t *testing.T) { + req := ZdoMgmtLeaveReqReply{ + Status: 1, + } + + data, err := bytecodec.Marshal(req) + + assert.NoError(t, err) + assert.Equal(t, []byte{0x01}, data) + }) + + t.Run("ZdoMgmtLeaveReqReply returns true if success", func(t *testing.T) { + g := ZdoMgmtLeaveReqReply{Status: ZSuccess} + assert.True(t, g.WasSuccessful()) + }) + + t.Run("ZdoMgmtLeaveReqReply returns false if not success", func(t *testing.T) { + g := ZdoMgmtLeaveReqReply{Status: ZFailure} + assert.False(t, g.WasSuccessful()) + }) + + t.Run("verify ZdoMgmtLeaveRsp marshals", func(t *testing.T) { + req := ZdoMgmtLeaveRsp{ + SourceAddress: zigbee.NetworkAddress(0x2000), + Status: 1, + } + + data, err := bytecodec.Marshal(req) + + assert.NoError(t, err) + assert.Equal(t, []byte{0x00, 0x20, 0x01}, data) + }) + + t.Run("ZdoMgmtLeaveRsp returns true if success", func(t *testing.T) { + g := ZdoMgmtLeaveRsp{Status: ZSuccess} + assert.True(t, g.WasSuccessful()) + }) + + t.Run("ZdoMgmtLeaveRsp returns false if not success", func(t *testing.T) { + g := ZdoMgmtLeaveRsp{Status: ZFailure} + assert.False(t, g.WasSuccessful()) + }) +} diff --git a/zstack.go b/zstack.go index b27299d..cf38cff 100644 --- a/zstack.go +++ b/zstack.go @@ -46,6 +46,8 @@ type ZStack struct { logger logwrap.Logger } +var _ zigbee.Provider = (*ZStack)(nil) + type JoinState uint8 const (