From adca8b5a4ce1d2cee8d854e919754bf1b42db56f Mon Sep 17 00:00:00 2001 From: Henrik Ekblad Date: Tue, 24 Mar 2015 10:58:28 +0100 Subject: [PATCH 1/4] Temporary OTA commit --- libraries/MySensors/MyConfig.h | 15 +- libraries/MySensors/MyHwATMega328.h | 2 + libraries/MySensors/MySensor.cpp | 30 +++ libraries/MySensors/MySensor.h | 41 +++ .../examples/RelayActuator/RelayActuator.ino | 4 +- libraries/MySensors/utility/SPIFlash.cpp | 246 ++++++++++++++++++ libraries/MySensors/utility/SPIFlash.h | 100 +++++++ 7 files changed, 436 insertions(+), 2 deletions(-) create mode 100644 libraries/MySensors/utility/SPIFlash.cpp create mode 100644 libraries/MySensors/utility/SPIFlash.h diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index a4f8a53..5f6423f 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -6,10 +6,23 @@ // to see what is actually is happening when developing #define DEBUG -// Serial output baud rate +// Serial output baud rate (for debug prints and serial gateway) #define BAUD_RATE 115200 +/********************************** +* Over the air firmware updates +***********************************/ + +// The following define enables the safe over-the-air firmware update feature +// which requires external flash and the DualOptiBoot bootloader. +// Note: You can still have OTA FW updates without external flash but it +// requires the MYSBootloader and disabled MY_OTA_FIRMWARE_FEATURE +#define MY_OTA_FIRMWARE_FEATURE + +// Slave select pin for external flash +#define MY_OTA_FLASH_SS 8 + /********************************** * Message Signing Settings diff --git a/libraries/MySensors/MyHwATMega328.h b/libraries/MySensors/MyHwATMega328.h index 684ef59..b45a5b9 100644 --- a/libraries/MySensors/MyHwATMega328.h +++ b/libraries/MySensors/MyHwATMega328.h @@ -10,6 +10,8 @@ #include #include #include +#include + #ifdef __cplusplus diff --git a/libraries/MySensors/MySensor.cpp b/libraries/MySensors/MySensor.cpp index aa777d4..c8e118f 100755 --- a/libraries/MySensors/MySensor.cpp +++ b/libraries/MySensors/MySensor.cpp @@ -55,6 +55,30 @@ MySensor::MySensor(MyTransport &_radio, MyHw &_hw } +#ifdef MY_OTA_FRIMWARE_FEATURE +// do a crc16 on the whole received firmware +static uint8_t MySensor::isValidFirmware() { + void* ptr = 0; + uint16_t crc = ~0; + int j; + + for (uint16_t i = 0; i < fc.blocks * FIRMWARE_BLOCK_SIZE; ++i) { + uint8_t a = pgm_read_byte((uint16_t) ptr + i); + crc ^= a; + for (j = 0; j < 8; ++j) + { + if (crc & 1) + crc = (crc >> 1) ^ 0xA001; + else + crc = (crc >> 1); + } + } + return crc == fc.crc; +} + +#endif + + void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, boolean _repeaterMode, uint8_t _parentNodeId) { hw_init(); repeaterMode = _repeaterMode; @@ -71,6 +95,12 @@ void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, b hw_readConfigBlock((void*)&nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(NodeConfig)); // Read latest received controller configuration from EEPROM hw_readConfigBlock((void*)&cc, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS, sizeof(ControllerConfig)); +#ifdef MY_OTA_FRIMWARE_FEATURE + // Read firmware config from EEPROM, i.e. type, version, CRC, blocks + hw_readConfigBlock((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig)); +#endif + + #ifdef MY_SIGNING_FEATURE // Read out the signing requirements from EEPROM hw_readConfigBlock((void*)doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, sizeof(doSign)); diff --git a/libraries/MySensors/MySensor.h b/libraries/MySensors/MySensor.h index df297cf..bb68d01 100644 --- a/libraries/MySensors/MySensor.h +++ b/libraries/MySensors/MySensor.h @@ -61,6 +61,7 @@ typedef MyHwATMega328 MyHwDriver; // Search for a new parent node after this many transmission failures #define SEARCH_FAILURES 5 + struct NodeConfig { uint8_t nodeId; // Current node id @@ -72,6 +73,42 @@ struct ControllerConfig { uint8_t isMetric; }; + +// Size of each firmware block +#define FIRMWARE_BLOCK_SIZE 16 +// Number of times a firmware block should be requested before giving up +#define FIRMWARE_MAX_REQUESTS 5 + +// FW config structure, stored in eeprom +typedef struct { + uint16_t type; + uint16_t version; + uint16_t blocks; + uint16_t crc; +} __attribute__((packed)) NodeFirmwareConfig; + +typedef struct { + uint16_t type; + uint16_t version; + uint16_t blocks; + uint16_t crc; + uint16_t BLVersion; +} __attribute__((packed)) RequestFirmwareConfig; + +typedef struct { + uint16_t type; + uint16_t version; + uint16_t block; +} __attribute__((packed)) RequestFWBlock; + +typedef struct { + uint16_t type; + uint16_t version; + uint16_t block; + uint8_t data[FIRMWARE_BLOCK_SIZE]; +} __attribute__((packed)) ReplyFWBlock; + + #ifdef __cplusplus class MySensor { @@ -243,6 +280,10 @@ class MySensor protected: NodeConfig nc; // Essential settings for node to work ControllerConfig cc; // Configuration coming from controller +#ifdef MY_OTA_FIRMWARE_FEATURE + NodeFirmwareConfig fc; +#endif + bool repeaterMode; bool autoFindParent; bool isGateway; diff --git a/libraries/MySensors/examples/RelayActuator/RelayActuator.ino b/libraries/MySensors/examples/RelayActuator/RelayActuator.ino index 9a7e7c7..615049d 100644 --- a/libraries/MySensors/examples/RelayActuator/RelayActuator.ino +++ b/libraries/MySensors/examples/RelayActuator/RelayActuator.ino @@ -3,6 +3,7 @@ #include #include +//#include #include #include #include @@ -14,8 +15,9 @@ // NRFRF24L01 radio driver (set low transmit power by default) MyTransportNRF24 radio(RF24_CE_PIN, RF24_CS_PIN, RF24_PA_LEVEL_GW); +//MyTransportRF69 radio; // Message signing driver (none default) -// MySigningNone signer; +//MySigningNone signer; // Select AtMega328 hardware profile MyHwATMega328 hw; // Construct MySensors library diff --git a/libraries/MySensors/utility/SPIFlash.cpp b/libraries/MySensors/utility/SPIFlash.cpp new file mode 100644 index 0000000..03d6a1a --- /dev/null +++ b/libraries/MySensors/utility/SPIFlash.cpp @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2013 by Felix Rusu + * SPI Flash memory library for arduino/moteino. + * This works with 256byte/page SPI flash memory + * For instance a 4MBit (512Kbyte) flash chip will have 2048 pages: 256*2048 = 524288 bytes (512Kbytes) + * Minimal modifications should allow chips that have different page size but modifications + * DEPENDS ON: Arduino SPI library + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#include + +byte SPIFlash::UNIQUEID[8]; + +/// IMPORTANT: NAND FLASH memory requires erase before write, because +/// it can only transition from 1s to 0s and only the erase command can reset all 0s to 1s +/// See http://en.wikipedia.org/wiki/Flash_memory +/// The smallest range that can be erased is a sector (4K, 32K, 64K); there is also a chip erase command + +/// Constructor. JedecID is optional but recommended, since this will ensure that the device is present and has a valid response +/// get this from the datasheet of your flash chip +/// Example for Atmel-Adesto 4Mbit AT25DF041A: 0x1F44 (page 27: http://www.adestotech.com/sites/default/files/datasheets/doc3668.pdf) +/// Example for Winbond 4Mbit W25X40CL: 0xEF30 (page 14: http://www.winbond.com/NR/rdonlyres/6E25084C-0BFE-4B25-903D-AE10221A0929/0/W25X40CL.pdf) +SPIFlash::SPIFlash(uint8_t slaveSelectPin, uint16_t jedecID) { + _slaveSelectPin = slaveSelectPin; + _jedecID = jedecID; +} + +/// Select the flash chip +void SPIFlash::select() { + noInterrupts(); + //save current SPI settings + _SPCR = SPCR; + _SPSR = SPSR; + //set FLASH chip SPI settings + SPI.setDataMode(SPI_MODE0); + SPI.setBitOrder(MSBFIRST); + SPI.setClockDivider(SPI_CLOCK_DIV4); //decided to slow down from DIV2 after SPI stalling in some instances, especially visible on mega1284p when RFM69 and FLASH chip both present + SPI.begin(); + digitalWrite(_slaveSelectPin, LOW); +} + +/// UNselect the flash chip +void SPIFlash::unselect() { + digitalWrite(_slaveSelectPin, HIGH); + //restore SPI settings to what they were before talking to the FLASH chip + SPCR = _SPCR; + SPSR = _SPSR; + interrupts(); +} + +/// setup SPI, read device ID etc... +boolean SPIFlash::initialize() +{ + _SPCR = SPCR; + _SPSR = SPSR; + pinMode(_slaveSelectPin, OUTPUT); + unselect(); + wakeup(); + + if (_jedecID == 0 || readDeviceId() == _jedecID) { + command(SPIFLASH_STATUSWRITE, true); // Write Status Register + SPI.transfer(0); // Global Unprotect + unselect(); + return true; + } + return false; +} + +/// Get the manufacturer and device ID bytes (as a short word) +word SPIFlash::readDeviceId() +{ +#if defined(__AVR_ATmega32U4__) // Arduino Leonardo, MoteinoLeo + command(SPIFLASH_IDREAD); // Read JEDEC ID +#else + select(); + SPI.transfer(SPIFLASH_IDREAD); +#endif + word jedecid = SPI.transfer(0) << 8; + jedecid |= SPI.transfer(0); + unselect(); + return jedecid; +} + +/// Get the 64 bit unique identifier, stores it in UNIQUEID[8]. Only needs to be called once, ie after initialize +/// Returns the byte pointer to the UNIQUEID byte array +/// Read UNIQUEID like this: +/// flash.readUniqueId(); for (byte i=0;i<8;i++) { Serial.print(flash.UNIQUEID[i], HEX); Serial.print(' '); } +/// or like this: +/// flash.readUniqueId(); byte* MAC = flash.readUniqueId(); for (byte i=0;i<8;i++) { Serial.print(MAC[i], HEX); Serial.print(' '); } +byte* SPIFlash::readUniqueId() +{ + command(SPIFLASH_MACREAD); + SPI.transfer(0); + SPI.transfer(0); + SPI.transfer(0); + SPI.transfer(0); + for (byte i=0;i<8;i++) + UNIQUEID[i] = SPI.transfer(0); + unselect(); + return UNIQUEID; +} + +/// read 1 byte from flash memory +byte SPIFlash::readByte(long addr) { + command(SPIFLASH_ARRAYREADLOWFREQ); + SPI.transfer(addr >> 16); + SPI.transfer(addr >> 8); + SPI.transfer(addr); + byte result = SPI.transfer(0); + unselect(); + return result; +} + +/// read unlimited # of bytes +void SPIFlash::readBytes(long addr, void* buf, word len) { + command(SPIFLASH_ARRAYREAD); + SPI.transfer(addr >> 16); + SPI.transfer(addr >> 8); + SPI.transfer(addr); + SPI.transfer(0); //"dont care" + for (word i = 0; i < len; ++i) + ((byte*) buf)[i] = SPI.transfer(0); + unselect(); +} + +/// Send a command to the flash chip, pass TRUE for isWrite when its a write command +void SPIFlash::command(byte cmd, boolean isWrite){ +#if defined(__AVR_ATmega32U4__) // Arduino Leonardo, MoteinoLeo + DDRB |= B00000001; // Make sure the SS pin (PB0 - used by RFM12B on MoteinoLeo R1) is set as output HIGH! + PORTB |= B00000001; +#endif + if (isWrite) + { + command(SPIFLASH_WRITEENABLE); // Write Enable + unselect(); + } + //wait for any write/erase to complete + // a time limit cannot really be added here without it being a very large safe limit + // that is because some chips can take several seconds to carry out a chip erase or other similar multi block or entire-chip operations + // a recommended alternative to such situations where chip can be or not be present is to add a 10k or similar weak pulldown on the + // open drain MISO input which can read noise/static and hence return a non 0 status byte, causing the while() to hang when a flash chip is not present + while(busy()); + select(); + SPI.transfer(cmd); +} + +/// check if the chip is busy erasing/writing +boolean SPIFlash::busy() +{ + /* + select(); + SPI.transfer(SPIFLASH_STATUSREAD); + byte status = SPI.transfer(0); + unselect(); + return status & 1; + */ + return readStatus() & 1; +} + +/// return the STATUS register +byte SPIFlash::readStatus() +{ + select(); + SPI.transfer(SPIFLASH_STATUSREAD); + byte status = SPI.transfer(0); + unselect(); + return status; +} + + +/// Write 1 byte to flash memory +/// WARNING: you can only write to previously erased memory locations (see datasheet) +/// use the block erase commands to first clear memory (write 0xFFs) +void SPIFlash::writeByte(long addr, uint8_t byt) { + command(SPIFLASH_BYTEPAGEPROGRAM, true); // Byte/Page Program + SPI.transfer(addr >> 16); + SPI.transfer(addr >> 8); + SPI.transfer(addr); + SPI.transfer(byt); + unselect(); +} + +/// write 1-256 bytes to flash memory +/// WARNING: you can only write to previously erased memory locations (see datasheet) +/// use the block erase commands to first clear memory (write 0xFFs) +/// WARNING: if you write beyond a page boundary (or more than 256bytes), +/// the bytes will wrap around and start overwriting at the beginning of that same page +/// see datasheet for more details +void SPIFlash::writeBytes(long addr, const void* buf, uint16_t len) { + command(SPIFLASH_BYTEPAGEPROGRAM, true); // Byte/Page Program + SPI.transfer(addr >> 16); + SPI.transfer(addr >> 8); + SPI.transfer(addr); + for (uint16_t i = 0; i < len; i++) + SPI.transfer(((byte*) buf)[i]); + unselect(); +} + +/// erase entire flash memory array +/// may take several seconds depending on size, but is non blocking +/// so you may wait for this to complete using busy() or continue doing +/// other things and later check if the chip is done with busy() +/// note that any command will first wait for chip to become available using busy() +/// so no need to do that twice +void SPIFlash::chipErase() { + command(SPIFLASH_CHIPERASE, true); + unselect(); +} + +/// erase a 4Kbyte block +void SPIFlash::blockErase4K(long addr) { + command(SPIFLASH_BLOCKERASE_4K, true); // Block Erase + SPI.transfer(addr >> 16); + SPI.transfer(addr >> 8); + SPI.transfer(addr); + unselect(); +} + +/// erase a 32Kbyte block +void SPIFlash::blockErase32K(long addr) { + command(SPIFLASH_BLOCKERASE_32K, true); // Block Erase + SPI.transfer(addr >> 16); + SPI.transfer(addr >> 8); + SPI.transfer(addr); + unselect(); +} + +void SPIFlash::sleep() { + command(SPIFLASH_SLEEP); + unselect(); +} + +void SPIFlash::wakeup() { + command(SPIFLASH_WAKE); + unselect(); +} + +/// cleanup +void SPIFlash::end() { + SPI.end(); +} \ No newline at end of file diff --git a/libraries/MySensors/utility/SPIFlash.h b/libraries/MySensors/utility/SPIFlash.h new file mode 100644 index 0000000..f0c071a --- /dev/null +++ b/libraries/MySensors/utility/SPIFlash.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2013 by Felix Rusu + * SPI Flash memory library for arduino/moteino. + * This works with 256byte/page SPI flash memory + * For instance a 4MBit (512Kbyte) flash chip will have 2048 pages: 256*2048 = 524288 bytes (512Kbytes) + * Minimal modifications should allow chips that have different page size but modifications + * DEPENDS ON: Arduino SPI library + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef _SPIFLASH_H_ +#define _SPIFLASH_H_ + +#if ARDUINO >= 100 +#include +#else +#include +#include "pins_arduino.h" +#endif + +#include + +/// IMPORTANT: NAND FLASH memory requires erase before write, because +/// it can only transition from 1s to 0s and only the erase command can reset all 0s to 1s +/// See http://en.wikipedia.org/wiki/Flash_memory +/// The smallest range that can be erased is a sector (4K, 32K, 64K); there is also a chip erase command + +/// Standard SPI flash commands +/// Assuming the WP pin is pulled up (to disable hardware write protection) +/// To use any write commands the WEL bit in the status register must be set to 1. +/// This is accomplished by sending a 0x06 command before any such write/erase command. +/// The WEL bit in the status register resets to the logical “0” state after a +/// device power-up or reset. In addition, the WEL bit will be reset to the logical “0” state automatically under the following conditions: +/// • Write Disable operation completes successfully +/// • Write Status Register operation completes successfully or aborts +/// • Protect Sector operation completes successfully or aborts +/// • Unprotect Sector operation completes successfully or aborts +/// • Byte/Page Program operation completes successfully or aborts +/// • Sequential Program Mode reaches highest unprotected memory location +/// • Sequential Program Mode reaches the end of the memory array +/// • Sequential Program Mode aborts +/// • Block Erase operation completes successfully or aborts +/// • Chip Erase operation completes successfully or aborts +/// • Hold condition aborts +#define SPIFLASH_WRITEENABLE 0x06 // write enable +#define SPIFLASH_WRITEDISABLE 0x04 // write disable + +#define SPIFLASH_BLOCKERASE_4K 0x20 // erase one 4K block of flash memory +#define SPIFLASH_BLOCKERASE_32K 0x52 // erase one 32K block of flash memory +#define SPIFLASH_BLOCKERASE_64K 0xD8 // erase one 64K block of flash memory +#define SPIFLASH_CHIPERASE 0x60 // chip erase (may take several seconds depending on size) + // but no actual need to wait for completion (instead need to check the status register BUSY bit) +#define SPIFLASH_STATUSREAD 0x05 // read status register +#define SPIFLASH_STATUSWRITE 0x01 // write status register +#define SPIFLASH_ARRAYREAD 0x0B // read array (fast, need to add 1 dummy byte after 3 address bytes) +#define SPIFLASH_ARRAYREADLOWFREQ 0x03 // read array (low frequency) + +#define SPIFLASH_SLEEP 0xB9 // deep power down +#define SPIFLASH_WAKE 0xAB // deep power wake up +#define SPIFLASH_BYTEPAGEPROGRAM 0x02 // write (1 to 256bytes) +#define SPIFLASH_IDREAD 0x9F // read JEDEC manufacturer and device ID (2 bytes, specific bytes for each manufacturer and device) + // Example for Atmel-Adesto 4Mbit AT25DF041A: 0x1F44 (page 27: http://www.adestotech.com/sites/default/files/datasheets/doc3668.pdf) + // Example for Winbond 4Mbit W25X40CL: 0xEF30 (page 14: http://www.winbond.com/NR/rdonlyres/6E25084C-0BFE-4B25-903D-AE10221A0929/0/W25X40CL.pdf) +#define SPIFLASH_MACREAD 0x4B // read unique ID number (MAC) + +class SPIFlash { +public: + static byte UNIQUEID[8]; + SPIFlash(byte slaveSelectPin, uint16_t jedecID=0); + boolean initialize(); + void command(byte cmd, boolean isWrite=false); + byte readStatus(); + byte readByte(long addr); + void readBytes(long addr, void* buf, word len); + void writeByte(long addr, byte byt); + void writeBytes(long addr, const void* buf, uint16_t len); + boolean busy(); + void chipErase(); + void blockErase4K(long address); + void blockErase32K(long address); + word readDeviceId(); + byte* readUniqueId(); + + void sleep(); + void wakeup(); + void end(); +protected: + void select(); + void unselect(); + byte _slaveSelectPin; + uint16_t _jedecID; + byte _SPCR; + byte _SPSR; +}; + +#endif \ No newline at end of file From c1a9a53ab916f9d9c431af4d4b3c89726871f1d5 Mon Sep 17 00:00:00 2001 From: Henrik Ekblad Date: Thu, 26 Mar 2015 14:30:07 +0100 Subject: [PATCH 2/4] WIP --- libraries/MySensors/MyConfig.h | 9 ++++++++- libraries/MySensors/MySensor.cpp | 29 ++++++++++++++++++++++++++++- libraries/MySensors/MySensor.h | 6 ++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index 5f6423f..4e5b579 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -19,9 +19,16 @@ // Note: You can still have OTA FW updates without external flash but it // requires the MYSBootloader and disabled MY_OTA_FIRMWARE_FEATURE #define MY_OTA_FIRMWARE_FEATURE - // Slave select pin for external flash #define MY_OTA_FLASH_SS 8 +// Number of times to request a fw block before giving up +#define MY_OTA_RETRY 5 +// Number of millisecons before re-request a fw block +#define MY_OTA_RETRY_DELAY 500 +// Bootloader version +#define MY_OTA_BOOTLOADER_MAJOR_VERSION 2 +#define MY_OTA_BOOTLOADER_MINOR_VERSION 0 +#define MY_OTA_BOOTLOADER_VERSION (MY_OTA_BOOTLOADER_MINOR_VERSION * 256 + MY_OTA_BOOTLOADER_MAJOR_VERSION) /********************************** diff --git a/libraries/MySensors/MySensor.cpp b/libraries/MySensors/MySensor.cpp index c8e118f..4be0330 100755 --- a/libraries/MySensors/MySensor.cpp +++ b/libraries/MySensors/MySensor.cpp @@ -57,7 +57,7 @@ MySensor::MySensor(MyTransport &_radio, MyHw &_hw #ifdef MY_OTA_FRIMWARE_FEATURE // do a crc16 on the whole received firmware -static uint8_t MySensor::isValidFirmware() { +bool MySensor::isValidFirmware() { void* ptr = 0; uint16_t crc = ~0; int j; @@ -188,6 +188,33 @@ void MySensor::setupNode() { // Wait configuration reply. wait(2000); + +#ifdef MY_OTA_FRIMWARE_FEATURE + RequestFirmwareConfig *reqFWConfig = (RequestFirmwareConfig *)msg.data; + mSetLength(msg, sizeof(RequestFirmwareConfig)); + mSetCommand(msg, C_STREAM); + mSetPayloadType(msg,P_CUSTOM); + // copy node settings to reqFWConfig + memcpy(reqFWConfig,&fc,sizeof(NodeFirmwareConfig)); + // add bootloader information + reqFWConfig->BLVersion = MYSBOOTLOADER_VERSION; + + // send node config and request FW config from controller + if (!sendAndWait(ST_FIRMWARE_CONFIG_REQUEST, ST_FIRMWARE_CONFIG_RESPONSE)) { + startup(); + } + + NodeFirmwareConfig *firmwareConfigResponse = (NodeFirmwareConfig *)inMsg.data; + + // compare with current node configuration, if equal startup + if (!memcmp(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig))) { + startup(); + } + + +#endif + + } } diff --git a/libraries/MySensors/MySensor.h b/libraries/MySensors/MySensor.h index bb68d01..40e5cd4 100644 --- a/libraries/MySensors/MySensor.h +++ b/libraries/MySensors/MySensor.h @@ -309,6 +309,12 @@ class MySensor void (*timeCallback)(unsigned long); // Callback for requested time messages void (*msgCallback)(const MyMessage &); // Callback for incoming messages from other nodes and gateway. +#ifdef MY_OTA_FRIMWARE_FEATURE +// do a crc16 on the whole received firmware + bool isValidFirmware(); +#endif + + void requestNodeId(); void setupNode(); void findParentNode(); From 3a59929b8ed8e1fca9876031264db5d6626876ff Mon Sep 17 00:00:00 2001 From: Henrik Ekblad Date: Mon, 30 Mar 2015 10:28:57 +0200 Subject: [PATCH 3/4] Initial untested OTA feature --- libraries/MySensors/MyConfig.h | 10 +- libraries/MySensors/MyHwATMega328.h | 1 - libraries/MySensors/MySensor.cpp | 137 +++++++++++++++++++++++----- libraries/MySensors/MySensor.h | 21 ++++- 4 files changed, 136 insertions(+), 33 deletions(-) diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index 4e5b579..4b7b4f1 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -21,14 +21,8 @@ #define MY_OTA_FIRMWARE_FEATURE // Slave select pin for external flash #define MY_OTA_FLASH_SS 8 -// Number of times to request a fw block before giving up -#define MY_OTA_RETRY 5 -// Number of millisecons before re-request a fw block -#define MY_OTA_RETRY_DELAY 500 -// Bootloader version -#define MY_OTA_BOOTLOADER_MAJOR_VERSION 2 -#define MY_OTA_BOOTLOADER_MINOR_VERSION 0 -#define MY_OTA_BOOTLOADER_VERSION (MY_OTA_BOOTLOADER_MINOR_VERSION * 256 + MY_OTA_BOOTLOADER_MAJOR_VERSION) +// Flash jdecid +#define MY_OTA_FLASH_JDECID 0x1F65 /********************************** diff --git a/libraries/MySensors/MyHwATMega328.h b/libraries/MySensors/MyHwATMega328.h index b45a5b9..8610f1b 100644 --- a/libraries/MySensors/MyHwATMega328.h +++ b/libraries/MySensors/MyHwATMega328.h @@ -10,7 +10,6 @@ #include #include #include -#include diff --git a/libraries/MySensors/MySensor.cpp b/libraries/MySensors/MySensor.cpp index 4be0330..c05df58 100755 --- a/libraries/MySensors/MySensor.cpp +++ b/libraries/MySensors/MySensor.cpp @@ -49,13 +49,16 @@ MySensor::MySensor(MyTransport &_radio, MyHw &_hw radio(_radio), #ifdef MY_SIGNING_FEATURE signer(_signer), +#endif +#ifdef MY_OTA_FIRMWARE_FEATURE + flash(MY_OTA_FLASH_SS, MY_OTA_FLASH_JDECID), #endif hw(_hw) { } -#ifdef MY_OTA_FRIMWARE_FEATURE +#ifdef MY_OTA_FIRMWARE_FEATURE // do a crc16 on the whole received firmware bool MySensor::isValidFirmware() { void* ptr = 0; @@ -63,7 +66,7 @@ bool MySensor::isValidFirmware() { int j; for (uint16_t i = 0; i < fc.blocks * FIRMWARE_BLOCK_SIZE; ++i) { - uint8_t a = pgm_read_byte((uint16_t) ptr + i); + uint8_t a = flash.readByte((uint16_t) ptr + i + FIRMWARE_START_OFFSET); crc ^= a; for (j = 0; j < 8; ++j) { @@ -95,7 +98,7 @@ void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, b hw_readConfigBlock((void*)&nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(NodeConfig)); // Read latest received controller configuration from EEPROM hw_readConfigBlock((void*)&cc, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS, sizeof(ControllerConfig)); -#ifdef MY_OTA_FRIMWARE_FEATURE +#ifdef MY_OTA_FIRMWARE_FEATURE // Read firmware config from EEPROM, i.e. type, version, CRC, blocks hw_readConfigBlock((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig)); #endif @@ -189,7 +192,7 @@ void MySensor::setupNode() { // Wait configuration reply. wait(2000); -#ifdef MY_OTA_FRIMWARE_FEATURE +#ifdef MY_OTA_FIRMWARE_FEATURE RequestFirmwareConfig *reqFWConfig = (RequestFirmwareConfig *)msg.data; mSetLength(msg, sizeof(RequestFirmwareConfig)); mSetCommand(msg, C_STREAM); @@ -197,21 +200,9 @@ void MySensor::setupNode() { // copy node settings to reqFWConfig memcpy(reqFWConfig,&fc,sizeof(NodeFirmwareConfig)); // add bootloader information - reqFWConfig->BLVersion = MYSBOOTLOADER_VERSION; - - // send node config and request FW config from controller - if (!sendAndWait(ST_FIRMWARE_CONFIG_REQUEST, ST_FIRMWARE_CONFIG_RESPONSE)) { - startup(); - } - - NodeFirmwareConfig *firmwareConfigResponse = (NodeFirmwareConfig *)inMsg.data; - - // compare with current node configuration, if equal startup - if (!memcmp(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig))) { - startup(); - } - - + reqFWConfig->BLVersion = MY_OTA_BOOTLOADER_VERSION; + fwUpdateOngoing = false; + sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_CONFIG_REQUEST, false)); #endif @@ -408,7 +399,29 @@ boolean MySensor::process() { hw_watchdogReset(); uint8_t to = 0; if (!radio.available(&to)) + { +#ifdef MY_OTA_FIRMWARE_FEATURE + unsigned long enter = hw_millis(); + if (fwUpdateOngoing && (enter - fwLastRequestTime > MY_OTA_RETRY_DELAY)) { + if (fwRetry == 0) { + debug(PSTR("fw upd fail\n")); + // Give up. We have requested MY_OTA_RETRY times without any packet in return. + fwUpdateOngoing = false; + return false; + } + fwRetry--; + fwLastRequestTime = enter; + // Time to (re-)request firmware block from controller + RequestFWBlock *firmwareRequest = (RequestFWBlock *)msg.data; + mSetLength(msg, sizeof(RequestFWBlock)); + firmwareRequest->type = fc.type; + firmwareRequest->version = fc.version; + firmwareRequest->block = (fwBlock - 1); + sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_REQUEST, false)); + } +#endif return false; + } #ifdef MY_SIGNING_FEATURE (void)signer.checkTimer(); // Manage signing timeout @@ -567,6 +580,55 @@ boolean MySensor::process() { return false; } } +#ifdef MY_OTA_FIRMWARE_FEATURE + else if (command == C_STREAM) { + if (type == ST_FIRMWARE_CONFIG_RESPONSE) { + NodeFirmwareConfig *firmwareConfigResponse = (NodeFirmwareConfig *)msg.data; + // compare with current node configuration, if they differ, start fw fetch process + if (memcmp(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig))) { + debug(PSTR("fw update\n")); + fwUpdateOngoing = true; + fwBlock = fc.blocks; + // copy new FW config + memcpy(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig)); + // Init flash + if (!flash.initialize()) { + debug(PSTR("flash init fail\n")); + } + + } + } else if (type == ST_FIRMWARE_RESPONSE) { + // Save block to eeprom + debug(PSTR("fw block %d\n"), fwBlock); + + ReplyFWBlock *firmwareResponse = (ReplyFWBlock *)msg.data; + // write to flash + flash.writeBytes(((fwBlock-1)*FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET, firmwareResponse->data, FIRMWARE_BLOCK_SIZE); + fwBlock--; + if (fwBlock == 0) { + // We're finished! Do a checksum and reboot. + if (isValidFirmware()) { + debug(PSTR("fw checksum ok\n")); + // All seems ok, write size and signature to flash (DualOptiboot will pick this up and flash it) + uint16_t fwsize = FIRMWARE_BLOCK_SIZE*fc.blocks; + flash.writeBytes(0, "FLXIMG:", 7); + flash.writeByte(7, fwsize >> 8); + flash.writeByte(8, fwsize); + flash.writeByte(9, ':'); + // Write the new firmware config to eeprom + hw_writeConfigBlock((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig)); + hw_reboot(); + } else { + debug(PSTR("fw checksum fail\n")); + } + } + } + // Make sure packet request occurs next time process() is called by timing out requestTime. + fwRetry = MY_OTA_RETRY+1; + fwLastRequestTime = 0; + return false; + } +#endif // Call incoming message callback if available if (msgCallback != NULL) { msgCallback(msg); @@ -613,17 +675,46 @@ void MySensor::wait(unsigned long ms) { } void MySensor::sleep(unsigned long ms) { - radio.powerDown(); - hw.sleep(ms); +#ifdef MY_OTA_FIRMWARE_FEATURE + if (fwUpdateOngoing) { + // Do not sleep node while fw update is ongoing + process(); + } else { +#endif + radio.powerDown(); + hw.sleep(ms); +#ifdef MY_OTA_FIRMWARE_FEATURE + } +#endif } bool MySensor::sleep(uint8_t interrupt, uint8_t mode, unsigned long ms) { +#ifdef MY_OTA_FIRMWARE_FEATURE + if (fwUpdateOngoing) { + // Do not sleep node while fw update is ongoing + process(); + return false; + } else { +#endif radio.powerDown(); return hw.sleep(interrupt, mode, ms) ; +#ifdef MY_OTA_FIRMWARE_FEATURE + } +#endif } int8_t MySensor::sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) { - radio.powerDown(); - return hw.sleep(interrupt1, mode1, interrupt2, mode2, ms) ; +#ifdef MY_OTA_FIRMWARE_FEATURE + if (fwUpdateOngoing) { + // Do not sleep node while fw update is ongoing + process(); + return -1; + } else { +#endif + radio.powerDown(); + return hw.sleep(interrupt1, mode1, interrupt2, mode2, ms) ; +#ifdef MY_OTA_FIRMWARE_FEATURE + } +#endif } diff --git a/libraries/MySensors/MySensor.h b/libraries/MySensors/MySensor.h index 40e5cd4..7873c22 100644 --- a/libraries/MySensors/MySensor.h +++ b/libraries/MySensors/MySensor.h @@ -23,6 +23,9 @@ #include "MySigningNone.h" #endif #include "MyMessage.h" +#ifdef MY_OTA_FIRMWARE_FEATURE +#include "utility/SPIFlash.h" +#endif #include #include @@ -78,6 +81,17 @@ struct ControllerConfig { #define FIRMWARE_BLOCK_SIZE 16 // Number of times a firmware block should be requested before giving up #define FIRMWARE_MAX_REQUESTS 5 +// Number of times to request a fw block before giving up +#define MY_OTA_RETRY 5 +// Number of millisecons before re-request a fw block +#define MY_OTA_RETRY_DELAY 500 +// Start offset for firmware in flash (DualOptiboot wants to keeps a signature first) +#define FIRMWARE_START_OFFSET 10 +// Bootloader version +#define MY_OTA_BOOTLOADER_MAJOR_VERSION 3 +#define MY_OTA_BOOTLOADER_MINOR_VERSION 0 +#define MY_OTA_BOOTLOADER_VERSION (MY_OTA_BOOTLOADER_MINOR_VERSION * 256 + MY_OTA_BOOTLOADER_MAJOR_VERSION) + // FW config structure, stored in eeprom typedef struct { @@ -282,6 +296,11 @@ class MySensor ControllerConfig cc; // Configuration coming from controller #ifdef MY_OTA_FIRMWARE_FEATURE NodeFirmwareConfig fc; + bool fwUpdateOngoing; + unsigned long fwLastRequestTime; + uint16_t fwBlock; + uint8_t fwRetry; + SPIFlash flash; #endif bool repeaterMode; @@ -309,7 +328,7 @@ class MySensor void (*timeCallback)(unsigned long); // Callback for requested time messages void (*msgCallback)(const MyMessage &); // Callback for incoming messages from other nodes and gateway. -#ifdef MY_OTA_FRIMWARE_FEATURE +#ifdef MY_OTA_FIRMWARE_FEATURE // do a crc16 on the whole received firmware bool isValidFirmware(); #endif From 0373b71a03365cd0235c1b5c0093b7446fb18e02 Mon Sep 17 00:00:00 2001 From: Henrik Ekblad Date: Mon, 30 Mar 2015 11:56:55 +0200 Subject: [PATCH 4/4] Fix Serial GW constructor --- .../MySensors/examples/SerialGateway/SerialGateway.ino | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/MySensors/examples/SerialGateway/SerialGateway.ino b/libraries/MySensors/examples/SerialGateway/SerialGateway.ino index 8dc5e26..d103407 100644 --- a/libraries/MySensors/examples/SerialGateway/SerialGateway.ino +++ b/libraries/MySensors/examples/SerialGateway/SerialGateway.ino @@ -23,8 +23,9 @@ #define NO_PORTB_PINCHANGES -#include #include +#include + #include #include #include @@ -43,9 +44,11 @@ // NRFRF24L01 radio driver (set low transmit power by default) MyTransportNRF24 radio(RF24_CE_PIN, RF24_CS_PIN, RF24_PA_LEVEL_GW); // Message signing driver (none default) -MySigningNone signer; +//MySigningNone signer; // Construct MySensors library -MySensor gw(radio, signer); +MyHwATMega328 hw; + +MySensor gw(radio, hw); char inputString[MAX_RECEIVE_LENGTH] = ""; // A string to hold incoming commands from serial/ethernet interface int inputPos = 0;