Six Tips to Harden Z-Wave Firmware: Ship reliable products that withstand errors and reduce returns

July 15, 2026

Blog

Once a Z-Wave device is installed, it often remains in place for many years. Indeed, thanks to Z-Wave’s low power consumption and guaranteed backwards compatibility, these smart home devices can be continuously used for decades. This includes sensors in inaccessible locations and mains-powered devices that cannot be easily rebooted: If a device bricks, it could be out of commission for months or even years. If your firmware is designed for resiliency, however, it won’t brick when it encounters a bug or impossible state. Built-in failsafes ensure the end users never even know something went wrong.

In this article, I will share best practices for hardening Z-Wave firmware, gleaned over more than two decades of Z-Wave development experience. This piece refers specifically to the Silicon Labs SDK, but similar techniques apply to other Z-Wave silicon providers, such as Trident IoT and most recently, Semtech.

Tip 1: Assume Everything is Broken

Whenever you write code, just assume that somehow, somewhere, it’s already broken. The hardware will fail to go “ready,” a queue is always full, a mutex never switches, an impossible state will occur. With this pessimistic philosophy in mind, always check for error conditions of any function that returns a value. Always check inputs for validity. 

The most insidious failures are stack overflows. They can cause untold damage, and due to the limited RAM on Z-Wave chips, it’s easy to overflow the stack. During a stack overflow, code can keep running even after destroying hundreds of memory locations.  

You can also get unpredictable errors when the power supply sags just enough to flip bits in ways that are supposedly impossible. Strong magnetic fields from nearby motors and even cosmic radiation can flip bits in “impossible” ways. There is truly nothing that “can’t happen”. Thus, always code with the assumption that no error is impossible. 

Tip 2: Use FOR instead of WHILE

The Z-Wave SDK includes several while(hardware_busy) loops. Under the wrong conditions, these loops can become infinite, causing the device to brick. For example, if you enable the LFXO (32KHz crystal oscillator) but don’t have the crystal wired up, the startup code waits for the LFXO to be “ready” with a while loop. This is an easy enough error to catch when debugging firmware, but, if the crystal stops working in the field, you’ve got a brick. 

The simple solution to this is to add a FOR loop with a timeout, enabling the code to continue. 

Example in em_cmu.c:

Replace: while ((LFXO->STATUS & _LFXO_STATUS_ENS_MASK) != 0U) {  }
With: for (int i=0; (i<1000)&&((LFXO->STATUS & _LFXO_STATUS_ENS_MASK) != 0U); i++) {__NOP()}

Note the __NOP() is necessary to prevent the compiler from optimizing the loop and removing it. The timeout value (1000 in this case) must be chosen based on testing. I usually set it to 10X the typical value. 

Following the FOR should be an assert to check that the timeout didn’t occur. Note the use of < (and not ==) for the check of the timeout. If the impossible were to happen, there is a chance “i” could skip past exactly 1000. Since this is a 32-bit number, the timeout would then be waiting for the 32-bit number to wrap all the way around. This is a subtle but important habit good embedded engineers develop over time.

Tip 3: Debugging Default Handler

The Silicon Labs SDK has only a single line for a default handler:  while (true); . This code counts on the watchdog to eventually reboot the chip; reboot isn’t guaranteed and there is no additional debugging information. All the fault handlers are mapped into this single handler, but they can be individually overridden due to weak assignments. Even something as simple as a divide by zero can cause a fault handler to be called and brick the device. 

Segger has a great article on debugging the many “fault handlers” in the Cortex-M processors. The article provides code to help debug the fault and make the code more resilient. At a minimum, put the Segger-recommended code in for at least some of the exception handlers to make debug easier. Generally, it is a good idea to light an LED or some other external indicator to help during debug.  

Tip 4: Train the Watchdog

Watchdog timers are crucial for reliable operation of an IoT device. A watchdog timer is a timer that slowly counts down. Every now and then, the firmware “feeds” the watchdog by resetting the counter to a high value. If the counter reaches zero, the watchdog “fires,” triggering a full reset of the chip to hopefully resolve the error condition. The trick to a resilient watchdog is deciding when to feed it, and more importantly, when not to. I wrote a blog post on watchdog timer best practices in 2018 which still applies today. Essentially, I advise that the watchdog should only be fed when everything is idle

