firmwareUpdater.js

/* Copyright (c) 2010 - 2017, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Use in source and binary forms, redistribution in binary form only, with
 * or without modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 2. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 3. This software, with or without modification, must only be used with a Nordic
 *    Semiconductor ASA integrated circuit.
 *
 * 4. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

const os = require('os');
const fs = require('fs');
const FirmwareRegistry = require('./firmwareRegistry');

/**
 * Converts from family ID (used by pc-nrfjprog-js) to family string.
 *
 * @param {Number} familyId Family ID. Can be 0 or 1.
 * @returns {string} Family string. Can be 'nrf51' or 'nrf52'.
 */
function getFamilyString(familyId) {
    if (familyId === 0) {
        return 'nrf51';
    } else if (familyId === 1) {
        return 'nrf52';
    }
    throw new Error(`Unsupported family: ${familyId}. Expected one of 0 or 1.`);
}

/**
 * Class that performs firmware update using pc-nrfjprog-js.
 *
 * @deprecated This class is being replaced by the nrf-device-setup npm module,
 * which supports firmware updates with both nrfjprog and serial DFU.
 */
class FirmwareUpdater {

    /**
     * Initialize the firmware updater with a programmer instance. The programmer
     * instance could be pc-nrfjprog-js or any other instance that implements the
     * read, getDeviceInfo, and program functions.
     *
     * @param {Object} programmer The programmer instance, e.g. pc-nrfjprog-js.
     */
    constructor(programmer) {
        if (!programmer.read || !programmer.getDeviceInfo || !programmer.program) {
            throw new Error('The argument passed to FirmwareUpdater does ' +
                'not implement all the required functions, i.e. "read", ' +
                '"getDeviceInfo", and "program".');
        }
        this._programmer = programmer;
    }

    /**
     * Get the latest connectivity firmware version for the given family and
     * platform.
     *
     * @param {number} family The devkit family: 0 = nrf51, 1 = nrf52.
     * @param {String} [platform] Optional value that can be one of 'win32',
     *     'linux', 'darwin'. Will use the detected platform if not provided.
     * @throws {Error} Throws error if unsupported family or platform.
     * @returns {string} The latest 'major.minor.patch' version.
     */
    static getLatestVersion(family, platform) {
        const familyString = getFamilyString(family);
        const firmware = FirmwareRegistry.getJlinkConnectivityFirmware(familyString, platform);
        return firmware.version;
    }

    /**
     * Get the baud rate that is supported for the given family and platform.
     *
     * @param {number} family The devkit family: 0 = nrf51, 1 = nrf52.
     * @param {String} [platform] Optional value that can be one of 'win32',
     *     'linux', 'darwin'. Will use the detected platform if not provided.
     * @throws {Error} Throws error if unsupported family or platform.
     * @returns {number} Baud rate.
     */
    static getBaudRate(family, platform) {
        const familyString = getFamilyString(family);
        const firmware = FirmwareRegistry.getJlinkConnectivityFirmware(familyString, platform);
        return firmware.baudRate;
    }

    /**
     * Get path to the latest firmware file for the given family and platform.
     *
     * @param {number} family The devkit family: 0 = nrf51, 1 = nrf52.
     * @param {String} [platform] Optional value that can be one of 'win32',
     *     'linux', 'darwin'. Will use the detected platform if not provided.
     * @throws {Error} Throws error if unsupported family or platform.
     * @returns {string} Absolute path to firmware file.
     */
    static getFirmwarePath(family, platform) {
        const familyString = getFamilyString(family);
        const firmware = FirmwareRegistry.getJlinkConnectivityFirmware(familyString, platform);
        return firmware.file;
    }

    /**
     * Get the latest firmware hex as a string.
     *
     * @param {number} family The devkit family: 0 = nrf51, 1 = nrf52.
     * @param {String} [platform] Optional value that can be one of 'win32',
     *     'linux', 'darwin'. Will use the detected platform if not provided.
     * @throws {Error} Throws error if unsupported family or platform.
     * @returns {string} Firmware hex string.
     */
    static getFirmwareString(family, platform) {
        const firmwarePath = FirmwareUpdater.getFirmwarePath(family, platform);
        return fs.readFileSync(firmwarePath, { encoding: 'utf8' });
    }

