Skip to content

Commit

Permalink
Work in progress support for Z-Stack 3 devices, moving away from SAPI…
Browse files Browse the repository at this point in the history
… to ZDO calls as Z3 doesn't support SAPI. While it starts both V1 and V3, neither now recovers after a restart.
  • Loading branch information
pwood committed Nov 7, 2020
1 parent 8fb3277 commit f91d3a9
Show file tree
Hide file tree
Showing 12 changed files with 650 additions and 259 deletions.
49 changes: 15 additions & 34 deletions adapter_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
42 changes: 28 additions & 14 deletions adapter_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
})
Expand All @@ -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)
})
}
105 changes: 83 additions & 22 deletions adapter_initialise.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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})
},
Expand All @@ -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})
Expand All @@ -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})
Expand All @@ -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{})
},
})
}
}

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 (
Expand All @@ -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
Loading

0 comments on commit f91d3a9

Please sign in to comment.