[Zigbee] FOTA mcuboot update

Setup
nRF Connect SDK 2.3.0
nRF52840

Hi all,

I would like to add mcuboot update over Zigbee FOTA to my application. 
I am aware of that the bootloader update over Zigbee is not currently supported by the nRF Connect SDK and it will require multiple steps to make it possible.

From my point of view the necessary steps are: 

  1. Generating the mcuboot FOTA image to be distributed by Zigbee FOTA server in a following way:
    1. build/mcuboot/zephyr.bin -> imgtool.py (with specific options like slot size, keys, etc.) -> dfu_multi_image.py -> zb_add_ota_header.py
  2. Determine the image type by image type field or image comment directly in the zigbee_fota subsys -> zigbee_fota_zcl_cb()
  3. Check the mcuboot fw versions in the s0 and s1 partition and choose lower one as current/active partition.
  4. Handle the incoming data: 
    1. Option A: Save the data directly to flash (current partition)
    2. Option B: Use DFU target library (?)
  5. Increase the version number of the mcuboot stored in current partition to be higher than mcuboot stored in alternative partition.
  6. Reboot 

The questions are: 

  1. Are there any other limitations of which I am not aware of ? 
  2. Is it possible to use DFU target library for handling the image flash saving ? By setting/changing some configs ? 

I am looking forward to hearing from you

