How to update the firmware:

The following guide is only valid for updating between 1.X.x and 1.Y.y versions. For details about the update process between 0.16.23 to 1.0.x please see the migration guide

Download the firmware

The firmware image is available on the ifm.com website. Navigate to the site and follow the steps below:

  • Create an account (if you do not already have one) and log in.

  • Use the search bar to find the OVP800. This is also valid if you have pre-release sample units, for example M04239 and OVP801 devices.

  • Navigate to the article page an click on the “Downloads” tab.

  • Select the firmware from the list. It will start downloading the file.

Starting firmware is version < 1.0.0

When updating to a firmware version 1.0.0 or above, starting with a firmware version below 1.0.0, please refer to the migration guide.

Starting firmware is version>= 1.0.0

(Optional) Save the current configuration

The configuration of the device will be erased when updating from FW 1.0.14 to FW 1.1.30. If you want to reuse the same configuration after updating, make sure you save it locally on your machine before updating. You can for example do so using the command line interface:

ifm3d dump > config_save.json

Reboot to recovery

When the starting firmware is version 1.0.0 and above, a reboot to recovery state is necessary to perform an update.

$ ifm3d reboot --recovery

With the web interface

Once the device is in recovery mode (see section above), you can open the web interface:

  1. Open http://192.168.0.69:8080/ in web browser. The SWUpdate web interface is shown.

  2. Drag and drop the *.swu firmware file into the software update-window. The upload procedure starts.

The system will automatically reboot in productive mode. The web interface will not be available anymore (it is only available in recovery mode).

With ifmVisionAssistant

Download the ifmVisionAssistant from https://www.ifm.com/us/en/product/OVP800?tab=documents

Note: Updating the firmware from 0.16.xx to 1.0.xx is currently not possible with the ifmVisionAssistant.

Note: Updating the firmware from 1.0.xx to 1.0.xx via ifmVisionAssistant(v2.6.12) is currently supported on a Windows machine only. The Debian package of ifmVisionAssistant supporting Linux distributions will be released soon.

In the instructions below, we expect that the user extracted the ZIP file and is executing the following commands in the same directory:

  • Open the command prompt and run the ifmVisionAssistant executable

C:\Users\Desktop\iVA_2_6_12 $ ifmVisionAssistant.exe

To get the logs during firmware update, execute the above command with -log flag as shown below.

C:\Users\Desktop\iVA_2_6_12 $ ifmVisionAssistant.exe -log

The log file is saved in C:\Users\<UserName>\AppData\Roaming\ifm electronic\ifmVisionAssistant\logs

  • Connect to the VPU and navigate to the VPU Settings window and click Update under the Firmware Update section. The version beside the Update button refers to the current version running on VPU.

  • Select the File and the update process will start.

With ifm3d

Once the device is in recovery mode, you can use ifm3d to update the firmware. In the instructions below, replace <path/to/firmware_image.swu> with the path to the firmware file you downloaded from ifm.com. The code below is continued from the “reboot to recovery” section.

$ ifm3d swupdate --file=<path/to/firmware_image.swu>

Note: the code snippets above do not show how to handle exceptions when they occur in the update process. Please refer to the API documentation for details on the potential exceptions thrown by each function.

Double check the firmware version after the update:

$ ifm3d dump | jq .device.swVersion.firmware

The full example script

#!/usr/bin/env python3
#############################################
# Copyright 2023-present ifm electronic, gmbh
# SPDX-License-Identifier: Apache-2.0
#############################################

# %%
import argparse
import time
import logging
import json
import os
from pathlib import Path
from datetime import datetime

import ifm3dpy
from ifm3dpy.device import O3R
from ifm3dpy.device import Error as ifm3dpy_error
from ifm3dpy.swupdater import SWUpdater
from bootup_monitor import BootUpMonitor

logger = logging.getLogger(__name__)
TIMEOUT_MILLIS = 300000


def _setup_logging(args):
    
    logPath = "./logs"  
    if not os.path.exists("./logs"):
        os.makedirs("./logs")
    current_datetime = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    fileName = f"FW_update{current_datetime}.log"

    logger.setLevel(logging.INFO - args.verbose * 10)
    logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s]  %(message)s")
    fileHandler = logging.FileHandler("{0}/{1}.log".format(logPath, fileName))

    fileHandler.setFormatter(logFormatter)
    consoleHandler = logging.StreamHandler()
    consoleHandler.setFormatter(logFormatter)

    logger.addHandler(fileHandler)
    logger.addHandler(consoleHandler)

    return logger
  
