From f91d3a99a33c7fb82e996e5c6712f4ef381cc9d4 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sat, 7 Nov 2020 08:50:36 +0000 Subject: [PATCH] Work in progress support for Z-Stack 3 devices, moving away from SAPI to ZDO calls as Z3 doesn't support SAPI. While it starts both V1 and V3, neither now recovers after a restart. --- adapter_info.go | 49 ++---- adapter_info_test.go | 42 +++-- adapter_initialise.go | 105 +++++++++--- adapter_initialise_test.go | 333 ++++++++++++++++++++++++++++++------- go.mod | 3 +- go.sum | 10 +- joining.go | 22 +-- joining_test.go | 67 ++++++-- messages.go | 24 ++- messages_test.go | 224 +++++++++++++++---------- nvram.go | 4 +- zstack.go | 26 ++- 12 files changed, 650 insertions(+), 259 deletions(-) diff --git a/adapter_info.go b/adapter_info.go index 5fb3cfb..3463f57 100644 --- a/adapter_info.go +++ b/adapter_info.go @@ -14,53 +14,34 @@ func (z *ZStack) AdapterNode() zigbee.Node { } func (z *ZStack) GetAdapterIEEEAddress(ctx context.Context) (zigbee.IEEEAddress, error) { - data, err := z.getAddressInfo(ctx, IEEEAddress) - - ieeeAddress := zigbee.IEEEAddress(data) + data, err := z.getAddressInfo(ctx) + ieeeAddress := data.IEEEAddress return ieeeAddress, err } func (z *ZStack) GetAdapterNetworkAddress(ctx context.Context) (zigbee.NetworkAddress, error) { - data, err := z.getAddressInfo(ctx, NetworkAddress) - - networkAddress := zigbee.NetworkAddress(data & 0xffff) + data, err := z.getAddressInfo(ctx) + networkAddress := data.NetworkAddress return networkAddress, err } -func (z *ZStack) getAddressInfo(ctx context.Context, parameter DeviceInfoParameter) (uint64, error) { - resp := SAPIZBGetDeviceInfoReply{} - - if err := z.requestResponder.RequestResponse(ctx, SAPIZBGetDeviceInfo{Parameter: parameter}, &resp); err != nil { - return 0, err - } +func (z *ZStack) getAddressInfo(ctx context.Context) (UtilGetDeviceInfoRequestReply, error) { + resp := UtilGetDeviceInfoRequestReply{} - return resp.Value, nil + err := z.requestResponder.RequestResponse(ctx, UtilGetDeviceInfoRequest{}, &resp) + return resp, err } -type DeviceInfoParameter uint8 - -const ( - State DeviceInfoParameter = 0x00 - IEEEAddress DeviceInfoParameter = 0x01 - NetworkAddress DeviceInfoParameter = 0x02 - ParentNetworkAddress DeviceInfoParameter = 0x03 - ParentIEEEAddress DeviceInfoParameter = 0x04 - OperatingChannel DeviceInfoParameter = 0x05 - OperatingPANID DeviceInfoParameter = 0x06 - OperatingExtendedPANID DeviceInfoParameter = 0x07 -) - -type SAPIZBGetDeviceInfo struct { - Parameter DeviceInfoParameter -} +type UtilGetDeviceInfoRequest struct{} -const SAPIZBGetDeviceInfoID uint8 = 0x06 +const UtilGetDeviceInfoRequestID uint8 = 0x00 -type SAPIZBGetDeviceInfoReply struct { - Parameter DeviceInfoParameter - Value uint64 +type UtilGetDeviceInfoRequestReply struct { + Status uint8 + IEEEAddress zigbee.IEEEAddress + NetworkAddress zigbee.NetworkAddress } -const SAPIZBGetDeviceInfoReplyID uint8 = 0x06 +const UtilGetDeviceInfoRequestReplyID uint8 = 0x00 diff --git a/adapter_info_test.go b/adapter_info_test.go index 418ab1f..e945068 100644 --- a/adapter_info_test.go +++ b/adapter_info_test.go @@ -2,6 +2,7 @@ package zstack import ( "context" + "github.com/shimmeringbee/bytecodec" . "github.com/shimmeringbee/unpi" unpiTest "github.com/shimmeringbee/unpi/testing" "github.com/shimmeringbee/zigbee" @@ -19,18 +20,16 @@ func Test_GetAdapterIEEEAddress(t *testing.T) { zstack := New(unpiMock, NewNodeTable()) defer unpiMock.Stop() - c := unpiMock.On(SREQ, SAPI, SAPIZBGetDeviceInfoID).Return(Frame{ + unpiMock.On(SREQ, UTIL, UtilGetDeviceInfoRequestID).Return(Frame{ MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBGetDeviceInfoReplyID, - Payload: []byte{0x01, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}, + Subsystem: UTIL, + CommandID: UtilGetDeviceInfoRequestReplyID, + Payload: []byte{0x00, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x12, 0x11}, }) address, err := zstack.GetAdapterIEEEAddress(ctx) assert.NoError(t, err) - assert.Equal(t, zigbee.IEEEAddress(0x08090a0b0c0d0e0f), address) - - assert.Equal(t, uint8(0x01), c.CapturedCalls[0].Frame.Payload[0]) + assert.Equal(t, zigbee.IEEEAddress(0x0203040506070809), address) unpiMock.AssertCalls(t) }) @@ -45,19 +44,34 @@ func Test_GetAdapterNetworkAddress(t *testing.T) { zstack := New(unpiMock, NewNodeTable()) defer unpiMock.Stop() - c := unpiMock.On(SREQ, SAPI, SAPIZBGetDeviceInfoID).Return(Frame{ + unpiMock.On(SREQ, UTIL, UtilGetDeviceInfoRequestID).Return(Frame{ MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBGetDeviceInfoReplyID, - Payload: []byte{0x02, 0x09, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Subsystem: UTIL, + CommandID: UtilGetDeviceInfoRequestReplyID, + Payload: []byte{0x00, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x12, 0x11}, }) address, err := zstack.GetAdapterNetworkAddress(ctx) assert.NoError(t, err) - assert.Equal(t, zigbee.NetworkAddress(0x0809), address) - - assert.Equal(t, uint8(0x02), c.CapturedCalls[0].Frame.Payload[0]) + assert.Equal(t, zigbee.NetworkAddress(0x1112), address) unpiMock.AssertCalls(t) }) } + +func Test_UtilGetDeviceInfoStructs(t *testing.T) { + t.Run("UtilGetDeviceInfoRequestReply", func(t *testing.T) { + s := UtilGetDeviceInfoRequestReply{ + Status: 0x01, + IEEEAddress: 0x0203040506070809, + NetworkAddress: 0x1112, + } + + actualBytes, err := bytecodec.Marshal(s) + + expectedBytes := []byte{0x01, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x12, 0x11} + + assert.NoError(t, err) + assert.Equal(t, expectedBytes, actualBytes) + }) +} diff --git a/adapter_initialise.go b/adapter_initialise.go index 993564e..1561f1c 100644 --- a/adapter_initialise.go +++ b/adapter_initialise.go @@ -7,47 +7,67 @@ import ( "github.com/shimmeringbee/retry" "github.com/shimmeringbee/zigbee" "reflect" + "time" ) -func (z *ZStack) Initialise(ctx context.Context, nc zigbee.NetworkConfiguration) error { +func (z *ZStack) Initialise(pctx context.Context, nc zigbee.NetworkConfiguration) error { z.NetworkProperties.PANID = nc.PANID z.NetworkProperties.ExtendedPANID = nc.ExtendedPANID z.NetworkProperties.NetworkKey = nc.NetworkKey z.NetworkProperties.Channel = nc.Channel + ctx, segmentEnd := z.logger.Segment(pctx, "Adapter Initialise.") + defer segmentEnd() + + z.logger.LogInfo(ctx, "Restarting adapter.") version, err := z.waitForAdapterReset(ctx) if err != nil { return err } + z.logger.LogInfo(ctx, "Verifying existing network configuration.") if valid, err := z.verifyAdapterNetworkConfig(ctx, version); err != nil { return err } else if !valid { + z.logger.LogWarn(ctx, "Adapter network configuration is invalid, resetting adapter.") if err := z.wipeAdapter(ctx); err != nil { return err } + z.logger.LogInfo(ctx, "Converting adapter to coordinator.") if err := z.makeCoordinator(ctx); err != nil { return err } + z.logger.LogInfo(ctx, "Configuring adapter.") if err := z.configureNetwork(ctx, version); err != nil { return err } } + z.logger.LogInfo(ctx, "Starting Zigbee stack.") if err := z.startZigbeeStack(ctx); err != nil { return err } + //z.logger.LogInfo(ctx, "Waiting for coordinator to start.") + //if err := z.waitForCoordinatorStart(ctx); err != nil { + // return err + //} + + z.logger.LogInfo(ctx, "Fetching adapter IEEE and Network addresses.") if err := z.retrieveAdapterAddresses(ctx); err != nil { return err } + z.logger.LogInfo(ctx, "Enforcing denial of network joins.") if err := z.DenyJoin(ctx); err != nil { return err } + channelBits := channelToBits(z.NetworkProperties.Channel) + z.writeNVRAM(ctx, ZCDNVChanList{Channels: channelBits}) + z.startNetworkManager() z.startMessageReceiver() @@ -115,10 +135,9 @@ func (z *ZStack) makeCoordinator(ctx context.Context) error { } func (z *ZStack) configureNetwork(ctx context.Context, version Version) error { + channelBits := channelToBits(z.NetworkProperties.Channel) + if err := retryFunctions(ctx, []func(context.Context) error{ - func(invokeCtx context.Context) error { - return z.writeNVRAM(invokeCtx, ZCDNVSecurityMode{Enabled: 1}) - }, func(invokeCtx context.Context) error { return z.writeNVRAM(invokeCtx, ZCDNVPreCfgKeysEnable{Enabled: 1}) }, @@ -129,7 +148,7 @@ func (z *ZStack) configureNetwork(ctx context.Context, version Version) error { return z.writeNVRAM(invokeCtx, ZCDNVZDODirectCB{Enabled: 1}) }, func(invokeCtx context.Context) error { - return z.writeNVRAM(invokeCtx, ZCDNVChanList{Channels: channelToBits(z.NetworkProperties.Channel)}) + return z.writeNVRAM(invokeCtx, ZCDNVChanList{Channels: channelBits}) }, func(invokeCtx context.Context) error { return z.writeNVRAM(invokeCtx, ZCDNVPANID{PANID: z.NetworkProperties.PANID}) @@ -142,7 +161,6 @@ func (z *ZStack) configureNetwork(ctx context.Context, version Version) error { } if !version.IsV3() { - /* Z-Stack 3.X.X has a valid default Trust Centre key, so this is not required. */ return retryFunctions(ctx, []func(context.Context) error{ func(invokeCtx context.Context) error { return z.writeNVRAM(invokeCtx, ZCDNVUseDefaultTCLK{Enabled: 1}) @@ -158,10 +176,29 @@ func (z *ZStack) configureNetwork(ctx context.Context, version Version) error { }) } else { /* Z-Stack 3.X.X requires configuration of Base Device Behaviour. */ + if err := retryFunctions(ctx, []func(context.Context) error{ + func(invokeCtx context.Context) error { + return z.requestResponder.RequestResponse(ctx, APPCNFBDBSetChannelRequest{IsPrimary: true, Channel: channelBits}, &APPCNFBDBSetChannelRequestReply{}) + }, + func(invokeCtx context.Context) error { + return z.requestResponder.RequestResponse(ctx, APPCNFBDBSetChannelRequest{IsPrimary: false, Channel: [4]byte{}}, &APPCNFBDBSetChannelRequestReply{}) + }, + func(invokeCtx context.Context) error { + return z.requestResponder.RequestResponse(ctx, APPCNFBDBStartCommissioningRequest{Mode: 0x04}, &APPCNFBDBStartCommissioningRequestReply{}) + }, + }); err != nil { + return err + } - // TODO + if err := z.waitForCoordinatorStart(ctx); err != nil { + return err + } - return nil + return retryFunctions(ctx, []func(context.Context) error{ + func(invokeCtx context.Context) error { + return z.requestResponder.RequestResponse(ctx, APPCNFBDBStartCommissioningRequest{Mode: 0x02}, &APPCNFBDBStartCommissioningRequestReply{}) + }, + }) } } @@ -193,18 +230,17 @@ func (z *ZStack) retrieveAdapterAddresses(ctx context.Context) error { } func (z *ZStack) startZigbeeStack(ctx context.Context) error { - if err := retry.Retry(ctx, DefaultZStackTimeout, DefaultZStackRetries, func(invokeCtx context.Context) error { - return z.requestResponder.RequestResponse(invokeCtx, SAPIZBStartRequest{}, &SAPIZBStartRequestReply{}) - }); err != nil { - return err - } + return retry.Retry(ctx, 30*time.Second, DefaultZStackRetries, func(invokeCtx context.Context) error { + return z.requestResponder.RequestResponse(invokeCtx, ZDOStartUpFromAppRequest{StartDelay: 100}, &ZDOStartUpFromAppRequestReply{}) + }) +} +func (z *ZStack) waitForCoordinatorStart(ctx context.Context) error { ch := make(chan bool, 1) defer close(ch) err, cancel := z.subscriber.Subscribe(&ZDOStateChangeInd{}, func(v interface{}) { stateChange := v.(*ZDOStateChangeInd) - if stateChange.State == DeviceZBCoordinator { ch <- true } @@ -245,14 +281,6 @@ func channelToBits(channel uint8) [4]byte { return channelBytes } -type SAPIZBStartRequest struct{} - -const SAPIZBStartRequestID uint8 = 0x00 - -type SAPIZBStartRequestReply struct{} - -const SAPIZBStartRequestReplyID uint8 = 0x00 - type ZBStartStatus uint8 const ( @@ -272,3 +300,36 @@ type ZDOStateChangeInd struct { } const ZDOStateChangeIndID uint8 = 0xc0 + +type APPCNFBDBStartCommissioningRequest struct { + Mode uint8 +} + +const APPCNFBDBStartCommissioningRequestID uint8 = 0x05 + +type APPCNFBDBStartCommissioningRequestReply GenericZStackStatus + +const APPCNFBDBStartCommissioningRequestReplyID uint8 = 0x05 + +type APPCNFBDBSetChannelRequest struct { + IsPrimary bool `bcwidth:"8"` + Channel [4]byte +} + +const APPCNFBDBSetChannelRequestID uint8 = 0x08 + +type APPCNFBDBSetChannelRequestReply GenericZStackStatus + +const APPCNFBDBSetChannelRequestReplyID uint8 = 0x08 + +type ZDOStartUpFromAppRequest struct { + StartDelay uint16 +} + +const ZDOStartUpFromAppRequestId uint8 = 0x40 + +type ZDOStartUpFromAppRequestReply struct { + Status uint8 +} + +const ZDOStartUpFromAppRequestReplyID uint8 = 0x40 diff --git a/adapter_initialise_test.go b/adapter_initialise_test.go index 713592e..e4096a0 100644 --- a/adapter_initialise_test.go +++ b/adapter_initialise_test.go @@ -12,7 +12,7 @@ import ( ) func Test_Initialise(t *testing.T) { - t.Run("an adapter with incorrect config is fully initialised", func(t *testing.T) { + t.Run("an z-stack 1.2.X adapter with incorrect config is fully initialised", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -39,13 +39,13 @@ func Test_Initialise(t *testing.T) { Subsystem: SYS, CommandID: SysOSALNVWriteReplyID, Payload: nvramWriteResponse, - }).Times(11) + }).Times(10) - unpiMock.On(SREQ, SAPI, SAPIZBStartRequestID).Return(Frame{ + unpiMock.On(SREQ, ZDO, ZDOStartUpFromAppRequestId).Return(Frame{ MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBStartRequestReplyID, - Payload: nil, + Subsystem: ZDO, + CommandID: ZDOStartUpFromAppRequestReplyID, + Payload: []byte{0x00}, }) go func() { @@ -58,28 +58,154 @@ func Test_Initialise(t *testing.T) { }) }() - unpiMock.On(SREQ, SAPI, SAPIZBGetDeviceInfoID).Return( - Frame{ - MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBGetDeviceInfoReplyID, - Payload: []byte{0x01, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}, - }, + unpiMock.On(SREQ, UTIL, UtilGetDeviceInfoRequestID).Return(Frame{ + MessageType: SRSP, + Subsystem: UTIL, + CommandID: UtilGetDeviceInfoRequestReplyID, + Payload: []byte{0x00, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x09, 0x08}, + }).Times(2) + + unpiMock.On(SREQ, ZDO, ZDOMgmtPermitJoinRequestID).Return(Frame{ + MessageType: SRSP, + Subsystem: ZDO, + CommandID: ZDOMgmtPermitJoinRequestReplyID, + Payload: []byte{0x00}, + }) + + nc := zigbee.NetworkConfiguration{ + PANID: zigbee.PANID(0x0102), + ExtendedPANID: zigbee.ExtendedPANID(0x0102030405060708), + NetworkKey: [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + Channel: zigbee.DefaultChannel, + } + + logicalTypeValue, _ := bytecodec.Marshal(ZCDNVLogicalType{LogicalType: zigbee.EndDevice}) + logicalTypeResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: logicalTypeValue}) + logicalTypeFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: logicalTypeResponse} + + unpiMock.On(SREQ, SYS, SysOSALNVReadID).Return( + logicalTypeFrame, + ).Times(1) + + err := zstack.Initialise(ctx, nc) + + assert.NoError(t, err) + unpiMock.AssertCalls(t) + + assert.Equal(t, nc.PANID, zstack.NetworkProperties.PANID) + assert.Equal(t, nc.ExtendedPANID, zstack.NetworkProperties.ExtendedPANID) + assert.Equal(t, nc.NetworkKey, zstack.NetworkProperties.NetworkKey) + assert.Equal(t, nc.Channel, zstack.NetworkProperties.Channel) + + assert.Equal(t, []byte{0x01}, resetOn.CapturedCalls[0].Frame.Payload) + assert.Equal(t, []byte{0x03, 0x00, 0x00, 0x01, 0x03}, nvramOn.CapturedCalls[0].Frame.Payload) + assert.Equal(t, []byte{0x01}, resetOn.CapturedCalls[1].Frame.Payload) + assert.Equal(t, []byte{0x87, 0x00, 0x00, 0x01, 0x00}, nvramOn.CapturedCalls[1].Frame.Payload) + assert.Equal(t, []byte{0x01}, resetOn.CapturedCalls[2].Frame.Payload) + assert.Equal(t, []byte{0x63, 0x00, 0x00, 0x01, 0x1}, nvramOn.CapturedCalls[2].Frame.Payload) + assert.Equal(t, []byte{0x62, 0x00, 0x00, 0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nvramOn.CapturedCalls[3].Frame.Payload) + assert.Equal(t, []byte{0x8f, 0x00, 0x00, 0x01, 0x01}, nvramOn.CapturedCalls[4].Frame.Payload) + assert.Equal(t, []byte{0x84, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00}, nvramOn.CapturedCalls[5].Frame.Payload) + assert.Equal(t, []byte{0x83, 0x00, 0x00, 0x02, 0x02, 0x01}, nvramOn.CapturedCalls[6].Frame.Payload) + assert.Equal(t, []byte{0x2d, 0x00, 0x00, 0x08, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}, nvramOn.CapturedCalls[7].Frame.Payload) + assert.Equal(t, []byte{0x6d, 0x00, 0x00, 0x01, 0x01}, nvramOn.CapturedCalls[8].Frame.Payload) + assert.Equal(t, []byte{0x01, 0x01, 0x00, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, nvramOn.CapturedCalls[9].Frame.Payload) + + assert.Equal(t, zigbee.IEEEAddress(0x08090a0b0c0d0e0f), zstack.NetworkProperties.IEEEAddress) + assert.Equal(t, zigbee.NetworkAddress(0x0809), zstack.NetworkProperties.NetworkAddress) + }) + + t.Run("an z-stack 3.X.X adapter with incorrect config is fully initialised", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + unpiMock := unpiTest.NewMockAdapter() + zstack := New(unpiMock, NewNodeTable()) + defer unpiMock.Stop() + defer zstack.Stop() + + resetResponse, _ := bytecodec.Marshal(SysResetInd{ + Reason: External, + Version: Version{ProductID: 1}, + }) + + resetOn := unpiMock.On(AREQ, SYS, SysResetReqID).Return(Frame{ + MessageType: AREQ, + Subsystem: SYS, + CommandID: SysResetIndID, + Payload: resetResponse, + }).Times(3) + + nvramWriteResponse, _ := bytecodec.Marshal(SysOSALNVWriteReply{Status: ZSuccess}) + nvramOn := unpiMock.On(SREQ, SYS, SysOSALNVWriteID).Return(Frame{ + MessageType: SRSP, + Subsystem: SYS, + CommandID: SysOSALNVWriteReplyID, + Payload: nvramWriteResponse, + }).Times(9) + // + //unpiMock.On(SREQ, SAPI, SAPIZBStartRequestID).Return(Frame{ + // MessageType: SRSP, + // Subsystem: SAPI, + // CommandID: SAPIZBStartRequestReplyID, + // Payload: nil, + //}) + + go func() { + time.Sleep(10 * time.Millisecond) + unpiMock.InjectOutgoing(Frame{ + MessageType: AREQ, + Subsystem: ZDO, + CommandID: ZDOStateChangeIndID, + Payload: []byte{0x09}, + }) + time.Sleep(10 * time.Millisecond) + unpiMock.InjectOutgoing(Frame{ + MessageType: AREQ, + Subsystem: ZDO, + CommandID: ZDOStateChangeIndID, + Payload: []byte{0x09}, + }) + }() + // + //unpiMock.On(SREQ, SAPI, SAPIZBGetDeviceInfoID).Return( + // Frame{ + // MessageType: SRSP, + // Subsystem: SAPI, + // CommandID: SAPIZBGetDeviceInfoReplyID, + // Payload: []byte{0x01, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}, + // }, + // Frame{ + // MessageType: SRSP, + // Subsystem: SAPI, + // CommandID: SAPIZBGetDeviceInfoReplyID, + // Payload: []byte{0x02, 0x09, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + // }, + //).Times(2) + // + //unpiMock.On(SREQ, SAPI, SAPIZBPermitJoiningRequestID).Return( + // Frame{ + // MessageType: SRSP, + // Subsystem: SAPI, + // CommandID: SAPIZBPermitJoiningRequestReplyID, + // Payload: []byte{0x00}, + // }) + + bdbSetChannel := unpiMock.On(SREQ, APP_CNF, APPCNFBDBSetChannelRequestID).Return( Frame{ MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBGetDeviceInfoReplyID, - Payload: []byte{0x02, 0x09, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - }, - ).Times(2) + Subsystem: APP_CNF, + CommandID: APPCNFBDBSetChannelRequestReplyID, + Payload: []byte{0x00}, + }).Times(2) - unpiMock.On(SREQ, SAPI, SAPIZBPermitJoiningRequestID).Return( + bdbStartCommissioning := unpiMock.On(SREQ, APP_CNF, APPCNFBDBStartCommissioningRequestID).Return( Frame{ MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBPermitJoiningRequestReplyID, + Subsystem: APP_CNF, + CommandID: APPCNFBDBStartCommissioningRequestReplyID, Payload: []byte{0x00}, - }) + }).Times(2) nc := zigbee.NetworkConfiguration{ PANID: zigbee.PANID(0x0102), @@ -118,8 +244,10 @@ func Test_Initialise(t *testing.T) { assert.Equal(t, []byte{0x84, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00}, nvramOn.CapturedCalls[6].Frame.Payload) assert.Equal(t, []byte{0x83, 0x00, 0x00, 0x02, 0x02, 0x01}, nvramOn.CapturedCalls[7].Frame.Payload) assert.Equal(t, []byte{0x2d, 0x00, 0x00, 0x08, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}, nvramOn.CapturedCalls[8].Frame.Payload) - assert.Equal(t, []byte{0x6d, 0x00, 0x00, 0x01, 0x01}, nvramOn.CapturedCalls[9].Frame.Payload) - assert.Equal(t, []byte{0x01, 0x01, 0x00, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, nvramOn.CapturedCalls[10].Frame.Payload) + assert.Equal(t, []byte{0x01, 0x00, 0x80, 0x00, 0x00}, bdbSetChannel.CapturedCalls[0].Frame.Payload) + assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00, 0x00}, bdbSetChannel.CapturedCalls[1].Frame.Payload) + assert.Equal(t, []byte{0x04}, bdbStartCommissioning.CapturedCalls[0].Frame.Payload) + assert.Equal(t, []byte{0x02}, bdbStartCommissioning.CapturedCalls[1].Frame.Payload) assert.Equal(t, zigbee.IEEEAddress(0x08090a0b0c0d0e0f), zstack.NetworkProperties.IEEEAddress) assert.Equal(t, zigbee.NetworkAddress(0x0809), zstack.NetworkProperties.NetworkAddress) @@ -146,11 +274,11 @@ func Test_Initialise(t *testing.T) { Payload: resetResponse, }).Times(1) - unpiMock.On(SREQ, SAPI, SAPIZBStartRequestID).Return(Frame{ + unpiMock.On(SREQ, ZDO, ZDOStartUpFromAppRequestId).Return(Frame{ MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBStartRequestReplyID, - Payload: nil, + Subsystem: ZDO, + CommandID: ZDOStartUpFromAppRequestReplyID, + Payload: []byte{0x00}, }) go func() { @@ -163,28 +291,19 @@ func Test_Initialise(t *testing.T) { }) }() - unpiMock.On(SREQ, SAPI, SAPIZBGetDeviceInfoID).Return( - Frame{ - MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBGetDeviceInfoReplyID, - Payload: []byte{0x01, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}, - }, - Frame{ - MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBGetDeviceInfoReplyID, - Payload: []byte{0x02, 0x09, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - }, - ).Times(2) + unpiMock.On(SREQ, UTIL, UtilGetDeviceInfoRequestID).Return(Frame{ + MessageType: SRSP, + Subsystem: UTIL, + CommandID: UtilGetDeviceInfoRequestReplyID, + Payload: []byte{0x00, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x09, 0x08}, + }).Times(2) - unpiMock.On(SREQ, SAPI, SAPIZBPermitJoiningRequestID).Return( - Frame{ - MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBPermitJoiningRequestReplyID, - Payload: []byte{0x00}, - }) + unpiMock.On(SREQ, ZDO, ZDOMgmtPermitJoinRequestID).Return(Frame{ + MessageType: SRSP, + Subsystem: ZDO, + CommandID: ZDOMgmtPermitJoinRequestReplyID, + Payload: []byte{0x00}, + }) nc := zigbee.NetworkConfiguration{ PANID: zigbee.PANID(0x0102), @@ -351,13 +470,29 @@ func Test_startZigbeeStack(t *testing.T) { zstack := New(unpiMock, NewNodeTable()) defer unpiMock.Stop() - unpiMock.On(SREQ, SAPI, SAPIZBStartRequestID).Return(Frame{ + unpiMock.On(SREQ, ZDO, ZDOStartUpFromAppRequestId).Return(Frame{ MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBStartRequestReplyID, - Payload: nil, + Subsystem: ZDO, + CommandID: ZDOStartUpFromAppRequestReplyID, + Payload: []byte{0x00}, }) + err := zstack.startZigbeeStack(ctx) + assert.NoError(t, err) + + unpiMock.AssertCalls(t) + }) +} + +func Test_waitForCoordinatorStart(t *testing.T) { + t.Run("waits for state change indication", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + unpiMock := unpiTest.NewMockAdapter() + zstack := New(unpiMock, NewNodeTable()) + defer unpiMock.Stop() + go func() { time.Sleep(50 * time.Millisecond) unpiMock.InjectOutgoing(Frame{ @@ -368,7 +503,7 @@ func Test_startZigbeeStack(t *testing.T) { }) }() - err := zstack.startZigbeeStack(ctx) + err := zstack.waitForCoordinatorStart(ctx) assert.NoError(t, err) unpiMock.AssertCalls(t) @@ -382,16 +517,90 @@ func Test_startZigbeeStack(t *testing.T) { zstack := New(unpiMock, NewNodeTable()) defer unpiMock.Stop() - unpiMock.On(SREQ, SAPI, SAPIZBStartRequestID).Return(Frame{ - MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBStartRequestReplyID, - Payload: nil, - }) - - err := zstack.startZigbeeStack(ctx) + err := zstack.waitForCoordinatorStart(ctx) assert.Error(t, err) unpiMock.AssertCalls(t) }) } + +func Test_APPCNFBDBStructs(t *testing.T) { + t.Run("APPCNFBDBStartCommissioningRequest", func(t *testing.T) { + s := APPCNFBDBStartCommissioningRequest{ + Mode: 3, + } + + actualBytes, err := bytecodec.Marshal(s) + + expectedBytes := []byte{0x03} + + assert.NoError(t, err) + assert.Equal(t, expectedBytes, actualBytes) + }) + + t.Run("APPCNFBDBStartCommissioningRequestReply", func(t *testing.T) { + s := APPCNFBDBStartCommissioningRequestReply{ + Status: 0x01, + } + + actualBytes, err := bytecodec.Marshal(s) + + expectedBytes := []byte{0x01} + + assert.NoError(t, err) + assert.Equal(t, expectedBytes, actualBytes) + }) + + t.Run("APPCNFBDBSetChannelRequest", func(t *testing.T) { + s := APPCNFBDBSetChannelRequest{ + IsPrimary: true, + Channel: [4]byte{0xaa, 0xbb, 0xcc, 0xdd}, + } + + actualBytes, err := bytecodec.Marshal(s) + + expectedBytes := []byte{0x01, 0xaa, 0xbb, 0xcc, 0xdd} + + assert.NoError(t, err) + assert.Equal(t, expectedBytes, actualBytes) + }) + + t.Run("APPCNFBDBSetChannelRequestReply", func(t *testing.T) { + s := APPCNFBDBSetChannelRequestReply{ + Status: 0x01, + } + + actualBytes, err := bytecodec.Marshal(s) + + expectedBytes := []byte{0x01} + + assert.NoError(t, err) + assert.Equal(t, expectedBytes, actualBytes) + }) + + t.Run("ZDOStartUpFromAppRequest", func(t *testing.T) { + s := ZDOStartUpFromAppRequest{ + StartDelay: 512, + } + + actualBytes, err := bytecodec.Marshal(s) + + expectedBytes := []byte{0x00, 0x02} + + assert.NoError(t, err) + assert.Equal(t, expectedBytes, actualBytes) + }) + + t.Run("ZDOStartUpFromAppRequestReply", func(t *testing.T) { + s := ZDOStartUpFromAppRequestReply{ + Status: 0x01, + } + + actualBytes, err := bytecodec.Marshal(s) + + expectedBytes := []byte{0x01} + + assert.NoError(t, err) + assert.Equal(t, expectedBytes, actualBytes) + }) +} diff --git a/go.mod b/go.mod index 0a2b1e0..294e022 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,9 @@ go 1.14 require ( github.com/shimmeringbee/bytecodec v0.0.0-20201010084809-f41297878cdb + 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-20201009192802-a3cad1a91f66 + github.com/shimmeringbee/unpi v0.0.0-20201106170609-f4c2e4b55202 github.com/shimmeringbee/zigbee v0.0.0-20201027194100-4e53cafc0f7a github.com/stretchr/testify v1.6.1 ) diff --git a/go.sum b/go.sum index f274270..11da77f 100644 --- a/go.sum +++ b/go.sum @@ -2,21 +2,26 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 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-20201010084809-f41297878cdb h1:eIpxXfxOLwQAQ6/C+Vaq/wILOTG/os+WruItCFdXXFU= github.com/shimmeringbee/bytecodec v0.0.0-20201010084809-f41297878cdb/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-20201009192802-a3cad1a91f66 h1:O6jD3B8FDka2tRxz/CETPCUYbA8UX9DYoBSM4m1kxT8= -github.com/shimmeringbee/unpi v0.0.0-20201009192802-a3cad1a91f66/go.mod h1:iAt5R5HT+VC7B9U77uBmN5Z6+DJo4U0z6ag68NH2mMw= +github.com/shimmeringbee/unpi v0.0.0-20201106170609-f4c2e4b55202 h1:7kQalW+nsFt3/LByuF175uPr/LDhf/ExHC9nfmJBQCk= +github.com/shimmeringbee/unpi v0.0.0-20201106170609-f4c2e4b55202/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/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= github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -24,6 +29,7 @@ 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= +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= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/joining.go b/joining.go index 7b7d507..ef28a09 100644 --- a/joining.go +++ b/joining.go @@ -19,11 +19,12 @@ func (z *ZStack) DenyJoin(ctx context.Context) error { } func (z *ZStack) sendJoin(ctx context.Context, address zigbee.NetworkAddress, timeout uint8, newState JoinState) error { - response := SAPIZBPermitJoiningRequestReply{} + response := ZDOMgmtPermitJoinRequestReply{} - if err := z.requestResponder.RequestResponse(ctx, SAPIZBPermitJoiningRequest{ - Destination: address, - Timeout: timeout, + if err := z.requestResponder.RequestResponse(ctx, ZDOMgmtPermitJoinRequest{ + Destination: address, + Duration: timeout, + TCSignificance: 0x00, }, &response); err != nil { return err } @@ -42,13 +43,14 @@ const ( JoiningOn uint8 = 0xff ) -type SAPIZBPermitJoiningRequest struct { - Destination zigbee.NetworkAddress - Timeout uint8 +type ZDOMgmtPermitJoinRequest struct { + Destination zigbee.NetworkAddress + Duration uint8 + TCSignificance uint8 } -const SAPIZBPermitJoiningRequestID uint8 = 0x08 +const ZDOMgmtPermitJoinRequestID = 0x36 -type SAPIZBPermitJoiningRequestReply GenericZStackStatus +type ZDOMgmtPermitJoinRequestReply GenericZStackStatus -const SAPIZBPermitJoiningRequestReplyID uint8 = 0x08 +const ZDOMgmtPermitJoinRequestReplyID uint8 = 0x36 diff --git a/joining_test.go b/joining_test.go index bf3f91f..49afb63 100644 --- a/joining_test.go +++ b/joining_test.go @@ -2,6 +2,7 @@ package zstack import ( "context" + "github.com/shimmeringbee/bytecodec" . "github.com/shimmeringbee/unpi" unpiTest "github.com/shimmeringbee/unpi/testing" "github.com/shimmeringbee/zigbee" @@ -19,10 +20,10 @@ func Test_PermitJoin(t *testing.T) { zstack := New(unpiMock, NewNodeTable()) defer unpiMock.Stop() - c := unpiMock.On(SREQ, SAPI, SAPIZBPermitJoiningRequestID).Return(Frame{ + c := unpiMock.On(SREQ, ZDO, ZDOMgmtPermitJoinRequestID).Return(Frame{ MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBPermitJoiningRequestReplyID, + Subsystem: ZDO, + CommandID: ZDOMgmtPermitJoinRequestReplyID, Payload: []byte{0x00}, }) @@ -31,7 +32,7 @@ func Test_PermitJoin(t *testing.T) { unpiMock.AssertCalls(t) - assert.Equal(t, []byte{0xfc, 0xff, 0xff}, c.CapturedCalls[0].Frame.Payload) + assert.Equal(t, []byte{0xfc, 0xff, 0xff, 0x00}, c.CapturedCalls[0].Frame.Payload) assert.Equal(t, OnAllRouters, zstack.NetworkProperties.JoinState) }) @@ -44,10 +45,10 @@ func Test_PermitJoin(t *testing.T) { defer unpiMock.Stop() zstack.NetworkProperties.NetworkAddress = zigbee.NetworkAddress(0x0102) - c := unpiMock.On(SREQ, SAPI, SAPIZBPermitJoiningRequestID).Return(Frame{ + c := unpiMock.On(SREQ, ZDO, ZDOMgmtPermitJoinRequestID).Return(Frame{ MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBPermitJoiningRequestReplyID, + Subsystem: ZDO, + CommandID: ZDOMgmtPermitJoinRequestReplyID, Payload: []byte{0x00}, }) @@ -56,7 +57,7 @@ func Test_PermitJoin(t *testing.T) { unpiMock.AssertCalls(t) - assert.Equal(t, []byte{0x02, 0x01, 0xff}, c.CapturedCalls[0].Frame.Payload) + assert.Equal(t, []byte{0x02, 0x01, 0xff, 0x00}, c.CapturedCalls[0].Frame.Payload) assert.Equal(t, OnCoordinator, zstack.NetworkProperties.JoinState) }) @@ -68,10 +69,10 @@ func Test_PermitJoin(t *testing.T) { zstack := New(unpiMock, NewNodeTable()) defer unpiMock.Stop() - unpiMock.On(SREQ, SAPI, SAPIZBPermitJoiningRequestID).Return(Frame{ + unpiMock.On(SREQ, ZDO, ZDOMgmtPermitJoinRequestID).Return(Frame{ MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBPermitJoiningRequestReplyID, + Subsystem: ZDO, + CommandID: ZDOMgmtPermitJoinRequestReplyID, Payload: []byte{0x01}, }) @@ -91,10 +92,10 @@ func Test_DenyJoin(t *testing.T) { zstack := New(unpiMock, NewNodeTable()) defer unpiMock.Stop() - c := unpiMock.On(SREQ, SAPI, SAPIZBPermitJoiningRequestID).Return(Frame{ + c := unpiMock.On(SREQ, ZDO, ZDOMgmtPermitJoinRequestID).Return(Frame{ MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBPermitJoiningRequestReplyID, + Subsystem: ZDO, + CommandID: ZDOMgmtPermitJoinRequestReplyID, Payload: []byte{0x00}, }) @@ -104,7 +105,7 @@ func Test_DenyJoin(t *testing.T) { unpiMock.AssertCalls(t) - assert.Equal(t, []byte{0xfc, 0xff, 0x00}, c.CapturedCalls[0].Frame.Payload) + assert.Equal(t, []byte{0xfc, 0xff, 0x00, 0x00}, c.CapturedCalls[0].Frame.Payload) assert.Equal(t, Off, zstack.NetworkProperties.JoinState) }) @@ -116,10 +117,10 @@ func Test_DenyJoin(t *testing.T) { zstack := New(unpiMock, NewNodeTable()) defer unpiMock.Stop() - unpiMock.On(SREQ, SAPI, SAPIZBPermitJoiningRequestID).Return(Frame{ + unpiMock.On(SREQ, ZDO, ZDOMgmtPermitJoinRequestID).Return(Frame{ MessageType: SRSP, - Subsystem: SAPI, - CommandID: SAPIZBPermitJoiningRequestReplyID, + Subsystem: ZDO, + CommandID: ZDOMgmtPermitJoinRequestReplyID, Payload: []byte{0x01}, }) @@ -129,3 +130,33 @@ func Test_DenyJoin(t *testing.T) { unpiMock.AssertCalls(t) }) } + +func Test_ZDOMgmtPermitJoin(t *testing.T) { + t.Run("ZDOMgmtPermitJoinRequest", func(t *testing.T) { + s := ZDOMgmtPermitJoinRequest{ + Destination: 0x0102, + Duration: 0x03, + TCSignificance: 0x04, + } + + actualBytes, err := bytecodec.Marshal(s) + + expectedBytes := []byte{0x02, 0x01, 0x03, 0x04} + + assert.NoError(t, err) + assert.Equal(t, expectedBytes, actualBytes) + }) + + t.Run("ZDOMgmtPermitJoinRequestReply", func(t *testing.T) { + s := ZDOMgmtPermitJoinRequestReply{ + Status: 0x01, + } + + actualBytes, err := bytecodec.Marshal(s) + + expectedBytes := []byte{0x01} + + assert.NoError(t, err) + assert.Equal(t, expectedBytes, actualBytes) + }) +} diff --git a/messages.go b/messages.go index 352c8c2..28fd710 100644 --- a/messages.go +++ b/messages.go @@ -16,15 +16,6 @@ func registerMessages(l *Library) { l.Add(SREQ, SYS, SysOSALNVWriteID, SysOSALNVWrite{}) l.Add(SRSP, SYS, SysOSALNVWriteReplyID, SysOSALNVWriteReply{}) - l.Add(SREQ, SAPI, SAPIZBStartRequestID, SAPIZBStartRequest{}) - l.Add(SRSP, SAPI, SAPIZBStartRequestReplyID, SAPIZBStartRequestReply{}) - - l.Add(SREQ, SAPI, SAPIZBPermitJoiningRequestID, SAPIZBPermitJoiningRequest{}) - l.Add(SRSP, SAPI, SAPIZBPermitJoiningRequestReplyID, SAPIZBPermitJoiningRequestReply{}) - - l.Add(SREQ, SAPI, SAPIZBGetDeviceInfoID, SAPIZBGetDeviceInfo{}) - l.Add(SRSP, SAPI, SAPIZBGetDeviceInfoReplyID, SAPIZBGetDeviceInfoReply{}) - l.Add(AREQ, ZDO, ZDOStateChangeIndID, ZDOStateChangeInd{}) l.Add(AREQ, ZDO, ZdoEndDeviceAnnceIndID, ZdoEndDeviceAnnceInd{}) @@ -71,6 +62,21 @@ func registerMessages(l *Library) { l.Add(SREQ, ZDO, ZdoNWKAddrReqID, ZdoNWKAddrReq{}) l.Add(SRSP, ZDO, ZdoNWKAddrReqReplyID, ZdoNWKAddrReqReply{}) l.Add(AREQ, ZDO, ZdoNWKAddrRspID, ZdoNWKAddrRsp{}) + + l.Add(SREQ, APP_CNF, APPCNFBDBStartCommissioningRequestID, APPCNFBDBStartCommissioningRequest{}) + l.Add(SRSP, APP_CNF, APPCNFBDBStartCommissioningRequestReplyID, APPCNFBDBStartCommissioningRequestReply{}) + + l.Add(SREQ, APP_CNF, APPCNFBDBSetChannelRequestID, APPCNFBDBSetChannelRequest{}) + l.Add(SRSP, APP_CNF, APPCNFBDBSetChannelRequestReplyID, APPCNFBDBSetChannelRequestReply{}) + + l.Add(SREQ, ZDO, ZDOStartUpFromAppRequestId, ZDOStartUpFromAppRequest{}) + l.Add(SRSP, ZDO, ZDOStartUpFromAppRequestReplyID, ZDOStartUpFromAppRequestReply{}) + + l.Add(SREQ, UTIL, UtilGetDeviceInfoRequestID, UtilGetDeviceInfoRequest{}) + l.Add(SRSP, UTIL, UtilGetDeviceInfoRequestReplyID, UtilGetDeviceInfoRequestReply{}) + + l.Add(SREQ, ZDO, ZDOMgmtPermitJoinRequestID, ZDOMgmtPermitJoinRequest{}) + l.Add(SRSP, ZDO, ZDOMgmtPermitJoinRequestReplyID, ZDOMgmtPermitJoinRequestReply{}) } type ZStackStatus uint8 diff --git a/messages_test.go b/messages_test.go index b3701b2..b55c99c 100644 --- a/messages_test.go +++ b/messages_test.go @@ -108,90 +108,6 @@ func Test_registerMessages(t *testing.T) { assert.Equal(t, reflect.TypeOf(SysOSALNVWriteReply{}), ty) }) - t.Run("SAPIZBStartRequest", func(t *testing.T) { - identity, found := ml.GetByObject(&SAPIZBStartRequest{}) - - assert.True(t, found) - assert.Equal(t, SREQ, identity.MessageType) - assert.Equal(t, SAPI, identity.Subsystem) - assert.Equal(t, uint8(0x00), identity.CommandID) - - ty, found := ml.GetByIdentifier(SREQ, SAPI, 0x00) - - assert.True(t, found) - assert.Equal(t, reflect.TypeOf(SAPIZBStartRequest{}), ty) - }) - - t.Run("SAPIZBStartRequestReply", func(t *testing.T) { - identity, found := ml.GetByObject(&SAPIZBStartRequestReply{}) - - assert.True(t, found) - assert.Equal(t, SRSP, identity.MessageType) - assert.Equal(t, SAPI, identity.Subsystem) - assert.Equal(t, uint8(0x00), identity.CommandID) - - ty, found := ml.GetByIdentifier(SRSP, SAPI, 0x00) - - assert.True(t, found) - assert.Equal(t, reflect.TypeOf(SAPIZBStartRequestReply{}), ty) - }) - - t.Run("SAPIZBPermitJoiningRequest", func(t *testing.T) { - identity, found := ml.GetByObject(&SAPIZBPermitJoiningRequest{}) - - assert.True(t, found) - assert.Equal(t, SREQ, identity.MessageType) - assert.Equal(t, SAPI, identity.Subsystem) - assert.Equal(t, uint8(0x08), identity.CommandID) - - ty, found := ml.GetByIdentifier(SREQ, SAPI, 0x08) - - assert.True(t, found) - assert.Equal(t, reflect.TypeOf(SAPIZBPermitJoiningRequest{}), ty) - }) - - t.Run("SAPIZBPermitJoiningRequestReply", func(t *testing.T) { - identity, found := ml.GetByObject(&SAPIZBPermitJoiningRequestReply{}) - - assert.True(t, found) - assert.Equal(t, SRSP, identity.MessageType) - assert.Equal(t, SAPI, identity.Subsystem) - assert.Equal(t, uint8(0x08), identity.CommandID) - - ty, found := ml.GetByIdentifier(SRSP, SAPI, 0x08) - - assert.True(t, found) - assert.Equal(t, reflect.TypeOf(SAPIZBPermitJoiningRequestReply{}), ty) - }) - - t.Run("SAPIZBGetDeviceInfo", func(t *testing.T) { - identity, found := ml.GetByObject(&SAPIZBGetDeviceInfo{}) - - assert.True(t, found) - assert.Equal(t, SREQ, identity.MessageType) - assert.Equal(t, SAPI, identity.Subsystem) - assert.Equal(t, uint8(0x06), identity.CommandID) - - ty, found := ml.GetByIdentifier(SREQ, SAPI, 0x06) - - assert.True(t, found) - assert.Equal(t, reflect.TypeOf(SAPIZBGetDeviceInfo{}), ty) - }) - - t.Run("SAPIZBGetDeviceInfoReply", func(t *testing.T) { - identity, found := ml.GetByObject(&SAPIZBGetDeviceInfoReply{}) - - assert.True(t, found) - assert.Equal(t, SRSP, identity.MessageType) - assert.Equal(t, SAPI, identity.Subsystem) - assert.Equal(t, uint8(0x06), identity.CommandID) - - ty, found := ml.GetByIdentifier(SRSP, SAPI, 0x06) - - assert.True(t, found) - assert.Equal(t, reflect.TypeOf(SAPIZBGetDeviceInfoReply{}), ty) - }) - t.Run("ZDOStateChangeInd", func(t *testing.T) { identity, found := ml.GetByObject(&ZDOStateChangeInd{}) @@ -667,4 +583,144 @@ func Test_registerMessages(t *testing.T) { assert.True(t, found) assert.Equal(t, reflect.TypeOf(ZdoNWKAddrRsp{}), ty) }) + + t.Run("APPCNFBDBStartCommissioningRequest", func(t *testing.T) { + identity, found := ml.GetByObject(&APPCNFBDBStartCommissioningRequest{}) + + assert.True(t, found) + assert.Equal(t, SREQ, identity.MessageType) + assert.Equal(t, APP_CNF, identity.Subsystem) + assert.Equal(t, uint8(0x05), identity.CommandID) + + ty, found := ml.GetByIdentifier(SREQ, APP_CNF, 0x05) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(APPCNFBDBStartCommissioningRequest{}), ty) + }) + + t.Run("APPCNFBDBStartCommissioningRequestReply", func(t *testing.T) { + identity, found := ml.GetByObject(&APPCNFBDBStartCommissioningRequestReply{}) + + assert.True(t, found) + assert.Equal(t, SRSP, identity.MessageType) + assert.Equal(t, APP_CNF, identity.Subsystem) + assert.Equal(t, uint8(0x05), identity.CommandID) + + ty, found := ml.GetByIdentifier(SRSP, APP_CNF, 0x05) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(APPCNFBDBStartCommissioningRequestReply{}), ty) + }) + + t.Run("APPCNFBDBSetChannelRequest", func(t *testing.T) { + identity, found := ml.GetByObject(&APPCNFBDBSetChannelRequest{}) + + assert.True(t, found) + assert.Equal(t, SREQ, identity.MessageType) + assert.Equal(t, APP_CNF, identity.Subsystem) + assert.Equal(t, uint8(0x08), identity.CommandID) + + ty, found := ml.GetByIdentifier(SREQ, APP_CNF, 0x08) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(APPCNFBDBSetChannelRequest{}), ty) + }) + + t.Run("APPCNFBDBSetChannelRequestReply", func(t *testing.T) { + identity, found := ml.GetByObject(&APPCNFBDBSetChannelRequestReply{}) + + assert.True(t, found) + assert.Equal(t, SRSP, identity.MessageType) + assert.Equal(t, APP_CNF, identity.Subsystem) + assert.Equal(t, uint8(0x08), identity.CommandID) + + ty, found := ml.GetByIdentifier(SRSP, APP_CNF, 0x08) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(APPCNFBDBSetChannelRequestReply{}), ty) + }) + + t.Run("ZDOStartUpFromAppRequest", func(t *testing.T) { + identity, found := ml.GetByObject(&ZDOStartUpFromAppRequest{}) + + assert.True(t, found) + assert.Equal(t, SREQ, identity.MessageType) + assert.Equal(t, ZDO, identity.Subsystem) + assert.Equal(t, uint8(0x40), identity.CommandID) + + ty, found := ml.GetByIdentifier(SREQ, ZDO, 0x40) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(ZDOStartUpFromAppRequest{}), ty) + }) + + t.Run("ZDOStartUpFromAppRequestReply", func(t *testing.T) { + identity, found := ml.GetByObject(&ZDOStartUpFromAppRequestReply{}) + + assert.True(t, found) + assert.Equal(t, SRSP, identity.MessageType) + assert.Equal(t, ZDO, identity.Subsystem) + assert.Equal(t, uint8(0x40), identity.CommandID) + + ty, found := ml.GetByIdentifier(SRSP, ZDO, 0x40) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(ZDOStartUpFromAppRequestReply{}), ty) + }) + + t.Run("UtilGetDeviceInfoRequest", func(t *testing.T) { + identity, found := ml.GetByObject(&UtilGetDeviceInfoRequest{}) + + assert.True(t, found) + assert.Equal(t, SREQ, identity.MessageType) + assert.Equal(t, UTIL, identity.Subsystem) + assert.Equal(t, uint8(0x00), identity.CommandID) + + ty, found := ml.GetByIdentifier(SREQ, UTIL, 0x00) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(UtilGetDeviceInfoRequest{}), ty) + }) + + t.Run("UtilGetDeviceInfoRequestReply", func(t *testing.T) { + identity, found := ml.GetByObject(&UtilGetDeviceInfoRequestReply{}) + + assert.True(t, found) + assert.Equal(t, SRSP, identity.MessageType) + assert.Equal(t, UTIL, identity.Subsystem) + assert.Equal(t, uint8(0x00), identity.CommandID) + + ty, found := ml.GetByIdentifier(SRSP, UTIL, 0x00) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(UtilGetDeviceInfoRequestReply{}), ty) + }) + + t.Run("ZDOMgmtPermitJoinRequest", func(t *testing.T) { + identity, found := ml.GetByObject(&ZDOMgmtPermitJoinRequest{}) + + assert.True(t, found) + assert.Equal(t, SREQ, identity.MessageType) + assert.Equal(t, ZDO, identity.Subsystem) + assert.Equal(t, uint8(0x36), identity.CommandID) + + ty, found := ml.GetByIdentifier(SREQ, ZDO, 0x36) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(ZDOMgmtPermitJoinRequest{}), ty) + }) + + t.Run("ZDOMgmtPermitJoinRequestReply", func(t *testing.T) { + identity, found := ml.GetByObject(&ZDOMgmtPermitJoinRequestReply{}) + + assert.True(t, found) + assert.Equal(t, SRSP, identity.MessageType) + assert.Equal(t, ZDO, identity.Subsystem) + assert.Equal(t, uint8(0x36), identity.CommandID) + + ty, found := ml.GetByIdentifier(SRSP, ZDO, 0x36) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(ZDOMgmtPermitJoinRequestReply{}), ty) + }) } diff --git a/nvram.go b/nvram.go index a3b4367..9f1a13a 100644 --- a/nvram.go +++ b/nvram.go @@ -37,7 +37,7 @@ func (z *ZStack) writeNVRAM(ctx context.Context, v interface{}) error { } if writeResponse.Status != ZSuccess { - return fmt.Errorf("%w: status = %v", NVRAMUnsuccessful, writeResponse.Status) + return fmt.Errorf("%w: write: configId = %v, status = %v", NVRAMUnsuccessful, configId, writeResponse.Status) } return nil @@ -68,7 +68,7 @@ func (z *ZStack) readNVRAM(ctx context.Context, v interface{}) error { } if readResponse.Status != ZSuccess { - return fmt.Errorf("%w: status = %v", NVRAMUnsuccessful, readResponse.Status) + return fmt.Errorf("%w: read: configId = %v, status = %v", NVRAMUnsuccessful, configId, readResponse.Status) } return bytecodec.Unmarshal(readResponse.Value, v) diff --git a/zstack.go b/zstack.go index 40a345d..b27299d 100644 --- a/zstack.go +++ b/zstack.go @@ -2,10 +2,15 @@ package zstack // import "github.com/shimmeringbee/zstack" import ( "context" + "github.com/shimmeringbee/logwrap" + "github.com/shimmeringbee/logwrap/impl/golog" + "github.com/shimmeringbee/logwrap/impl/nest" "github.com/shimmeringbee/unpi/broker" "github.com/shimmeringbee/unpi/library" "github.com/shimmeringbee/zigbee" "io" + "log" + "os" "time" ) @@ -37,6 +42,8 @@ type ZStack struct { nodeTable *NodeTable transactionIdStore chan uint8 + + logger logwrap.Logger } type JoinState uint8 @@ -75,7 +82,7 @@ func New(uart io.ReadWriter, nodeTable *NodeTable) *ZStack { transactionIDs <- uint8(i) } - return &ZStack{ + zstack := &ZStack{ requestResponder: znp, awaiter: znp, subscriber: znp, @@ -85,9 +92,26 @@ func New(uart io.ReadWriter, nodeTable *NodeTable) *ZStack { nodeTable: nodeTable, transactionIdStore: transactionIDs, } + + zstack.WithGoLogger(log.New(os.Stderr, "", log.LstdFlags)) + + return zstack } func (z *ZStack) Stop() { z.stopNetworkManager() z.stopMessageReceiver() } + +func (z *ZStack) WithGoLogger(parentLogger *log.Logger) { + z.withLogWrapImpl(golog.Wrap(parentLogger)) +} + +func (z *ZStack) WithLogWrapLogger(parentLogger logwrap.Logger) { + z.withLogWrapImpl(nest.Wrap(parentLogger)) +} + +func (z *ZStack) withLogWrapImpl(impl logwrap.Impl) { + z.logger = logwrap.New(impl) + z.logger.AddOptionsToLogger(logwrap.Source("zstack")) +}