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.

Files at this revision

API Documentation at this revision

Comitter:
WiredHome
Date:
Sat Jun 21 19:13:30 2014 +0000
Parent:
8:8e840a036116
Child:
10:78d727e8d0de
Commit message:
Several updates to make it more robust and reliable, including removal of all other .bin files.

Changed in this revision

SWUpdate.cpp Show annotated file Show diff for this revision Revisions of this file
SWUpdate.h Show annotated file Show diff for this revision Revisions of this file
--- a/SWUpdate.cpp	Sun Jun 15 20:01:58 2014 +0000
+++ b/SWUpdate.cpp	Sat Jun 21 19:13:30 2014 +0000
@@ -30,6 +30,7 @@
     int newCksum = 0;
     int newFSize = 0;
     FILE *fh = fopen(fname, "rb");
+    
     INFO("IntegrityCheck(%s,%d,%d)", fname, cksum, fsize);
     if (fh) {
         char buf;
@@ -47,17 +48,96 @@
     return res;
 }
 
-bool SoftwareUpdate(const char *url, const char * name, Reboot_T reboot) {
+/// 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.
+///
+/// @param a is the character to convert
+/// @returns the lower case equivalent to a
+///
+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.
+///
+/// @param l is a pointer to the string on the left
+/// @param r is a pointer to the string on the right
+/// @param 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%03d.bin", name, ver);
+    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;
+}
+
+SWUpdate_T SoftwareUpdate(const char *url, const char * name, Reboot_T action) {
     HTTPClient http;
     //http.setTimeout( 15000 ); 
-    char fqurl[150];    // fully qualified url
-    char verfn[32];     // local version file
-    char fwfn[32];
-    bool result = false;    // many things can go wrong, assume failure
-    char buf[50];
+    char fqurl[SW_MAX_URL];    // fully qualified url
+    char verfn[SW_MAX_FQFN];     // local version file
+    char fwfn[SW_MAX_FQFN];
+    uint16_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);
-    snprintf(verfn, 32, "/local/%s.ver", name);
+    snprintf(verfn, SW_MAX_FQFN, "/local/%s.ver", name);
 
     /* Read installed version string */
     int inst_ver = -1;
@@ -70,7 +150,7 @@
     
     /* Download latest version string */
     HTTPText server_ver("test message");
-    snprintf(fqurl, 150, "%s/%s.txt", url, name);
+    snprintf(fqurl, SW_MAX_URL, "%s/%s.txt", url, name);
     HTTPResult r = http.get(fqurl, buf, sizeof(buf));
     if (r == HTTP_OK) {
         int latest_ver = -1;
@@ -79,25 +159,21 @@
         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);
+            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);
-                sprintf(fwfn, "/local/%s%d.BIN", name, latest_ver);
+                sprintf(fwfn, "/local/%s%03d.BIN", name, latest_ver);
                 snprintf(fqurl, 150, "%s/%s.bin", url, name);
         
                 HTTPFile latest(fwfn);
                 r = http.get(fqurl, &latest);
                 if (r == HTTP_OK) {
-                    // Check the integrity of the freshly downloaded file,
-                    // before swapping out the old version.
-                    // ... to appear here ...
                     if (PassesIntegrityCheck(fwfn, cksum, fsize)) {
-                        sprintf(fwfn, "/local/%s%d.BIN", name, inst_ver);
-                        INFO("  Firmware downloaded, removing old version (%s).", fwfn);
-                        if (remove(fwfn)) {
-                            ERR("  *** Failed to remove old version. ***");
+                        if (!RemoveOtherBinFiles(name, latest_ver)) {
+                            ERR("  *** Failed to remove old version(s). ***");
+                            result |= SWUP_OLD_STUCK;
                         }
                         INFO("Updating stored version number.");
                         fv = fopen(verfn, "w");
@@ -106,30 +182,35 @@
                             if (fr < 0) {
                                 ERR("Failed (%d) to update stored version number.", fr);
                                 fclose( fv );
+                                result |= SWUP_VER_STUCK;
                             } else {
                                 fclose( fv );
-                                if (reboot == AUTO_REBOOT) {
+                                if (action == AUTO_REBOOT) {
                                     WARN("Resetting...\n");
                                     wait_ms(200);
                                     mbed_reset();
                                 }
-                                result = true;
                             }
                         } else {
                             WARN("Failed to update local version info in %s.", verfn);
+                            result |= SWUP_VWRITE_FAILED;
                         }
                     } else {
                         WARN("New file {%s} did not pass integrity check.", fwfn);
+                        result |= SWUP_INTEGRITY_FAILED;
                     }
                 } else {
                     WARN("Failed to download lastest firmware.");
+                    result |= SWUP_BAD_URL;
                 }
             } else {
-                INFO("Online version is same as installed version.", parseCount);
+                INFO("Online version is same as installed version.");
+                result |= SWUP_SAME_VER;
             }
         }
     } else {
         WARN("Failed to download online firmware version number.");
+        result |= SWUP_HTTP_ERR;
     }
-    return result;
+    return (SWUpdate_T)result;
 }
--- a/SWUpdate.h	Sun Jun 15 20:01:58 2014 +0000
+++ b/SWUpdate.h	Sat Jun 21 19:13:30 2014 +0000
@@ -1,4 +1,7 @@
-/// Firmware Over The Air (FOTA) Update.
+/// Automatic Software Update via the network.
+///
+/// This library provides a reasonably simple interface to updating sofware
+/// semi-automatically via the network.
 ///
 #include "mbed.h"
 
@@ -8,29 +11,82 @@
 #ifndef SWUPDATE_H
 #define SWUPDATE_H
 
