OTA over HTTP working example!

UPDATE: I was doing something dumb (see below). The code here works!

I’m implementing my own Boron OTA update routine that pulls firmware from a web server. I’ve based my code on some of the great examples here on the forum, and all seems good until Spark_Finish_Firmware_Update fails with return code 1.

After running with debug output and looking into deviceOS, I determined the reason for the return code of 1 (failure) is caused by CRC not matching. Specifically it is a CRC mismatch (MODULE_VALIDATION_INTEGRITY).

Results of debug output:

0000025665 [app] INFO: Firmware Received!
0000025665 [app] INFO: Received 23904 bytes
0000025666 [app] INFO: Launching Update
0000025677 [hal] INFO: module fetched 1, checks=5e, result=5c
0000025677 [hal] WARN: OTA module not applied

I have verified that the firmware is being downloaded correctly by dumping the raw hex to Serial output as it is being saved by Spark_Save_Firmware_Chunk. I converted that output back to binary, and it matches byte for byte the firmware binary I am trying to flash. The binary I used was a small (about 5KB) binary built in Workbench. I used target/simple.bin (the project firmware is named simple).

Any guidance on what I’m doing wrong here? I’ve seen a few other cases where people were having trouble with the CRC, but haven’t found any solutions.

My test code does nothing in setup() or loop() except enable Serial and start the OTA. Any help would be appreciated!

ota-test.ino:

#include <OTA.h>
SerialLogHandler logHandler(LOG_LEVEL_TRACE);
char c;

// setup() runs once, when the device is first turned on.
void setup() {
  // Put initialization like pinMode and begin functions here.
  Serial.begin(115200);
  delay(5000);
  Serial.println("V2");
  Serial.println("Hello! Press o for OTA.");
}

// loop() runs over and over again, as quickly as it can execute.
void loop() {
  if (Serial.available()) {
    c=Serial.read();
    if (c=='o') {
      c=0;
      OTA *ota = new OTA();
      ota->GetUpdate("mydomain.com","/firmware/simple.bin");
    }
  }
}

OTA.cpp:

#include <spark_wiring_tcpclient.h>
#include <Particle.h>
#include <system_update.h>
#include "OTA.h"
#include "version.h"

TCPClient *swclient;

OTA::OTA() {
    Serial.println("OTA() created without TLS");
    
}
OTA::~OTA() {
    delete swclient;
}

