Step 3b: ZWave - NIF and CC_VERSION

 

The code for this step is found here.

 

Our application in Step 3a sends a NIF with a bogus command class list. We want to fix that, and, since we haven't created any commands, will add one, CC_VERSION. This means we need to finish setting up the framework, figuring out how frames are routed through the event handlers to interact with the command class code. We then build the correct command class list and provide it to ZAF.

 

Incoming frames arrive in two places. The framework leaves a stub that the transport layer calls, Transport_ApplicationCommandHandlerEx(), for command classes that it has already unpacked. The frame handler stores the frame as the payload to a SZwaveReceivePackage structure, but does not unpack it. There are separate ways of processing the data.

 

An unpacked command is put in a ZW_APPLICATION_TX_BUFFER structure, whose first two members are the command class and command bytes. The third member is a structure particular to the command, and the type of this member is a union of all possibilities. ${SDK}/ZWave/API/ZW_classcmd.h defines these. ZW_VERSION_REPORT_2BYTE_V2_FRAME, for example, has eleven more bytes with the library type, protocol version, firmware version, hardware version, and up to two additional firmware targets.

 

Transport_ApplicationCommandHandlerEx() is passed this structure, along with the receive options flags and frame size. It switches on the command class byte and calls the command class directly. The ZAF command classes call this a handler.

 

The incoming frame handler pulls the frame off the ZwRxQueue, of type SZwaveReceivePackage, defined in ${SDK}/ZWave/API/ZW_application_transport_interface.h. The data is identified with an EZwaveReceiveType enumeration, with values for SINGLE, MULTI, NODE_UPDATE, and SECURITY_EVENT. The second member, uReceiveParams, is a union of structures, one per type. For SINGLE the structure includes the payload, its length, and the receive options flags; MULTI also adds a node mask. NODE_UPDATE is for controllers, and SECURITY_EVENT for a specific S2 need. The payload has not been unpacked, so ZAF includes another notification system called the CommandPublisher to do that. The publisher knows about command classes by some trickery during the build: the command class source file uses the REGISTER_CC() macro to create a global variable that's added to an array in a named section of memory (called the HANDLER_SECTION) during compilation. The publisher can loop through the array to find the command class and call the corresponding receive handler after unpacking the frame; it can also pick a handler from the command byte.

 

The application's handle_frame() pulls a frame off the queue and switches on the received type, where we need only support SINGLE. It hands off the payload to the CommandPublisher which does the unpacking and calls the appropriate function in the appropriate command class. The application source code contains a comment showing how you can start to pick apart the payload if you wish.

 

ZAF needs to know which command classes the application actually supports, which may differ from those available through the publisher. It expects up to three byte arrays with the CC lists, one for unsecured commands, a second for those that can be accessed unsecured even if the node is included securely, and a third for secured. These arrays and their length go in a global variable of type app_node_information_t, along with the device options mask we've put in src/config_app.h (set to APPLICATION_NODEINFO_LISTENING) and the node's generic and specific type, also defined in src/config_app.h. For this step we're just going to support Version unsecured, and will pass empty pointers for the other two options; we'll see how to include secure commands in Step 3e. This NIF is passed to Transport_OnApplicationInitSW() at the start of the application task.

 

Confusingly, the framework actually contains two versions of the NIF, in two different structures. In addition to app_node_information_t, there is also a SAppNodeInfo_t, which is stored in a second global variable appnif2 and passed to ZW_ApplicationRegisterTask() in the initialization function. The two structures contain the same information, just with different names and in a different order (arrays and lengths are swapped). We add a function copy_nif() to transform between the two.

 

To put all this simply, adding a command class requires:

OK, maybe that's not so simple. It's certainly repetitive. If you compare the changes to the application source code between Step 3a and Step 3b, you'll see this is all that's happened (plus one addition for Version, described below). It requires several changes to the project's Properties and files:

  1. Removing ${SDK}/ZAF/CommandClasses/Common from the C/C++ Include list.

  2. Linking on the file system ${SDK}/ZAF/CommandClasses/Common/CC_Common.h under ZAF_CC/.

  3. Linking on the file system ${SDK}/ZAF/CommandClasses/Version/CC_Version.h under ZAF_CC/.

  4. Linking on the file system ${SDK}/ZAF/CommandClasses/Supervision/CC_Supervision.h under ZAF_CC/.

  5. Linking on the file system ${SDK}/ZAF/CommandClasses/MultiChan/multichannel.h under ZAF_CC/.

  6. Linking ${SDK}/ZAF/ApplicationUtilities/ZAF_tx_mutex.c under ZAF_AppUtil/.

  7. Linking ${SDK}/ZAF/CommandClasses/Supervision/CC_Supervision.c under ZAF_CC/.

  8. Removing our edited ZAF_AppUtil/ZW_TransportEndpoint.c.

  9. Linking ${SDK}/ZAF/ApplicationUtilities/ZW_TransportEndpoint.c under ZAF_AppUtil/.

  10. Adding '#define NUMBER_OF_ENDPOINTS 0' to src/config_app.h.

