How to force a handshake for OTA updates

The Device OS API will send a status of “firmware_update_begin, firmware_update_progress, etc” depending on if a firmware update is coming through. This automatically happens when your device is part of a product. You should always get a status from this if a firmware .bin has been released and is marked to deploy to that device.

You can add an event handler to handle these system events.

System.on(firmware_update, firmware_update_handler);

In your handler, you can then use that info to switch your own state for your firmware so you can do whatever you want during each state. For instance, you can change a switch state based on these parameters that come through the system event. See the “status” variable below.

void firmware_update_handler(system_event_t event, int status) {
  switch(status) {

This is the parameter from the Device OS API. When this changes, we log to the console and switch our gFirmwareUpdateStatus variable so we can use that outside of this handler.

  switch(status) {
    case firmware_update_begin:
      gFirmwareUpdateStatus = FW_UPDATING;
      appLog.info("Firmware update beginning event was triggered.");
      break;
    case firmware_update_progress:
      gFirmwareUpdateStatus = FW_UPDATING;
      appLog.info("Firmware update progress event was triggered.");
      break;
    case firmware_update_complete:
      gFirmwareUpdateStatus = FW_DONE;
      appLog.info("Firmware update complete event was triggered.");
      break;
    case firmware_update_failed:
      gFirmwareUpdateStatus = FW_DONE;
      appLog.error("Firmware update failed event was triggered.");
      break;
    default:
      appLog.warn("Unknown firmware_update_handler status %i", status);
      break;
  }

For instance, outside of this handler, we want to have the device hang there and do a particle.process while the update is happening, so we add this while loop.

while (gFirmwareUpdateStatus == FW_UPDATING) {
    Particle.process();
  }

The other states don’t really matter to us as the device reboots automatically after an update. But, for instance, we could do something like flash an LED if our state switches to “firmware_update_failed” to let us know something bad happened.

3 Likes

Awesome! Thanks :slight_smile:

Do you just use this on start up, or do you allow users to “Update Firmware” and then enable these handlers?

@dcliff9 I just added this to our project and it still doesn’t solve the issue of triggering the update. If the update is triggered, it looks like it will work, but part of my issue with this is that I can’t trigger the device to end a session and try to reconnect (when all these firmware update events are initiated). Have you been able to solve this?

p.s. Thanks so much, we’ve been scrambling to try to get this done for our customers, so this is super helpful!

That handler should run as soon as the status changes. That is triggered by a firmware release to the product automatically.

Are you following this process to upload and release a firmware binary?
https://docs.particle.io/tutorials/device-cloud/ota-updates/#fleet-wide-ota

@dcliff9

We are using the Standard releases. We aren’t enterprise, so we don’t have access to the Intelligent method. But we are creating releases and assigning them to devices, if that is what you mean.

Something you can try. Put this in your code instead.

void setup()
{
    // listen for network events and firmware update events
    System.on(all_events, handle_all_the_events);
}

void handle_all_the_events(system_event_t event, int param)
{
    Serial.printlnf("got event %d with value %d", event, param);
}

This will allow you to see all of the system events in a console. You can set up a device and watch this while you try to release your firmware. If it is getting released correctly, you should see the “firmware_update_begin” value come through on the “firmware_update” event.

1 Like

We have opted not to automatically update the device on start, but instead allowing the user to select “Update Firmware”. This is probably the crux of our problem and maybe some of the confusion in this conversation. We run System.enableUpdates() when they want to update firmware, then end our session and put the device in safe mode to accept updates. This previously worked, but no longer does.

Are you doing this?

  • Your devices are in a product.
  • The product and firmware version number is in your code.
  • One of the devices in that product is already running your firmware version. (Flashed via USB).
  • In the particle console, you go to that product, click the “firmware” button on the left, upload your new firmware binary, then hit the “release firmware” button.

That shouldn’t be an issue. You can default to System.disableUpdates() and then enable system updates via System.enableUpdates() when the client chooses. Once that is enabled, the firmware update status should change as long as there is a newer released firmware waiting. I have seen it take as much as 10 seconds for that firmware_update system event to update, but it eventually does. Note that this only appears to work in v1.2.0 or later.

Huh! I must have been doing something wrong. That is good news though, because maybe we can make this happen.

When question, thought though. Why wait for the event to run Particle.process? Why not run it before the events hit?

You can (and should) run Particle.process whenever you can when not in Automatic mode.
The reason we do it the way I showed you is because our devices wake up, take measurements, connect, and then go to sleep immediately to save power. In our scenario, when our device connects, we need it to sit and wait for the update to complete instead of going back to sleep. Hence the wait with the particle.process in it. Since an OTA update will automatically reboot the device, we can just sit and Particle.process until it’s done. We’ve also seen that if we let code continue to run during an update, it can cause it to fail.

Ah. That makes sense. Do you put it into listening mode or safe mode? System.enterSafeMode? That might be what is causing ours to fail. It could be interrupting the Particle.process

Ok, right now I’m doing this when the user selects “Firmware Update” and it doesn’t appear to be hitting the handler:

void handle_firmware_updates(system_event_t event, int status) {
    Serial.printlnf("got event %d with value %d", event, status);
    System.enterSafeMode();
}

System.enableUpdates();
System.enableReset();
System.on(all_events, handle_firmware_updates);

I presume you are also disabling updates to start with then as well? This is the mechanism I was using that stopped working for me around DeviceOS 1.3ish.

I then switched to using System.disableReset() and then System.enableReset(). This seems to be working a lot better and makes more sense in our situation. It still pulls down the update, it just doesn't reboot (the part we're trying to control) until it's safe to do so.

Not sure if this would make sense for you tho.

We do not. The firmware update process baked into the Device OS is automatic. Once it receives the firmware binary and starts the update, I think automatically reboots itself into a safe mode. You can see it when the update is happening as the LED turns magenta. I would say that trying to force it into safe or listening mode yourself would probably cause the update to fail.

Put System.on(all_events, handle_firmware_updates); in the setup.
Also disable system updates in the setup.

void setup()
{
    System.on(all_events, handle_firmware_updates);
    System.disableUpdates();
}

Remove the System.enterSafeMode(); completely.

void handle_firmware_updates(system_event_t event, int status) {
    Serial.printlnf("got event %d with value %d", event, status);
    // System.enterSafeMode();
}

Then enable updates in whatever function the user is hitting to do the firmware update.

void your_user_function {
   System.enableUpdates();
}

Have your handler function somewhere to be called.

void handle_all_the_events(system_event_t event, int param)
{
    Serial.printlnf("got event %d with value %d", event, param);
}

Now when a system event happens, you will get “got event XXX with value XXX”.
If you have an update released for the product, you should see the firmware_update events once the user hits the your_user_function(). The update should start automatically (maybe after 10 seconds or so).

From here, if you want to break that handler out into different states, you can control things a bit more as described above. This will allow you to pause any other non blocking code so that it doesn’t disrupt the update. (Like sitting in a wait loop with particle.process running).

1 Like

@dcliff9 Thanks again for your help earlier. I’ve tried this implementation and for whatever reason the handler function is never called.

There are a few things that I’m going to look into next.

  1. See if our application code is interfering with this approach. I’ll isolate this code in a simple app to see I can make it work that way.
  2. I’m wondering if this potentially having issues because it is flashed code, not a release. Will flashed code (a non-release) accept updates from releases? I would think so, I’m just not sure.

Thanks again,
Brian

We are disabling updates to start with. Are you making updates in a similar fashion to @dcliff9’s update handler then as well?

Very welcome. Hope you can get it figured out. Simplifying to troubleshoot is always a good step. :slight_smile:

Regarding the released updates, as long as the binary you flashed to the device states the product and version number in your code, it should accept updates. Of course, that “flashed” device needs to be in that product. You really need 2 devices to test OTA updates. Here is how I usually do it. (Seems complicated written out, but it isn’t.)

Let’s assume you are trying to release firmware version 105 to all devices in a product.

Create two binaries. They can both have your latest code but mark one as v105 in your code. Mark the other as v104.

Flash v105 to one device via USB and v104 to the other (also via USB).

Mark the v105 device as a development device in the product console. Let it connect and make sure it is showing as v105 in the console.

Now upload the v105 binary and release it in the product console.

Now turn on your v104 and let it connect. As long as you do NOT have this device marked as a development device, it should update to v105. When it does, this handler should kick in.

Thanks again. That all makes sense and sounds very reasonable.

It actually never occurred to me that we might have a demotion issue with the device I’ve been using. In that device, we are already on v105 in the console, but want to downgrade it to v104 release. Because v105 is the latest version, maybe the upgrade event isn’t sent?