Step 4: Application Framework

 

This brings us to the end of the demo. We've learned how projects are set up in Simplicity Studio, created one from scratch, and added in the SDK hardware and ZAF files needed to compile a program within the framework. We needed to link some hardware source files, used some code libraries (components), added libraries for Zwave and radio testing, defined a few symbols for changing the configuration of the hardware files, and set many many directories on the include path. The Application Framework runs under FreeRTOS and, at the the application level at least, uses a single task and four event handlers. Events can come from the hardware (button presses), state changes within the framework (EZWCOMMANDSTATUS_*), or incoming frames routed by the packet type (EZWAVERECEIVETYPE_*) or, for ZWave commands embedded in a frame, by the command class (ZW_Common member of ZW_APPLICATION_TX_BUFFER). The packet type router uses the CommandPublisher to unpack a frame and send it to the command handler, if the transport layer hasn't already done so. New ZWave command classes are added in a file with a function that acts on the specific command (ZW_Command member of ZW_APPLICATION_TX_BUFFER); it may also have an outgoing path for application events (for us, button presses) and replies. Security is handled automatically according to the contents of the node information frame (NIF).

 

We can define the skeleton of a ZAF project; it will include a minimum amount of configuration information, a FreeRTOS task, event handlers, and whichever ZWave commands are needed by the product.

  1. Define version, device type, manufacturer and product ID, radio parameters, number of endpoints, and security in config_app.h and config_rf.h.

  2. Define the NIF contents in a global variable, holding arrays of command classes grouped by security level.

  3. Implement ApplicationInit(), which does initial set up and registers the task with the application.

    1. Provide ApplicationTask() which does board level set up and creates the event queue, then enters the main loop that distributes events to one of four handlers:

      1. AppTimerNotificationHandler(), defined by ZAF.

      2. handle_frame(), which routes incoming frames based on received packet type, and which calls the CommandPublisher to unpack embedded ZWave commands.

      3. handle_cmdstatus(), which routes framework/ZWave state changes, for example for learning.

      4. handle_event(), which routes hardware events.

  4. Implement Transport_ApplicationCommandHandlerEx(), which sends frames that have been unpacked by the transport layer to the appropriate command class.

  5. Implement application-specific functionality for the command classes. This depends on the command class; see binary switch and user code for example, or for this project, the multilevel sensor and configuration commands.

The rest of the demo implements functionality triggered off these events or command classes.

 

The Framework contains more than 60 source and header files, excluding the command classes. We've only used a small part of what it offers. Unfortunately the only documentation seems to be reading the source. As a central reference, we've used the following functions:

AppTimer.[ch]

function

role

called from

AppTimerInit

set up

ApplicationInit()

AppTimerSetReceiverTask

cache FreeRTOS task in timer

ApplicationTask()

AppTimerRegister

set callback function when timer expires

ApplicationTask()

AppTimerNotificationHandler

framework handler for timer events

ApplicationTask()

 

board.[ch]

function

role

called from

BTN_EVENT_*

macros to decode button presses

HW event handler

Board_Init

set up

ApplicationInit()

Board_SetLed

turn LED on or off

internal

Board_IndicatorInit

set up LED for learn mode

ApplicationTask()

Board_IndicatorControl

turn indicator on/off or set flashing

internal

Board_EnableButton

set up push buttons

ApplicationTask()

 

ZAF_CmdPublisher.[ch]

function

role

called from

ZAF_CP_CommandPublish

route singlecast packet to commands

frame handler

 

ZAF_network_learn.[ch]

function

role

called from

ZAF_setNetworkLearnMode

start/stop learn mode from push button

HW event handler

 

ZAF_uart_utils.[ch]

function

role

called from

ZAF_UART0_enable

start UART for debug logging (DPRINTF)

ApplicationInit()

ZAF_UART0_tx_send

send debug log over UART

ApplicationInit()

 

ZW_TransportEndpoint.[ch]

function

role

called from

Transport_SendResponseEP

transmit REPORT or other response

command class

Transport_Application

CommandHandlerEx

handle incoming frames by packet type

event distributor

RxToTxOptions

convert receive serial options to transmit

command class

Check_not_legal_response_job

verify multicast/supervision encapsulation

command class

 

ZAF_Common_interface.h

function

role

called from

ZAF_getAppHandle

get pointer to ZWave configuration, queues

HW event handler

ZAF_GetSecurityKeys

bit-encoded list of security keys available

command class

ZAF_getCPHandle

get pointer to CommandPublisher

frame handler

 

zaf_event_helper.[ch]

function

role

called from

ZAF_EventHelperInit

start event queue

ApplicationTask

 

ZAF_TSE.[ch]

function

role

called from

ZAF_TSE_TXCallback

callback for Transport_SendRequestEP

ZAF status handler

 

The Called From column indicates where the function is used in the demo. It isn't clear what has to go in ApplicationInit() and what in ApplicationTask().

 

