The FirmwareUpdater is a mbed firmware update library with HTTP server on cloud.

Dependents:   FirmwareUpdater_TestProgram geigercounter04 firm LPC1768_up_frim

FirmwareUpdater.cpp

Committer:
shintamainjp
Date:
2010-11-03
Revision:
2:a9a32355af69
Parent:
1:0305a8120f06
Child:
3:851bd91fa0ae

File content as of revision 2:a9a32355af69:

/**
 * =============================================================================
 * Firmware updater (Version 0.0.2)
 * =============================================================================
 * Copyright (c) 2010 Shinichiro Nakamura (CuBeatSystems)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * =============================================================================
 */

#include "FirmwareUpdater.h"

#include <stdio.h>
#include <stdarg.h>

extern "C" void mbed_reset();

const std::string FirmwareUpdater::EXT_BIN = ".bin";
const std::string FirmwareUpdater::EXT_BINTMP = ".b__";
const std::string FirmwareUpdater::EXT_TXT = ".txt";
const std::string FirmwareUpdater::EXT_TXTTMP = ".t__";

/**
 * Create.
 *
 * @param url URL for firmware. Do not include a target file name.
 * @param name An application name. Do not include a extention.
 * @param log True if logging.
 */
FirmwareUpdater::FirmwareUpdater(std::string url, std::string name, bool log)
        : url(url), name(name), log(log), local("local") {
    client.setTimeout(10000);

    /*
     * A file name on the mbed local file system should keep '8 + 3' types of name.
     */
    const int PREFIX_MAXLEN = 4;
    if (name.length() > PREFIX_MAXLEN) {
        LOG("ERR : Invalid firmware name '%s' found. The maximum length is PREFIX_MAXLEN.\n", name.c_str());
        error("ERR : Invalid firmware name '%s' found. The maximum length is PREFIX_MAXLEN.\n", name.c_str());
    }
}

/**
 * Dispose.
 */
FirmwareUpdater::~FirmwareUpdater() {
}

/**
 * Get a URL.
 *
 * @return URL.
 */
const std::string FirmwareUpdater:: getURL() const {
    return url;
}

/**
 * Get a name.
 *
 * @return name.
 */
const std::string FirmwareUpdater:: getName() const {
    return name;
}

/**
 * Checking a new firmware.
 * Compare versions of the software between local storage on mbed and on webserver.
 *
 * @return Return 0 if a new firmware exists.
 */
int FirmwareUpdater::exist() {
    int ver_local, ver_server;

    /*
     * Fetch the version from a local.
     */
    std::string file_local = "/local/" + name + EXT_TXT;
    ver_local = readVersionFromFile(file_local.c_str());
    if (ver_local < 0) {
        return -1;
    }

    /*
     * Fetch the version from a server.
     */
    std::string file_server = url + "/" + name + EXT_TXT;
    ver_server = readVersionFromURL(file_server.c_str());
    if (ver_server < 0) {
        return -2;
    }

    return (ver_local < ver_server) ? 0 : 1;
}

/**
 * Execute update.
 *
 * @return Return 0 if it succeed.
 */
int FirmwareUpdater::execute() {
    /*
     * Fetch the files.
     */
    std::string serv_txt = url + "/" + name + EXT_TXT;
    std::string file_txttmp = "/local/" + name + EXT_TXTTMP;
    if (fetch(serv_txt, file_txttmp) != 0) {
        LOG("ERR : Aborted...\n");
        return -1;
    }
    std::string serv_bin = url + "/" + name + EXT_BIN;
    std::string file_bintmp = "/local/" + name + EXT_BINTMP;
    if (fetch(serv_bin, file_bintmp) != 0) {
        LOG("ERR : Aborted...\n");
        return -2;
    }

    /*
     * Check the firmware versions.
     */
    std::string file_txt = "/local/" + name + EXT_TXT;
    int ver_old = readVersionFromFile(file_txt.c_str());
    int ver_new = readVersionFromFile(file_txttmp.c_str());
    if (ver_old < 0) {
        LOG("ERR : Could not read the previous firmware version.\n");
        LOG("ERR : Aborted...\n");
        return -3;
    }
    if (ver_new < 0) {
        LOG("ERR : Could not read the new firmware version.\n");
        LOG("ERR : Aborted...\n");
        return -4;
    }
    if (ver_new < ver_old) {
        LOG("ERR : Ignore the new firmware. (old=%d, new=%d)\n", ver_old, ver_new);
        LOG("ERR : Aborted...\n");
        return -5;
    }
    LOG("INFO: Firmware updating... (%d -> %d)\n", ver_old, ver_new);

    /*
     * Cleanup the previous versions.
     *
     * Note:
     *  A file time stamp on mbed is always '12:00 01/01/2008'.
     *  mbed can't sense updated firmware when the file name is same as previous version.
     *
     *  So I decided to cleanup all bin files.
     *  And the new firmware name is 'name-VERSION.bin'.
     *  To remove previous versions at first means 'start critical section on the system'.
     */
    cleanupAllBinFiles();

    /*
     * Copy it.
     */
    char nbuf[32];
    snprintf(nbuf, sizeof(nbuf) - 1, "-%d", ver_new);
    std::string file_bin = "/local/" + name + std::string(nbuf) + EXT_BIN;
    if (copy(file_txttmp, file_txt) != 0) {
        return -6;
    }
    if (copy(file_bintmp, file_bin) != 0) {
        return -7;
    }
    /*
     * Delete the temporary files.
     */
    remove(file_txttmp.c_str());
    remove(file_bintmp.c_str());
    return 0;
}