Parents
  • Hi, I came back here just to share with you how I implemented the Zigbee FOTA bootloader update: 
    1. Create module for dfu update based on dfu_target_stream API. (init, target_done, offset_get, write, get_bootloader_versions) -> As a reference I took dfu_target_mcuboot module.
    2. Basically copy the implementation of Zigbee FOTA subsys for app image and adapt it to created earlier  dfu module.
    3. Create Kconfigs for bootloader image types like:  CONFIG_ZIGBEE_FOTA_S1_IMAGE,_TYPE CONFIG_ZIGBEE_FOTA_S0_IMAGE_TYPE
    3. To generate fota images from the bootloaders .bin files, I added following code in the CMakeLists.txt

    if (CONFIG_ZIGBEE_FOTA_GENERATE_LEGACY_IMAGE_TYPE)
    		set(legacy_cmd "--legacy")
    	elseif(CONFIG_DFU_MULTI_IMAGE_PACKAGE_BUILD)
    		set(legacy_cmd )
    	endif()
    
    	set(add_zigbee_header_cmd
    		${PYTHON_EXECUTABLE}
    		${NRF_DIR}/scripts/bootloader/zb_add_ota_header.py
    		#								Provide acceptable format x.y.z
    		--application-version-string 	"0.0.${mcuboot_CONFIG_FW_INFO_FIRMWARE_VERSION}"
    		--zigbee-manufacturer-id 		${CONFIG_ZIGBEE_FOTA_MANUFACTURER_ID}
    		--zigbee-ota-min-hw-version 	${CONFIG_ZIGBEE_FOTA_MIN_HW_VERSION}
    		--zigbee-ota-max-hw-version 	${CONFIG_ZIGBEE_FOTA_MAX_HW_VERSION}
    		--out-directory 				${PROJECT_BINARY_DIR}/zephyr
    		${legacy_cmd}
    	)
    
    	set(FOTA_HEADER_STEP_S0 "dummy_s0")
    	add_custom_command(
    		COMMAND ${add_zigbee_header_cmd} --application ${PROJECT_BINARY_DIR}/zephyr/signed_by_mcuboot_and_b0_s0_image_update.bin
    		--zigbee-image-type ${CONFIG_ZIGBEE_FOTA_S0_IMAGE_TYPE}
    		--zigbee-comment "${CONFIG_ZIGBEE_FOTA_BOOTLOADER_COMMENT}_s0"
    		OUTPUT ${FOTA_HEADER_STEP_S0}
    		DEPENDS zigbee_ota_image
    		COMMENT "Add Zigbee FOTA header to s0 bootloader image..."
    	)
    
    	set(FOTA_HEADER_STEP_S1 "dummy_s1")
    	add_custom_command(
    		COMMAND ${add_zigbee_header_cmd} --application ${PROJECT_BINARY_DIR}/zephyr/signed_by_mcuboot_and_b0_s1_image_update.bin
    		--zigbee-image-type ${CONFIG_ZIGBEE_FOTA_S1_IMAGE_TYPE}
    		--zigbee-comment "${CONFIG_ZIGBEE_FOTA_BOOTLOADER_COMMENT}_s1"
    		OUTPUT ${FOTA_HEADER_STEP_S1}
    		DEPENDS ${FOTA_HEADER_STEP_S0}
    		COMMENT "Add Zigbee FOTA header to s1 bootloader image..."
    	)
    
    	add_custom_target(
    		post_bin ALL
    		DEPENDS
    		${FOTA_HEADER_STEP_S1}
    	)


    4. Multiple FOTA images handling on the Zigbee End device.
    I just have a timer which in the middle of CONFIG_ZIGBEE_FOTA_IMAGE_QUERY_INTERVAL_MIN changes two attributes in OTA Upgrade Cluster, which is ImageType and FileVersion.
    Handling of these images is really application depended. I did in such way but You can change the requested image type (QueryNextImageRequest) on specific events.

    I would like to get your thoughts about that implementation and I will be very grateful if let me know if You notice any problems in provided solution. 



  • Hello,

    Thank you for the update.

    From what I can see your implementation looks good.

    I have one minor concern regarding 4.:

    Pawel(embeddedsolutions.pl) said:
    I just have a timer which in the middle of CONFIG_ZIGBEE_FOTA_IMAGE_QUERY_INTERVAL_MIN changes two attributes in OTA Upgrade Cluster, which is ImageType and FileVersion.

    Can you please share how this is implemented? Or if you don't want to share more of your code, please describe more of the intended functionality.
    Do I understand correctly that the updated values for the attributes will be relevant from the next time the client queries the server?

    BR,

    Maria

  • Hi, 
    I will be more than happy to share the source code.
    I wrongly thought that it is working correctly. Unfortunately it is not. 


    The goal is to change the FileVersion and ImageType attributes in the middle of the query interval then the the updated values for the attributes will be relevant from the next time the client queries the server.

    I would like also to achieve following sequence: 

    Desired images sequence: Boot -> 1min -> QueryNextImageRequest (s0) -> ~1.5min -> keep s0 image -> ~3min -> QueryNextImageRequest(s0) -> ~4.5min -> switch to s1 image -> ~6min -> QueryNextImageRequest(s1) -> ~7.5min -> switch to app image -> ~9min -> QueryNextImageRequest(app) -> ~10.5min -> keep app image data, stop image switcher and set interval to 24h

    This is the code which is trying to do such thing but what I have noticed is that the query interval does not change at all. QueryInterval is something about 1 minute.

    /* CONFIG_ZIGBEE_FOTA_IMAGE_QUERY_INTERVAL_MIN set to 3 minutes */
    
    
    
    /* Application is able to update s0, s1 (bootloader partitions) and app image. ZBOSS sends QueryNextImageRequest
     * each CONFIG_ZIGBEE_FOTA_IMAGE_QUERY_INTERVAL_MIN. In the middle of defined time ZBOSS changes the requested Image
     * Type and Image version */
    static void zb_fota_start_image_switcher_timer(void) {
        k_timer_start(&image_type_switcher_timer, K_SECONDS(CONFIG_ZIGBEE_FOTA_IMAGE_QUERY_INTERVAL_MIN * 60 / 2),
                      K_MINUTES(CONFIG_ZIGBEE_FOTA_IMAGE_QUERY_INTERVAL_MIN));
        LOG_DBG("Image Type switcher timer started");
    }
    
    /* desired images sequence: Boot -> 1min -> QueryNextImageRequest (s0) -> 1.5min -> keep s0 -> 3min ->
     * QueryNextImageRequest(s0) -> 4.5min -> switch to s1 -> 6min QueryNextImageRequest(s1) -> 7.5min -> switch to app ->
     * 9min -> QueryNextImageRequest(app) -> 10.5min -> keep app data, stop image switcher, set interval to 24h */
    /* Zigbee STACK sends first QueryNextImageRequest after 1min after boot and it is unchangeable */
    static void zb_fota_change_requested_image_type(struct k_timer *switcher_timer) {
        ARG_UNUSED(switcher_timer);
        static zb_uint8_t idx = 0;
    
        if (restart_image_seq) {
            idx = 0;
            restart_image_seq = false;
        }
    
        zb_uint16_t image_types_list[] = {CONFIG_ZIGBEE_FOTA_S0_IMAGE_TYPE, CONFIG_ZIGBEE_FOTA_S1_IMAGE_TYPE,
                                          CONFIG_ZIGBEE_FOTA_IMAGE_TYPE};
    
        if (idx >= ARRAY_SIZE(image_types_list)) {
            /*  ImageType switcher reached 4th transition without detecting FOTA upgrades -> keep application image data and
             * change the interval to 24h */
            LOG_DBG("Set 24h interval for app image FOTA requests");
            k_timer_stop(&image_type_switcher_timer);
            zb_zcl_ota_upgrade_set_query_interval(CONFIG_ZB_DEVICE_ENDPOINT,
                                                  CONFIG_ZIGBEE_FOTA_APP_IMAGE_QUERY_INTERVAL_TIME);
            return;
        }
    
        uint32_t img_ver = zb_fota_get_image_ver(image_types_list[idx]);
        ZB_ZCL_SET_ATTRIBUTE(CONFIG_ZIGBEE_FOTA_ENDPOINT, ZB_ZCL_CLUSTER_ID_OTA_UPGRADE, ZB_ZCL_CLUSTER_CLIENT_ROLE,
                             ZB_ZCL_ATTR_OTA_UPGRADE_FILE_VERSION_ID, (zb_uint8_t *)&img_ver, ZB_FALSE);
    
        ZB_ZCL_SET_ATTRIBUTE(CONFIG_ZIGBEE_FOTA_ENDPOINT, ZB_ZCL_CLUSTER_ID_OTA_UPGRADE, ZB_ZCL_CLUSTER_CLIENT_ROLE,
                             ZB_ZCL_ATTR_OTA_UPGRADE_IMAGE_TYPE_ID, (zb_uint8_t *)&image_types_list[idx], ZB_FALSE);
    
        LOG_INF("Image Type changed to=0x%03X", image_types_list[idx]);
        idx++;
    }
    
    
    
    /*
     Overwrite default Zigbee FOTA subsys attributes to send QueryNextImageRequest with s0 bootloader related
     attributes values rather than application image data */
    static void zb_fota_overwrite_default_attr(void) {
        uint32_t img_ver = zb_fota_get_image_ver(CONFIG_ZIGBEE_FOTA_S0_IMAGE_TYPE);
        ZB_ZCL_SET_ATTRIBUTE(CONFIG_ZIGBEE_FOTA_ENDPOINT, ZB_ZCL_CLUSTER_ID_OTA_UPGRADE, ZB_ZCL_CLUSTER_CLIENT_ROLE,
                             ZB_ZCL_ATTR_OTA_UPGRADE_FILE_VERSION_ID, (zb_uint8_t *)&img_ver, ZB_FALSE);
    
        zb_uint16_t img_type = CONFIG_ZIGBEE_FOTA_S0_IMAGE_TYPE;
        ZB_ZCL_SET_ATTRIBUTE(CONFIG_ZIGBEE_FOTA_ENDPOINT, ZB_ZCL_CLUSTER_ID_OTA_UPGRADE, ZB_ZCL_CLUSTER_CLIENT_ROLE,
                             ZB_ZCL_ATTR_OTA_UPGRADE_IMAGE_TYPE_ID, (zb_uint8_t *)&img_type, ZB_FALSE);
    }
    
    
    void zb_fota_init(void) {
        zigbee_fota_abort();
        zb_fota_nsib_abort();
        int err = zigbee_fota_init(zb_fota_evt_handler);
        if (err) {
            LOG_ERR("err=%d zboss fota init", err);
            return;
        }
    
        /* Overwrite default OTA Upgrade ImageType, FileVersion attributes */
        zb_fota_overwrite_default_attr();
    
        err = zb_fota_nsib_register_cb(zb_fota_evt_handler);
        if (err) {
            LOG_ERR("err=%d zigbee nsib callback register", err);
            return;
        }
    
        err = zb_fota_confirm_image();
        if (err) {
            LOG_ERR("err=%d img confirm", err);
        }
    
        k_timer_init(&image_type_switcher_timer, zb_fota_change_requested_image_type, NULL);
        zb_fota_start_image_switcher_timer();
    }

    There is of course another part of the code responsible for restarting the sequence/stopping the timer based on various events detected in the zigbee_fota_zcl_cb but I do no think it is important in that case.

  • Hello once again,

    I solved that problem. 

    As far as I noticed that timers are not the best option here, I have registered the endpoint handler and based on Query Next Image Response I change the ZB_ZCL_ATTR_OTA_UPGRADE_FILE_VERSION_ID and ZB_ZCL_ATTR_OTA_UPGRADE_IMAGE_TYPE_ID. 
    The rest is to handle properly the sequence restarting depending on different events in the app, but this is totally application depended which is not the goal of this topic

  • Hello,

    I noticed that this case returned to my work queue today.

    Do you have any current need for support?

    Best regards,

    Maria

  • Hello, 

    I was going through my tickets and changing tickets titles to be in compliance with other ones. 
    Didn't mean to bother you. 

    Anyway, regarding this ticket I think it deserves an update as a lot changed in between v2.3.0 and v2.9.1.
    So for anyone interested in that topic, after migration to sysbuild it is still possible to support MCUboot FOTA via Zigbee by modifying the build and having a little bit of logic implemented to handle multiple images types.

    Regards, 

    Pawel

Reply
  • Hello, 

    I was going through my tickets and changing tickets titles to be in compliance with other ones. 
    Didn't mean to bother you. 

    Anyway, regarding this ticket I think it deserves an update as a lot changed in between v2.3.0 and v2.9.1.
    So for anyone interested in that topic, after migration to sysbuild it is still possible to support MCUboot FOTA via Zigbee by modifying the build and having a little bit of logic implemented to handle multiple images types.

    Regards, 

    Pawel

Children
No Data
Related