Software Update via Ethernet - the mbed application can pull down an updated application binary from a web server and activate that binary. This library works only with the LPC1768, as it relies on the magic-chip boot-loader mechanism.

Dependents:   WattEye X10Svr PUB_SWUpdate

Success!! With this library, a network connection, and a web server hosting a new binary image, you can update the mbed firmware over the air (FOTA) - well, at least via Ethernet so far.

As of March 2015, it has been tested with the following mbed official libraries:

And a custom derivation:

  • HTTPClient v33, v32, which includes a custom HTTPFile.

Part of the update process involves checking the integrity of the downloaded binary file, for both a checksum and the program (file) size. To create this additional information, a small perl script is used (the important part is only 20 lines of code). See the documentation in the header file.

After the new binary is successfully downloaded, the checksum and the size are evaluated and if correct, then the old binary file is removed (this is the only way to cause the new binary to activate).

The mbed can then be automatically reset to activate the new image, or this may be deferred in case there is some other process necessary for an orderly restart.

Details are in the SWUpdate header file, and PUB_SWUpdate is a publicly accessible demonstration program for this library.

SWUpdate.cpp

Committer:
WiredHome
Date:
2020-09-13
Revision:
29:f67a7f54c173
Parent:
27:3d3089b8212d
Parent:
28:881661d475c1

File content as of revision 29:f67a7f54c173:


// Software Update via Ethernet from forum -
// http://mbed.org/forum/mbed/topic/1183/
//
#include "mbed.h"
#include "SWUpdate.h"
#include "HTTPFile.h"
#include <stdio.h>

extern "C" void mbed_reset();

//#define DEBUG "SWup"
#include <cstdio>
#if (defined(DEBUG) && !defined(TARGET_LPC11U24))
#define DBG(x, ...)  std::printf("[DBG %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define WARN(x, ...) std::printf("[WRN %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define ERR(x, ...)  std::printf("[ERR %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define INFO(x, ...) std::printf("[INF %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#else
#define DBG(x, ...)
#define WARN(x, ...)
#define ERR(x, ...)
#define INFO(x, ...)
#endif

static HTTPResult HTTPErrorCode;

typedef enum {
    ok,
    no_file,
    bad_crc
} Integrity_t;


const char * SWErrorMsg[] = {
    "OK",               // SWUP_OK               = 0x00,   ///< Software Update succeeded as planned.
    "Same version",     // SWUP_SAME_VER         = 0x01,   ///< Online version is the same as the installed version.
    "Get bin error",    // SWUP_HTTP_BIN         = 0x02,   ///< HTTP get returned an error while trying to fetch the bin file.
    "Old file stuck",   // SWUP_OLD_STUCK        = 0x03,   ///< Old file could not be removed.
    "Old vers stuck",   // SWUP_VER_STUCK        = 0x04,   ///< Old version number could not be updated.
    "Ver write fail",   // SWUP_VWRITE_FAILED    = 0x05,   ///< Can't open for write the version tracking file.
    "Integrity fail",   // SWUP_INTEGRITY_FAILED = 0x06,   ///< Integrity check of downloaded file failed.
    "Get ver fail",     // SWUP_HTTP_VER         = 0x07,   ///< HTTP get returned an error while trying to fetch the version file.
    "Filesys full",     // SWUP_NO_SPACE         = 0x08,   ///< No space on file system for new version.
    "Does not exist",   // SWUP_NO_FILE          = 0x09,  ///< Specified file does not exist
};

const char * SoftwareUpdateGetHTTPErrorMsg(SWUpdate_T r)
{
    const char * p = "invalid result code";
    if (r <= SWUP_NO_FILE)
        p = SWErrorMsg[r];
    return p;
}

HTTPResult SoftwareUpdateGetHTTPErrorCode(void)
{
    return HTTPErrorCode;
}