    /**
     * Parse the version info struct that can be found in the connectivity
     * firmware. See the connectivity firmware patch in pc-ble-driver/hex/sd_api_v*
     * for details.
     *
     * @param {number[]} versionStruct Array of integers from the firmware.
     * @returns {Object} Parsed version info struct as an object.
     */
    static parseVersionStruct(versionStruct) {
        return FirmwareRegistry.parseVersionStruct(versionStruct);
    }

    _read(serialNumber, startAddress, length) {
        return new Promise((resolve, reject) => {
            this._programmer.read(serialNumber, startAddress, length, (err, content) => (
                err ? reject(err) : resolve(content)
            ));
        });
    }

    _getDeviceInfo(serialNumber) {
        return new Promise((resolve, reject) => {
            this._programmer.getDeviceInfo(serialNumber, (err, deviceInfo) => (
                err ? reject(err) : resolve(deviceInfo)
            ));
        });
    }

    _program(serialNumber, filePath, options) {
        return new Promise((resolve, reject) => {
            this._programmer.program(serialNumber, filePath, options, err => (
                err ? reject(err) : resolve()
            ));
        });
    }

    _createVersionInfo(serialNumber, family, platform) {
        const VERSION_INFO_START = 0x20000;
        const VERSION_INFO_LENGTH = 24;
        return this._read(serialNumber, VERSION_INFO_START, VERSION_INFO_LENGTH)
            .then(versionStruct => {
                const info = FirmwareRegistry.parseVersionStruct(versionStruct);
                const familyString = getFamilyString(family);
                const firmwareInfo = FirmwareRegistry.getJlinkConnectivityFirmware(familyString, platform);
                const isUpdateRequired =
                    info.version !== firmwareInfo.version ||
                    info.baudRate !== firmwareInfo.baudRate;
                return Object.assign({}, info, { isUpdateRequired });
            });
    }

    /**
     * Get version info from the firmware for the given serial number. If no
     * connectivity firmware is present, then the returned object will only
     * contain isUpdateRequired = true. This will also be true if the version
     * is not the latest, or if the baud rate is not supported.
     *
     * <ul>
     * <li>{boolean} isUpdateRequired: True if firmware needs to be updated.</li>
     * <li>{string} version: The 'major.minor.patch' version for the firmware.</li>
     * <li>{number} sdBleApiVersion: The SoftDevice version.</li>
     * <li>{number} transportType: Transport type, 1 = UART_HCI.</li>
     * <li>{number} baudRate: UART transport baud rate.</li>
     * </ul>
     *
     * @param {number} serialNumber The serial number to get version info for.
     * @param {function(Error, Object)} callback Signature: (err, versionInfo) => {}.
     * @returns {void}
     */
    getVersionInfo(serialNumber, callback) {
        this._getDeviceInfo(serialNumber)
            .then(deviceInfo => this._createVersionInfo(serialNumber, deviceInfo.family, os.platform()))
            .then(versionInfo => callback(null, versionInfo))
            .catch(err => callback(new Error('Unable to get version info for ' +
                `${serialNumber}: ${err.message}`), null));
    }

    /**
     * Program the given serial number with the latest connectivity firmware. The
     * new version info object (ref. getVersionInfo) is returned through the callback.
     *
     * @param {number} serialNumber The serial number to program.
     * @param {function(Error, Object)} callback Signature: (err, versionInfo) => {}.
     * @see getVersionInfo
     * @returns {void}
     */
    update(serialNumber, callback) {
        this._getDeviceInfo(serialNumber)
            .then(deviceInfo => {
                const firmwareString = FirmwareUpdater.getFirmwareString(deviceInfo.family, os.platform());
                const INPUT_FORMAT_HEX_STRING = 1;
                return this._program(serialNumber, firmwareString, { inputFormat: INPUT_FORMAT_HEX_STRING })
                    .then(() => deviceInfo);
            })
            .then(deviceInfo => this._createVersionInfo(serialNumber, deviceInfo.family, os.platform()))
            .then(versionInfo => callback(null, versionInfo))
            .catch(err => callback(new Error('Unable to update firmware for ' +
                `${serialNumber}: ${err.message}`), null));
    }
}

module.exports = FirmwareUpdater;