Skip to content

Firmware OTA Update

This tutorial walks you through remotely updating firmware (A/B update) on a fleet of IoT or embedded Linux devices, using the SocketXP OTA update feature end-to-end.

Before you start, make sure you have downloaded and installed the SocketXP agent on your IoT devices and completed the Getting Started guide.

Workflow Script - update.sh

The OTA update workflow script contains all the instructions required to update the firmware installed on the IoT devices.

The workflow script is designed to provide a robust, automated mechanism for updating the root filesystem of an embedded Linux device that uses U-Boot as its bootloader. It implements an A/B partitioning scheme, allowing for safe updates and reliable rollback on failure.

Warning

This workflow script is just a blueprint provided for guidance. You should modify it to suit your hardware and bootloader requirements — it may not work as-is on many hardware platforms.

The firmware update script used in this tutorial is available in our official GitHub repository.

#!/bin/bash

#================================================
# Firmware Update Workflow Script
#================================================

# The script implements A/B system update with U-Boot bootloader.
# U-Boot's "bootcmd" should refer to the "bootpart" variable to decide
# which boot partition to boot from.
# Performs healthcheck and rollback on failure.

# Configuration
NEW_ROOTFS_IMAGE="$1"
ROOTFS_A="/dev/mmcblk0p2"
ROOTFS_B="/dev/mmcblk0p3"
TEMP_IMAGE="/tmp/new_rootfs.img"
MOUNT_POINT="/mnt/new_rootfs"
HEALTH_CHECK_SCRIPT="/etc/init.d/health_check.sh"
UBOOT_BOOT_PART_VAR="bootpart"
UBOOT_BOOT_COUNT_VAR="bootcount"
UBOOT_BOOT_LIMIT="3"
UBOOT_PART_A_VALUE="1"
UBOOT_PART_B_VALUE="2"

# Function to get current boot partition
get_current_boot_part() {
  local bootpart=$(fw_printenv "$UBOOT_BOOT_PART_VAR" | awk -F '=' '{print $2}')
  echo "$bootpart"
}

# Function to set boot partition
set_boot_part() {
  local part_value="$1"
  fw_setenv "$UBOOT_BOOT_PART_VAR" "$part_value"
}

# Function to reset boot count
reset_boot_count() {
  fw_setenv "$UBOOT_BOOT_COUNT_VAR" "0"
}

# Function to switch boot partition
switch_boot_part() {
  local current_part=$(get_current_boot_part)
  if [ "$current_part" == "$UBOOT_PART_A_VALUE" ]; then
    set_boot_part "$UBOOT_PART_B_VALUE"
  else
    set_boot_part "$UBOOT_PART_A_VALUE"
  fi
  reset_boot_count
}

# Main update process
echo "$(date) - Starting OTA update..."

# Download the new rootfs image
echo "$(date) - Downloading image from $NEW_ROOTFS_IMAGE..."
curl -s -o "$TEMP_IMAGE" "$NEW_ROOTFS_IMAGE"
if [ $? -ne 0 ]; then
  echo "$(date) - Error downloading image."
  rm "$TEMP_IMAGE"
  exit 1
fi

# Download the checksum file
echo "$(date) - Downloading checksum file..."
curl -s -o "$CHECKSUM_FILE" "${NEW_ROOTFS_IMAGE}.sha256"
if [ $? -ne 0 ]; then
  echo "$(date) - Error downloading checksum file."
  rm "$TEMP_IMAGE"
  rm "$CHECKSUM_FILE"
  exit 1
fi

# Verify checksum
echo "$(date) - Verifying checksum..."
if ! sha256sum -c "$CHECKSUM_FILE" 2>/dev/null | grep -q "OK"; then
  echo "$(date) - Checksum verification failed."
  rm "$TEMP_IMAGE"
  rm "$CHECKSUM_FILE"
  exit 1
fi

# Determine the target partition
current_part=$(get_current_boot_part)
if [ "$current_part" == "$UBOOT_PART_A_VALUE" ]; then
  TARGET_ROOTFS="$ROOTFS_B"
else
  TARGET_ROOTFS="$ROOTFS_A"
fi

# Unmount target rootfs if mounted.
if mount | grep "$TARGET_ROOTFS" ; then
        umount "$TARGET_ROOTFS"
fi

# Write the new image to the target partition
echo "$(date) - Writing image to $TARGET_ROOTFS..."
dd if="$TEMP_IMAGE" of="$TARGET_ROOTFS" bs=4M status=progress
if [ $? -ne 0 ]; then
  echo "$(date) - Error writing image to $TARGET_ROOTFS."
  rm "$TEMP_IMAGE"
  rm "$CHECKSUM_FILE"
  exit 1
fi

# Clean up the downloaded image and checksum file
rm "$TEMP_IMAGE"
rm "$CHECKSUM_FILE"


# Mount the new rootfs to copy health check script
mkdir -p "$MOUNT_POINT"
mount "$TARGET_ROOTFS" "$MOUNT_POINT"
if [ $? -ne 0 ]; then
        echo "$(date) - Failed to mount $TARGET_ROOTFS"
        exit 1
fi