bool OTA::GetUpdate(char *host, const char *path) {
    unsigned char   buf[256];
    char            tmpstr[4];
    unsigned char   c = 0;
    unsigned char   last = 0;
    system_tick_t   timeout = 0;
    bool            done = false;
    bool            gotHeader = false;
    bool            hf = false;
    bool            ok = false;
    int             avail = 0;
    char            *pos;
    unsigned int    response = 0;
    unsigned int    length = 0;
    unsigned int    timePrg = 0;
    unsigned int    received = 0;

    FileTransfer::Descriptor file;
    swclient = new TCPClient();
    String header = "";

    Log("OTA: free memory %d bytes", System.freeMemory());    

    Log("Getting %s%s", host, path);
    Log("mem: %d", System.freeMemory());
    int result = swclient->connect((char *)host, 80);
    Log("connect: %d", result);
    if (result < 0) {
        return false;
    }

    // Send request to web server.
    int len = sprintf((char *)buf, 
        "GET %s HTTP/1.0\r\n"
        "Host: %s\r\n"
        "Content-Length: 0\r\n"
        "User-Agent: picsil Sense %s\r\n"
        "\r\n", path, host, PICSIL_VERSION);

    Log("OTA request: %s", buf);
    swclient->write(buf, len );
    
    // GET response.
    memset(buf, 0, sizeof(buf));
    timeout = millis();
    delay(250);
    while (!done) {        
        if (!swclient->connected()) {
            Log("Connection Error!");
            delay(1000);
            System.reset(); //TODO: set reason
        }
        if (millis() - timeout > 120000) {
            Log("Timeout!");
            delay(1000);
            System.reset(); //TODO: set reason
        }
        avail = swclient->available();
        if (avail>0) {
            //Log("avail: %d", avail);
            if (!gotHeader) {
                c = swclient->read();
                Serial.printf("%c",c);
                sprintf(tmpstr, "%c", c);                
                header.concat(tmpstr);

                if (c=='\n' && last=='\r') {
                    if (hf) {
                        gotHeader=true;
                        Log("Header: %s", header.c_str());

                        pos=strstr(header.c_str(), " ");
                        if (pos) {
                            pos += 1;
                            response = atoi(pos);
                            Log("Response Code: %d", response);                            
                        }
                        pos = strstr(header.c_str(), "Content-Length: ");
                        if (pos) {
                            pos += 16;
                            length = atoi(pos);
                            Log("Download Length: %d", length);
                        }

                        if (response != 200) {
                            Log("Invalid Response Code %d!", response);
                            delay(1000);
                            System.reset(); //TODO: set reason    
                        }                            
                        
                        if (!done && length>0) {
                            Log("Preparing Update");
                            file.file_length = length;
                            file.file_address = 0;
                            file.chunk_size = 256;
                            file.store = FileTransfer::Store::FIRMWARE;

                            result = Spark_Prepare_For_Firmware_Update(file, 0, NULL);
                            if (result != 0) {
                                Log("Prepare Failed with result %d", result);
                                delay(1000);
                                System.reset(); //TODO: add reason
                            } else {
                                file.chunk_address = file.file_address;
                            }
                        }
                    } else {
                        hf = true;
                    }
                } else {
                    if (c != '\r') {
                        hf = false;
                    }
                }
                last = c;
                timePrg = millis();
            } else {
                //Log("avail: %d", avail);
                if (avail>sizeof(buf)) avail = sizeof(buf);
                avail = swclient->read(&buf[0], avail);
                received += avail;

                //Set chunk size from downloaded data
                file.chunk_size = avail;
                result = Spark_Save_Firmware_Chunk(file, &buf[0], NULL);
                if (result != 0) {
                    Log("Save chunk failed with result %d", result);
                    delay(1000);
                    System.reset(); //TODO: set reason
                }
                Log("Received %d of %d bytes. Chunk Size %d. Chunk addr=%x", received, length, avail, file.chunk_address);

                //Move start to next chunk
                file.chunk_address += file.chunk_size;

                if (length > 0 && received >= length) {
                    done=true;
                    ok=true;
                    Log("Firmware Received!");
                }
            }
            timeout = millis();
        }
    }
    if (received == 0) {
        Log("Header(2): %s", header.c_str());
    } else {
        Log("Received %d bytes", received);
    }
    
    //swclient->stop();

    if (ok) {
        Log("Launching Update");
    } else {
        Log("Process finished with error. Resetting.");
        delay(1000);
        System.reset();
    }

    result = Spark_Finish_Firmware_Update(file, ok, NULL);
    if (result != 0) {
        Log("Finish failed with result code %d", result);
        delay(1000);
        System.reset();
    } else {
        Log("Success!! System resetting.");
        delay(1000);
        System.reset();
    }
}

Finally, here is the output of the above code:

0000010682 [comm.protocol] ERROR: Channel failed to send message with error-code <0>
V2
Hello! Press o for OTA.
OTA() created without TLS
0000018736 [app] INFO: OTA: free memory 70184 bytes
0000018737 [app] INFO: Getting mydomain.com/firmware/simple.bin
0000018738 [app] INFO: mem: 70184
0000019127 [app] INFO: connect: 1
0000019127 [app] INFO: OTA request: GET /firmware/simple.bin HTTP/1.0
Host: mydomain.com
Content-Length: 0
User-Agent: picsil Sense 0.0.1


HTTP/1.1 200 OK
Date: Sun, 26 May 2019 18:12:19 GMT
Server: Apache/2.4.39 (cPanel) OpenSSL/1.0.2r mod_bwlimited/1.4
Last-Modified: Sun, 26 May 2019 16:57:04 GMT
ETag: "480419-1394-589cd523e17e7"
Accept-Ranges: bytes
Content-Length: 5012
Connection: close
Content-Type: application/octet-stream

