Sonoff Zigbee Dongle firmware update on a Raspberry Pi

If you are using a Raspberry Pi with Sonoff Zigbee 3.0 Dongle Plus (ZBDongle-P) as a Zigbee coordinator to manage your zigbee smart home devices, as I am, then you can easily update (flash) the firmware on the dongle without even unplugging it from the Pi.

This Sonoff Zigbee dongle comes with firmware pre-installed, so its only necessary to do this to update the firmware to a new version. You can do all of the following steps by opening a terminal on your Pi (either the terminal app on the desktop GUI or over SSH).

Python should already be installed on the Pi, but you can check by running python --version. It should output something like Python 3.9.2. If not, you need to first install Python on your Pi by running this command:

sudo apt install python3

Next, we need to update the pip package manager and install some 3rd party packages to allow us to run the scripts we need to do the firmware update:

python -m pip install --upgrade pip

pip install wheel pyserial intelhex python-magic zigpy-znp

Now we need to find which port the dongle is plugged into. In linux, USB devices are mounted in the /dev filesystem so we can easily find it by opening the /dev directory and checking for a device name that looks like ours:

cd /dev/serial/by-id/

ls -lah

I only have one USB device plugged in, so the output looks like this for me:

lrwxrwxrwx 1 root root 13 Feb  8 12:17 usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_029ea6556029ec125f3440c9ce8d-if00-port0 -> ../../ttyUSB0

Depending how many USB devices you have plugged in, you may see more than one item here. Find the one that has the sonoff zigbee name. This output shows that the device is symlinked to the /dev/ttyUSB0 path. Remember this or note it down because we will use it in later commands.

Create a new folder in your home directory (or somewhere else) to store the firmware file and scripts that we will use:

mkdir ~/zigbee-dongle

cd ~/zigbee-dongle

Note: All future commands will be run from this directory

If something goes wrong while flashing the firmware, the dongle will be bricked and it won't be usable, so it is very important that we take a backup first. You can use this command to take a backup of the raw nvram state of the dongle:

python -m zigpy_znp.tools.nvram_read /dev/ttyUSB0 -o dongle_nvram_backup.json

Lots of text will be written to the screen and a JSON file called dongle_nvram_backup.json will be created in the folder that you ran the script in.

Note: if nothing happens for a while, and then a strange timeout error occurs (eg. asyncio.exceptions.CancelledError) it probably means that the USB device is in use by another process. I use zigbee2mqtt and I suspected that was using the device, so I stopped zigbee2mqtt and then ran the above command again and it worked.

Next, we need to download two things:

  1. cc2538-bsl - A python script for applying firmware to a device via the serial boot loader
  2. The firmware itself

Download and unzip cc2538-bsl using these commands:

wget https://github.com/JelmerT/cc2538-bsl/archive/refs/heads/master.zip

unzip master.zip

Then, find the new firmware that we are going to install. Open this Github page and find the file that has "launchpad_coordinator" in the name (usually the one at the top) and click on it. In the page that opens, there will be a small download button in the middle right corner. Right click on the link and copy it, type wget in your terminal and paste the link. For example:

wget https://github.com/Koenkk/Z-Stack-firmware/raw/master/coordinator/Z-Stack_3.x.0/bin/CC1352P2_CC2652P_launchpad_coordinator_20221226.zip

Note: This firmware is for a coordinator device. If your sonoff zigbee dongle is being used as a router, then you need to find the launchpad_router file from here instead.

If you don't know if your device is a coordinator or a router: is this zigbee dongle being used as your main zigbee hub, or is another device? If this dongle is your main hub, then it is a coordinator. Otherwise its probably a router.

If the device doesn't work after updating the coordinator firmware, try downloading and installing the router firmware instead.

Unzip the downloaded zip file (replace the filename with the one you downloaded), and we should be left with a .hex file:

unzip CC1352P2_CC2652P_launchpad_coordinator_20221226.zip

Copy the .hex file into the cc2538-bsl-master directory (replace the filename with the one you downloaded), and switch into that directory:

cp CC1352P2_CC2652P_launchpad_coordinator_20221226.hex ./cc2538-bsl-master/

cd cc2538-bsl-master

Now we're ready to start flashing the USB device. Run this command, replacing the path to the USB device as we determined earlier, and replacing the hex file name with the one you unzipped a moment ago:

python cc2538-bsl.py -e -v -w -p /dev/ttyUSB0 --bootloader-sonoff-usb CC1352P2_CC2652P_launchpad_coordinator_20221226.hex

If all goes well, you'll see a few messages appear including Erase done, Write done and Verified. And that's it - the device firmware is updated. Now we can restart the process that was using the USB device before (eg. zigbee2mqtt) and everything should continue to work as before.

If something went wrong during the firmware flashing, you can restore the backup we took earlier to the device by using these commands:

cd ../

python -m zigpy_znp.tools.nvram_write /dev/ttyUSB0 -i dongle_nvram_backup.json