static Integrity_t PassesIntegrityCheck(const char * fname, int cksum, int fsize)
{
    Integrity_t res = bad_crc;    // assume things go wrong...
    int newCksum = 0;
    int newFSize = 0;
    FILE *fh = fopen(fname, "rb");

    INFO("IntegrityCheck(%s,%d,%d)", fname, cksum, fsize);
    if (fh) {
        char buf;
        while (fread(&buf, 1, 1, fh)) {
            newCksum = (newCksum + buf) & 0xFFFF;
            newFSize++;
        }
        fclose(fh);
        INFO("      Check(...,%d,%d)", newCksum, newFSize);
        if (newCksum == cksum && newFSize == fsize)
            res = ok;
    } else {
        WARN("failed to open %s.", fname);
        res = no_file;
    }
    return res;
}

/// mytolower exists because not all compiler libraries have this function
///
/// This takes a character and if it is upper-case, it converts it to
/// lower-case and returns it.
///
/// @note this only works for characters in the range 'A' - 'Z'.
///
/// a is the character to convert
/// returns the lower case equivalent to the supplied character.
///
static char mytolower(char a)
{
    if (a >= 'A' && a <= 'Z')
        return (a - 'A' + 'a');
    else
        return a;
}

/// mystrnicmp exists because not all compiler libraries have this function.
///
/// Some have strnicmp, others _strnicmp, and others have C++ methods, which
/// is outside the scope of this C-portable set of functions.
///
/// l is a pointer to the string on the left
/// r is a pointer to the string on the right
/// n is the number of characters to compare
/// returns -1 if l < r
/// returns 0 if l == r
/// returns +1 if l > r
///
static int mystrnicmp(const char *l, const char *r, size_t n)
{
    int result = 0;

    if (n != 0) {
        do {
            result = mytolower(*l++) - mytolower(*r++);
        } while ((result == 0) && (*l != '\0') && (--n > 0));
    }
    if (result < -1)
        result = -1;
    else if (result > 1)
        result = 1;
    return result;
}


// Scan the local file system for any .bin files and
// if they don't match the current one, remove them.
//
static bool RemoveOtherBinFiles(const char * name, int ver)
{
    char curbin[SW_MAX_FQFN];
    DIR *d;
    struct dirent *p;
    bool noFailed = true;

    snprintf(curbin, SW_MAX_FQFN, "%s%02d.bin", name, (ver % 100));
    INFO("Remove bin files excluding {%s}", curbin);
    d = opendir("/local/");
    // Get a directory handle
    if ( d != NULL ) {
        // Walk the directory
        while ( (p = readdir(d)) != NULL ) {
            INFO("  check {%s}", p->d_name);
            // if the file is .bin and not curbin
            if (0 == mystrnicmp(p->d_name + strlen(p->d_name) - 4, ".bin", 4)
                    && (0 != mystrnicmp(p->d_name, curbin, strlen(curbin)))) {
                // remove the file
                char toremove[SW_MAX_FQFN];
                snprintf(toremove, SW_MAX_FQFN, "/local/%s", p->d_name);
                INFO("    removing %s.", toremove);
                if (remove(toremove)) {
                    // set flag if it could not be removed
                    noFailed = false;
                }
            }
        }
        closedir(d);
    }
    return noFailed;
}


int GetSoftwareVersionNumber(const char * name)
{
    char nameroot[7];
    char verfn[SW_MAX_FQFN];     // local version file
    
    strncpy(nameroot, name, 6);
    nameroot[6] = '\0';
    snprintf(verfn, SW_MAX_FQFN, "/local/%s.ver", nameroot);

    /* Read installed version string */
    int inst_ver = -1;
    FILE *fv = fopen(verfn, "r");
    if (fv) {
        fscanf(fv, "%d", &inst_ver);
        fclose(fv);
    } else {
        inst_ver = -1;
    }
    INFO("  Installed version: %d", inst_ver);
    return inst_ver;
}