It is important in steps #2-5 that the links be created on disk and not within Simplicity Studio, otherwise the header files will not physically exist and the build will fail. This is not true for source files as in #6 and #7, which correctly translate into SDK references when the makefile is built. This project layout is a preference for us, to keep things in a concise file hierarchy and to shorten the include path and compiler command line. There are two alternatives Silicon Labs prefers, based on the demo programs. You could instead add each command class directory to the C/C++ Include list, or you could use project modules within Simplicity Studio. On the project Properties, the build section contains an entry called Project Modules, and here you can check which commands you want to use. Simplicity Studio will then make a local copy on disk from the SDK, one directory per command class.

 

Bringing up the Version command requires a few more changes. The command itself is built into the framework and we don't need to start from scratch. That implementation leaves three functions as stubs, which we must supply in our ZAF/CC_Version.c: CC_Version_getNumberOfFirmwareTargets(), CC_Version_GetFirmwareVersion_handler(), and CC_Version_GetHardwareVersion_handler(). We fill in dummy values for each of these. The source code requires the corresponding header which was linked in #3 above. Finally, during the application's initialization we need to call CC_Version_SetApplicationVersionInfo() with the values we've defined in src/config_app.h.

 

At this time we've undone the simplifications back in Step 2a to get the framework set up, except for the edits to ZAF_TSE.c to avoid dealing with association groups. The remaining source code edits deal with setting up the NIF and command routing and implementing the missing pieces to the CC_Version functionality that's built in to the framework.

 

 

object

status

role

description

appnif1 (global var)

new

transport

NIF for transport layer

ApplicationInit

edit

ZAF

set NIFs, version information

ApplicationTask

edit

ZAF

register transport layer NIF

Transport_AppCmdHandlerEx

new

transport

transport layer command router

handle_frame

edit

ZAF

pass frame to command publisher

copy_nif

new

ZAF

copy transport NIF to protocol NIF

ZAF_CC/Version.c

new

command

add missing functions

 

In the file listing we've made ZW_TransportEndpoint.c a Simplicity Studio link and populated the command class directory.

 

hw/

src/

ZAF_AppUtil/

ZAF_CC/

displayconfigapp.h

config_app.h

ZAF_TSE.c

CC_Version.c

displayfont16x20HT.h

config_rf.h

 

 

em_types.h

HTSensor1.c

 

 

graphdisplay.c

i2cspmconfig.h

 

 

graphdisplay.h

 

 

 

textdisplay.c

 

 

 

testdisplayconfig.h

 

 

 

textdisplay.h

 

 

 

linked within Simplicity Studio

display.c

 

application_properties.c

CC_Supervision.c

displayls013b7dh03.c

 

AppTimer.c

multichannel.c

displaypalemlib.c

 

board_BRD420x.c

 

em_i2c.c

 

board_indicator.c

 

em_letimer.c

 

board.c

 

em_prs.c

 

ZAF_command_class_utils.c

 

gpiointerrupts.c

 

zaf_event_helper.c

 

i2cspm.c

 

ZAF_network_learn.c

 

startup_zgm13.S

 

ZAF_uart_utils.c

 

system_zgm13.c

 

ZW_TransportEndpoint.c

 

 

 

ZW_TransportMulticast.c

 

 

 

ZW_TransportSecProtocol.c

 

linked on file system

 

 

 

CC_Common.h

 

 

 

CC_Supervision.h

 

 

 

CC_Version.h

 

 

 

multichannel.h

 

Sending VERSION_GET will return the right information. The command list has 0x86 (Version) and we see the correction application and library versions. Now we can implement the sensor class.

 

step3B_zipgw

zipgateway information after inclusion. Command classes now include 0x86 (Version).

 

zpiffer trace of inclusion with CC_Version

zpiffer trace of inclusion. AppUpdate in left (blue) window shows CC_Version.

 

probing of CC_Version during inclusion

zpiffer shows further probing of CC_Version during inclusion, including command class capabilities (left/blue) and ZWave and app versions (right/red). The SDK and ZW fields match the library version, the app fields match config_app.h.