def _get_firmware_version(o3r: O3R) -> tuple:
    try:
        firmware = str(o3r.firmware_version())
    except Exception as err:
        logger.error(err)
        raise err
    logger.debug(f"VPU firmware: {firmware}")
    try:
        major, minor, patch = firmware.split(".")
        try:
            patch, build_id = patch.split("-")
        except ValueError:
            build_id = None
            logger.debug("Build id not available.")
        return (major, minor, patch, build_id)
    except ValueError as err:
        raise err 


def _update_firmware_016_to_10x(o3r: O3R, filename: Path) -> None:
    """Update the OVP firmware from 0.16.x to 1.x.x series.
    This update requires a specific process: the update has
    to be performed twice to install the recovery partition.

    :raises RuntimeError: if the device fails to reboot or
                          the update process did not complete.
    """
    logger.info(f"Start FW update with file: {filename}")
    logger.info(
        "The firmware will be updated twice for the 0.16.x to 1.0.x transition."
    )
    sw_updater = SWUpdater(o3r)

    # 1st application of FW update
    logger.info("First flash FW file")
    sw_updater.flash_firmware(str(filename), timeout_millis=TIMEOUT_MILLIS)

    logger.info("Rebooting to recovery")
    if not sw_updater.wait_for_recovery(120000):
        raise RuntimeError("Device failed to boot into recovery in 2 minutes")

    logger.info("Boot to recovery mode successful")

    # 2nd application of FW update: final flash
    logger.info("Second flash FW file")
    if not sw_updater.flash_firmware(str(filename), timeout_millis=TIMEOUT_MILLIS):
        _reboot_productive(o3r=o3r)
        logger.info("Request reboot to productive after second FW flash")
        raise RuntimeError("Firmware update failed")
    logger.info("Second FW flash successful")

    if not sw_updater.wait_for_productive(120000):
        raise RuntimeError("Device failed to boot into productive mode in 2 minutes")


def _update_firmware_via_recovery(o3r: O3R, filename: Path) -> None:
    logger.info(f"Start FW update with file: {filename}")

    sw_updater = SWUpdater(o3r)
    logger.info("Rebooting the device to recovery mode")
    sw_updater.reboot_to_recovery()

    if not sw_updater.wait_for_recovery(120000):  # Change 60000 to 120000
        raise RuntimeError("Device failed to boot into recovery in 2 minutes")

    logger.info("Boot to recovery mode successful")
    if not sw_updater.flash_firmware(str(filename), timeout_millis=TIMEOUT_MILLIS):
        logger.info("Firmware update failed. Boot to productive mode")
        _reboot_productive(o3r=o3r)
        logger.info("Reboot to productive system completed")
        raise RuntimeError("Firmware update failed.")

    logger.info("Flashing FW via recovery successful")
    logger.info("Requesting final reboot after FW update")

    _reboot_productive(o3r)
    logger.info("Reboot to productive system completed")


def _reboot_productive(o3r: O3R) -> None:
    sw_updater = SWUpdater(o3r)
    logger.info("reboot to productive system")
    sw_updater.reboot_to_productive()
    sw_updater.wait_for_productive(120000)

def _check_ifm3dpy_version():
    if ifm3dpy.__version__ < "1.3.3":
        raise RuntimeError(
            "ifm3dpy version not compatible. \nUpgrade via pip install -U ifm3dpy"
        )
    
def _reapply_config(o3r: O3R, config_file: Path) -> None:
    with open(config_file, "r") as f:
        try:
            logger.info("Reapplying pre-update configuration.")
            o3r.set(json.load(f))
        except ifm3dpy_error as e:
            logger.error(f"Failed to apply previous configuration: {e}")
            schema_fp = Path("json_schema.json")
            with open(schema_fp, "w", encoding="utf-8") as f:
                json.dump(o3r.get_schema(), f, ensure_ascii=False, indent=4)
                logger.info(
                    f"Current json schema dumped to: {Path.absolute(schema_fp)}"
                )

            logger.warning(
                f"Check config against json schema: \n{Path.absolute(schema_fp)}"
            )