+// This defines the maximum string length for a fully qualified
+// filename. Usually, this will be pretty short 
+// (e.g. "/local/myprogramname.bin"), but we want to be generous.
+#define SW_MAX_FQFN 80
+
+// This defines the maximum string length for the url, including
+// the base filename of interest.
+#define SW_MAX_URL 150
+
 /// After downloading, the user can choose what happens next.
 typedef enum {
     DEFER_REBOOT,   ///< Do not reboot to activate the new firmware.
     AUTO_REBOOT     ///< Automatically reboot to activate the new firmware.
 } Reboot_T;
 
-/// This API performs some processing to see if a web server
-/// has an updated version of software. If it does, then it
-/// will try to download it. If that succeeds, then it can
+/// Bit-Field return codes from the SoftwareUpdate API.
+///
+/// Various things can go wrong in the software update process. The return
+/// value is a bit-field that flags the possibilities.
+typedef enum {
+    SWUP_OK               = 0x00,   ///< Software Update succeeded as planned.
+    SWUP_SAME_VER         = 0x01,   ///< Online version is the same as the installed version.
+    SWUP_BAD_URL          = 0x02,   ///< Bad URL provided, File missing on server, etc.
+    SWUP_OLD_STUCK        = 0x04,   ///< Old file could not be removed,
+    SWUP_VER_STUCK        = 0x08,   ///< Old version number could not be updated.
+    SWUP_VWRITE_FAILED    = 0x10,   ///< Can't open for write the version tracking file.
+    SWUP_INTEGRITY_FAILED = 0x20,   ///< Integrity check of downloaded file failed.
+    SWUP_HTTP_ERR         = 0x40,   ///< HTTP get returned an error
+} SWUpdate_T;
+
+/// This library provides a reasonably simple interface to updating sofware
+/// semi-automatically via a network connection.
+/// 
+/// This API performs some processing to see if a web server has an updated 
+/// version of the embedded software application. If it does, then it
+/// will try to download the updated software. If that succeeds, then it can
 /// optionally reboot to activate the new software.
 ///
-/// The files on the web server are as follows:
-///   @li myprog.bin - The actual binary file. The name is unimportant, but
-///             this is the binary file that was generated from the sources.
+/// While the name shown in the examples here is "myprog", this is unimportant.
+/// Your application will have a name of your choosing, which you will 
+/// use in the API.
+///
+/// Local File System Files:
+///
+/// The files of interest on the local file system are as follows:
+///
+///   @li myprog023.bin - The actual application binary file that is currently
+///             executing. In this case, this is the 23rd version that
+///             has been installed. You can go to 999 after which you might
+///             want to start over.
+///   @li myprog.ver - A text file, maintained by this software update
+///             application, that was downloaded from the server with
+///             application version 23. 
+///
+/// If "myprog.ver" does not exist, it will assume that the server has a 
+/// newer application, so it will be downloaded and activated (even if all
+/// it does is to replace the existing myprog023.bin file).
+///
+/// Web Server Files:
+///
+/// The files on the web server are as follows. 
+///
+///   @li myprog.bin - The latest version of the application binary file. 
+///             Note that this file does not have any version number
+///             embedded into its filename as is the case on the local
+///             file system.
 ///   @li myprog.txt - A corresponding text file. The root name must match
 ///             that of the binary file.
 ///
 /// The myprog.txt file shall have 3 comma-separated numbers in it.
-///   version,checksum,filesize (ex: "21,41384,107996")
+///   version,checksum,filesize (e.g. "23,41384,107996").
 ///
 ///   @li version is a simple number. If the number is different than
 ///             what is stored on the local file system, then the program
 ///             will be updated (even if the server number is lower).
+///             This bidirectional "update" can let you downgrade.
 ///   @li checksum is the decimal representation of a simple 16-bit checksum.
 ///   @li filesize is the decimal representation of the size of the file.
 ///
@@ -43,7 +99,7 @@
 /// $ver =~ s/(\d+),.*/$1/;
 /// print "Current Version is {$ver}\n";
 /// 
-/// # Read new .bin file
+/// # Read the [assumed new] .bin file
 /// open (FB, "<$bin") || die("Can't read $bin.");
 /// binmode FB;
 /// while (sysread(FB, $c, 1))
@@ -59,11 +115,28 @@
 /// close FT;
 /// @endcode
 ///
+/// Now, for this actual API, we simply give it the web server URL that
+/// is hosting the embedded software. We also give it the "root" name
+/// of the file of interest. The additional optional parameter lets
+/// you decide what happens if a new version is installed.
+///
+/// @code
+///     ...
+///     if (NowIsTheTimeToCheckForSoftwareUpdates()) {
+///         if (SWUP_OK == SoftwareUpdate("http://192.168.1.200", "myprog", DEFER_REBOOT)) {
+///             printf("Software updated, rebooting now...\r\n");
+///             wait_ms(5000);
+///             mbed_reset();
+///         }
+///     }
+///     ...
+/// @endcode
+///
 /// @param url is a pointer to a text string of the url from which to download.
 /// @param name is the base filename of the binary file.
-/// @param reboot determines whether to automatically reboot to activate the new bin.
+/// @param action determines whether to automatically reboot to activate the new bin.
 /// @return true if the update succeeded (and the reboot was set to DEFER_REBOOT).
 ///
-bool SoftwareUpdate(const char *url, const char * name, Reboot_T reboot = DEFER_REBOOT);
+SWUpdate_T SoftwareUpdate(const char *url, const char * name, Reboot_T action = AUTO_REBOOT);
 
 #endif // SWUPDATE_H