What is the right way to OTA the Device OS of a Xenon

I always fail to do a OTA Device OS update for my Xenons.
I failed updating my 3 Xenons without collecting and connecting via USB when I upgraded from 0.8.0-rc.27 to 0.9.0. Same now with 0.9.0 to 1.1.0-rc.1.

This time I tried:

  • For the first xenon I used the Workbench: I switched the configuration for my project to 1.1.0-rc.1 and then did a ‘Particle: Cloud Flash’. This ended up with a somehow disconnected xenon in the safe mode (breathing magenta). It was not reachable or re-flashable. I waited for about 1/2 hour but no change.

  • For the second I used the online IDE: I changed the ‘Device OS target:’ dropdown to 1.1.0-rc.1 and flashed an empty sketch. Same result: disconnected xenon in the safe mode, not reachable or re-flashable. I waited again for about 1/2 hour but no change.

So what would be the correct way to update the Device OS without the need to get the xenon and connect them to the USB of my laptop? I really would like to have a way since the third xenon is the hardest to get out from its place/enclosure.


PS: I could fix the two xenons by connecting them USB and flash the system xenon-system-part1@1.1.0-rc.1.bin via dfu.

While the wired update is always the safest bet, I usually don’t have issues with OTA updates of the device OS.
But the most crucial point for that to work reliably is to have well behaving application firmware running on the Xenon and the gateway. If one of the two isn’t donating as much connection time as possible to the update the update will often fail.

Good coding practice in application code is key.
A workaround would be to add a Particle.function() that puts the device in Safe Mode (Xenon first, then gateway) before you start the OTA update.

1 Like

Thanks @ScruffR for the feedback.

donating as much connection time as possible to the update

Both the Xenon and the Argon (gateway) use SYSTEM_THREAD(ENABLED), with which I hoped this would be given or rather taken care of by the Device OS.

Good coding practice in application code is key.

Currently my Argon (gateway) application does nothing:

#include <Particle.h>


SerialLogHandler sLogHandler(LOG_LEVEL_TRACE);

void setup() {

void loop() {

The Xenon application is reading a I2C sensor all 10 seconds and publishes the values. In between it mainly does check in the loop() if it is time for read and publish.

#include <Particle.h>
#include <Bt/Core/PeriodicCallback.h>
#include <Bt/Sensors/SHT31.h>


constexpr size_t MSG_BUFFER_SIZE = 255;

Bt::Sensors::SHT31 sSHT(Wire);

Bt::Core::PeriodicCallback sReadSensor(
        char messageBuffer[MSG_BUFFER_SIZE] = {0};

        BT_CORE_LOG_INFO("** Read Sensor **");
        auto reading = sSHT.read();
        if(std::get<0>(reading)) {
            BT_CORE_LOG_INFO("==> Temperature %.3f ", std::get<1>(reading));
            BT_CORE_LOG_INFO("==> Humidity    %.3f ", std::get<2>(reading)); 
            snprintf(messageBuffer, MSG_BUFFER_SIZE, "{\"topic\":\"sensors\",\"payload\":{\"temperature\":%.3f, \"humidity\":%.3f}}", std::get<1>(reading),std::get<2>(reading));
            Particle.publish("bt/mesh/raw", messageBuffer, PRIVATE);
        } else {
            BT_CORE_LOG_INFO(" ==> failed");    

void setup()

void loop()


Scheduling PeriodicCallback::workcycle() {
   uint32_t now = timeNow(mTimeUnit);
   uint32_t diff = now - mStartTime;
   if (diff >= mPeriod) {
      mStartTime = now;
      if(mCallback) {

So I think this should not block the connection time. I have no idea what else would need to be done by an application code to follow the “Good coding practice” since there are no busy loops or similar blocking things.

A workaround would be to add a Particle.function() that puts the device in Safe Mode (Xenon first, then gateway) before you start the OTA update.

I really hoped that I would not need to do such stuff in my application code when I use Device OS. Or asked differently why does the Device OS not do this if it can not reliably do the OTA otherwise.

Which library are you using for reading your SHT31?
Is this known to be non-blocking?
Shared resources like I2C may interfere with OTA updates even with SYSTEM_THREAD(ENABLED) - or rather with multi threading enabled as your code will keep running concurrently to the firmware download which would otherwise be blocked during that periode.

As I suggested, you can do this …

I use my own implementation. The reading is actually blocking, but only 20 milliseconds in worst and ~13 milliseconds mainly. This is still only 0.2% of the 10 seconds. Also the Particle.publish might use up some time but I think it’s still under 1% and for me this seems low to interfere with OTA which should somehow get nearly 99% of the time.

But I will see how I can integrate your suggested the Safe Mode workaround. I just hoped that the Particle Device OS would handle the OTA updates without the need to add workarounds to the application code.