# %%
def update_fw(filename: Path, ip:str) -> None:
    """
    Perform a firmware update on the device with the given IP address.

    Parameters:
    - filename (str): The name of the firmware file to be used for the update.
    - ip (str): The IP address of the device to be updated.

    Returns:
    - None: This function does not return anything.

    Note:
    Ensure that the firmware file exists in the specified location before running this function.
    """
    # Check compatibility of ifm3dpy version
    _check_ifm3dpy_version()
    # Check that swu file exists
    if not os.path.exists(filename):
        raise ValueError("Provided swu file does not exist")

    logger.info(f"device IP: {IP}")
    logger.info(f"FW swu file: {filename}")
    logger.info(f"Monitoring of FW update via messages tab here: http://{IP}:8080/")

    o3r = O3R(ip=ip)

    # check FW 0.16.23
    major, minor, patch, build_id = _get_firmware_version(o3r)
    logger.info(f"Firmware version before update: {(major, minor, patch)}")
    if int(major) == 0 and any([int(minor) < 16, int(patch) < 23]):
        raise RuntimeError(
            "Update to FW 0.16.23 first before updating to version 1.0.14"
        )

    config_back_fp = Path("config_backup.json")
    with open(config_back_fp, "w", encoding="utf-8") as f:
        json.dump(o3r.get(), f, ensure_ascii=False, indent=4)
        logger.info(f"Current config dumped to: {Path.absolute(config_back_fp)}")

    # update firmware
    logger.info("///////////////////////")
    logger.info("Start FW update process.")
    logger.info("///////////////////////")
    if (int(major), int(minor), int(patch)) == (0, 16, 23):
        logger.info("FW Update 0.16.23 to 1.0.x started")
        _update_firmware_016_to_10x(o3r, filename)
        logger.info("Update process: file transfer completed")
    elif (int(major), int(minor)) >= (1, 0):
        logger.info("FW Update via recovery started")
        _update_firmware_via_recovery(o3r, filename)
        logger.info("Update process: file transfer completed")
    else:
        logger.error("This FW update is not supported")
        raise RuntimeError("FW on the VPU is not supported")

    logger.info("FW update via SWU file applied - waiting to reboot")


    def _vpu_ready(o3r:O3R) -> bool:
        while True: # vpu addressable
            try:
                config = o3r.get()
                if config:
                    logger.debug("Connected.")
                    break
                time.sleep(5)
            except ifm3dpy_error:
                logger.debug("Awaiting data from VPU.")

        while True: # check for full boot-up of the VPU via confInitStages
            try:
                fully_booted = o3r.get(["/device/diagnostic/confInitStages"])["device"]["diagnostic"]["confInitStages"] == ["device", "ports", "applications"]
                if fully_booted:
                    logger.info("VPU fully booted.")
                    return True
            except ifm3dpy_error:
                logger.debug("Awaiting data from VPU.")
                time.sleep(5)
    # wait for system to be ready


    logger.info("///////////////////////")
    logger.info("Firmware update complete.")
    logger.info("///////////////////////")

    # check firmware version after update
    _vpu_ready(o3r)
    major, minor, patch, build_id = _get_firmware_version(o3r)
    logger.info(f"Firmware version after update: {(major, minor, patch)}")

    # reapply configuration after update
    logger.info("Reapply configuration before FW update")
    _reapply_config(o3r=o3r, config_file=config_back_fp)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        prog="Firmware update helper", description="Update the O3R embedded firmware"
    )
    parser.add_argument(
        "--filename", 
        help="SWU filename in the cwd",
        required=True,
        type=Path)
    parser.add_argument(
        "-v",
        "--verbose",
        help="Increase output verbosity",
        action="count",
        default=0,
        dest="verbose",
    )
    parser.add_argument(
        "--log-file",
        help="The file to save relevant output",
        type=Path,
        required=False,
        default=Path("deleted_configurations.log"),
        dest="log_file",
    )

    args = parser.parse_args()

    # register a stream and file handler for all logging messages
    logger = _setup_logging(args=args)

    try:
        # If the example python package was build, import the configuration
        from ovp8xxexamples import config

        IP = config.IP
        log_to_file = config.LOG_TO_FILE

    except ImportError:
        # Otherwise, use default values
        print(
            "Unable to import the configuration.\nPlease run 'pip install -e .' from the python root directory"
        )
        print("Defaulting to the default configuration.")
        IP = "192.168.0.69"


    update_fw(filename=args.filename, ip=IP)
# %%