How to Develop A Z-Wave Plus Application#

The Z-Wave Plus application’s basic functionality is defined by the device type and role type, which are explained further in [15] and [2]. It is important to determine the right combination of device type and role type for your Z-Wave Plus application.

Once the device and role types are determined, the application development can start.

Create Application Folder and Set Up Build Environment#

Select the Application to Start With#

Pick one of existing software samples and modify it to match your needs. Refer to chapter 5 in [20] for more details. Selection should be made mainly based on Role Type:

  • Always On End Device: SwitchOnOff, WallController, PowerStrip

  • Reporting Sleeping End Device: SensorPIR

  • Listening Sleeping End Device: DoorLock

In addition, there are some additional options to consider:

  • Endpoint implementation is supported by Power Strip.

  • Association groups are implemented in Wall Controller.

Create New Simplicity Studio Project#

Follow these steps to set up a working directory for your application:

  1. Get a general understanding of the build environment, refer to [3] for more information.

  2. Select one of the applications and create an example project in Simplicity studio.

  3. Rename imported application to a suitable name for your application by renaming the root folder, i.e. to <MyApp>.

  4. Navigate in to the <MyApp>/src folder and rename .c file to <MyApp>.c.

  5. As for any other example, search for APP_FREQ and replace it with the frequency you are using. For more details, refer to chapter 6 in [3].

  6. Build the application to verify that the changes did not break anything. It is also possible to download the application to the chip to check that the application runs without error.

Setting Up Files in Non-Volatile Memory#

An NVM3 [18] file system that is used by the ZAF for storing files in non-volatile memory. The file system can also be used by applications to store application specific files.

The application file system occupies an area of 12k bytes in flash. For the file system to function properly in all circumstances it is recommended that one not store more than 4k bytes of data in it, including both application-specific files and files used by ZAF. Application developers must not increase the default size of the application file system (NVM3_APP_NVM_SIZE) since it will make it collide with the area used for storing Z-Wave protocol files.

The NVM3 file system uses a cache in RAM for storing the locations of files in flash. By default, the maximum number of files in the cache is set to 10. If the total number of files used by both ZAF and application exceeds 10, NVM3_APP_CACHE_SIZE in zpal_nvm.c must be increased accordingly, or the file system will slow down considerably.

Each file in an NVM3 file system is assigned an individual 20-bit object key as file identifier [18]. Application specific files should use identifiers in the range 0x00000—0x0FFFF, so the effective identifier address range is reduced to 16 bits.

File identifiers used by the ZAF are listed in ZAF_file_ids.h. These are guaranteed not to clash with the Application specific files, as the Z-Wave Platform Abstraction layer assigns these files a handle in a range that is different from the Application specific files (the exact range varies based on the actual hardware used. See zpal_nvm.c for more details).

All application file systems must have a file with file identifier ZAF_FILE_ID_APP_VERSION that contains the version number of the application. On startup, the application checks if this file is present. If it is missing, the file system is considered unwritten or corrupt, leading to reformatting of the file system and all files being reset to their default state.

To introduce an application-specific file to the file system, just define a new 16-bit object key number that is not already in use. The file will be added to the file system once it is first written using the function ZAF_nvm_app_write() [19].

#define FILE_ID_MYNEWFILE 0x0112

typedef struct SMyAppData
{
  int32_t status;
  uint8_t vector[10];
} SMyAppData;

SMyAppData wrtData;
wrtData.status = -5;
memset(&(wrtData.vector), 0x55, sizeof(wrtData.vector));

// This call will create and write a file containing wrtData in the file system.
ZAF_nvm_app_write(FILE_ID_MYNEWFILE, &wrtData, sizeof(wrtData));

SMyAppData rdData;

/*
 * This call will read the content of the file to rdData. Reading only part of a file
 * is also possible, using the function ZAF_nvm_app_read_object_part().
 */
ZAF_nvm_app_read(FILE_ID_MYNEWFILE, &rdData, sizeof(rdData));

It is recommended that one write default content to application-specific files in the function zaf_nvm_app_set_default_configuration(), that gets called when a device is excluded or included into a new network.

When performing a firmware update, the content of the application file system remains intact. If the file identifiers remain the same, old files used by the previous firmware can still be used. When developing a newer version of the firmware where new files are added or files are changed in any way, it is necessary to increase the APP_VERSION, APP_REVISION or APP_PATCH number in the application's SLC project (.slcp) file. The bootloader does not accept files having a lower or equal version number. Then, on the first startup of the new firmware, the application will be able to recognize that the file system on the chip belongs to an older version of the firmware by reading the file with identifier ZAF_FILE_ID_APP_VERSION. The function zaf_nvm_app_load_configuration_migration() can then be used to perform the necessary migration steps.

Watchdog Enable/Disable#

The watchdog timer is enabled in the application by default and will reset the device if the application task runs for more than 1 second without releasing the processor.

To disable the watchdog during development, comment out the WDOGn_Enable() function call inside the ApplicationTask function as shown below:

Note: The watchdog should always be enabled in production code but can be disabled in debug build.

