/zstack

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

Primary LanguageGoApache License 2.0Apache-2.0

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.