/**
 * Reset system.
 */
void FirmwareUpdater::reset() {
    mbed_reset();
}

/**
 * Fetch a file.
 *
 * @param src_url URL of a source file.
 * @param local_file Local file name.
 *
 * @return Return 0 if it succeed.
 */
int FirmwareUpdater::fetch(std::string src_url, std::string local_file) {
    /*
     * Fetch the source file from URL to a temporary file on local.
     */
    HTTPFile file(local_file.c_str());
    int r = client.get(src_url.c_str(), &file);
    if (r != HTTP_OK) {
        LOG("ERR : Fetch '%s' to '%s'.\n", src_url.c_str(), local_file.c_str());
        return -1;
    }
    LOG("INFO: Fetch '%s' to '%s'.\n", src_url.c_str(), local_file.c_str());
    return 0;
}

/**
 * Copy a file.
 *
 * @param local_file1 Source file.
 * @param local_file2 Destination file.
 *
 * @return Return 0 if it succeed.
 */
int FirmwareUpdater::copy(std::string local_file1, std::string local_file2) {
    LOG("INFO: File copying... (%s->%s)\n", local_file1.c_str(), local_file2.c_str());
    FILE *rp = fopen(local_file1.c_str(), "rb");
    if (rp == NULL) {
        LOG("ERR : File '%s' open failed.\n", local_file1.c_str());
        return -1;
    }
    remove(local_file2.c_str());
    FILE *wp = fopen(local_file2.c_str(), "wb");
    if (wp == NULL) {
        LOG("ERR : File '%s' open failed.\n", local_file2.c_str());
        fclose(rp);
        return -2;
    }
    int c;
    while ((c = fgetc(rp)) != EOF) {
        fputc(c, wp);
    }
    fclose(rp);
    fclose(wp);
    LOG("INFO: File copied. (%s->%s)\n", local_file1.c_str(), local_file2.c_str());
    return 0;
}

/**
 * Output a message to a log file.
 *
 * @param format ...
 */
void FirmwareUpdater::LOG(const char* format, ...) {
    if (log) {
        FILE *fplog = fopen("/local/update.log", "a");
        if (fplog != NULL) {
            char buf[BUFSIZ];
            va_list p;
            va_start(p, format);
            vsnprintf(buf, sizeof(buf) - 1, format, p);
            fprintf(fplog, "%s", buf);
            // printf("%s", buf); /* If you want to check a message from a console. */
            va_end(p);
            fclose(fplog);
        }
    }
}

/**
 * Cleanup all bin files.
 */
int FirmwareUpdater::cleanupAllBinFiles(void) {
    struct dirent *p;
    DIR *dir = opendir("/local");
    if (dir == NULL) {
        return -1;
    }
    while ((p = readdir(dir)) != NULL) {
        char *str = p->d_name;
        if ((strstr(str, ".bin") != NULL) || (strstr(str, ".BIN") != NULL)) {
            char buf[BUFSIZ];
            snprintf(buf, sizeof(buf) - 1, "/local/%s", str);
            if (remove(buf) == 0) {
                LOG("INFO: Deleted '%s'.\n", buf);
            } else {
                LOG("ERR : Delete '%s' failed.\n", buf);
            }
        }
    }
    closedir(dir);
    return 0;
}

/**
 * Read a version from a file.
 *
 * @param filename file name.
 * @return A version.
 */
int FirmwareUpdater::readVersionFromFile(const char *filename) {
    int ver;
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL) {
        LOG("ERR : Version file '%s' open failed.\n", filename);
        return -1;
    }
    if (fscanf(fp, "%d", &ver) != 1) {
        LOG("ERR : Version file '%s' is invalid.\n", filename);
        return -2;
    }
    fclose(fp);
    LOG("INFO: Version file '%s': Version %d.\n", filename, ver);
    return ver;
}

/**
 * Read a version from a URL.
 *
 * @param url URL.
 * @return A version.
 */
int FirmwareUpdater::readVersionFromURL(const char *url) {
    int ver;
    HTTPText text;
    HTTPResult r = client.get(url, &text);
    if (r != HTTP_OK) {
        LOG("ERR : Version file '%s' open failed.\n", url);
        return -1;
    }
    if (sscanf(text.gets(), "%d", &ver) != 1) {
        LOG("ERR : Version file '%s' is invalid.\n", url);
        return -2;
    }
    LOG("INFO: Version file '%s': Version %d.\n", url, ver);
    return ver;
}