0000019631 [app] INFO: Header: HTTP/1.1 200 OK
Date: Sun, 26 May 2019 18:12:19 GMT
Server: Apache/2.4.39 (cPanel) OpenSSL/1.0.2r mod_bwlimited/1.4
Last-Modified: Sun, 26 May 2019~
0000019633 [app] INFO: Response Code: 200
0000019634 [app] INFO: Download Length: 5012
0000019634 [app] INFO: Preparing Update
0000019635 [app] INFO: Received 79 of 5012 bytes. Chunk Size 79. Chunk addr=202a06a8
0000019636 [app] INFO: Received 207 of 5012 bytes. Chunk Size 128. Chunk addr=202a06f7
0000019638 [app] INFO: Received 335 of 5012 bytes. Chunk Size 128. Chunk addr=202a0777
0000019639 [app] INFO: Received 463 of 5012 bytes. Chunk Size 128. Chunk addr=202a07f7
0000019640 [app] INFO: Received 591 of 5012 bytes. Chunk Size 128. Chunk addr=202a0877
0000019642 [app] INFO: Received 655 of 5012 bytes. Chunk Size 64. Chunk addr=202a08f7
0000019679 [app] INFO: Received 783 of 5012 bytes. Chunk Size 128. Chunk addr=202a0937
0000019680 [app] INFO: Received 911 of 5012 bytes. Chunk Size 128. Chunk addr=202a09b7
0000019682 [app] INFO: Received 1039 of 5012 bytes. Chunk Size 128. Chunk addr=202a0a37
0000019683 [app] INFO: Received 1167 of 5012 bytes. Chunk Size 128. Chunk addr=202a0ab7
0000019684 [app] INFO: Received 1295 of 5012 bytes. Chunk Size 128. Chunk addr=202a0b37
0000019686 [app] INFO: Received 1423 of 5012 bytes. Chunk Size 128. Chunk addr=202a0bb7
0000019687 [app] INFO: Received 1551 of 5012 bytes. Chunk Size 128. Chunk addr=202a0c37
0000019688 [app] INFO: Received 1615 of 5012 bytes. Chunk Size 64. Chunk addr=202a0cb7
0000019780 [app] INFO: Received 1743 of 5012 bytes. Chunk Size 128. Chunk addr=202a0cf7
0000019782 [app] INFO: Received 1871 of 5012 bytes. Chunk Size 128. Chunk addr=202a0d77
0000019783 [app] INFO: Received 1999 of 5012 bytes. Chunk Size 128. Chunk addr=202a0df7
0000019784 [app] INFO: Received 2127 of 5012 bytes. Chunk Size 128. Chunk addr=202a0e77
0000019786 [app] INFO: Received 2255 of 5012 bytes. Chunk Size 128. Chunk addr=202a0ef7
0000019787 [app] INFO: Received 2383 of 5012 bytes. Chunk Size 128. Chunk addr=202a0f77
0000019788 [app] INFO: Received 2511 of 5012 bytes. Chunk Size 128. Chunk addr=202a0ff7
0000019790 [app] INFO: Received 2575 of 5012 bytes. Chunk Size 64. Chunk addr=202a1077
0000019881 [app] INFO: Received 2703 of 5012 bytes. Chunk Size 128. Chunk addr=202a10b7
0000019883 [app] INFO: Received 2831 of 5012 bytes. Chunk Size 128. Chunk addr=202a1137
0000019884 [app] INFO: Received 2959 of 5012 bytes. Chunk Size 128. Chunk addr=202a11b7
0000019885 [app] INFO: Received 3087 of 5012 bytes. Chunk Size 128. Chunk addr=202a1237
0000019887 [app] INFO: Received 3215 of 5012 bytes. Chunk Size 128. Chunk addr=202a12b7
0000019888 [app] INFO: Received 3343 of 5012 bytes. Chunk Size 128. Chunk addr=202a1337
0000019889 [app] INFO: Received 3471 of 5012 bytes. Chunk Size 128. Chunk addr=202a13b7
0000019891 [app] INFO: Received 3535 of 5012 bytes. Chunk Size 64. Chunk addr=202a1437
0000020034 [app] INFO: Received 3663 of 5012 bytes. Chunk Size 128. Chunk addr=202a1477
0000020035 [app] INFO: Received 3791 of 5012 bytes. Chunk Size 128. Chunk addr=202a14f7
0000020037 [app] INFO: Received 3919 of 5012 bytes. Chunk Size 128. Chunk addr=202a1577
0000020038 [app] INFO: Received 4047 of 5012 bytes. Chunk Size 128. Chunk addr=202a15f7
0000020039 [app] INFO: Received 4175 of 5012 bytes. Chunk Size 128. Chunk addr=202a1677
0000020041 [app] INFO: Received 4303 of 5012 bytes. Chunk Size 128. Chunk addr=202a16f7
0000020042 [app] INFO: Received 4431 of 5012 bytes. Chunk Size 128. Chunk addr=202a1777
0000020044 [app] INFO: Received 4559 of 5012 bytes. Chunk Size 128. Chunk addr=202a17f7
0000020045 [app] INFO: Received 4687 of 5012 bytes. Chunk Size 128. Chunk addr=202a1877
0000020046 [app] INFO: Received 4815 of 5012 bytes. Chunk Size 128. Chunk addr=202a18f7
0000020048 [app] INFO: Received 4943 of 5012 bytes. Chunk Size 128. Chunk addr=202a1977
0000020049 [app] INFO: Received 5012 of 5012 bytes. Chunk Size 69. Chunk addr=202a19f7
0000020050 [app] INFO: Firmware Received!
0000020050 [app] INFO: Received 5012 bytes
0000020051 [app] INFO: Launching Update
0000021063 [app] INFO: Finish failed with result code 1