# Copy health check script to the new rootfs
cat <<EOF > "$MOUNT_POINT$HEALTH_CHECK_SCRIPT"
#!/bin/bash

# Configuration
UBOOT_BOOT_COUNT_VAR="bootcount"
UBOOT_BOOT_LIMIT="3"

# Function to get current boot count
get_boot_count() {
  fw_printenv "\$UBOOT_BOOT_COUNT_VAR" | awk -F '=' '{print \$2}'
}

# Function to increment boot count
increment_boot_count() {
  local current_count=\$(get_boot_count)
  local new_count=\$((\$current_count + 1))
  fw_setenv "\$UBOOT_BOOT_COUNT_VAR" "\$new_count"
}

# Function to switch boot partition (rollback)
switch_boot_part() {
  local current_part=\$(fw_printenv "bootpart" | awk -F '=' '{print \$2}')
  if [ "\$current_part" == "1" ]; then
    fw_setenv "bootpart" "2"
  else
    fw_setenv "bootpart" "1"
  fi
  fw_setenv "\$UBOOT_BOOT_COUNT_VAR" "0"
}

# Main health check logic
sleep 60
ping -c 1 google.com > /dev/null
if [ \$? -ne 0 ]; then
  echo "\$(date) - Health check failed."
  increment_boot_count

  local current_count=\$(get_boot_count)
  if [ "\$current_count" -ge "\$UBOOT_BOOT_LIMIT" ]; then
    echo "\$(date) - Boot limit reached. Triggering rollback."
    switch_boot_part
    reboot
  else
    echo "\$(date) - Boot count incremented. Rebooting."
    reboot
  fi
else
  echo "\$(date) - Health check successful. Resetting boot count."
  fw_setenv "\$UBOOT_BOOT_COUNT_VAR" "0"
  rm /etc/init.d/health_check.sh # Remove health check script
  exit 0
fi
EOF

chmod +x "$MOUNT_POINT$HEALTH_CHECK_SCRIPT"
umount "$MOUNT_POINT"
rmdir "$MOUNT_POINT"

# Switch boot to the new rootfs
switch_boot_part

# Reboot to load the new rootfs
reboot

What the script does:

  • Initializes configuration variables, including the download URL for the new root filesystem image, the target partitions for the A/B update, temporary file paths, U-Boot environment variable names, and boot count limits. It also defines functions to manipulate the U-Boot environment: getting/setting the boot partition, resetting the boot count, and switching between boot partitions.

  • Downloads the new root filesystem image and its checksum file, and verifies the image's integrity against the checksum. If verification fails, the script exits. Otherwise, it determines the target partition based on the current boot partition, unmounts it if necessary, and writes the new image to it using dd.

  • Mounts the new root filesystem to a temporary mount point and writes a health check script into it. This health check script runs after reboot and verifies basic system functionality (network connectivity). If the health check fails, it increments the boot count in U-Boot and triggers a rollback to the previous partition once the boot count exceeds the configured limit; otherwise it reboots and retries. If the health check succeeds, it resets the boot count, removes itself, and exits.

  • Finally, the main script switches the U-Boot boot partition to the newly updated partition and reboots the device.

Upload the Artifact

We'll upload the update.sh workflow script directly to the SocketXP Artifact Registry as a script type artifact.

Log in to your SocketXP account and go to the OTA Update page. In the Artifacts table, click Upload new artifact.

SocketXP IoT OTA Update - Update IoT firmware on remote embedded Linux devices

Select artifact type script. Browse and select the update.sh file. Specify the artifact version, 1.0.0 in this example.

Specify the destination folder on the target device where the script should be downloaded and executed, for example: /tmp or /home/user/myapp or /usr/bin.

Specify the command to execute the script on the target device, for example: /bin/bash update.sh.

Click Upload to upload the artifact to the cloud registry. You'll see a message saying "File uploaded successfully".

Deploy the Artifact

SocketXP OTA Update upload an IoT app binary to SocketXP cloud artifact registry

From the Artifacts table, select the artifact you just uploaded.

Note

If you don't see your artifact yet, click Refresh to reload the table data.

Click the + icon next to the artifact to create a new deployment.

SocketXP OTA Update - deploy and update firmware on a fleet of remote iot and embedded Linux devices

Give the deployment a name, for example, deploy-version-1.0.0-to-test-devices. Specify the target device ID, device group, or tag to deploy the artifact on.

Note

You can deploy the artifact on a single device ID, device group, or device tag. To deploy on more than one group or tag, repeat the "Create New Deployment" process for each.

Click Create Deployment. Go to the Deployments tab and click Refresh.

SocketXP OTA Update - View the summary of firmware OTA updates deployed

Select the deployment to see its progress. Click More Info < > to monitor the progress on each target device in the group or tag. Click Refresh to view the progress.

SocketXP OTA Update - View the progress of firmware OTA updates deployed on each IoT or embedded Linux device

You can check the stdout and stderr logs generated by the update process by clicking the view log buttons.

Log in to one of the devices to verify that the firmware update was successful.

You have successfully updated the firmware on remote IoT or embedded Linux devices using SocketXP OTA update.

Next Steps

Now that you've learned how to deploy firmware OTA updates, check out the other artifact types: