From 4f4f0605c09cc1222607d51343e16728ee0e7d67 Mon Sep 17 00:00:00 2001 From: Henrik Ekblad Date: Tue, 15 Sep 2015 00:28:47 +0200 Subject: [PATCH] Split out some more components to make core class cleaner --- libraries/MySensors/MyConfig.h | 4 +- libraries/MySensors/MySensor.h | 75 +- .../MySensors/core/MyGatewayTransport.cpp | 56 ++ libraries/MySensors/core/MyGatewayTransport.h | 24 +- .../core/MyGatewayTransportEthernet.cpp | 2 +- .../core/MyGatewayTransportSerial.cpp | 2 +- libraries/MySensors/core/MyInclusionMode.cpp | 59 ++ libraries/MySensors/core/MyInclusionMode.h | 35 + libraries/MySensors/core/MyLeds.cpp | 99 +++ libraries/MySensors/core/MyLeds.h | 54 ++ libraries/MySensors/core/MySensorCore.cpp | 820 +----------------- libraries/MySensors/core/MySensorCore.h | 133 +-- libraries/MySensors/core/MySigning.cpp | 5 + libraries/MySensors/core/MySigning.h | 6 + libraries/MySensors/core/MyTransport.cpp | 547 ++++++++++++ libraries/MySensors/core/MyTransport.h | 78 +- libraries/MySensors/core/MyTransportNRF24.h | 8 - libraries/MySensors/core/MyTransportRFM69.h | 8 - libraries/MySensors/drivers/pubsubclient | 1 + .../EthernetGateway/EthernetGateway.ino | 6 +- .../examples/MotionSensor/MotionSensor.ino | 7 +- 21 files changed, 1096 insertions(+), 933 deletions(-) create mode 100644 libraries/MySensors/core/MyGatewayTransport.cpp create mode 100644 libraries/MySensors/core/MyInclusionMode.cpp create mode 100644 libraries/MySensors/core/MyInclusionMode.h create mode 100644 libraries/MySensors/core/MyLeds.cpp create mode 100644 libraries/MySensors/core/MyLeds.h create mode 100644 libraries/MySensors/core/MyTransport.cpp create mode 160000 libraries/MySensors/drivers/pubsubclient diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index 818eae5..dc014fc 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -95,9 +95,9 @@ // but now can be used in any sensor node. Also the LEDs can now be // disabled in the gateway. -//#define MY_WITH_LEDS_BLINKING +//#define MY_LEDS_BLINKING_FEATURE -// The following setting allows you to inverse the blinking feature WITH_LEDS_BLINKING +// The following setting allows you to inverse the blinking feature MY_LEDS_BLINKING_FEATURE // When WITH_LEDS_BLINKING_INVERSE is enabled LEDSs are normally turned on and switches // off when blinking diff --git a/libraries/MySensors/MySensor.h b/libraries/MySensors/MySensor.h index 7f80bda..62951a9 100644 --- a/libraries/MySensors/MySensor.h +++ b/libraries/MySensors/MySensor.h @@ -45,6 +45,19 @@ #include "core/MyHwATMega328.cpp" #endif +// LEDS +#if defined(MY_LEDS_BLINKING_FEATURE) + #include "core/MyLeds.cpp" +#else + #include "core/MyLeds.h" +#endif + +// INCLUSION MODE +#if defined(MY_INCLUSION_MODE_FEATURE) + #include "core/MyInclusionMode.cpp" +#endif + + // SIGNING #if defined(MY_SIGNING_ATSHA204) || defined(MY_SIGNING_SOFT) #include "MySigning.cpp" @@ -60,42 +73,21 @@ #endif #endif -// RADIO -#if defined(MY_RADIO_NRF24) || defined(MY_RADIO_RFM69) - #define MY_RADIO_FEATURE - // SOFTSPI - #ifdef MY_SOFTSPI - #if defined(ARDUINO_ARCH_ESP8266) - #error Soft SPI is not available on ESP8266 - #endif - #include "drivers/AVR/DigitalIO/SoftI2cMaster.cpp" - #include "drivers/AVR/DigitalIO/PinIO.cpp" - #endif - - // FLASH - #ifdef MY_OTA_FIRMWARE_FEATURE - #include "drivers/SPIFlash/SPIFlash.cpp" - SPIFlash _flash(MY_OTA_FLASH_SS, MY_OTA_FLASH_JDECID); - #endif - - #if defined(MY_RADIO_NRF24) - #include "drivers/RF24/RF24.cpp" - #include "core/MyTransportNRF24.cpp" - #elif defined(MY_RADIO_RFM69) - #include "drivers/RFM69/RFM69.cpp" - #include "core/MyTransportRFM69.cpp" - #endif -#endif // GATEWAY - TRANSPORT #if defined(MY_GATEWAY_FEATURE) + // GATEWAY - COMMON FUNCTIONS + #include "core/MyGatewayTransport.cpp" + // GATEWAY - PROTOCOL #if defined(MY_GATEWAY_PROTOCOL_DEFAULT) #include "core/MyProtocolDefault.cpp" #else #error No gateway protocol specified! #endif + + // GATEWAY - CONFIGURATION #if defined(MY_RADIO_FEATURE) // We assume that a gateway having a radio also should act as repeater #define MY_REPEATER_FEATURE @@ -108,6 +100,7 @@ #endif #if defined(MY_GATEWAY_W5100) + // GATEWAY - W5100 #include "drivers/AVR/Ethernet_W5100/utility/socket.cpp" #include "drivers/AVR/Ethernet_W5100/utility/w5100.cpp" #include "drivers/AVR/Ethernet_W5100/DNS.cpp" @@ -126,6 +119,7 @@ // What do we do here? #endif #elif defined(MY_GATEWAY_ENC28J60) + // GATEWAY - ENC28J60 #undef MY_USE_UDP // Will not fit (or compile) on ENC28J60 #include "drivers/AVR/Ethernet_UIP/src/utility/uipethernet-conf.h" #include "drivers/AVR/Ethernet_UIP/src/utility/uip-conf.h" @@ -143,6 +137,7 @@ #include "core/MyGatewayTransportEthernet.cpp" #elif defined(MY_GATEWAY_SERIAL) + // GATEWAY - SERIAL #include "core/MyGatewayTransportSerial.cpp" #endif #else @@ -175,6 +170,34 @@ #define MY_IS_GATEWAY false #endif +// RADIO +#if defined(MY_RADIO_NRF24) || defined(MY_RADIO_RFM69) + #define MY_RADIO_FEATURE + // SOFTSPI + #ifdef MY_SOFTSPI + #if defined(ARDUINO_ARCH_ESP8266) + #error Soft SPI is not available on ESP8266 + #endif + #include "drivers/AVR/DigitalIO/SoftI2cMaster.cpp" + #include "drivers/AVR/DigitalIO/PinIO.cpp" + #endif + + // FLASH + #ifdef MY_OTA_FIRMWARE_FEATURE + #include "drivers/SPIFlash/SPIFlash.cpp" + SPIFlash _flash(MY_OTA_FLASH_SS, MY_OTA_FLASH_JDECID); + #endif + #include "core/MyTransport.cpp" + #if defined(MY_RADIO_NRF24) + #include "drivers/RF24/RF24.cpp" + #include "core/MyTransportNRF24.cpp" + #elif defined(MY_RADIO_RFM69) + #include "drivers/RFM69/RFM69.cpp" + #include "core/MyTransportRFM69.cpp" + #endif +#endif + + #include "core/MyMessage.cpp" #include "core/MySensorCore.cpp" diff --git a/libraries/MySensors/core/MyGatewayTransport.cpp b/libraries/MySensors/core/MyGatewayTransport.cpp new file mode 100644 index 0000000..0072946 --- /dev/null +++ b/libraries/MySensors/core/MyGatewayTransport.cpp @@ -0,0 +1,56 @@ +/** + * 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 Tomas Hozza + * Copyright (C) 2015 Tomas Hozza + * 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 "MyGatewayTransport.h" + +inline void gatewayTransportProcess() { + if (gatewayTransportAvailable()) { + MyMessage &_msg = gatewayTransportReceive(); + if (_msg.destination == GATEWAY_ADDRESS) { + // Check if sender requests an ack back. + if (mGetRequestAck(_msg)) { + // Copy message + _msgTmp = _msg; + mSetRequestAck(_msgTmp, false); // Reply without ack flag (otherwise we would end up in an eternal loop) + mSetAck(_msgTmp, true); + _msgTmp.sender = _nc.nodeId; + _msgTmp.destination = _msg.sender; + gatewayTransportSend(_msgTmp); + } + if (mGetCommand(_msg) == C_INTERNAL) { + if (_msg.type == I_VERSION) { + // Request for version. Create the response + gatewayTransportSend(buildGw(_msg, I_VERSION).set(LIBRARY_VERSION)); + #ifdef MY_INCLUSION_MODE_FEATURE + } else if (_msg.type == I_INCLUSION_MODE) { + // Request to change inclusion mode + inclusionModeSet(atoi(_msg.data) == 1); + #endif + } else { + processInternalMessages(); + } + } else { + // Call incoming message callback if available + if (_msgCallback != NULL) { + _msgCallback(_msg); + } + } + } + } +} diff --git a/libraries/MySensors/core/MyGatewayTransport.h b/libraries/MySensors/core/MyGatewayTransport.h index 4df3244..e971bd1 100644 --- a/libraries/MySensors/core/MyGatewayTransport.h +++ b/libraries/MySensors/core/MyGatewayTransport.h @@ -23,19 +23,33 @@ #include "MyConfig.h" #include "MyMessage.h" #include "MyProtocol.h" +#include "MySensorCore.h" + +// Common gateway functions + +void gatewayTransportProcess(); +// Gateway "interface" functions -// initialize the driver -bool gatewayTransportBegin(); +/** + * initialize the driver + */ +bool gatewayTransportInit(); -// Send message to controller +/** + * Send message to controller + */ bool gatewayTransportSend(MyMessage &message); -// Check if a new message is available from controller +/* + * Check if a new message is available from controller + */ bool gatewayTransportAvailable(); -// Pick up last message received from controller +/* + * Pick up last message received from controller + */ MyMessage& gatewayTransportReceive(); #endif /* MyGatewayTransportEthernet_h */ diff --git a/libraries/MySensors/core/MyGatewayTransportEthernet.cpp b/libraries/MySensors/core/MyGatewayTransportEthernet.cpp index 18bd233..8e661fd 100644 --- a/libraries/MySensors/core/MyGatewayTransportEthernet.cpp +++ b/libraries/MySensors/core/MyGatewayTransportEthernet.cpp @@ -35,7 +35,7 @@ MyMessage _ethernetMsg; #endif /* USE_UDP */ -bool gatewayTransportBegin() { +bool gatewayTransportInit() { #ifdef MY_IP_ADDRESS Ethernet.begin(_ethernetGatewayMAC, _ethernetGatewayIP); #else diff --git a/libraries/MySensors/core/MyGatewayTransportSerial.cpp b/libraries/MySensors/core/MyGatewayTransportSerial.cpp index 313efac..390dc17 100644 --- a/libraries/MySensors/core/MyGatewayTransportSerial.cpp +++ b/libraries/MySensors/core/MyGatewayTransportSerial.cpp @@ -26,7 +26,7 @@ MyMessage _serialMsg; -bool gatewayTransportBegin() { +bool gatewayTransportInit() { return true; } diff --git a/libraries/MySensors/core/MyInclusionMode.cpp b/libraries/MySensors/core/MyInclusionMode.cpp new file mode 100644 index 0000000..604983a --- /dev/null +++ b/libraries/MySensors/core/MyInclusionMode.cpp @@ -0,0 +1,59 @@ +/** + * 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 + * Copyright (C) 2013-2015 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 "MyInclusionMode.h" + +unsigned long _inclusionStartTime; +bool _inclusionMode; + +inline void inclusionInit() { + _inclusionMode = false; + #if defined(MY_INCLUSION_BUTTON_FEATURE) + // Setup digital in that triggers inclusion mode + pinMode(MY_INCLUSION_MODE_BUTTON_PIN, INPUT); + digitalWrite(MY_INCLUSION_MODE_BUTTON_PIN, HIGH); + #endif + +} + + +void inclusionModeSet(boolean newMode) { + if (newMode != inclusionMode) { + _inclusionMode = newMode; + // Send back mode change to controller + gatewayTransportSend(buildGw(_msg, I_INCLUSION_MODE).set(_inclusionMode?1:0)); + if (_inclusionMode) { + _inclusionStartTime = hwMillis(); + } + } +} + +inline void inclusionProcess() { + #ifdef MY_INCLUSION_BUTTON_FEATURE + if (!inclusionMode && digitalRead(MY_INCLUSION_MODE_BUTTON_PIN) == LOW) { + // Start inclusion mode + inclusionModeSet(true); + } + #endif + + if (_inclusionMode && hwMillis()-_inclusionStartTime>MY_INCLUSION_MODE_DURATION*1000L) { + // inclusionTimeInMinutes minute(s) has passed.. stop inclusion mode + inclusionModeSet(false); + } +} diff --git a/libraries/MySensors/core/MyInclusionMode.h b/libraries/MySensors/core/MyInclusionMode.h new file mode 100644 index 0000000..03ac12b --- /dev/null +++ b/libraries/MySensors/core/MyInclusionMode.h @@ -0,0 +1,35 @@ +/** + * 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 + * Copyright (C) 2013-2015 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. + */ + +#ifndef MyInclusionMode_h +#define MyInclusionMode_h + +#include "MySensorCore.h" + +unsigned long inclusionStartTime; +bool inclusionMode; + +extern bool gatewayTransportSend(MyMessage &message); + +void inclusionInit(); +void inclusionModeSet(boolean newMode); +void inclusionProcess(); + + +#endif diff --git a/libraries/MySensors/core/MyLeds.cpp b/libraries/MySensors/core/MyLeds.cpp new file mode 100644 index 0000000..10ce2f5 --- /dev/null +++ b/libraries/MySensors/core/MyLeds.cpp @@ -0,0 +1,99 @@ +/** + * 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 + * Copyright (C) 2013-2015 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 "MyLeds.h" + +// these variables don't need to be volatile, since we are not using interrupts +uint8_t _countRx; +uint8_t _countTx; +uint8_t _countErr; +unsigned long _blink_next_time; + +inline void ledsInit() { + // Setup led pins + pinMode(MY_DEFAULT_RX_LED_PIN, OUTPUT); + pinMode(MY_DEFAULT_TX_LED_PIN, OUTPUT); + pinMode(MY_DEFAULT_ERR_LED_PIN, OUTPUT); + + // Set initial state of leds + hwDigitalWrite(MY_DEFAULT_RX_LED_PIN, LED_OFF); + hwDigitalWrite(MY_DEFAULT_TX_LED_PIN, LED_OFF); + hwDigitalWrite(MY_DEFAULT_ERR_LED_PIN, LED_OFF); + + // initialize counters + _countRx = 0; + _countTx = 0; + _countErr = 0; +} + +inline void ledsProcess() { + // Just return if it is not the time... + // http://playground.arduino.cc/Code/TimingRollover + if ((long)(hwMillis() - _blink_next_time) < 0) + return; + else + _blink_next_time = hwMillis() + MY_DEFAULT_LED_BLINK_PERIOD; + + // do the actual blinking + if(_countRx && _countRx != 255) { + // switch led on + hwDigitalWrite(MY_DEFAULT_RX_LED_PIN, LED_ON); + } + else if(!_countRx) { + // switching off + hwDigitalWrite(MY_DEFAULT_RX_LED_PIN, LED_OFF); + } + if(_countRx != 255) + --_countRx; + + if(_countTx && _countTx != 255) { + // switch led on + hwDigitalWrite(MY_DEFAULT_TX_LED_PIN, LED_ON); + } + else if(!_countTx) { + // switching off + hwDigitalWrite(MY_DEFAULT_TX_LED_PIN, LED_OFF); + } + if(_countTx != 255) + --_countTx; + + if(_countErr && _countErr != 255) { + // switch led on + hwDigitalWrite(MY_DEFAULT_ERR_LED_PIN, LED_ON); + } + else if(!_countErr) { + // switching off + hwDigitalWrite(MY_DEFAULT_ERR_LED_PIN, LED_OFF); + } + if(_countErr != 255) + --_countErr; +} + +void ledsBlinkRx(uint8_t cnt) { + if(_countRx == 255) { _countRx = cnt; } +} + +void ledsBlinkTx(uint8_t cnt) { + if(_countTx == 255) { _countTx = cnt; } +} + +void ledsBlinkErr(uint8_t cnt) { + if(_countErr == 255) { _countErr = cnt; } +} + diff --git a/libraries/MySensors/core/MyLeds.h b/libraries/MySensors/core/MyLeds.h new file mode 100644 index 0000000..6549f36 --- /dev/null +++ b/libraries/MySensors/core/MyLeds.h @@ -0,0 +1,54 @@ +/** + * 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 + * Copyright (C) 2013-2015 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. + */ + +#ifndef MyLeds_h +#define MyLeds_h + + +#ifdef WITH_LEDS_BLINKING_INVERSE +#define LED_ON 0x1 +#define LED_OFF 0x0 +#else +#define LED_ON 0x0 +#define LED_OFF 0x1 +#endif + +#ifdef MY_LEDS_BLINKING_FEATURE + #define ledBlinkTx(x,...) ledsBlinkTx(x) + #define ledBlinkRx(x,...) ledsBlinkRx(x) + #define ledBlinkErr(x,...) ledsBlinkErr(x) + + /** + * Blink with LEDs + * @param cnt how many blink cycles to keep the LED on. Default cycle is 300ms + */ + void ledsInit(); + void ledsBlinkRx(uint8_t cnt); + void ledsBlinkTx(uint8_t cnt); + void ledsBlinkErr(uint8_t cnt); + void ledsProcess(); // do the actual blinking + +#else + // Remove led functions if feature is disabled + #define ledBlinkTx(x,...) + #define ledBlinkRx(x,...) + #define ledBlinkErr(x,...) +#endif + +#endif diff --git a/libraries/MySensors/core/MySensorCore.cpp b/libraries/MySensors/core/MySensorCore.cpp index 0d8b496..da576b6 100644 --- a/libraries/MySensors/core/MySensorCore.cpp +++ b/libraries/MySensors/core/MySensorCore.cpp @@ -17,41 +17,12 @@ * version 2 as published by the Free Software Foundation. */ - - #include "MySensorCore.h" - -NodeConfig _nc; // Essential settings for node to work ControllerConfig _cc; // Configuration coming from controller -#ifdef MY_OTA_FIRMWARE_FEATURE -NodeFirmwareConfig _fc; -bool _fwUpdateOngoing; -unsigned long _fwLastRequestTime; -uint16_t _fwBlock; -uint8_t _fwRetry; -#endif - -#ifdef MY_RADIO_FEATURE -bool _autoFindParent; -uint8_t _failedTransmissions; -#endif - +NodeConfig _nc; // Essential settings for node to work MyMessage _msg; // Buffer for incoming messages. -MyMessage _msgTmp ; // Buffer for temporary messages (acks and nonces among others). - -#ifdef MY_LEDS_BLINKING_FEATURE -// these variables don't need to be volatile, since we are not using interrupts -uint8_t _countRx; -uint8_t _countTx; -uint8_t _countErr; -unsigned long _blink_next_time; -#endif - -#ifdef MY_SIGNING_FEATURE -uint16_t _doSign[16]; // Bitfield indicating which sensors require signed communication -MyMessage _msgSign; // Buffer for message to sign. -#endif +MyMessage _msgTmp; // Buffer for temporary messages (acks and nonces among others). #ifdef MY_DEBUG char convBuf[MAX_PAYLOAD*2+1]; @@ -62,124 +33,30 @@ void (*_timeCallback)(unsigned long); // Callback for requested time messages void (*_msgCallback)(const MyMessage &); // Callback for incoming messages from other nodes and gateway. -#define DISTANCE_INVALID (0xFF) +void process() { + hwWatchdogReset(); -#ifdef MY_SIGNING_FEATURE -// Macros for manipulating signing requirement table -#define DO_SIGN(node) (node == 0 ? (~_doSign[0]&1) : (~_doSign[node>>4]&(node%16))) -#define SET_SIGN(node) (node == 0 ? (_doSign[0]&=~1) : (_doSign[node>>4]&=~(node%16))) -#define CLEAR_SIGN(node) (node == 0 ? (_doSign[0]|=1) : (_doSign[node>>4]|=(node%16))) -#endif + #if defined (MY_LEDS_BLINKING_FEATURE) + ledsProcess(); + #endif -// Inline function and macros -static inline MyMessage& build(MyMessage &msg, uint8_t sender, uint8_t destination, uint8_t sensor, uint8_t command, uint8_t type, bool enableAck) { - msg.sender = sender; - msg.destination = destination; - msg.sensor = sensor; - msg.type = type; - mSetCommand(msg,command); - mSetRequestAck(msg,enableAck); - mSetAck(msg,false); - return msg; + #if defined(MY_INCLUSION_MODE_FEATURE) + inclusionProcess(); + #endif + + #if defined(MY_GATEWAY_FEATURE) + gatewayTransportProcess(); + #endif + + #if defined(MY_RADIO_FEATURE) + transportProcess(); + #endif } -static inline MyMessage& buildGw(MyMessage &msg, uint8_t type) { - msg.sender = GATEWAY_ADDRESS; - msg.destination = GATEWAY_ADDRESS; - msg.sensor = 0; - msg.type = type; - mSetCommand(msg, C_INTERNAL); - mSetRequestAck(msg, false); - mSetAck(msg, false); - return msg; -} static inline bool isValidParent( const uint8_t parent ) { return parent != AUTO; } -static inline bool isValidDistance( const uint8_t distance ) { - return distance != DISTANCE_INVALID; -} - - - -#ifdef MY_OTA_FIRMWARE_FEATURE -// do a crc16 on the whole received firmware -bool isValidFirmware() { - // init crc - uint16_t crc = ~0; - for (uint16_t i = 0; i < _fc.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); - } - } - return crc == _fc.crc; -} - -#endif - -#ifdef MY_LEDS_BLINKING_FEATURE -void handleLedsBlinking() { - static unsigned long next_time = hwMillis() + MY_DEFAULT_LED_BLINK_PERIOD; - - // Just return if it is not the time... - // http://playground.arduino.cc/Code/TimingRollover - if ((long)(hwMillis() - _blink_next_time) < 0) - return; - else - _blink_next_time = hwMillis() + ledBlinkPeriod; - - // do the actual blinking - if(_countRx && _countRx != 255) { - // switch led on - hwDigitalWrite(MY_DEFAULT_RX_LED_PIN, LED_ON); - } - else if(!_countRx) { - // switching off - hwDigitalWrite(MY_DEFAULT_RX_LED_PIN, LED_OFF); - } - if(_countRx != 255) - --_countRx; - - if(_countTx && _countTx != 255) { - // switch led on - hwDigitalWrite(MY_DEFAULT_TX_LED_PIN, LED_ON); - } - else if(!_countTx) { - // switching off - hwDigitalWrite(MY_DEFAULT_TX_LED_PIN, LED_OFF); - } - if(_countTx != 255) - --_countTx; - - if(_countErr && _countErr != 255) { - // switch led on - hwDigitalWrite(MY_DEFAULT_ERR_LED_PIN, LED_ON); - } - else if(!_countErr) { - // switching off - hwDigitalWrite(MY_DEFAULT_ERR_LED_PIN, LED_OFF); - } - if(_countErr != 255) - --_countErr; -} - -void rxBlink(uint8_t cnt) { - if(_countRx == 255) { _countRx = cnt; } -} - -void txBlink(uint8_t cnt) { - if(_countTx == 255) { _countTx = cnt; } -} - -void errBlink(uint8_t cnt) { - if(_countErr == 255) { _countErr = cnt; } -} -#endif void dataCallback(void (*msgCallback)(const MyMessage &)) { @@ -187,29 +64,21 @@ void dataCallback(void (*msgCallback)(const MyMessage &)) { } void begin() { - - #if defined(MY_INCLUSION_MODE_FEATURE) - inclusionMode = false; - #endif - - #ifndef MY_DISABLED_SERIAL + #if defined(MY_DISABLED_SERIAL) hwInit(); #endif - #ifdef MY_GATEWAY_FEATURE - #if defined(MY_INCLUSION_MODE_FEATURE) && defined(MY_INCLUSION_BUTTON_FEATURE) - // Setup digital in that triggers inclusion mode - pinMode(MY_INCLUSION_MODE_BUTTON_PIN, INPUT); - digitalWrite(MY_INCLUSION_MODE_BUTTON_PIN, HIGH); + #if defined(MY_GATEWAY_FEATURE) + #if defined(MY_INCLUSION_BUTTON_FEATURE) + inclusionInit(); #endif - // initialize the transport driver - if (!gatewayTransportBegin()) { + + // initialize the transport driver + if (!gatewayTransportInit()) { debug(PSTR("transport driver init fail\n")); while(1); // Nothing more we can do } - #endif - #if defined(MY_GATEWAY_FEATURE) gatewayTransportSend(buildGw(_msg, I_GATEWAY_READY).set("Gateway startup complete.")); #endif @@ -223,27 +92,14 @@ void begin() { } #endif - #ifdef MY_SIGNING_FEATURE + #if defined(MY_SIGNING_FEATURE) // Read out the signing requirements from EEPROM hwReadConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, sizeof(_doSign)); #endif -#ifdef MY_LEDS_BLINKING_FEATURE - // Setup led pins - pinMode(MY_DEFAULT_RX_LED_PIN, OUTPUT); - pinMode(MY_DEFAULT_TX_LED_PIN, OUTPUT); - pinMode(MY_DEFAULT_ERR_LED_PIN, OUTPUT); - - // Set initial state of leds - hwDigitalWrite(MY_DEFAULT_RX_LED_PIN, LED_OFF); - hwDigitalWrite(MY_DEFAULT_TX_LED_PIN, LED_OFF); - hwDigitalWrite(MY_DEFAULT_ERR_LED_PIN, LED_OFF); - - // initialize counters - _countRx = 0; - _countTx = 0; - _countErr = 0; -#endif + #if defined(MY_LEDS_BLINKING_FEATURE) + ledsInit(); + #endif // Read latest received controller configuration from EEPROM hwReadConfigBlock((void*)&_cc, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS, sizeof(ControllerConfig)); @@ -277,7 +133,7 @@ void begin() { _nc.distance = 1; } else if (!isValidParent(_nc.parentNodeId)) { // Auto find parent, but parent in eeprom is invalid. Try find one. - findParentNode(); + transportFindParentNode(); } if (MY_NODE_ID != AUTO) { @@ -287,12 +143,12 @@ void begin() { hwWriteConfig(EEPROM_NODE_ID_ADDRESS, MY_NODE_ID); } else if (_nc.nodeId == AUTO && isValidParent(_nc.parentNodeId)) { // Try to fetch node-id from gateway - requestNodeId(); + transportRequestNodeId(); } #endif #if defined(MY_RADIO_FEATURE) - setupNode(); + transportSetupNode(); #endif debug(PSTR("%s started, id=%d, parent=%d, distance=%d\n"), MY_NODE_TYPE, _nc.nodeId, _nc.parentNodeId, _nc.distance); } @@ -308,271 +164,23 @@ ControllerConfig getConfig() { return _cc; } -#if defined(MY_RADIO_FEATURE) -void requestNodeId() { - debug(PSTR("req id\n")); - transportSetAddress(_nc.nodeId); - build(_msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_ID_REQUEST, false).set(""); - sendWrite(_nc.parentNodeId, _msg); - wait(2000); -} -#endif - -#if defined(MY_RADIO_FEATURE) - - -void setupNode() { - // Open reading pipe for messages directed to this node (set write pipe to same) - transportSetAddress(_nc.nodeId); - // Present node and request config - #ifndef MY_GATEWAY_FEATURE - if (_nc.nodeId != AUTO) { - #ifdef MY_SIGNING_FEATURE - // Notify gateway (and possibly controller) about the signing preferences of this node - sendRoute(build(_msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_REQUEST_SIGNING, false).set(signer.requestSignatures())); - - // If we do require signing, wait for the gateway to tell us how it prefer us to transmit our messages - if (signer.requestSignatures()) { - wait(2000); - } - #endif - - // Send presentation for this radio node - #ifdef MY_REPEATER_FEATURE - present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE); - #else - present(NODE_SENSOR_ID, S_ARDUINO_NODE); - #endif - // Send a configuration exchange request to controller - // Node sends parent node. Controller answers with latest node configuration - // which is picked up in process() - sendRoute(build(_msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CONFIG, false).set(_nc.parentNodeId)); - - // Wait configuration reply. - wait(2000); - - #ifdef MY_OTA_FIRMWARE_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 = MY_OTA_BOOTLOADER_VERSION; - _fwUpdateOngoing = false; - sendRoute(build(_msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_CONFIG_REQUEST, false)); - #endif - } - #endif -} -#endif - -#if defined(MY_RADIO_FEATURE) -void findParentNode() { - static boolean findingParentNode = false; - - if (findingParentNode) - return; - findingParentNode = true; - - _failedTransmissions = 0; - - // Set distance to max - _nc.distance = 255; - - // Send ping message to BROADCAST_ADDRESS (to which all relaying nodes and gateway listens and should reply to) - debug(PSTR("find parent\n")); - - build(_msg, _nc.nodeId, BROADCAST_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT, false).set(""); - // Write msg, but suppress recursive parent search - sendWrite(BROADCAST_ADDRESS, _msg); - - // Wait for ping response. - wait(2000); - findingParentNode = false; -} -#endif - - boolean sendRoute(MyMessage &message) { - #if defined(MY_GATEWAY_FEATURE) if (message.destination == _nc.nodeId) { // This is a message sent from a sensor attached on the gateway node. // Pass it directly to the gateway transport layer. - #ifdef MY_LEDS_BLINKING_FEATURE - txBlink(1); - #endif + ledBlinkTx(1); return gatewayTransportSend(message); } #endif - - #if defined(MY_RADIO_FEATURE) - uint8_t sender = message.sender; - uint8_t dest = message.destination; - uint8_t last = message.last; - bool ok; - - // If we still don't have any parent id, re-request and skip this message. - if (_nc.parentNodeId == AUTO) { - findParentNode(); - #ifdef MY_LEDS_BLINKING_FEATURE - errBlink(1); - #endif - return false; - } - - // If we still don't have any node id, re-request and skip this message. - if (_nc.nodeId == AUTO) { - requestNodeId(); - #ifdef MY_LEDS_BLINKING_FEATURE - errBlink(1); - #endif - return false; - } - - mSetVersion(message, PROTOCOL_VERSION); - - #ifdef MY_SIGNING_FEATURE - // If destination is known to require signed messages and we are the sender, sign this message unless it is an ACK or a handshake message - if (DO_SIGN(message.destination) && message.sender == _nc.nodeId && !mGetAck(message) && mGetLength(message) && - (mGetCommand(message) != C_INTERNAL || - (message.type != I_GET_NONCE && message.type != I_GET_NONCE_RESPONSE && message.type != I_REQUEST_SIGNING && - message.type != I_ID_REQUEST && message.type != I_ID_RESPONSE && - message.type != I_FIND_PARENT && message.type != I_FIND_PARENT_RESPONSE))) { - bool signOk = false; - // Send nonce-request - if (!sendRoute(build(_msgTmp, _nc.nodeId, message.destination, message.sensor, C_INTERNAL, I_GET_NONCE, false).set(""))) { - debug(PSTR("nonce tr err\n")); - return false; - } - // We have to wait for the nonce to arrive before we can sign our original message - // Other messages could come in-between. We trust process() takes care of them - unsigned long enter = hwMillis(); - _msgSign = message; // Copy the message to sign since message buffer might be touched in process() - while (hwMillis() - enter < MY_VERIFICATION_TIMEOUT_MS) { - if (process()) { - if (mGetCommand(getLastMessage()) == C_INTERNAL && getLastMessage().type == I_GET_NONCE_RESPONSE) { - // Proceed with signing if nonce has been received - if (signer.putNonce(getLastMessage()) && signer.signMsg(_msgSign)) { - message = _msgSign; // Write the signed message back - signOk = true; - } - break; - } - } - } - if (hwMillis() - enter > MY_VERIFICATION_TIMEOUT_MS) { - debug(PSTR("nonce tmo\n")); - #ifdef MY_LEDS_BLINKING_FEATURE - errBlink(1); - #endif - return false; - } - if (!signOk) { - debug(PSTR("sign fail\n")); - #ifdef MY_LEDS_BLINKING_FEATURE - errBlink(1); - #endif - return false; - } - // After this point, only the 'last' member of the message structure is allowed to be altered if the message has been signed, - // or signature will become invalid and the message rejected by the receiver - } else mSetSigned(message, 0); // Message is not supposed to be signed, make sure it is marked unsigned - #endif - - if (dest == GATEWAY_ADDRESS || !MY_IS_REPEATER) { - // Store this address in routing table (if repeater) - #if defined (MY_IS_REPEATER) - hwWriteConfig(EEPROM_ROUTES_ADDRESS+sender, last); - #endif - // If destination is the gateway or if we aren't a repeater, let - // our parent take care of the message - ok = sendWrite(_nc.parentNodeId, message); - } else { - // Relay the message - uint8_t route = hwReadConfig(EEPROM_ROUTES_ADDRESS+dest); - if (route > GATEWAY_ADDRESS && route < BROADCAST_ADDRESS) { - // This message should be forwarded to a child node. If we send message - // to this nodes pipe then all children will receive it because the are - // all listening to this nodes pipe. - // - // +----B - // -A - // +----C------D - // - // We're node C, Message comes from A and has destination D - // - // Message destination is not gateway and is in routing table for this node. - // Send it downstream - return sendWrite(route, message); - } else if (sender == GATEWAY_ADDRESS && dest == BROADCAST_ADDRESS) { - // Node has not yet received any id. We need to send it - // by doing a broadcast sending, - return sendWrite(BROADCAST_ADDRESS, message); - } else if (MY_IS_GATEWAY) { - // Destination isn't in our routing table and isn't a broadcast address - // Nothing to do here - return false; - } else { - // A message comes from a child node and we have no - // route for it. - // - // +----B - // -A - // +----C------D <-- Message comes from D - // - // We're node C - // - // Message should be passed to node A (this nodes relay) - - // This message should be routed back towards sensor net gateway - ok = sendWrite(_nc.parentNodeId, message); - // Add this child to our "routing table" if it not already exist - hwWriteConfig(EEPROM_ROUTES_ADDRESS+sender, last); - } - } - - if (!ok) { - // Failure when sending to parent node. The parent node might be down and we - // need to find another route to gateway. - #ifdef MY_LEDS_BLINKING_FEATURE - errBlink(1); - #endif - _failedTransmissions++; - if (_autoFindParent && _failedTransmissions > SEARCH_FAILURES) { - findParentNode(); - } - } else { - _failedTransmissions = 0; - } - return ok; + transportSendRoute(message); #else return false; #endif } -#ifdef MY_RADIO_FEATURE -boolean sendWrite(uint8_t to, MyMessage &message) { - mSetVersion(message, PROTOCOL_VERSION); - uint8_t length = mGetSigned(message) ? MAX_MESSAGE_LENGTH : mGetLength(message); - message.last = _nc.nodeId; - #ifdef MY_LEDS_BLINKING_FEATURE - txBlink(1); - #endif - bool ok = transportSend(to, &message, min(MAX_MESSAGE_LENGTH, HEADER_SIZE + length)); - - debug(PSTR("send: %d-%d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d,st=%s:%s\n"), - message.sender,message.last, to, message.destination, message.sensor, mGetCommand(message), message.type, - mGetPayloadType(message), mGetLength(message), mGetSigned(message), to==BROADCAST_ADDRESS ? "bc" : (ok ? "ok":"fail"), message.getString(convBuf)); - - return ok; -} -#endif - bool send(MyMessage &message, bool enableAck) { message.sender = _nc.nodeId; mSetCommand(message,C_SET); @@ -611,320 +219,7 @@ void requestTime(void (* timeCallback)(unsigned long)) { sendRoute(build(_msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_TIME, false).set("")); } -void process() { - hwWatchdogReset(); - - #ifdef MY_LEDS_BLINKING_FEATURE - handleLedsBlinking(); - #endif - - #ifdef MY_INCLUSION_MODE_FEATURE - checkInclusionMode(); - #endif - - #if defined (MY_GATEWAY_FEATURE) - if (gatewayTransportAvailable()) { - MyMessage &_msg = gatewayTransportReceive(); - if (_msg.destination == GATEWAY_ADDRESS) { - // Check if sender requests an ack back. - if (mGetRequestAck(_msg)) { - // Copy message - _msgTmp = _msg; - mSetRequestAck(_msgTmp, false); // Reply without ack flag (otherwise we would end up in an eternal loop) - mSetAck(_msgTmp, true); - _msgTmp.sender = _nc.nodeId; - _msgTmp.destination = _msg.sender; - gatewayTransportSend(_msgTmp); - } - if (mGetCommand(_msg) == C_INTERNAL) { - if (_msg.type == I_VERSION) { - // Request for version. Create the response - gatewayTransportSend(buildGw(_msg, I_VERSION).set(LIBRARY_VERSION)); - #ifdef MY_INCLUSION_MODE_FEATURE - } else if (_msg.type == I_INCLUSION_MODE) { - // Request to change inclusion mode - setInclusionMode(atoi(_msg.data) == 1); - #endif - } else { - processInternalMessages(); - } - } else { - // Call incoming message callback if available - if (_msgCallback != NULL) { - _msgCallback(_msg); - } - } - } - } - #endif - - #if defined(MY_RADIO_FEATURE) - uint8_t to = 0; - if (!transportAvailable(&to)) - { - #ifdef MY_OTA_FIRMWARE_FEATURE - unsigned long enter = hwMillis(); - if (_fwUpdateOngoing && (enter - _fwLastRequestTime > MY_OTA_RETRY_DELAY)) { - if (!_fwRetry) { - debug(PSTR("fw upd fail\n")); - // Give up. We have requested MY_OTA_RETRY times without any packet in return. - _fwUpdateOngoing = false; - #ifdef MY_LEDS_BLINKING_FEATURE - errBlink(1); - #endif - return; - } - _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; - } - - #ifdef MY_SIGNING_FEATURE - (void)signer.checkTimer(); // Manage signing timeout - #endif - - uint8_t len = transportReceive((uint8_t *)&_msg); - (void)len; //until somebody makes use of 'len' - #ifdef MY_LEDS_BLINKING_FEATURE - rxBlink(1); - #endif - - #ifdef MY_SIGNING_FEATURE - // Before processing message, reject unsigned messages if signing is required and check signature (if it is signed and addressed to us) - // Note that we do not care at all about any signature found if we do not require signing, nor do we care about ACKs (they are never signed) - if (signer.requestSignatures() && _msg.destination == _nc.nodeId && mGetLength(_msg) && !mGetAck(_msg) && - (mGetCommand(_msg) != C_INTERNAL || - (_msg.type != I_GET_NONCE_RESPONSE && _msg.type != I_GET_NONCE && _msg.type != I_REQUEST_SIGNING && - _msg.type != I_ID_REQUEST && _msg.type != I_ID_RESPONSE && - _msg.type != I_FIND_PARENT && _msg.type != I_FIND_PARENT_RESPONSE))) { - if (!mGetSigned(_msg)) { - // Got unsigned message that should have been signed - debug(PSTR("no sign\n")); - #ifdef MY_LEDS_BLINKING_FEATURE - errBlink(1); - #endif - return; - } - else if (!signer.verifyMsg(_msg)) { - debug(PSTR("verify fail\n")); - #ifdef MY_LEDS_BLINKING_FEATURE - errBlink(1); - #endif - return; // This signed message has been tampered with! - } - } - #endif - - // Add string termination, good if we later would want to print it. - _msg.data[mGetLength(_msg)] = '\0'; - debug(PSTR("read: %d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d:%s\n"), - _msg.sender, _msg.last, _msg.destination, _msg.sensor, mGetCommand(_msg), _msg.type, mGetPayloadType(_msg), mGetLength(_msg), mGetSigned(_msg), _msg.getString(convBuf)); - mSetSigned(_msg,0); // Clear the sign-flag now as verification (and debug printing) is completed - - if(!(mGetVersion(_msg) == PROTOCOL_VERSION)) { - debug(PSTR("ver mismatch\n")); - #ifdef MY_LEDS_BLINKING_FEATURE - errBlink(1); - #endif - return; - } - - uint8_t command = mGetCommand(_msg); - uint8_t type = _msg.type; - uint8_t sender = _msg.sender; - uint8_t last = _msg.last; - uint8_t destination = _msg.destination; - - if (destination == _nc.nodeId) { - // This message is addressed to this node - - if (MY_IS_REPEATER && last != _nc.parentNodeId) { - // Message is from one of the child nodes. Add it to routing table. - hwWriteConfig(EEPROM_ROUTES_ADDRESS+sender, last); - } - - // Check if sender requests an ack back. - if (mGetRequestAck(_msg)) { - // Copy message - _msgTmp = _msg; - mSetRequestAck(_msgTmp,false); // Reply without ack flag (otherwise we would end up in an eternal loop) - mSetAck(_msgTmp,true); - _msgTmp.sender = _nc.nodeId; - _msgTmp.destination = _msg.sender; - sendRoute(_msgTmp); - } - - if (command == C_INTERNAL) { - if (type == I_FIND_PARENT_RESPONSE) { - if (_autoFindParent) { - // We've received a reply to a FIND_PARENT message. Check if the distance is - // shorter than we already have. - uint8_t distance = _msg.getByte(); - if (isValidDistance(distance)) - { - // Distance to gateway is one more for us w.r.t. parent - distance++; - if (isValidDistance(distance) && (distance < _nc.distance)) { - // Found a neighbor closer to GW than previously found - _nc.distance = distance; - _nc.parentNodeId = _msg.sender; - hwWriteConfig(EEPROM_PARENT_NODE_ID_ADDRESS, _nc.parentNodeId); - hwWriteConfig(EEPROM_DISTANCE_ADDRESS, _nc.distance); - debug(PSTR("parent=%d, d=%d\n"), _nc.parentNodeId, _nc.distance); - } - } - } - return; - #ifdef MY_SIGNING_FEATURE - } else if (type == I_GET_NONCE) { - if (signer.getNonce(_msg)) { - sendRoute(build(_msg, _nc.nodeId, _msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_GET_NONCE_RESPONSE, false)); - } - return false; // Nonce exchange is an internal MySensor protocol message, no need to inform caller about this - } else if (type == I_REQUEST_SIGNING) { - if (_msg.getBool()) { - // We received an indicator that the sender require us to sign all messages we send to it - SET_SIGN(_msg.sender); - } else { - // We received an indicator that the sender does not require us to sign all messages we send to it - CLEAR_SIGN(_msg.sender); - } - // Save updated table - hwWriteConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, sizeof(_doSign)); - - // Inform sender about our preference if we are a gateway, but only require signing if the sender required signing - // We do not currently want a gateway to require signing from all nodes in a network just because it wants one node - // to sign it's messages - if (MY_GATEWAY_FEATURE) { - if (signer.requestSignatures() && DO_SIGN(_msg.sender)) - sendRoute(build(_msg, _nc.nodeId, _msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_REQUEST_SIGNING, false).set(true)); - else - sendRoute(build(_msg, _nc.nodeId, _msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_REQUEST_SIGNING, false).set(false)); - } - return; // Signing request is an internal MySensor protocol message, no need to inform caller about this - } else if (type == I_GET_NONCE_RESPONSE) { - return; // Just pass along nonce silently (no need to call callback for these) - #endif - } else if (sender == GATEWAY_ADDRESS) { - if (type == I_ID_RESPONSE && _nc.nodeId == AUTO) { - _nc.nodeId = _msg.getByte(); - if (_nc.nodeId == AUTO) { - // sensor net gateway will return max id if all sensor id are taken - debug(PSTR("full\n")); - while (1); // Wait here. Nothing else we can do... - } - setupNode(); - // Write id to EEPROM - hwWriteConfig(EEPROM_NODE_ID_ADDRESS, _nc.nodeId); - debug(PSTR("id=%d\n"), _nc.nodeId); - } else { - processInternalMessages(); - } - return; - } - } - #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")); - // copy new FW config - memcpy(&_fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig)); - // Init flash - if (!_flash.initialize()) { - debug(PSTR("flash init fail\n")); - _fwUpdateOngoing = false; - } else { - // erase lower 32K -> max flash size for ATMEGA328 - _flash.blockErase32K(0); - // wait until flash erased - while ( _flash.busy() ); - _fwBlock = _fc.blocks; - _fwUpdateOngoing = true; - // reset flags - _fwRetry = MY_OTA_RETRY+1; - _fwLastRequestTime = 0; - } - return ; - } else debug(PSTR("fw update skipped\n")); - } else if (type == ST_FIRMWARE_RESPONSE) { - if (_fwUpdateOngoing) { - // Save block to flash - debug(PSTR("fw block %d\n"), _fwBlock); - // extract FW block - ReplyFWBlock *firmwareResponse = (ReplyFWBlock *)_msg.data; - // write to flash - _flash.writeBytes( ((_fwBlock - 1) * FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET, firmwareResponse->data, FIRMWARE_BLOCK_SIZE); - // wait until flash written - while ( _flash.busy() ); - _fwBlock--; - if (!_fwBlock) { - // We're finished! Do a checksum and reboot. - _fwUpdateOngoing = false; - 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; - uint8_t OTAbuffer[10] = {'F','L','X','I','M','G',':',(fwsize >> 8),fwsize,':'}; - _flash.writeBytes(0, OTAbuffer, 10); - // Write the new firmware config to eeprom - hwWriteConfigBlock((void*)&_fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig)); - hwReboot(); - } else { - debug(PSTR("fw checksum fail\n")); - } - } - // reset flags - _fwRetry = MY_OTA_RETRY+1; - _fwLastRequestTime = 0; - } else { - debug(PSTR("No fw update ongoing\n")); - } - return; - } - - } - #endif - // Call incoming message callback if available - if (_msgCallback != NULL) { - _msgCallback(_msg); - } - return; - } else if (MY_IS_REPEATER && _nc.nodeId != AUTO) { - // If this node have an id, relay the message - if (command == C_INTERNAL && type == I_FIND_PARENT) { - if (sender != _nc.parentNodeId) { - if (_nc.distance == DISTANCE_INVALID) - findParentNode(); - - if (_nc.distance != DISTANCE_INVALID) { - // Relaying nodes should always answer ping messages - // Wait a random delay of 0-2 seconds to minimize collision - // between ping ack messages from other relaying nodes - wait(hwMillis() & 0x3ff); - sendWrite(sender, build(_msg, _nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT_RESPONSE, false).set(_nc.distance)); - } - } - } else if (to == _nc.nodeId) { - // We should try to relay this message to another node - sendRoute(_msg); - } - } - #endif -} - - +// Message delivered through _msg void processInternalMessages() { bool isMetric; uint8_t type = _msg.type; @@ -938,8 +233,12 @@ void processInternalMessages() { isMetric = _msg.getString()[0] == 'M' ; _cc.isMetric = isMetric; hwWriteConfig(EEPROM_CONTROLLER_CONFIG_ADDRESS, isMetric); - } else if (type == I_CHILDREN) { - if (MY_IS_REPEATER && _msg.getString()[0] == 'C') { + } else if (type == I_TIME && _timeCallback != NULL) { + // Deliver time to callback + _timeCallback(_msg.getULong()); + } + #if defined(MY_REPEATER_FEATURE) + if (type == I_CHILDREN && _msg.getString()[0] == 'C') { // Clears child relay data for this node debug(PSTR("clear\n")); uint8_t i = 255; @@ -950,26 +249,19 @@ void processInternalMessages() { hwWriteConfig(EEPROM_PARENT_NODE_ID_ADDRESS, 0xFF); hwWriteConfig(EEPROM_DISTANCE_ADDRESS, 0xFF); // Find parent node - findParentNode(); + transportFindParentNode(); sendRoute(build(_msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CHILDREN,false).set("")); } - } else if (type == I_TIME) { - if (_timeCallback != NULL) { - // Deliver time to callback - _timeCallback(_msg.getULong()); - } - } + #endif } -#if defined (MY_REPEATER_FEATURE) void saveState(uint8_t pos, uint8_t value) { hwWriteConfig(EEPROM_LOCAL_CONFIG_ADDRESS+pos, value); } uint8_t loadState(uint8_t pos) { return hwReadConfig(EEPROM_LOCAL_CONFIG_ADDRESS+pos); } -#endif void wait(unsigned long ms) { unsigned long enter = hwMillis(); @@ -1029,31 +321,5 @@ int8_t sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode } -#ifdef MY_INCLUSION_MODE_FEATURE -void setInclusionMode(boolean newMode) { - if (newMode != inclusionMode) { - inclusionMode = newMode; - // Send back mode change to controller - gatewayTransportSend(buildGw(_msg, I_INCLUSION_MODE).set(inclusionMode?1:0)); - if (inclusionMode) { - inclusionStartTime = hwMillis(); - } - } -} - -void checkInclusionMode() { - #ifdef MY_INCLUSION_BUTTON_FEATURE - if (!inclusionMode && digitalRead(MY_INCLUSION_MODE_BUTTON_PIN) == LOW) { - // Start inclusion mode - setInclusionMode(true); - } - #endif - - if (inclusionMode && hwMillis()-inclusionStartTime>MY_INCLUSION_MODE_DURATION*1000L) { - // inclusionTimeInMinutes minute(s) has passed.. stop inclusion mode - setInclusionMode(false); - } -} -#endif diff --git a/libraries/MySensors/core/MySensorCore.h b/libraries/MySensors/core/MySensorCore.h index 24a7874..ce9ed5d 100644 --- a/libraries/MySensors/core/MySensorCore.h +++ b/libraries/MySensors/core/MySensorCore.h @@ -35,24 +35,12 @@ #include #include - - - #ifdef MY_DEBUG #define debug(x,...) hwDebugPrint(x, ##__VA_ARGS__) #else #define debug(x,...) #endif -#ifdef WITH_LEDS_BLINKING_INVERSE -#define LED_ON 0x1 -#define LED_OFF 0x0 -#else -#define LED_ON 0x0 -#define LED_OFF 0x1 -#endif - - // EEPROM start address for mysensors library data #define EEPROM_START 0 // EEPROM location of node id @@ -70,8 +58,8 @@ #define EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS (EEPROM_FIRMWARE_CRC_ADDRESS+2) #define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS+32) // First free address for sketch static configuration -// Search for a new parent node after this many transmission failures -#define SEARCH_FAILURES 5 +// This is the nodeId for sensor net gateway receiver sketch (where all sensors should send their data). +#define GATEWAY_ADDRESS ((uint8_t)0) struct NodeConfig @@ -86,65 +74,15 @@ struct ControllerConfig { }; -// 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 -// 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 { - 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; - - - /** -* Begin operation of the MySensors library +* Set the callback method for incoming messages * -* Call this in setup(), before calling any other sensor net library methods. -* @param incomingMessageCallback Callback function for incoming messages from other nodes or controller and request responses. Default is NULL. +* @param incomingMessageCallback Callback function for incoming messages from other nodes or controller and request responses. */ - - void dataCallback(void (* msgCallback)(const MyMessage &)=NULL); /** - * Return the nodes nodeId. + * Return this nodes id. */ uint8_t getNodeId(); @@ -284,47 +222,46 @@ bool sleep(uint8_t interrupt, uint8_t mode, unsigned long ms=0); int8_t sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms=0); - /****** PRIVATE ********/ + void begin(); void process(void); void processInternalMessages(); -#ifdef MY_LEDS_BLINKING_FEATURE - /** - * Blink with LEDs - * @param cnt how many blink cycles to keep the LED on. Default cycle is 300ms - */ - void rxBlink(uint8_t cnt); - void txBlink(uint8_t cnt); - void errBlink(uint8_t cnt); - void handleLedsBlinking(); // do the actual blinking -#endif - -#ifdef MY_OTA_FIRMWARE_FEATURE - // do a crc16 on the whole received firmware - bool isValidFirmware(); -#endif - - -#ifdef MY_INCLUSION_MODE_FEATURE - void setInclusionMode(bool newMode); - void checkInclusionMode(); - unsigned long inclusionStartTime; - bool inclusionMode; -#endif - - boolean sendRoute(MyMessage &message); -#if defined(MY_RADIO_FEATURE) - boolean sendWrite(uint8_t dest, MyMessage &message); - void requestNodeId(); - void setupNode(); - void findParentNode(); + +extern NodeConfig _nc; +extern MyMessage _msg; // Buffer for incoming messages. +extern MyMessage _msgTmp; // Buffer for temporary messages (acks and nonces among others). +extern void (*_msgCallback)(const MyMessage &); // Callback for incoming messages from other nodes and gateway. +#ifdef MY_DEBUG + extern char convBuf[MAX_PAYLOAD*2+1]; #endif +// Inline function and macros +static inline MyMessage& build(MyMessage &msg, uint8_t sender, uint8_t destination, uint8_t sensor, uint8_t command, uint8_t type, bool enableAck) { + msg.sender = sender; + msg.destination = destination; + msg.sensor = sensor; + msg.type = type; + mSetCommand(msg,command); + mSetRequestAck(msg,enableAck); + mSetAck(msg,false); + return msg; +} + +static inline MyMessage& buildGw(MyMessage &msg, uint8_t type) { + msg.sender = GATEWAY_ADDRESS; + msg.destination = GATEWAY_ADDRESS; + msg.sensor = 0; + msg.type = type; + mSetCommand(msg, C_INTERNAL); + mSetRequestAck(msg, false); + mSetAck(msg, false); + return msg; +} #endif diff --git a/libraries/MySensors/core/MySigning.cpp b/libraries/MySensors/core/MySigning.cpp index 03c3ee3..ad0d78c 100644 --- a/libraries/MySensors/core/MySigning.cpp +++ b/libraries/MySensors/core/MySigning.cpp @@ -19,6 +19,11 @@ #include "MySigning.h" + +uint16_t _doSign[16]; // Bitfield indicating which sensors require signed communication +MyMessage _msgSign; // Buffer for message to sign. + + MySigning::MySigning(bool requestSignatures) : _requestSignatures(requestSignatures) { } diff --git a/libraries/MySensors/core/MySigning.h b/libraries/MySensors/core/MySigning.h index e65d546..637b281 100644 --- a/libraries/MySensors/core/MySigning.h +++ b/libraries/MySensors/core/MySigning.h @@ -30,6 +30,12 @@ #include #include "MyMessage.h" + +// Macros for manipulating signing requirement table +#define DO_SIGN(node) (node == 0 ? (~_doSign[0]&1) : (~_doSign[node>>4]&(node%16))) +#define SET_SIGN(node) (node == 0 ? (_doSign[0]&=~1) : (_doSign[node>>4]&=~(node%16))) +#define CLEAR_SIGN(node) (node == 0 ? (_doSign[0]|=1) : (_doSign[node>>4]|=(node%16))) + class MySigning { public: diff --git a/libraries/MySensors/core/MyTransport.cpp b/libraries/MySensors/core/MyTransport.cpp new file mode 100644 index 0000000..309bc4d --- /dev/null +++ b/libraries/MySensors/core/MyTransport.cpp @@ -0,0 +1,547 @@ +/** + * 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 + * Copyright (C) 2013-2015 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 "MyTransport.h" + +bool _autoFindParent; +uint8_t _failedTransmissions; + + +#ifdef MY_OTA_FIRMWARE_FEATURE + NodeFirmwareConfig _fc; + bool _fwUpdateOngoing; + unsigned long _fwLastRequestTime; + uint16_t _fwBlock; + uint8_t _fwRetry; +#endif + + +static inline bool isValidDistance( const uint8_t distance ) { + return distance != DISTANCE_INVALID; +} + + +inline void transportProcess() { + uint8_t to = 0; + if (!transportAvailable(&to)) + { + #ifdef MY_OTA_FIRMWARE_FEATURE + unsigned long enter = hwMillis(); + if (_fwUpdateOngoing && (enter - _fwLastRequestTime > MY_OTA_RETRY_DELAY)) { + if (!_fwRetry) { + debug(PSTR("fw upd fail\n")); + // Give up. We have requested MY_OTA_RETRY times without any packet in return. + _fwUpdateOngoing = false; + ledBlinkErr(1); + return; + } + _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; + } + + #ifdef MY_SIGNING_FEATURE + (void)signer.checkTimer(); // Manage signing timeout + #endif + + uint8_t len = transportReceive((uint8_t *)&_msg); + (void)len; //until somebody makes use of 'len' + ledBlinkRx(1); + + #ifdef MY_SIGNING_FEATURE + // Before processing message, reject unsigned messages if signing is required and check signature (if it is signed and addressed to us) + // Note that we do not care at all about any signature found if we do not require signing, nor do we care about ACKs (they are never signed) + if (signer.requestSignatures() && _msg.destination == _nc.nodeId && mGetLength(_msg) && !mGetAck(_msg) && + (mGetCommand(_msg) != C_INTERNAL || + (_msg.type != I_GET_NONCE_RESPONSE && _msg.type != I_GET_NONCE && _msg.type != I_REQUEST_SIGNING && + _msg.type != I_ID_REQUEST && _msg.type != I_ID_RESPONSE && + _msg.type != I_FIND_PARENT && _msg.type != I_FIND_PARENT_RESPONSE))) { + if (!mGetSigned(_msg)) { + // Got unsigned message that should have been signed + debug(PSTR("no sign\n")); + ledBlinkErr(1); + return; + } + else if (!signer.verifyMsg(_msg)) { + debug(PSTR("verify fail\n")); + ledBlinkErr(1); + return; // This signed message has been tampered with! + } + } + #endif + + // Add string termination, good if we later would want to print it. + _msg.data[mGetLength(_msg)] = '\0'; + debug(PSTR("read: %d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d:%s\n"), + _msg.sender, _msg.last, _msg.destination, _msg.sensor, mGetCommand(_msg), _msg.type, mGetPayloadType(_msg), mGetLength(_msg), mGetSigned(_msg), _msg.getString(convBuf)); + mSetSigned(_msg,0); // Clear the sign-flag now as verification (and debug printing) is completed + + if(!(mGetVersion(_msg) == PROTOCOL_VERSION)) { + debug(PSTR("ver mismatch\n")); + ledBlinkErr(1); + return; + } + + uint8_t command = mGetCommand(_msg); + uint8_t type = _msg.type; + uint8_t sender = _msg.sender; + uint8_t last = _msg.last; + uint8_t destination = _msg.destination; + + if (destination == _nc.nodeId) { + // This message is addressed to this node + + #if defined(MY_REPEATER_FEATURE) + if (last != _nc.parentNodeId) { + // Message is from one of the child nodes. Add it to routing table. + hwWriteConfig(EEPROM_ROUTES_ADDRESS+sender, last); + } + #endif + + // Check if sender requests an ack back. + if (mGetRequestAck(_msg)) { + // Copy message + _msgTmp = _msg; + mSetRequestAck(_msgTmp,false); // Reply without ack flag (otherwise we would end up in an eternal loop) + mSetAck(_msgTmp,true); + _msgTmp.sender = _nc.nodeId; + _msgTmp.destination = _msg.sender; + sendRoute(_msgTmp); + } + + if (command == C_INTERNAL) { + if (type == I_FIND_PARENT_RESPONSE) { + if (_autoFindParent) { + // We've received a reply to a FIND_PARENT message. Check if the distance is + // shorter than we already have. + uint8_t distance = _msg.getByte(); + if (isValidDistance(distance)) + { + // Distance to gateway is one more for us w.r.t. parent + distance++; + if (isValidDistance(distance) && (distance < _nc.distance)) { + // Found a neighbor closer to GW than previously found + _nc.distance = distance; + _nc.parentNodeId = _msg.sender; + hwWriteConfig(EEPROM_PARENT_NODE_ID_ADDRESS, _nc.parentNodeId); + hwWriteConfig(EEPROM_DISTANCE_ADDRESS, _nc.distance); + debug(PSTR("parent=%d, d=%d\n"), _nc.parentNodeId, _nc.distance); + } + } + } + return; + #ifdef MY_SIGNING_FEATURE + } else if (type == I_GET_NONCE) { + if (signer.getNonce(_msg)) { + sendRoute(build(_msg, _nc.nodeId, _msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_GET_NONCE_RESPONSE, false)); + } + return false; // Nonce exchange is an internal MySensor protocol message, no need to inform caller about this + } else if (type == I_REQUEST_SIGNING) { + if (_msg.getBool()) { + // We received an indicator that the sender require us to sign all messages we send to it + SET_SIGN(_msg.sender); + } else { + // We received an indicator that the sender does not require us to sign all messages we send to it + CLEAR_SIGN(_msg.sender); + } + // Save updated table + hwWriteConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, sizeof(_doSign)); + + // Inform sender about our preference if we are a gateway, but only require signing if the sender required signing + // We do not currently want a gateway to require signing from all nodes in a network just because it wants one node + // to sign it's messages + if (MY_GATEWAY_FEATURE) { + if (signer.requestSignatures() && DO_SIGN(_msg.sender)) + sendRoute(build(_msg, _nc.nodeId, _msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_REQUEST_SIGNING, false).set(true)); + else + sendRoute(build(_msg, _nc.nodeId, _msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_REQUEST_SIGNING, false).set(false)); + } + return; // Signing request is an internal MySensor protocol message, no need to inform caller about this + } else if (type == I_GET_NONCE_RESPONSE) { + return; // Just pass along nonce silently (no need to call callback for these) + #endif + } else if (sender == GATEWAY_ADDRESS) { + if (type == I_ID_RESPONSE && _nc.nodeId == AUTO) { + _nc.nodeId = _msg.getByte(); + if (_nc.nodeId == AUTO) { + // sensor net gateway will return max id if all sensor id are taken + debug(PSTR("full\n")); + while (1); // Wait here. Nothing else we can do... + } + transportSetupNode(); + // Write id to EEPROM + hwWriteConfig(EEPROM_NODE_ID_ADDRESS, _nc.nodeId); + debug(PSTR("id=%d\n"), _nc.nodeId); + } else { + processInternalMessages(); + } + return; + } + } + #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")); + // copy new FW config + memcpy(&_fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig)); + // Init flash + if (!_flash.initialize()) { + debug(PSTR("flash init fail\n")); + _fwUpdateOngoing = false; + } else { + // erase lower 32K -> max flash size for ATMEGA328 + _flash.blockErase32K(0); + // wait until flash erased + while ( _flash.busy() ); + _fwBlock = _fc.blocks; + _fwUpdateOngoing = true; + // reset flags + _fwRetry = MY_OTA_RETRY+1; + _fwLastRequestTime = 0; + } + return ; + } else debug(PSTR("fw update skipped\n")); + } else if (type == ST_FIRMWARE_RESPONSE) { + if (_fwUpdateOngoing) { + // Save block to flash + debug(PSTR("fw block %d\n"), _fwBlock); + // extract FW block + ReplyFWBlock *firmwareResponse = (ReplyFWBlock *)_msg.data; + // write to flash + _flash.writeBytes( ((_fwBlock - 1) * FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET, firmwareResponse->data, FIRMWARE_BLOCK_SIZE); + // wait until flash written + while ( _flash.busy() ); + _fwBlock--; + if (!_fwBlock) { + // We're finished! Do a checksum and reboot. + _fwUpdateOngoing = false; + if (transportIsValidFirmware()) { + 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; + uint8_t OTAbuffer[10] = {'F','L','X','I','M','G',':',(fwsize >> 8),fwsize,':'}; + _flash.writeBytes(0, OTAbuffer, 10); + // Write the new firmware config to eeprom + hwWriteConfigBlock((void*)&_fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig)); + hwReboot(); + } else { + debug(PSTR("fw checksum fail\n")); + } + } + // reset flags + _fwRetry = MY_OTA_RETRY+1; + _fwLastRequestTime = 0; + } else { + debug(PSTR("No fw update ongoing\n")); + } + return; + } + + } + #endif + // Call incoming message callback if available + if (_msgCallback != NULL) { + _msgCallback(_msg); + } + return; + } + #if defined(MY_REPEATER_FEATURE) + if (_nc.nodeId != AUTO) { + // If this node have an id, relay the message + if (command == C_INTERNAL && type == I_FIND_PARENT) { + if (sender != _nc.parentNodeId) { + if (_nc.distance == DISTANCE_INVALID) + transportFindParentNode(); + + if (_nc.distance != DISTANCE_INVALID) { + // Relaying nodes should always answer ping messages + // Wait a random delay of 0-2 seconds to minimize collision + // between ping ack messages from other relaying nodes + wait(hwMillis() & 0x3ff); + sendWrite(sender, build(_msg, _nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT_RESPONSE, false).set(_nc.distance)); + } + } + } else if (to == _nc.nodeId) { + // We should try to relay this message to another node + sendRoute(_msg); + } + } + #endif +} + +#ifdef MY_OTA_FIRMWARE_FEATURE +// do a crc16 on the whole received firmware +bool transportIsValidFirmware() { + // init crc + uint16_t crc = ~0; + for (uint16_t i = 0; i < _fc.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); + } + } + return crc == _fc.crc; +} + +#endif + + +boolean transportSendWrite(uint8_t to, MyMessage &message) { + mSetVersion(message, PROTOCOL_VERSION); + uint8_t length = mGetSigned(message) ? MAX_MESSAGE_LENGTH : mGetLength(message); + message.last = _nc.nodeId; + ledBlinkTx(1); + bool ok = transportSend(to, &message, min(MAX_MESSAGE_LENGTH, HEADER_SIZE + length)); + + debug(PSTR("send: %d-%d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d,st=%s:%s\n"), + message.sender,message.last, to, message.destination, message.sensor, mGetCommand(message), message.type, + mGetPayloadType(message), mGetLength(message), mGetSigned(message), to==BROADCAST_ADDRESS ? "bc" : (ok ? "ok":"fail"), message.getString(convBuf)); + + return ok; +} + + +boolean transportSendRoute(MyMessage &message) { + uint8_t sender = message.sender; + uint8_t dest = message.destination; + uint8_t last = message.last; + bool ok; + + // If we still don't have any parent id, re-request and skip this message. + if (_nc.parentNodeId == AUTO) { + transportFindParentNode(); + ledBlinkErr(1); + return false; + } + + // If we still don't have any node id, re-request and skip this message. + if (_nc.nodeId == AUTO) { + transportRequestNodeId(); + ledBlinkErr(1); + return false; + } + + mSetVersion(message, PROTOCOL_VERSION); + + #ifdef MY_SIGNING_FEATURE + // If destination is known to require signed messages and we are the sender, sign this message unless it is an ACK or a handshake message + if (DO_SIGN(message.destination) && message.sender == _nc.nodeId && !mGetAck(message) && mGetLength(message) && + (mGetCommand(message) != C_INTERNAL || + (message.type != I_GET_NONCE && message.type != I_GET_NONCE_RESPONSE && message.type != I_REQUEST_SIGNING && + message.type != I_ID_REQUEST && message.type != I_ID_RESPONSE && + message.type != I_FIND_PARENT && message.type != I_FIND_PARENT_RESPONSE))) { + bool signOk = false; + // Send nonce-request + if (!sendRoute(build(_msgTmp, _nc.nodeId, message.destination, message.sensor, C_INTERNAL, I_GET_NONCE, false).set(""))) { + debug(PSTR("nonce tr err\n")); + return false; + } + // We have to wait for the nonce to arrive before we can sign our original message + // Other messages could come in-between. We trust process() takes care of them + unsigned long enter = hwMillis(); + _msgSign = message; // Copy the message to sign since message buffer might be touched in process() + while (hwMillis() - enter < MY_VERIFICATION_TIMEOUT_MS) { + if (process()) { + if (mGetCommand(getLastMessage()) == C_INTERNAL && getLastMessage().type == I_GET_NONCE_RESPONSE) { + // Proceed with signing if nonce has been received + if (signer.putNonce(getLastMessage()) && signer.signMsg(_msgSign)) { + message = _msgSign; // Write the signed message back + signOk = true; + } + break; + } + } + } + if (hwMillis() - enter > MY_VERIFICATION_TIMEOUT_MS) { + debug(PSTR("nonce tmo\n")); + ledBlinkErr(1); + return false; + } + if (!signOk) { + debug(PSTR("sign fail\n")); + ledBlinkErr(1); + return false; + } + // After this point, only the 'last' member of the message structure is allowed to be altered if the message has been signed, + // or signature will become invalid and the message rejected by the receiver + } else { + mSetSigned(message, 0); // Message is not supposed to be signed, make sure it is marked unsigned + } + #endif + + if (dest == GATEWAY_ADDRESS || !MY_IS_REPEATER) { + // Store this address in routing table (if repeater) + #if defined (MY_IS_REPEATER) + hwWriteConfig(EEPROM_ROUTES_ADDRESS+sender, last); + #endif + // If destination is the gateway or if we aren't a repeater, let + // our parent take care of the message + ok = transportSendWrite(_nc.parentNodeId, message); + } else { + // Relay the message + uint8_t route = hwReadConfig(EEPROM_ROUTES_ADDRESS+dest); + if (route > GATEWAY_ADDRESS && route < BROADCAST_ADDRESS) { + // This message should be forwarded to a child node. If we send message + // to this nodes pipe then all children will receive it because the are + // all listening to this nodes pipe. + // + // +----B + // -A + // +----C------D + // + // We're node C, Message comes from A and has destination D + // + // Message destination is not gateway and is in routing table for this node. + // Send it downstream + return transportSendWrite(route, message); + } else if (sender == GATEWAY_ADDRESS && dest == BROADCAST_ADDRESS) { + // Node has not yet received any id. We need to send it + // by doing a broadcast sending, + return transportSendWrite(BROADCAST_ADDRESS, message); + } else if (MY_IS_GATEWAY) { + // Destination isn't in our routing table and isn't a broadcast address + // Nothing to do here + return false; + } else { + // A message comes from a child node and we have no + // route for it. + // + // +----B + // -A + // +----C------D <-- Message comes from D + // + // We're node C + // + // Message should be passed to node A (this nodes relay) + + // This message should be routed back towards sensor net gateway + ok = transportSendWrite(_nc.parentNodeId, message); + // Add this child to our "routing table" if it not already exist + hwWriteConfig(EEPROM_ROUTES_ADDRESS+sender, last); + } + } + + if (!ok) { + // Failure when sending to parent node. The parent node might be down and we + // need to find another route to gateway. + ledBlinkErr(1); + _failedTransmissions++; + if (_autoFindParent && _failedTransmissions > SEARCH_FAILURES) { + transportFindParentNode(); + } + } else { + _failedTransmissions = 0; + } + return ok; +} + + +void transportRequestNodeId() { + debug(PSTR("req id\n")); + transportSetAddress(_nc.nodeId); + build(_msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_ID_REQUEST, false).set(""); + transportSendWrite(_nc.parentNodeId, _msg); + wait(2000); +} + +void transportSetupNode() { + // Open reading pipe for messages directed to this node (set write pipe to same) + transportSetAddress(_nc.nodeId); + // Present node and request config + #ifndef MY_GATEWAY_FEATURE + if (_nc.nodeId != AUTO) { + #ifdef MY_SIGNING_FEATURE + // Notify gateway (and possibly controller) about the signing preferences of this node + sendRoute(build(_msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_REQUEST_SIGNING, false).set(signer.requestSignatures())); + + // If we do require signing, wait for the gateway to tell us how it prefer us to transmit our messages + if (signer.requestSignatures()) { + wait(2000); + } + #endif + + // Send presentation for this radio node + #ifdef MY_REPEATER_FEATURE + present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE); + #else + present(NODE_SENSOR_ID, S_ARDUINO_NODE); + #endif + // Send a configuration exchange request to controller + // Node sends parent node. Controller answers with latest node configuration + // which is picked up in process() + sendRoute(build(_msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CONFIG, false).set(_nc.parentNodeId)); + + // Wait configuration reply. + wait(2000); + + #ifdef MY_OTA_FIRMWARE_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 = MY_OTA_BOOTLOADER_VERSION; + _fwUpdateOngoing = false; + sendRoute(build(_msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_CONFIG_REQUEST, false)); + #endif + } + #endif +} + +void transportFindParentNode() { + static boolean findingParentNode = false; + + if (findingParentNode) + return; + findingParentNode = true; + + _failedTransmissions = 0; + + // Set distance to max + _nc.distance = 255; + + // Send ping message to BROADCAST_ADDRESS (to which all relaying nodes and gateway listens and should reply to) + debug(PSTR("find parent\n")); + + build(_msg, _nc.nodeId, BROADCAST_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT, false).set(""); + // Write msg, but suppress recursive parent search + transportSendWrite(BROADCAST_ADDRESS, _msg); + + // Wait for ping response. + wait(2000); + findingParentNode = false; +} diff --git a/libraries/MySensors/core/MyTransport.h b/libraries/MySensors/core/MyTransport.h index 503f8c1..f72ea8f 100644 --- a/libraries/MySensors/core/MyTransport.h +++ b/libraries/MySensors/core/MyTransport.h @@ -21,13 +21,87 @@ #define MyTransport_h #include +#include "MySensorCore.h" + +// 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 +// 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) + +// Search for a new parent node after this many transmission failures +#define SEARCH_FAILURES 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; + + #define AUTO 0xFF // 0-254. Id 255 is reserved for auto initialization of nodeId. #define NODE_SENSOR_ID 0xFF // Node child id is always created for when a node -// This is the nodeId for sensor net gateway receiver sketch (where all sensors should send their data). -#define GATEWAY_ADDRESS ((uint8_t)0) #define BROADCAST_ADDRESS ((uint8_t)0xFF) +// invalid distance when searching for parent +#define DISTANCE_INVALID (0xFF) + +// Common functions in all radio drivers +#ifdef MY_OTA_FIRMWARE_FEATURE + // do a crc16 on the whole received firmware + bool transportIsValidFirmware(); +#endif + + +void transportProcess(); +void transportRequestNodeId(); +void transportSetupNode(); +void transportFindParentNode(); +boolean transportSendRoute(MyMessage &message); +boolean transportSendWrite(uint8_t to, MyMessage &message); + +// "Interface" functions for radio driver +bool transportInit(); +void transportSetAddress(uint8_t address); +uint8_t transportGetAddress(); +bool transportSend(uint8_t to, const void* data, uint8_t len); +bool transportAvailable(uint8_t *to); +uint8_t transportReceive(void* data); +void transportPowerDown(); #endif diff --git a/libraries/MySensors/core/MyTransportNRF24.h b/libraries/MySensors/core/MyTransportNRF24.h index 0d0bcf8..3f895c5 100644 --- a/libraries/MySensors/core/MyTransportNRF24.h +++ b/libraries/MySensors/core/MyTransportNRF24.h @@ -32,12 +32,4 @@ #define CURRENT_NODE_PIPE ((uint8_t)1) #define BROADCAST_PIPE ((uint8_t)2) -bool transportInit(); -void transportSetAddress(uint8_t address); -uint8_t transportGetAddress(); -bool transportSend(uint8_t to, const void* data, uint8_t len); -bool transportAvailable(uint8_t *to); -uint8_t transportReceive(void* data); -void transportPowerDown(); - #endif diff --git a/libraries/MySensors/core/MyTransportRFM69.h b/libraries/MySensors/core/MyTransportRFM69.h index 36ef1a9..ec3227d 100644 --- a/libraries/MySensors/core/MyTransportRFM69.h +++ b/libraries/MySensors/core/MyTransportRFM69.h @@ -27,12 +27,4 @@ #include "drivers/SPI/SPI.h" -bool transportInit(); -void transportSetAddress(uint8_t address); -uint8_t transportGetAddress(); -bool transportSend(uint8_t to, const void* data, uint8_t len); -bool transportAvailable(uint8_t *to); -uint8_t transportReceive(void* data); -void transportPowerDown(); - #endif diff --git a/libraries/MySensors/drivers/pubsubclient b/libraries/MySensors/drivers/pubsubclient new file mode 160000 index 0000000..2f97e4a --- /dev/null +++ b/libraries/MySensors/drivers/pubsubclient @@ -0,0 +1 @@ +Subproject commit 2f97e4a558a0fb38b4ac20a93b1a1b609771bb51 diff --git a/libraries/MySensors/examples/EthernetGateway/EthernetGateway.ino b/libraries/MySensors/examples/EthernetGateway/EthernetGateway.ino index 29f6870..f8e3992 100644 --- a/libraries/MySensors/examples/EthernetGateway/EthernetGateway.ino +++ b/libraries/MySensors/examples/EthernetGateway/EthernetGateway.ino @@ -64,8 +64,8 @@ // Enable gateway type - - +#define MY_GATEWAY_W5100 +//#define MY_GATEWAY_W5100 // Set default gateway protocol (http://www.mysensors.org/download/serial_api_15) #define MY_GATEWAY_PROTOCOL_DEFAULT @@ -96,7 +96,7 @@ #define MY_MAC_ADDRESS 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED // Flash leds on rx/tx/err -#define MY_WITH_LEDS_BLINKING +#define MY_LEDS_BLINKING_FEATURE // Set blinking period #define MY_DEFAULT_LED_BLINK_PERIOD 300 diff --git a/libraries/MySensors/examples/MotionSensor/MotionSensor.ino b/libraries/MySensors/examples/MotionSensor/MotionSensor.ino index 35483f6..d07b2a7 100644 --- a/libraries/MySensors/examples/MotionSensor/MotionSensor.ino +++ b/libraries/MySensors/examples/MotionSensor/MotionSensor.ino @@ -37,8 +37,11 @@ //#define MY_SIGNING_SOFT //#define MY_REPEATER_FEATURE -// #define MY_GATEWAY_SERIAL // REMOVE-ME -// #define MY_GATEWAY_PROTOCOL_DEFAULT // REMOVE-ME +//#define MY_INCLUSION_MODE_FEATURE +//#define MY_INCLUSION_BUTTON_FEATURE +//#define MY_GATEWAY_SERIAL // REMOVE-ME +//#define MY_GATEWAY_PROTOCOL_DEFAULT // REMOVE-ME +//#define MY_LEDS_BLINKING_FEATURE #include