5 Likes

Bumping this thread up a little bit since it was posted over a US holiday weekend. Does anyone have any insight into the issue above?

I can’t respond to this. But very interesting.

Sometimes it’s the obvious that gets in my way. The above code works! What should have been obvious to me is that when debugging, you flash a monolithic firmware. I was trying to flash modular firmware OTA, which will always fail. Reflashing deviceOS and running this example as modular worked great!

After further testing I can confirm that this also updates deviceOS firmware with no changes. I upgraded a Boron from 1.2.1-rc.1 to 1.2.1-rc.2 OTA. Boron came back online using third party SIM with no problems. :+1:

1 Like

Thanks for your Post.

Thanks for this awesome example!
I tried it out and it works just sometimes for me. I am using SYSTEM_MODE(MANUAL); and I get the following errors pretty often:

...
0000204406 [app.OTA] INFO: Firmware Received!
0000204407 [app.OTA] INFO: Received 15924 bytes
0000204431 [app.OTA] INFO: Launching Update
0000204454 [comm.protocol] ERROR: Event loop error 3
0000204454 [comm] WARN: error receiving acknowledgements: 3

The device then resets and the firmware is not updated. Any ideas why this happens? Could it be possible that some system related stuff is not called because of the manual system mode?

I use it with manual mode as well and haven’t had this issue. Do you have SYSTEM_THREAD(ENABLED)? If so you might try disabling that. Also make sure you have plenty of free RAM. It needs at least 20KB free if I recall. I dispose of a few things like MQTT-TLS before entering this routine.

Thanks for helping!
Yes I used THREAD but I also tried it without. Do you think it is sufficient to call SYSTEM_THREAD(DISABLED) right before the routine?
Yes I have approx. 60KB of free RAM so I think that should be good.

EDIT: Which device os did you use? I am using currently 1.4.2

I haven’t tried it on 1.4.x yet. It was working for me on 1.2 and 1.3

There is no SYSTEM_THREAD(DISABLED) call really.

The SYSTEM_THREAD(ENABLED) macro controls how the firmware should/will be running. It will setup a dedicated thread for the system tasks and a separate thread for the application and once separated these two threads can't be join back together.

1 Like

I tried the example but it won’t work. Do I need to keep something in mind when compiling the binary for OTA? I always do it by the Particle Workbench (“Particle: Compile application (local)”) but it never works. I always get the following error:

0000131789 [hal] WARN: OTA module not applied
0000131789 [comm.protocol] ERROR: Event loop error 3
0000131789 [comm] WARN: error receiving acknowledgements: 3

Any ideas?

At some point this method broke and I have not had a chance to revisit it.

Thanks for the information. What a pity. I will see if I can investigate further.

UPDATE: One source of problem is that you are not setting file.chunk_address = 0;

1 Like

Thanks @lympik. I’m back to testing this and so far it looks like setting file.chunk_address = 0 works. I’ve flash a few versions of my user firmware now and all were successful. More testing is needed, especially with deviceOS. Preliminary results look good though.

I’m not able to edit the code blocks in the original post, so I am reposting with changes that appear to work.

ota-test.ino

#include <OTA.h>
SerialLogHandler logHandler(LOG_LEVEL_TRACE);
char c;

// setup() runs once, when the device is first turned on.
void setup() {
  // Put initialization like pinMode and begin functions here.
  Serial.begin(115200);
  delay(5000);
  Serial.println("V2");
  Serial.println("Hello! Press o for OTA.");
}

// loop() runs over and over again, as quickly as it can execute.
void loop() {
  if (Serial.available()) {
    c=Serial.read();
    if (c=='o') {
      c=0;
      OTA *ota = new OTA();
      ota->GetUpdate("mydomain.com","/firmware/simple.bin");
    }
  }
}

OTA.cpp:

#include <spark_wiring_tcpclient.h>
#include <Particle.h>
#include <system_update.h>
#include "OTA.h"
#include "version.h"

TCPClient *swclient;

OTA::OTA() {
    Serial.println("OTA() created without TLS");
    
}
OTA::~OTA() {
    delete swclient;
}

bool OTA::GetUpdate(char *host, const char *path) {
    unsigned char   buf[256];
    char            tmpstr[4];
    unsigned char   c = 0;
    unsigned char   last = 0;
    system_tick_t   timeout = 0;
    bool            done = false;
    bool            gotHeader = false;
    bool            hf = false;
    bool            ok = false;
    int             avail = 0;
    char            *pos;
    unsigned int    response = 0;
    unsigned int    length = 0;
    unsigned int    timePrg = 0;
    unsigned int    received = 0;

    FileTransfer::Descriptor file;
    swclient = new TCPClient();
    String header = "";

    Log("OTA: free memory %d bytes", System.freeMemory());    

    Log("Getting %s%s", host, path);
    Log("mem: %d", System.freeMemory());
    int result = swclient->connect((char *)host, 80);
    Log("connect: %d", result);
    if (result < 0) {
        return false;
    }

    // Send request to web server.
    int len = sprintf((char *)buf, 
        "GET %s HTTP/1.0\r\n"
        "Host: %s\r\n"
        "Content-Length: 0\r\n"
        "User-Agent: picsil Sense %s\r\n"
        "\r\n", path, host, PICSIL_VERSION);

    Log("OTA request: %s", buf);
    swclient->write(buf, len );
    
    // GET response.
    memset(buf, 0, sizeof(buf));
    timeout = millis();
    delay(250);
    while (!done) {        
        if (!swclient->connected()) {
            Log("Connection Error!");
            delay(1000);
            System.reset(); //TODO: set reason
        }
        if (millis() - timeout > 120000) {
            Log("Timeout!");
            delay(1000);
            System.reset(); //TODO: set reason
        }
        avail = swclient->available();
        if (avail>0) {
            //Log("avail: %d", avail);
            if (!gotHeader) {
                c = swclient->read();
                Serial.printf("%c",c);
                sprintf(tmpstr, "%c", c);                
                header.concat(tmpstr);

                if (c=='\n' && last=='\r') {
                    if (hf) {
                        gotHeader=true;
                        Log("Header: %s", header.c_str());

                        pos=strstr(header.c_str(), " ");
                        if (pos) {
                            pos += 1;
                            response = atoi(pos);
                            Log("Response Code: %d", response);                            
                        }
                        pos = strstr(header.c_str(), "Content-Length: ");
                        if (pos) {
                            pos += 16;
                            length = atoi(pos);
                            Log("Download Length: %d", length);
                        }

                        if (response != 200) {
                            Log("Invalid Response Code %d!", response);
                            delay(1000);
                            System.reset(); //TODO: set reason    
                        }                            
                        
                        if (!done && length>0) {
                            Log("Preparing Update");
                            file.file_length = length;
                            file.file_address = 0;
                            file.chunk_size = 256;
                            file.chunk_address = 0; 
                            file.store = FileTransfer::Store::FIRMWARE;

                            result = Spark_Prepare_For_Firmware_Update(file, 0, NULL);
                            if (result != 0) {
                                Log("Prepare Failed with result %d", result);
                                delay(1000);
                                System.reset(); //TODO: add reason
                            } else {
                                file.chunk_address = file.file_address;
                            }
                        }
                    } else {
                        hf = true;
                    }
                } else {
                    if (c != '\r') {
                        hf = false;
                    }
                }
                last = c;
                timePrg = millis();
            } else {
                //Log("avail: %d", avail);
                if (avail>sizeof(buf)) avail = sizeof(buf);
                avail = swclient->read(&buf[0], avail);
                received += avail;

                //Set chunk size from downloaded data
                file.chunk_size = avail;
                result = Spark_Save_Firmware_Chunk(file, &buf[0], NULL);
                if (result != 0) {
                    Log("Save chunk failed with result %d", result);
                    delay(1000);
                    System.reset(); //TODO: set reason
                }
                Log("Received %d of %d bytes. Chunk Size %d. Chunk addr=%x", received, length, avail, file.chunk_address);

                //Move start to next chunk
                file.chunk_address += file.chunk_size;

                if (length > 0 && received >= length) {
                    done=true;
                    ok=true;
                    Log("Firmware Received!");
                }
            }
            timeout = millis();
        }
    }
    if (received == 0) {
        Log("Header(2): %s", header.c_str());
    } else {
        Log("Received %d bytes", received);
    }
    
    //swclient->stop();

    if (ok) {
        Log("Launching Update");
    } else {
        Log("Process finished with error. Resetting.");
        delay(1000);
        System.reset();
    }

    result = Spark_Finish_Firmware_Update(file, ok, NULL);
    if (result != 0) {
        Log("Finish failed with result code %d", result);
        delay(1000);
        System.reset();
    } else {
        Log("Success!! System resetting.");
        delay(1000);
        System.reset();
    }
}