A complete ZWave device must implement more functionality. ZAF is designed to easily support these extra requirements, including all command classes for ZWave+, Smart Start, and application groups and the True Search Engine (lifeline support). The five demo programs in the SDK have a similar architecture to each other. They have more application states than ours, and use the event queue not just for ZWave functionality but also for custom events that manage the application state. The logic defining the state machine/event handler is often fairly involved. Comparing our demo against those in the SDK, as well as the ZAF specification INS14259, gives us an idea of the additional work we would need to do to make this a full-fledged product.

 

ZWave+ Command Classes. At the start of Step 3a we listed the command classes required by ZWave+. The common ones are available in the SDK under ZAF/CommandClasses and can be included as a project module or by linking the source and header files into the project, as we did for CC_Security and CC_Supervision.

 

Application Events. We have used only the events ZAF generates for button presses in handle_event(). The SDK demos use a richer set to handle basic functionality as well as application-specific states, and they use a separate job queue to temporarily store work when reacting to button presses in the event loop. The events are defined in the header file events.h in the source directory; ZAF provides no standard list, and all are application-specific. The complete list over the five SDK demos shows us the type of functionality they track; a star (*) before an event means it's provided in most of the demos and can be considered key functionality.

 

EVENT_APP_*

Lock

Power Strip

Sensor PIR

OnOff Switch

Wall Ctlr

*

INIT

 

x

x

x

x

*

REFRESH_MMI

x

x

 

x

x

*

FLUSHMEM_READY

x

x

x

x

x

*

SMARTSTART_IN_PROGRESS

x

x

x

x

x

*

LEARN_IN_PROGRESS

x

x

x

x

x

 

LEARN_MODE_FINISH

 

 

 

 

x

 

TOGGLE_LEARN_MODE

 

 

 

 

x

*

NEXT_EVENT_JOB

x

 

x

 

x

*

FINISH_EVENT_JOB

x

x

x

 

x

 

BASIC_STOP_JOB

 

 

x

 

 

 

BASIC_START_JOB

 

 

x

 

 

 

NOTIFICATION_START_JOB

 

 

x

 

 

 

NOTIFICATION_STOP_JOB

 

 

x

 

 

 

START_TIMER_EVENTJOB_STOP

 

 

x

 

 

 

CC_BASIC_JOB

 

 

 

 

x

 

CC_SWITCH_MULTILEVEL_JOB

 

 

 

 

x

 

CENTRAL_SCENE_JOB

 

 

 

 

x

 

SEND_OVERLOAD_NOTIFICATION

 

x

 

 

 

 

SEND_BATTERY_LEVEL_REPORT

x

 

x

 

 

 

IS_POWERING_DOWN

x

 

x

 

 

 

START_USER_CODE_EVENT

x

 

 

 

 

 

START_KEYPAD_ACTIVE

x

 

 

 

 

 

FINISH_KEYPAD_ACTIVE

x

 

 

 

 

 

PERIODIC_BATTERY_CHECK_TRIGGER

x

 

 

 

 

 

REFRESH_MMI handles the network indicator. FLUSHMEM_READY resets non-volatile memory and re-loads the application configuration or does a soft reset, depending on the application state. SMARTSTART_IN_PROGRESS, LEARN_IN_PROGRESS, LEARN_MODE_FINISH, and TOGGLE_LEARN_MODE track progress through inclusion and transition into SmartStart modes.

 

Job Queue. The job queue is used to delay executing actions while the application is busy processing something else. It stores application events. The demos push NEXT_EVENT_JOB on the event queue which, when handled, will take the top job event and push it onto the event queue, or FINISH_EVENT_JOB if there are no jobs. Only one event is moved at a time, to disturb the main event flow as little as possible. NEXT_EVENT_JOB is typically pushed after a transmission is done, either if it's sent directly or in a callback. The use of the job queue can be quite simple, as in the PowerStrip demo where it's used to cleanup a notification and switch to the idle application state, or complex, as in the WallController demo which uses it to work through central scene notifications, or may not be used at all, as in the SwitchOnOff demo.

 

SmartStart. The SDK demos implement SmartStart by finer control of the application state. They define an enumeration _STATE_APP_ to track progress through start-up and inclusion, and a function AppStateManager() to transition through the states as application events come in. In our demo we've simplified the state to a couple of booleans, tracking if the log is running or we're including or excluding the device, and transitions are scattered through the code. The AppStateManager() replaces our handle_event(). SmartStart learn mode begins in EVENT_APP_INIT, the initialization state, with a call to ZAF_setNetworkLearnMode(). It also triggers when learning is stopped by a button press (Button1 in our list), if inclusion fails, or if the device is excluded from a network.

 

Association Groups. Association groups are created with several defines in app_config.h, including NUMBER_OF_ENDPOINTS, MAX_ASSOCIATION_GROUPS, MAX_ASSOCIATION_IN_GROUP, AGITABLE_LIFELINE_GROUP, AGITABLE_ROOTDEVICE_GROUPS, AGITABLE_ENDPOINT_*_GROUPS, and ASSOCIATION_ROOT_GROUP_MAPPING_CONFIG. The first three are numbers, the fourth a comma-separated list of pairs of command classes and commands, and the remaining are described in INS14259 Section 5.2.4. The application contains globals to hold the setup, with agiTableLifeLine[] initialized to AGITABLE_LIFELINEGROUP and agiTableRootDeviceGroups[] to AGITABLE_ROOTDEVICE_GROUPS. These are passed to CC_AGI_LifeLineGroupSetup() and AGI_ResourceGroupSetup() during application initialization. The WallController demo has a simple association group setup, the PowerStrip a much more complicated one, with multiple endpoints. Sections 5.6.2 and 7.1.1 of INS14259 also have more details.

 

