Giter Site home page Giter Site logo

shimmeringbee / zstack Goto Github PK

View Code? Open in Web Editor NEW
25.0 3.0 8.0 201 KB

Implementation of a ZNP and support code designed to interface with Texas Instruments Z-Stack, written in Go.

Home Page: https://shimmeringbee.io/

License: Apache License 2.0

Go 100.00%
cc2531 cc2530 cc253x go znp z-stack zigbee-network-processor

zstack's Introduction

Shimmering Bee: Z-Stack

license standard-readme compliant Actions Status

Implementation of a ZNP and support code designed to interface with Texas Instruments Z-Stack, written in Go.

Table of Contents

Background

Z-Stack is a Zigbee Stack made available by Texas Instruments for use on their CC 2.4Ghz SOC processors. This library implements a Zigbee Network Processor that is capable of controlling a Z-Stack implementation, specifically it supports the CC series of Zigbee sniffers flashed with the zigbee2mqtt Z-Stack coordinator firmware.

More information about Z-Stack is available from Texas Instruments directly or from Z-Stack Developer's Guide.

Another implementation of a Z-Stack compatible ZNP exists for Golang, it did hold no license for a period and the author could not be contacted. This has been rectified, so it may be of interest you. This is a complete reimplementation of the library, however it is likely there will be strong coincidences due to Golang standards.

Supported Devices

The following chips and sticks are known to work, though it's likely others in the series will too:

Huge thanks to @Koenkk for his work in providing Z-Stack firmware for these chips. You can grab the firmware from GitHub.

Install

Add an import and most IDEs will go get automatically, if it doesn't go build will fetch.

import "github.com/shimmeringbee/zstack"

Usage

This libraries API is unstable and should not yet be relied upon.

Open Serial Connection and Start ZStack

/* Obtain a ReadWriter UART interface to CC253X */
serialPort :=

/* Construct node table, cache of network nodes. */
t := zstack.NewNodeTable()

/* Create a new ZStack struct. */
z := zstack.New(serialPort, t)

/* Generate random Zigbee network, on default channel (15) */
netCfg, _ := zigbee.GenerateNetworkConfiguration()

/* Obtain context for timeout of initialisation. */
ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Minute)
defer cancel()

/* Initialise ZStack and CC253X */)
err = z.Initialise(ctx, nc)

Handle Events

It is critical that this is handled until you wish to stop the Z-Stack instance.

for {
    ctx := context.Background()
    event, err := z.ReadEvent(ctx)

    if err != nil {
        return
    }

    switch e := event.(type) {
    case zigbee.NodeJoinEvent:
        log.Printf("join: %v\n", e.Node)
        go exploreDevice(z, e.Node)
    case zigbee.NodeLeaveEvent:
        log.Printf("leave: %v\n", e.Node)
    case zigbee.NodeUpdateEvent:
        log.Printf("update: %v\n", e.Node)
    case zigbee.NodeIncomingMessageEvent:
        log.Printf("message: %v\n", e)
    }
}

Permit Joins

err := z.PermitJoin(ctx, true)

Deny Joins

err := z.DenyJoin(ctx)

Query Device For Details

func exploreDevice(z *zstack.ZStack, node zigbee.Node) {
	log.Printf("node %v: querying", node.IEEEAddress)

	ctx, cancel := context.WithTimeout(context.Background(), 1 * time.Minute)
	defer cancel()

	descriptor, err := z.QueryNodeDescription(ctx, node.IEEEAddress)

	if err != nil {
		log.Printf("failed to get node descriptor: %v", err)
		return
	}

	log.Printf("node %v: descriptor: %+v", node.IEEEAddress, descriptor)

	endpoints, err := z.QueryNodeEndpoints(ctx, node.IEEEAddress)

	if err != nil {
		log.Printf("failed to get node endpoints: %v", err)
		return
	}

	log.Printf("node %v: endpoints: %+v", node.IEEEAddress, endpoints)

	for _, endpoint := range endpoints {
		endpointDes, err := z.QueryNodeEndpointDescription(ctx, node.IEEEAddress, endpoint)

		if err != nil {
			log.Printf("failed to get node endpoint description: %v / %d", err, endpoint)
		} else {
			log.Printf("node %v: endpoint: %d desc: %+v", node.IEEEAddress, endpoint, endpointDes)
		}
	}
}