bool SetSoftwareVersionNumber(const char * name, int ver, int cksum, int filesize)
{
    char nameroot[7];
    char verfn[SW_MAX_FQFN];     // local version file
    char buf[40];
    
    strncpy(nameroot, name, 6);
    nameroot[6] = '\0';
    snprintf(verfn, SW_MAX_FQFN, "/local/%s.ver", nameroot);
    snprintf(buf, 40, "%d,%d,%d", ver, cksum, filesize);
    FILE *fv = fopen(verfn, "w");
    if (fv) {
        int fr = fputs(buf, fv);
        fclose( fv );
        if (fr >= 0) {
            return true;
        } else {
            ERR("Failed (%d) to update stored version number.", fr);
        }
    } else {
        WARN("Failed to update local version info in %s.", verfn);
    }
    return false;
}

SWUpdate_T SoftwareUpdate(const char *url, const char * name, Reboot_T action)
{
    HTTPClient http;
    //http.setTimeout( 15000 );
    char fqurl[SW_MAX_URL];    // fully qualified url
    char fwfn[SW_MAX_FQFN];
    char nameroot[7];
    SWUpdate_T result = SWUP_OK;    // starting out quite optimistic, for all the things that can go wrong
    char buf[50];           // long enough for 3 comma separated numbers...

    INFO("SoftwareUpdate(%s , %s)", url, name);
    strncpy(nameroot, name, 6);
    nameroot[6] = '\0';
    int inst_ver = GetSoftwareVersionNumber(name);
    /* Download latest version string */
    //HTTPText server_ver("test message");
    snprintf(fqurl, SW_MAX_URL, "%s/%s.txt", url, name);
    INFO("Query %s", fqurl);
    HTTPErrorCode = http.get(fqurl, buf, sizeof(buf));
    if (HTTPErrorCode == HTTP_OK) {
        int latest_ver = -1;
        int cksum = 0;
        int fsize = 0;
        int parseCount;
        parseCount = sscanf(buf, "%d,%d,%d", &latest_ver, &cksum, &fsize);
        if (parseCount == 3) {
            INFO("        web version: %d", latest_ver);
            INFO("           checksum: %d", cksum);
            INFO("          file size: %d", fsize);
            if (inst_ver != latest_ver) {
                INFO("  Downloading firmware ver %d ...", latest_ver);
                snprintf(fwfn, SW_MAX_FQFN, "/local/%s%02d.BIN", nameroot, (latest_ver % 100));
                snprintf(fqurl, SW_MAX_URL, "%s/%s.bin", url, name);

                HTTPFile latest(fwfn);
                INFO("Fetch %s", fqurl);
                HTTPErrorCode = http.get(fqurl, &latest);
                if (HTTPErrorCode == HTTP_OK) {
                    Integrity_t t = PassesIntegrityCheck(fwfn, cksum, fsize);
                    if (t == no_file) {
                        ERR("  *** No space on file system. ***");
                        result = SWUP_NO_SPACE;
                    } else if (t == ok) {
                        if (!RemoveOtherBinFiles(nameroot, latest_ver)) {
                            ERR("  *** Failed to remove old version(s). ***");
                            result = SWUP_OLD_STUCK;
                        }
                        INFO("Updating stored version number.");
                        if (SetSoftwareVersionNumber(name, latest_ver, cksum, fsize)) {
                            // ok
                            if (action == AUTO_REBOOT) {
                                WARN("Resetting...\n");
                                wait_ms(200);
                                mbed_reset();
                            }
                        } else {
                            // failed
                            ERR("Failed to update stored version number.");
                            result = SWUP_VWRITE_FAILED;
                        }
                    } else /* t == bad_crc */ {
                        WARN("New file {%s} did not pass integrity check.", fwfn);
                        result = SWUP_INTEGRITY_FAILED;
                    }
                } else {
                    WARN("Failed to download lastest firmware.");
                    result = SWUP_HTTP_BIN;
                }
            } else {
                INFO("Online version is same as installed version.");
                result = SWUP_SAME_VER;
            }
        }
    } else {
        int ec = http.getHTTPResponseCode();
        WARN("Failed accessing {%s}. Extended Error Code = %d", fqurl, HTTPErrorCode);
        WARN("  HTTP Response Code %d", ec);
        if (ec == 404)
            result = SWUP_NO_FILE;
        else
            result = SWUP_HTTP_VER;
    }
    return result;
}