True Status Engine. The True Status Engine is used to send reports and notifications to the lifeline after a short delay to avoid packet collisions and spamming the network. Command classes will define callback functions CC_<cmdclass>_report_stx(). The function has two arguments, transmit options with the destination filled in, and a command-specific data structure that includes as its first member a RECEIVE_OPTIONS_TYPE_EX structure with information about the incoming frame. The callback will build the appropriate frame and will call Transport_SendRequestEP(). The application should call ZAF_TSE_Trigger() on an event or state change that generates traffic to the lifeline.

 

NVM. To use non-volatile memory the application must define a structure to hold application data that must persist over power loss, and an associated global variable. Accessing memory is done through the driver in ${SDK}/platform/emdrv/nvm3 and ${SDK}/ZAF/ApplicationUtilities/ZAF_nvm3_app.[ch]. Call ApplicationFileSystemInit() to get a pointer to the file system stored in NVM, normally when loading the configuration during application initialization. ${SDK}/ZAF/ApplicationUtilities/ZAF_file_ids.h defines some standard entries that the framework or command classes will use or provide:

ZAF_FILE_ID_APP_VERSION

ZAF version numbers (required)

ZAF_FILE_ID_ASSOCIATIONINFO

groups per endpoint

ZAF_FILE_ID_USERCODE

receive options, user ID

ZAF_FILE_ID_BATTERYDATA

last reported level

ZAF_FILE_ID_NOTIFICATIONDATA

alarm status

ZAF_FILE_ID_WAKEUPCCDATA

master node and sleep period

 

Each has an accompanying ZAF_FILE_SIZE_* and both are needed in the global variable that contains all file system entries. Application-specific data is defined by setting a file identifier, normally 0, and the size of the structure. The first can go in config_app.h, the second in the application source. For example, a binary switch might need to store the application version, association groups, battery level, and switch state. The local NVM structure and combined file system would look like:

struct NVMAppData { uint8_t switchOn; }

SFileDescriptor fileIDs[] = {

{ .ObjectKey=ZAF_FILE_ID_APP_VERSION, .iDataSize=ZAF_FILE_SIZE_APP_VERSION },

{ .ObjectKey=ZAF_FILE_ID_ASSOCIATIONINFO, .iDataSize=ZAF_FILE_SIZE_ASSOCIATIONINFO },

{ .ObjectKey=ZAF_FILE_ID_BATTERYDATA, .iDataSize=ZAF_FILE_SIZE_BATTERYDATA },

{ .ObjectKey=0x00, .iDataSize=sizeof(struct NVMAppData) }

}

EFileSysVerifyStatus fileStatus[sizeof_array(fileIDs)];

Associations are stored after learning is complete with a call to AssociationInit(). The application version is stored when initializing the NVM file system or after a reset with a call to nvm3_writeData(). Command classes may or may not handle their own file: wake-up and notification do, battery and user code do not. If not then the application is responsible for saving changes. Getting information out of NVM is done with nvm3_readData(). Both take the file ID, a pointer to the source or destination memory, and the size of the structure. Application data should be written after every state change.

 

Power Management. There are five power management modes: EM0 (active), EM1 (sleep), EM2 (deep sleep), EM3 (stop), EM4 (hibernate), and EM4 (shutdown). The data sheet DSH14299 defines which hardware modules are active in which modes. The LETimer, for example, is available in EM0, EM1, and EM2, as is the LESense sensor interface. Peripherals are grouped in two domains. The first includes one analog comparator, the pulse counter, ADC, LETimer, LESense, and analog port. The second has the other analog comparator, capsense, voltage and current DACs, UART, and I2C. If all members of a group are unused then all are powered off in low-power modes. Wake-up times vary, being 3 clocks out of EM1, 4-11 us from EM2 or EM3, 90 us from hibernate, and 300 us from shutdown. The power manager is set up in ${SDK}/ZWave/API/ZW_PowerManager_api.h, which defines a power level SPowerLock_t to prevent the chip from entering a lower power mode, and ${SDK}/ZAF/ApplicationUtilities/PowerManagement/ZAF_PM_Wrapper.h, which has functions to set this level. PM_TYPE_RADIO will prevent the chip from entering EM2-4, PM_TYPE_PERIPHERAL from EM3-4. ZAF_PM_StayAwake() and ZAF_PM_Cancel() bracket the code section where the power level is set. ZAF_PM_Register() sets up the lock for the power level. For example, the wakeup command class will set PM_TYPE_RADIO for 10 seconds after a wake-up notification, canceling it early after a No More Information has been sent. Note that the AppTimer also provides EM4 functionality.