static void
ApplicationTask(SApplicationHandles* pAppHandles)
{
  // Init
  DPRINT("Enabling watchdog\n");
  WDOGn_Enable(DEFAULT_WDOG, true); // <-- Comment out this line to disable the watchdog
}

Source File#

The application must implement one function for initialization: ApplicationInit(..). The function is called by the Z-Wave main function during system startup and gets the wakeup reason as input argument, see the ZW_basis_api.h file. Application specific hardware initializations like pin configuration should be done from this function. After configuration is finished the function must register an application task by calling ZW_UserTask_ApplicationRegisterTask(..) so that the application can start running.

The application task function, ApplicationTask(..), is registered by FreeRTOS in the list of tasks that are ready to run. In the software examples, ApplicationTask(..) first performs application specific initialization of software on startup. Among other things it configures the event distribution functionality with event handlers for different types of events. Subsequently it goes into an infinite loop where it lets the event distributor handle any events. See the description in Event Distributor. The FreeRTOS scheduler will ensure that the application task is given processor time according to its priority.

Endpoint Configuration#

Please see “When and how to create a Multi Channel application” in the Doxygen output.

Multi-Threaded Applications#

A Z-Wave application runs by default the following threads:

  • The Z-Wave protocol thread for the execution of the protocol stack

  • The main application thread for the execution of the application code, and as an interface to the Z-Wave protocol thread.

An application can be extended creating additional FreeRTOS threads\tasks. These threads are called User Tasks to differentiate them from the main application thread (responsible for the communication with the Z-Wave protocol thread).

The User Tasks are created using the ZW_UserTask module (ZW_UserTask.h), which contains the definition of the ZW_UserTask_t data type and the available APIs to create a task.

ZW_UserTask_t is defined as follows:

typedef struct {
  TaskFunction_t         pTaskFunc;
  char*                  pTaskName;
  void*                  pUserTaskParam;
  ZW_UserTask_Priority_t priority;
  ZW_UserTask_Buffer_t*  taskBuffer;
} ZW_UserTask_t;

Where

  • pTaskFunc is the function executed by the task

  • pTaskName is the name of the task

  • pUserTaskParam is the pointer to the parameter passed to the task function

  • priority is the priority level of the task. The available priorities are:

    • USERTASK_PRIORITY_BACKGROUND

    • USERTASK_PRIORITY_NORMAL

    • USERTASK_PRIORITY_HIGHEST

  • taskBuffer is the statically allocated memory required by FreeRTOS to handle the task.

The following APIs are available:

ReturnCode_t ZW_UserTask_Init(void);
  • ZW\_UserTask\_Init() function is used to initialize the ZW_UserTask module. It does not need to be explicitly called as it is already called at boot time.

  • If this function is called again, it will return Code_Fail_InvalidState.

ReturnCode_t ZW_UserTask_CreateTask(ZW_UserTask_t\* task, TaskHandle_t\* xHandle);
  • ZW_UserTask_CreateTask() can be used to create additional tasks.

    • This function requires the following input parameters:

      • task is the pointer to the ZW_UserTask task parameters.

      • xHandle is the returned task handle by FreeRTOS.

    • This function can return the following codes:

      • Code_Fail_InvalidState: if the module is not yet initialized.

      • Code_Fail_InvalidOperation: if the maximum of user tasks is already created.

      • Code_Fail_InvalidParameter: if the priority setting is out of bounds or any of the input parameters are NULL.

      • Code_Fail_Unknown: if FreeRTOS fails to create the task.

bool ZW_UserTask_ApplicationRegisterTask(
                                  VOID_CALLBACKFUNC(appTaskFunc)(SApplicationHandles*),
                                  
                                  uint8_t iZwRxQueueTaskNotificationBitNumber,
                                  
                                  uint8_t iZwCommandStatusQueueTaskNotificationBitNumber,
                                  
                                  const SProtocolConfig_t * pProtocolConfig
);
  • ZW_UserTask_ApplicationRegisterTask() is used to create the main application thread. For additional details on the function usage and behavior see Source File and 9.5.2.

For additional information on the APIs, consult the ZW_UserTask.h file. For detailed information on the available return codes see ReturnCode_t defined in ZW_global_definitions.h.

An example of how to use these APIs is available in the SensorPIR application (see ApplicationInit(…), in SensorPIR.c).

Limitations and Recommendations#

The following limitations and recommendations apply to the User Tasks:

  • A maximum of three User Tasks can be created in addition to the main application thread.

  • The User Task priorities are allocated such that they are all below the priority of the Z-Wave protocol thread (this is enforced within the ZW_UserTask module, where the available priority levels are defined).

  • User Tasks must be created (using function ZW_UserTask_CreateTask) before the FreeRTOS scheduler starts, therefore, their creation is only possible within ApplicationInit(…) (see Source File).

  • The communication between the main application thread and the User Tasks is not provided. However, the use of FreeRTOS queues and events is recommended.

  • The User Tasks cannot communicate directly with the Z-Wave protocol thread. They need to use the main application thread as intermediary.

  • With the exception of the power manager APIs that can be called from all tasks.