Files
MySensors/core/MyOTAFirmwareUpdate.cpp
2017-12-06 23:02:57 +01:00

210 lines
7.8 KiB
C++

/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2017 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyOTAFirmwareUpdate.h"
// global variables
extern MyMessage _msg;
extern MyMessage _msgTmp;
// local variables
#ifdef MY_OTA_USE_I2C_EEPROM
I2CEeprom _flash(MY_OTA_I2C_ADDR);
#else
SPIFlash _flash(MY_OTA_FLASH_SS, MY_OTA_FLASH_JDECID);
#endif
LOCAL nodeFirmwareConfig_t _nodeFirmwareConfig;
LOCAL bool _firmwareUpdateOngoing = false;
LOCAL uint32_t _firmwareLastRequest;
LOCAL uint16_t _firmwareBlock;
LOCAL uint8_t _firmwareRetry;
LOCAL void readFirmwareSettings(void)
{
hwReadConfigBlock((void*)&_nodeFirmwareConfig, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS,
sizeof(nodeFirmwareConfig_t));
}
LOCAL void firmwareOTAUpdateRequest(void)
{
const uint32_t enterMS = hwMillis();
if (_firmwareUpdateOngoing && (enterMS - _firmwareLastRequest > MY_OTA_RETRY_DELAY)) {
if (!_firmwareRetry) {
setIndication(INDICATION_ERR_FW_TIMEOUT);
OTA_DEBUG(PSTR("!OTA:FRQ:FW UPD FAIL\n")); // fw update failed
// Give up. We have requested MY_OTA_RETRY times without any packet in return.
_firmwareUpdateOngoing = false;
return;
}
_firmwareRetry--;
_firmwareLastRequest = enterMS;
// Time to (re-)request firmware block from controller
requestFirmwareBlock_t firmwareRequest;
firmwareRequest.type = _nodeFirmwareConfig.type;
firmwareRequest.version = _nodeFirmwareConfig.version;
firmwareRequest.block = (_firmwareBlock - 1);
OTA_DEBUG(PSTR("OTA:FRQ:FW REQ,T=%04" PRIX16 ",V=%04" PRIX16 ",B=%04" PRIX16 "\n"),
_nodeFirmwareConfig.type,
_nodeFirmwareConfig.version, _firmwareBlock - 1); // request FW update block
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_REQUEST,
false).set(&firmwareRequest, sizeof(requestFirmwareBlock_t)));
}
}
LOCAL bool firmwareOTAUpdateProcess(void)
{
if (_msg.type == ST_FIRMWARE_CONFIG_RESPONSE) {
if(_firmwareUpdateOngoing) {
OTA_DEBUG(PSTR("!OTA:FWP:UPDO\n")); // FW config response received, FW update already ongoing
return true;
}
nodeFirmwareConfig_t *firmwareConfigResponse = (nodeFirmwareConfig_t *)_msg.data;
// compare with current node configuration, if they differ, start FW fetch process
if (memcmp(&_nodeFirmwareConfig, firmwareConfigResponse, sizeof(nodeFirmwareConfig_t))) {
setIndication(INDICATION_FW_UPDATE_START);
OTA_DEBUG(PSTR("OTA:FWP:UPDATE\n")); // FW update initiated
// copy new FW config
(void)memcpy(&_nodeFirmwareConfig, firmwareConfigResponse, sizeof(nodeFirmwareConfig_t));
// Init flash
if (!_flash.initialize()) {
setIndication(INDICATION_ERR_FW_FLASH_INIT);
OTA_DEBUG(PSTR("!OTA:FWP:FLASH INIT FAIL\n")); // failed to initialise flash
_firmwareUpdateOngoing = false;
} else {
// erase lower 32K -> max flash size for ATMEGA328
_flash.blockErase32K(0);
// wait until flash erased
while ( _flash.busy() ) {}
_firmwareBlock = _nodeFirmwareConfig.blocks;
_firmwareUpdateOngoing = true;
// reset flags
_firmwareRetry = MY_OTA_RETRY + 1;
_firmwareLastRequest = 0;
}
return true;
}
OTA_DEBUG(PSTR("OTA:FWP:UPDATE SKIPPED\n")); // FW update skipped, no newer version available
} else if (_msg.type == ST_FIRMWARE_RESPONSE) {
if (_firmwareUpdateOngoing) {
// extract FW block
replyFirmwareBlock_t *firmwareResponse = (replyFirmwareBlock_t *)_msg.data;
OTA_DEBUG(PSTR("OTA:FWP:RECV B=%04" PRIX16 "\n"), firmwareResponse->block); // received FW block
if (firmwareResponse->block != _firmwareBlock - 1) {
OTA_DEBUG(PSTR("!OTA:FWP:WRONG FWB\n")); // received FW block
// wrong firmware block received
setIndication(INDICATION_FW_UPDATE_RX_ERR);
// no further processing required
return true;
}
setIndication(INDICATION_FW_UPDATE_RX);
// Save block to flash
_flash.writeBytes( ((_firmwareBlock - 1) * FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET,
firmwareResponse->data, FIRMWARE_BLOCK_SIZE);
// wait until flash written
while (_flash.busy()) {}
#ifdef OTA_EXTRA_FLASH_DEBUG
{
char prbuf[8];
uint32_t addr = ((_firmwareBlock - 1) * FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET;
OTA_DEBUG(PSTR("OTA:FWP:FL DUMP "));
sprintf_P(prbuf,PSTR("%04" PRIX16 ":"), (uint16_t)addr);
MY_SERIALDEVICE.print(prbuf);
for(uint8_t i=0; i<FIRMWARE_BLOCK_SIZE; i++) {
uint8_t data = _flash.readByte(addr + i);
sprintf_P(prbuf,PSTR("%02" PRIX8 ""), (uint8_t)data);
MY_SERIALDEVICE.print(prbuf);
}
OTA_DEBUG(PSTR("\n"));
}
#endif
_firmwareBlock--;
if (!_firmwareBlock) {
// We're done! Do a checksum and reboot.
OTA_DEBUG(PSTR("OTA:FWP:FW END\n")); // received FW block
_firmwareUpdateOngoing = false;
if (transportIsValidFirmware()) {
OTA_DEBUG(PSTR("OTA:FWP:CRC OK\n")); // FW checksum ok
// Write the new firmware config to eeprom
hwWriteConfigBlock((void*)&_nodeFirmwareConfig, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS,
sizeof(nodeFirmwareConfig_t));
// All seems ok, write size and signature to flash (DualOptiboot will pick this up and flash it)
const uint16_t firmwareSize = FIRMWARE_BLOCK_SIZE * _nodeFirmwareConfig.blocks;
const uint8_t OTAbuffer[FIRMWARE_START_OFFSET] = {'F','L','X','I','M','G',':', (uint8_t)(firmwareSize >> 8), (uint8_t)(firmwareSize & 0xff),':'};
_flash.writeBytes(0, OTAbuffer, FIRMWARE_START_OFFSET);
// wait until flash ready
while (_flash.busy()) {}
hwReboot();
} else {
setIndication(INDICATION_ERR_FW_CHECKSUM);
OTA_DEBUG(PSTR("!OTA:FWP:CRC FAIL\n"));
}
}
// reset flags
_firmwareRetry = MY_OTA_RETRY + 1;
_firmwareLastRequest = 0;
} else {
OTA_DEBUG(PSTR("!OTA:FWP:NO UPDATE\n"));
}
return true;
}
return false;
}
LOCAL void presentBootloaderInformation(void)
{
requestFirmwareConfig_t *requestFirmwareConfig = (requestFirmwareConfig_t *)_msgTmp.data;
mSetLength(_msgTmp, sizeof(requestFirmwareConfig_t));
mSetCommand(_msgTmp, C_STREAM);
mSetPayloadType(_msgTmp, P_CUSTOM);
// copy node settings to reqFWConfig
(void)memcpy(requestFirmwareConfig, &_nodeFirmwareConfig, sizeof(nodeFirmwareConfig_t));
// add bootloader information
requestFirmwareConfig->BLVersion = MY_OTA_BOOTLOADER_VERSION;
_firmwareUpdateOngoing = false;
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM,
ST_FIRMWARE_CONFIG_REQUEST, false));
}
LOCAL bool isFirmwareUpdateOngoing(void)
{
return _firmwareUpdateOngoing;
}
// do a crc16 on the whole received firmware
LOCAL bool transportIsValidFirmware(void)
{
// init crc
uint16_t crc = ~0;
for (uint16_t i = 0; i < _nodeFirmwareConfig.blocks * FIRMWARE_BLOCK_SIZE; ++i) {
crc ^= _flash.readByte(i + FIRMWARE_START_OFFSET);
for (int8_t j = 0; j < 8; ++j) {
if (crc & 1) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc = (crc >> 1);
}
}
}
OTA_DEBUG(PSTR("OTA:CRC:B=%04" PRIX16 ",C=%04" PRIX16 ",F=%04" PRIX16 "\n"),
_nodeFirmwareConfig.blocks,crc,
_nodeFirmwareConfig.crc);
return crc == _nodeFirmwareConfig.crc;
}