Depending on the silicon and SDK you are using, you may need to improve the watchdog to ensure the chip resets when necessary. For example, some implementations feed the watchdog  every time the FreeRTOS idle task is executed. In this case, the watchdog will fire only if FreeRTOS crashes and stops servicing the idle task, or if a task sits in a tight loop long enough. However, there are plenty of error conditions that brick the device but continue to execute the idle task. 

Z-Wave chips have a second watchdog timer so you can create your own robust watchdog following the best practices. To improve the watchdog, identify everything that can cause the code to lock up. Check that all queues, mutexes, state machines, and perhaps even peripherals are idle before feeding the watchdog. 

As a side note, it is also crucial to disable the watchdog during development. Watchdog resets are notoriously good at hiding faults. When the watchdog is active, a chip may briefly stop but then work perfectly as soon as you re-send the command. The chip rebooted, hiding the bug: great in the field, but not during development. 

Tip 5: Reboot When no Communication for a Day

If the controller hasn’t sent a frame and/or hasn’t acknowledged the receipt of a frame in twenty-four hours, a reboot might clear things up. Set up a 24-hour software timer and check the RX/TX statistics. If nothing has made it through, reboot. This fixes rare, “impossible” conditions before most customers even notice something is wrong. 

This check is only needed for always-on or FLiRs (LSEN) devices; deep-sleeping devices reboot every time they wake up. Below is an implementation you can drop into your application. 

// Usually this is in app.c or your own application files

#include <AppTimer.h>

#define ONCE_PER_DAY (1000*60*60*24UL)

static SSwTimer CheckTxStatsTimer;

void CheckTxStatsCallback(SSwTimer *pTimer){

  pStats = ZAF_getNetworkStatistics();

  if ((pStats->tx_frames == 0) && (pStats->rx_frames == 0)) {

    while(true);  // reboot if no frames have been transmitted or received in the last 24hrs

  }

  zpal_radio_clear_network_stats(); // zero the stats.

}

// in ApplicationTask just before the FOR loop

AppTimerInit(EAPPLICATIONEVENT_TIMER, xTaskGetCurrentTaskHandle());

AppTimerRegister(&CheckTxStatsTimer, true, CheckTxStatsCallback); // auto reloads

TimerStart(&CheckTxStatsTimer,ONCE_PER_DAY);

Tip 6: Stack Overflow Checking

Stack overflows often require several things to go wrong at the same time. They are rare and hard to replicate by nature, and thus frequently overlooked. Unfortunately, they occur frequently in the field.

FreeRTOS has a feature to check for a stack overflow. The variable configCHECK_FOR_STACK_OVERFLOW is set to 2 by default in FreeRTOSConfig.h. This enables some checking and fills the stack space with 0xA5s, which is then checked with each task switch. Inspecting RAM after running the code for some time in the debugger can provide insights as to how close to overflowing the stack has come so far. The check calls vApplicationStackOverflowHook if there is a failure, but there is only an assert in the weak function. My recommendation is to add a breakpoint here during testing and consider rebooting in the released code. 

Stack overflow checking is only recommended during development and testing due to the additional overhead.

Resources

The tips above will help you improve any Z-Wave firmware, but debugging is an art every developer studies for a lifetime. I highly recommend Jack Ganssle’s Embedded Muse newsletter as a continuing education resource. He features a failure case study in every issue, with plenty of interesting stories and tips on resilient embedded C coding. Micheal Barrs’ C Coding Standard should also be on every firmware engineer’s bookshelf: it is filled with practical advice on coding for resilience and maintainability. You can also find more resources dedicated to Z-Wave device development on the Z-Wave Alliance website, including my own ongoing Developer’s Journey blog series. 

Whatever can go wrong, will go wrong – and when it comes to IoT firmware, the “impossible” happens all the time. Your job is to design firmware that ensures your device can recover from inevitable errors. With some healthy pessimism, testing best practices, and SDK improvements, you can code Z-Wave firmware that keeps devices operational in the field even in the face of the impossible.