OTA.h:

#ifndef _OTA_H
#define _OTA_H

class OTA {
    public:
        //OTA(const char *amazonIoTRootCaPem);
        OTA();
        ~OTA();

        bool GetUpdate(const char *host, const char *path);        
    private:

    typedef enum module_function_t {
        MODULE_FUNCTION_NONE = MOD_FUNC_NONE,

        /* The module_info and CRC is not part of the resource. */
        MODULE_FUNCTION_RESOURCE = MOD_FUNC_RESOURCE,

        /* The module is the bootloader */
        MODULE_FUNCTION_BOOTLOADER = MOD_FUNC_BOOTLOADER,

        /* The module is complete system and user firmware */
        MODULE_FUNCTION_MONO_FIRMWARE = MOD_FUNC_MONO_FIRMWARE,

        /* The module is a system part */
        MODULE_FUNCTION_SYSTEM_PART = MOD_FUNC_SYSTEM_PART,

        /* The module is a user part */
        MODULE_FUNCTION_USER_PART = MOD_FUNC_USER_PART,

        /* Rewrite persisted settings. (Not presently used?) */
        MODULE_FUNCTION_SETTINGS = MOD_FUNC_SETTINGS,

        /* Firmware targeted for the NCP. */
        MODULE_FUNCTION_NCP_FIRMWARE = MOD_FUNC_NCP_FIRMWARE,

        /* Radio stack module */
        MODULE_FUNCTION_RADIO_STACK = MOD_FUNC_RADIO_STACK

    } module_function_t;

    typedef enum {

        MODULE_STORE_MAIN = 0,
        /**
         * Factory restore module.
         */
        MODULE_STORE_FACTORY = 1,

        /**
         * An area that saves a copy of modules.
         */
        MODULE_STORE_BACKUP = 2,

        /**
         * Temporary area used to store the module before transferring to it's
         * target.
         */
        MODULE_STORE_SCRATCHPAD = 3,

    } module_store_t;

    typedef struct module_dependency_t {
        uint8_t module_function;        // module function, lowest 4 bits
        uint8_t module_index;           // moudle index, lowest 4 bits.
        uint16_t module_version;        // version/release number of the module.
    } module_dependency_t;

    typedef struct module_info_t {
        const void* module_start_address;   /* the first byte of this module in flash */
        const void* module_end_address;     /* the last byte (exclusive) of this smodule in flash. 4 byte crc starts here. */
        uint8_t reserved;                   /* Platform-specific definition (mcu_target on Gen3) */
        uint8_t flags;                      /* module_info_flags_t */
        uint16_t module_version;            /* 16 bit version */
        uint16_t platform_id;               /* The platform this module was compiled for. */
        uint8_t  module_function;           /* The module function */
        uint8_t  module_index;              /* distinguish modules of the same type */
        module_dependency_t dependency;
        module_dependency_t dependency2;
    } module_info_t;

    typedef struct {
        uint32_t maximum_size;      // the maximum allowable size for the entire module image
        uint32_t start_address;     // the designated start address for the module
        uint32_t end_address;       //
        module_function_t module_function;
        uint8_t module_index;
        module_store_t store;
        uint8_t mcu_identifier;		// which MCU is targeted by this module. 0 means main/primary MCU. HAL_PLATFORM_MCU_ANY
    } module_bounds_t;

    typedef struct {
        module_bounds_t bounds;
        const module_info_t* info;      // pointer to the module info in the module, may be NULL
        const module_info_crc_t* crc;
        const module_info_suffix_t* suffix;
        uint16_t validity_checked;    // the flags that were checked
        uint16_t validity_result;     // the result of the checks
    } hal_module_t;      

};
#endif

In the end I found out what the issue was! It has to do with the alignment of bytes when writing to the flash. It was quite deep down in the Device OS. After that everything worked perfectly. You can read about the issue here:

It should be fixed and working.