Node Table Cache

zstack requires a NodeTable structure to cache a devices IEEE address to its Zibgee network address. A design decision for zstack was that all operations would reference the IEEE address. This cache must be persisted between program runs as the coordinator hardware does not retain this information between restarts.

// Create new table
nodeTable := NewNodeTable()

// Dump current content
nodes := nodeTable.Nodes()

// Load previous content - this should be done before starting ZStack.
nodeTable.Load(nodes)

ZCL

To handle ZCL messages you must handle zigbee.NodeIncomingMessageEvent messages and process the ZCL payload with the ZCL library, responses can be sent with z.SendNodeMessage.

Maintainers

@pwood

Contributing

Feel free to dive in! Open an issue or submit PRs.

All Shimmering Bee projects follow the Contributor Covenant Code of Conduct.

License

Copyright 2019-2020 Shimmering Bee Contributors

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

zstack's People

Contributors

melair avatar pwood avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

zstack's Issues

Join/leave works but cannot receive a single NodeIncomingMessageEvent

I am able to join/leave devices, but no incoming messages come through when I trigger any of the sensors.

I don't know where to start debugging, I tried to insert logging statements into node_receive_message.go.

func (z *ZStack) startMessageReceiver() {
	log.Println("startMessageReceiver: subscribing")

	_, z.messageReceiverStop = z.subscriber.Subscribe(&AfIncomingMsg{}, func(v interface{}) {
		log.Println("startMessageReceiver: got one")

I got "subscribing" but didn't get a single "got one" message. My code is:

	port, err := serial.Open("/dev/ttyACM0", &serial.Mode{
		BaudRate: 115200, // from TI's docs
	})
	if err != nil {
		return err
	}
	defer port.Close()
	port.SetRTS(true) // "modem status bit RequestToSend"

	nodeTable, err := loadNodeTable()
	if err != nil {
		return err
	}

	saveNodeTableToDisk := func() error {
		return jsonfile.Write(nodeTableFilename, nodeTable.Nodes())
	}

	zigbeeComms := zstack.New(port, nodeTable)
	zigbeeComms.WithGoLogger(logex.Prefix("zigbee", rootLogger))
	defer zigbeeComms.Stop()

	netCfg := zigbee.NetworkConfiguration{ // not my actual details
		PANID:         zigbee.PANID(12345),
		ExtendedPANID: zigbee.ExtendedPANID(1357768367493424001),
		NetworkKey:    zigbee.NetworkKey([16]byte{149, 221, 234, 37, 233, 64, 31, 188, 12, 84, 124, 202, 39, 142, 31, 13}),
		Channel:       15,
	}

	// initialise ZStack and CC253X
	if err := func() error {
		ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
		defer cancel()

		return zigbeeComms.Initialise(ctx, netCfg)
	}(); err != nil {
		return fmt.Errorf("initialize: %w", err)
	}

	if err := zigbeeComms.PermitJoin(ctx, true); err != nil {
		return fmt.Errorf("PermitJoin: %w", err)
	}

	for {
		event, err := zigbeeComms.ReadEvent(ctx)
		if err != nil {
			return err
		}

		switch e := event.(type) {
		case zigbee.NodeJoinEvent:
			logl.Info.Printf("JOIN: %v\n", e.Node)
			go exploreDevice(zigbeeComms, e.Node)
		case zigbee.NodeLeaveEvent:
			logl.Info.Printf("LEAVE: %v\n", e.Node)
		case zigbee.NodeUpdateEvent: // this gets fired every 30s regardless of network activity
			logl.Debug.Printf("UPDATE: %v\n", e.Node)

			// it's a good trigger to update the node table
			// TODO: detect if we have changes
			if err := saveNodeTableToDisk(); err != nil {
				return err
			}
		case zigbee.NodeIncomingMessageEvent:
			logl.Debug.Printf("MSG: %v\n", e)
		default:
			logl.Error.Println("got unrecognized event")
		}
	}
}

I'm running CC2531 with the recommended firmware, I flashed the dongle just to be sure. It works out-of-the-box with https://github.com/dyrkin/zigbee-steward

Initialisation fails on linux/arm7

While trying to run code using zstack on a Raspberry Pi 3, initialisation fails, where as the same configuration works on an AMD64 architectured machine.

This could be to do with different sizes of int on the different platforms, they are the same endian so thankfull doesn't appear to be a byte order issue.

This issue may also effect ZCL, bytecodec or unpi.

Add support for querying endpoints.

Add support for registering endpoints, ZDO_SIMPLE_DESC_REQ syncronous, command 0x04.

Add support for registering endpoints, ZDO_SIMPLE_DESC_RSP syncronous, command 0x84.

Support CC2652 adapter

Add support for CC2652 based adapters, these use firmware from Koenkk which is based on Z-Stack 3.X.0, because this supporst Zigbee 3.0 there are some differences to the initialisation of the adapter. To do this, we will need to:

  • Query the version of the adapter with SYS_VERSION, alternatively it's also returned in SYS_RESET_IND.

CC2531

(*zstack.SysResetInd)(0xc00000b8f0)({
 Reason: (zstack.ResetReason) 0,
 TransportRevision: (uint8) 2,
 ProductID: (uint8) 0,
 MajorRelease: (uint8) 2,
 MinorRelease: (uint8) 6,
 HardwareRevision: (uint8) 3
})

CC2653

(*zstack.SysResetInd)(0xc000204008)({
 Reason: (zstack.ResetReason) 0,
 TransportRevision: (uint8) 2,
 ProductID: (uint8) 1,
 MajorRelease: (uint8) 2,
 MinorRelease: (uint8) 7,
 HardwareRevision: (uint8) 1
})

zStack 1.2.0 is indicated by 0, 3.x.0 is indiciated by 1 and 3.0.x is indiciated by 2. (No not a typo).

Based upon the SysResetInd.ProductID we can switch initialisation behaviour, if the device does not return a valid version, then assume CC2531 behaviour.

  • If > 1.2.0, when verifying if the network configuration is correct, if the PANID reads as 0xffff, then it is not a failure.
  • If == 3.x.0, then reading the network key uses a different call.
  • If > 1.2.0, during initialisation:
    • the trust centre key does not need to be written (already valid)
    • writing network key is done different.
    • the base device behaviour system must be initialised, with the channel, wait for start, then start comissioning.

Zigbee herdsman implementation can be found here: https://github.com/Koenkk/zigbee-herdsman/blob/master/src/adapter/z-stack/adapter/startZnp.ts

Haromnise names of ZStack message structs

The naming of ZStack message stucts is inconsistent. Sometimes the shimmeringbee code has created a "Resp" struct which is the response to a "Req", but ZStack natively calls some async responses "Resp".

Messages should be renamed to be, for example:

SYS_VERSION = SysVersion and SysVersionReply

ZB_BIND_DEVICE = ZbBindDevice/ZbBindDeviceReply
ZB_BIND_CONFIRM = ZbBindConfirm

ZDO_ACTIVE_EP_REQ = ZdoActiveEpReq/ZdoActiveEpReqReply
ZDO_ACTIVE_EP_RSP = ZdoActiveEpResp

That is, when a message is syncronous the name of the SRSP is the same as SREQ, with Reply appended.

If End Devices parent is the Coordinator, it may have issues after restart

When some low power End Devices choose the Coordinator as their parent, they may have issues if ZStack is restarted.

This is caused by ZStack requiring resolution of the IEEE address for any event. As the coordinator is responsible for the End Device it is also responsible for knowing their IEEE address. When ZStack restarts the adapter on Initialise it issues a hard reset, this clears the CC2530's child cache (amongst other things).

return z.writeNVRAM(invokeCtx, ZCDNVStartUpOption{StartOption: 0x03})

This is not a problem for Routers as they regularily reintroduce themselves and the mesh network reforms, some End Devices do not, or do so very infrequently.

It is sensible to issue the hard reset when the adapter is being reconfigured to be a different network, however during normal operation (and restarts) we should not hard reset the adapter.

We could take a couple of approaches:

  • Defer this choice to the ZStack consumer.
  • Query the CC2530 for Channel, Network ID, PAN ID, Extended PAN ID and Network Key, if they differ hard reset.

Implement initialisation of CC253X adapter

Our initial target for support is the CC253X USB adapters flashed with the zigbee2mqtt.io z-stack coordinator firmware. Thus zstack must be capable of initialising the adapter and starting it in a coordinator role.

Finding documentation on this is a little tricky, so learning from other existing open source tooling out there for the CC253X, we should initialise as follows.

Reset Adapter

	// Reset (SOFT)

	// Perform Configuration and State Reset - NVRAM - ZCD_NV_STARTUP_OPTION (0x0003) 0x03 (Clear State, Clear Config)

	// Reset (SOFT)

	// Set Logical Type as COORDINATOR - NVRAM - ZCD_NV_LOGICAL_TYPE (0x0087) 0x00 (Coordinator) (01 = Router, 02 = End Device)

	// Enable Network Security - NVRAM - ZCD_NV_SECURITY_MODE (0x0064) 0x01 (Enable Security)

	// Enable distribution of network keys - NVRAM - ZCD_NV_PRECFGKEYS_ENABLE (0x0063) 0x01 (Use precfg keys)

	// Set Network Key - NVRAM - ZCD_NV_PRECFGKEY (0x0062) [16]byte (Set network key)

	// Set ZDO Direct Callback - NVRAM - ZCD_NV_ZDO_DIRECT_CB (0x008f) 0x01 (True, Don't write in ZDO_MSG_CB_INCOMING)

	// Set Channels - NVRAM - ZCD_NV_CHANLIST (0x0084) Bitmap (Setting 1 statically uses that, multiple scan least busy)

	// Set PAN ID - NVRAM - ZCD_NV_PANID (0x0083) [2]byte (Set PAN ID)

	// Set Extended PAN ID - NVRAM - ZCD_NV_EXTPANID (0x002d) [8]byte (Set extended PAN ID)

	// Set Enable TC Link Key - NVRAM - ZCD_NV_USE_DEFAULT_TCLK (0x006d) 0x01 (Enable TC Link Key)

	// Set TC Link Key - NVRAM - ZCD_NV_TCLK_TABLE_START (0x0101) [20]byte (Default TC Link, more complex than just key structure)

Initialize() fails when config is changed (or just generated), but works on second try

Given this code:

netCfg, err := zigbee.GenerateNetworkConfiguration()
if err != nil {
	return err
}

if err := zigbeeComms.Initialise(ctx, netCfg); err != nil {
	return err
}

.. Initialize() fails every time.

If I take what GenerateNetworkConfiguration() gives and type it as static config, Initialize() fails for the first time my process runs (when it notices that some config inside the stick doesn't match what I was given), but if I run my process again, it now starts working. So I guess it has something to do with this branch:

z.logger.LogWarn(ctx, "Adapter network configuration is invalid, resetting adapter.")

IIRC the error message I got was adapter rejected permit join state change: state ... (sorry I don't have the dynamic part of that error message written down..) from here

return fmt.Errorf("adapter rejected permit join state change: state=%v", response.Status)

My hardware/firmware is documented in #20

Z-Stack appears to crash on CC2652R

On a large network after an short to medium amount of time the USB stick appears to crash. With two sticks running alongside each other, the network with 50 devices will crash, the stick with 2 devices remains stable.

Looking at zigbee2mqtt's zigbee-herdsman the implementation for znp uses a queue with concurrency limits. Searching their previous issues suggests the crashes can happen due to very busy interaction with the stick.

Our implementation needs to look into a technique to limit concurrency, we could either queue go routines in a queue with a pool of go routines (ala zigbee-herdsman), or some kind of syncronisation mechanism similar to a concurrency limited rwmutex.

Implement persistable network address and IEEE address cache

The Z-Stack software on the CC chips maintain a mapping of network address to IEEE addresses (extended addresses) while they are operating, once they reboot this means that they loose the cache (unless they are children of the coordinator, or there is a binding).

The Z-Stack library has been designed around accepting the IEEE address as a destination for packets/commands because they do not change - as such once the CC chip restarts this can result in z-stack not being able to send messages or receiving messages.

Thus zstack needs a network<->IEEE cache which can be presisted between program runs. Like znp it's probably worth abstracting this out to something that can be provided to zstack on start up, and persisted periodically.

Fix Channel Selection

The channel selection is either ignored or incorrectly managed, as a network of 15 ended up being around 23.

It could be the bitshift logic does not work correctly.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.