gatewayTransportSend

This commit is contained in:
Dmitry Borisenko
2022-12-01 02:10:06 +01:00
parent 2c61580157
commit cb50965c3b
293 changed files with 67232 additions and 3 deletions

View File

@@ -0,0 +1,244 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*/
/**
* @file MyCapabilities.h
* @ingroup MyCapabilities
*/
#ifndef MyCapabilities_h
#define MyCapabilities_h
/**
* @defgroup MyCapabilities Node capabilities indicator
* @ingroup MyConfigGrp
*
* @brief MySensors capabilities indications.
*
* At node startup, a capabilities string is shown as part of the initialization logs.
* This string indicate what configuration the node is running with.
*
* The string symbols are ordered in the following way:
* | Setting | Reset | Radio | OTA | Node | Architecture | Signing | Buffering | Encryption
* |-----------|-------------------|-------------------|--------------------|------------------|------------------|------------------|-------------------|-----------------
* | Indicator | @ref MY_CAP_RESET | @ref MY_CAP_RADIO | @ref MY_CAP_OTA_FW | @ref MY_CAP_TYPE | @ref MY_CAP_ARCH | @ref MY_CAP_SIGN | @ref MY_CAP_RXBUF | @ref MY_CAP_ENCR
*
* @see MY_CAPABILITIES
*
* @{
*/
// Remote reset
/**
* @def MY_CAP_RESET
* @brief Indicate the remote reset setting.
*
* @see MY_DISABLE_REMOTE_RESET
*
* | Setting | Indicator
* |------------|----------
* | Enabled | R
* | Disabled | N
*/
#if defined(MY_DISABLE_REMOTE_RESET)
#define MY_CAP_RESET "N"
#else
#define MY_CAP_RESET "R"
#endif
// OTA firmware update feature
/**
* @def MY_CAP_OTA_FW
* @brief Indicate the OTA update setting.
*
* @see MY_OTA_FIRMWARE_FEATURE
*
* | Setting | Indicator
* |------------|----------
* | Enabled | O
* | Disabled | N
*/
#if defined(MY_OTA_FIRMWARE_FEATURE)
#define MY_CAP_OTA_FW "O"
#else
#define MY_CAP_OTA_FW "N"
#endif
// Transport
/**
* @def MY_CAP_RADIO
* @brief Indicate the type of transport selected.
*
* @see MY_RADIO_RF24, MY_RADIO_NRF5_ESB, MY_RADIO_RFM69, MY_RFM69_NEW_DRIVER, MY_RADIO_RFM95, MY_RS485
*
* | Radio | Indicator
* |--------------|----------
* | nRF24/nRF5 | N
* | %RFM69 (old) | R
* | %RFM69 (new) | P
* | RFM95 | L
* | RS485 | S
* | None | -
*/
#if defined(MY_RADIO_RF24) || defined(MY_RADIO_NRF5_ESB)
#define MY_CAP_RADIO "N"
#elif defined(MY_RADIO_RFM69)
#if !defined(MY_RFM69_NEW_DRIVER)
// old RFM69 driver
#define MY_CAP_RADIO "R"
#else
// new RFM69 driver
#define MY_CAP_RADIO "P"
#endif
#elif defined(MY_RADIO_RFM95)
#define MY_CAP_RADIO "L"
#elif defined(MY_RS485)
#define MY_CAP_RADIO "S"
#else
#define MY_CAP_RADIO "-"
#endif
// Node type
/**
* @def MY_CAP_TYPE
* @brief Indicate the type of node.
*
* @see MY_GATEWAY_FEATURE, MY_REPEATER_FEATURE, MY_PASSIVE_NODE
*
* | Node type | Indicator
* |-----------|----------
* | Gateway | G
* | Repeater | R
* | Passive | P
* | Node | N
*/
#if defined(MY_GATEWAY_FEATURE)
#define MY_CAP_TYPE "G"
#elif defined(MY_REPEATER_FEATURE)
#define MY_CAP_TYPE "R"
#elif defined(MY_PASSIVE_NODE)
#define MY_CAP_TYPE "P"
#else
#define MY_CAP_TYPE "N"
#endif
// Architecture
/**
* @def MY_CAP_ARCH
* @brief Indicate the architecture.
*
* @see ARDUINO_ARCH_SAMD, ARDUINO_ARCH_NRF5, ARDUINO_ARCH_ESP8266, ARDUINO_ARCH_AVR, ARDUINO_ARCH_STM32F1, TEENSYDUINO
*
* | Architecture | Indicator
* |--------------|----------
* | SAMD | S
* | nRF5 | N
* | ESP8266 | E
* | AVR | A
* | STM32F1 | F
* | TEENSY | T
* | Linux | L
* | Unknown | -
*/
#if defined(ARDUINO_ARCH_SAMD)
#define MY_CAP_ARCH "S"
#elif defined(ARDUINO_ARCH_NRF5)
#define MY_CAP_ARCH "N"
#elif defined(ARDUINO_ARCH_ESP8266)
#define MY_CAP_ARCH "E"
#elif defined(ARDUINO_ARCH_ESP32)
#define MY_CAP_ARCH "F"
#elif defined(ARDUINO_ARCH_AVR)
#define MY_CAP_ARCH "A"
#elif defined(ARDUINO_ARCH_STM32F1)
#define MY_CAP_ARCH "F"
#elif defined(__arm__) && defined(TEENSYDUINO)
#define MY_CAP_ARCH "T"
#elif defined(__linux__)
#define MY_CAP_ARCH "L"
#else
#define MY_CAP_ARCH "-"
#endif
// Signing
/**
* @def MY_CAP_SIGN
* @brief Indicate the signing backend used.
*
* @see MY_SIGNING_ATSHA204, MY_SIGNING_SOFT
*
* | Signing backend | Indicator
* |-----------------|----------
* | ATSHA204 | A
* | Software | S
* | No signing | -
*/
#if defined(MY_SIGNING_ATSHA204)
#define MY_CAP_SIGN "A"
#elif defined(MY_SIGNING_SOFT)
#define MY_CAP_SIGN "S"
#else
#define MY_CAP_SIGN "-"
#endif
// RX queue
/**
* @def MY_CAP_RXBUF
* @brief Indicate the rx message buffer setting.
*
* @see MY_RX_MESSAGE_BUFFER_FEATURE
*
* | Setting | Indicator
* |------------|----------
* | Enabled | Q
* | Disabled | -
*/
#if defined(MY_RX_MESSAGE_BUFFER_FEATURE)
#define MY_CAP_RXBUF "Q"
#else
#define MY_CAP_RXBUF "-"
#endif
// Radio encryption
/**
* @def MY_CAP_ENCR
* @brief Indicate the encryption setting.
*
* @see MY_ENCRYPTION_FEATURE
*
* | Setting | Indicator
* |------------|----------
* | Enabled | X
* | Disabled | -
*/
#if defined(MY_ENCRYPTION_FEATURE)
#define MY_CAP_ENCR "X"
#else
#define MY_CAP_ENCR "-"
#endif
/**
* @def MY_CAPABILITIES
* @brief This is the resulting capabilities string.
*
* @see MY_CAP_RESET, MY_CAP_RADIO, MY_CAP_OTA_FW, MY_CAP_TYPE, MY_CAP_ARCH, MY_CAP_SIGN, MY_CAP_RXBUF, MY_CAP_ENCR
*/
#define MY_CAPABILITIES MY_CAP_RESET MY_CAP_RADIO MY_CAP_OTA_FW MY_CAP_TYPE MY_CAP_ARCH MY_CAP_SIGN MY_CAP_RXBUF MY_CAP_ENCR
/** @}*/ // End of MyCapabilities group
#endif /* MyCapabilities_h */

View File

@@ -0,0 +1,92 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*/
/**
* @file MyEepromAddresses.h
* @brief Eeprom addresses for MySensors library data
*
* @defgroup MyEepromAddressesgrp MyEepromAddresses
* @ingroup internals
* @{
*
*/
#ifndef MyEepromAddresses_h
#define MyEepromAddresses_h
// EEPROM variable sizes, in bytes
#define SIZE_NODE_ID (1u) //!< Size node ID
#define SIZE_PARENT_NODE_ID (1u) //!< Size parent node ID
#define SIZE_DISTANCE (1u) //!< Size GW distance
#define SIZE_ROUTES (256u) //!< Size routing table
#define SIZE_CONTROLLER_CONFIG (23u) //!< Size controller config
#define SIZE_PERSONALIZATION_CHECKSUM (1u) //!< Size personalization checksum
#define SIZE_FIRMWARE_TYPE (2u) //!< Size firmware type
#define SIZE_FIRMWARE_VERSION (2u) //!< Size firmware version
#define SIZE_FIRMWARE_BLOCKS (2u) //!< Size firmware blocks
#define SIZE_FIRMWARE_CRC (2u) //!< Size firmware CRC
#define SIZE_SIGNING_REQUIREMENT_TABLE (32u) //!< Size signing requirement table
#define SIZE_WHITELIST_REQUIREMENT_TABLE (32u) //!< Size whitelist requirement table
#define SIZE_SIGNING_SOFT_HMAC_KEY (32u) //!< Size soft signing HMAC key
#define SIZE_SIGNING_SOFT_SERIAL (9u) //!< Size soft signing serial
#define SIZE_RF_ENCRYPTION_AES_KEY (16u) //!< Size RF AES encryption key
#define SIZE_NODE_LOCK_COUNTER (1u) //!< Size node lock counter
/** @brief EEPROM start address */
#define EEPROM_START (0u)
/** @brief Address node ID */
#define EEPROM_NODE_ID_ADDRESS EEPROM_START
/** @brief Address parent node ID */
#define EEPROM_PARENT_NODE_ID_ADDRESS (EEPROM_NODE_ID_ADDRESS + SIZE_NODE_ID)
/** @brief Address distance to GW */
#define EEPROM_DISTANCE_ADDRESS (EEPROM_PARENT_NODE_ID_ADDRESS + SIZE_PARENT_NODE_ID)
/** @brief Address routing table */
#define EEPROM_ROUTES_ADDRESS (EEPROM_DISTANCE_ADDRESS + SIZE_DISTANCE)
/** @brief Address configuration bytes sent by controller */
#define EEPROM_CONTROLLER_CONFIG_ADDRESS (EEPROM_ROUTES_ADDRESS + SIZE_ROUTES)
/** @brief Personalization checksum (set by SecurityPersonalizer.ino) */
#define EEPROM_PERSONALIZATION_CHECKSUM_ADDRESS (EEPROM_CONTROLLER_CONFIG_ADDRESS + SIZE_CONTROLLER_CONFIG)
/** @brief Address firmware type */
#define EEPROM_FIRMWARE_TYPE_ADDRESS (EEPROM_PERSONALIZATION_CHECKSUM_ADDRESS + SIZE_PERSONALIZATION_CHECKSUM)
/** @brief Address firmware version */
#define EEPROM_FIRMWARE_VERSION_ADDRESS (EEPROM_FIRMWARE_TYPE_ADDRESS + SIZE_FIRMWARE_TYPE)
/** @brief Address firmware blocks */
#define EEPROM_FIRMWARE_BLOCKS_ADDRESS (EEPROM_FIRMWARE_VERSION_ADDRESS + SIZE_FIRMWARE_VERSION)
/** @brief Address firmware CRC */
#define EEPROM_FIRMWARE_CRC_ADDRESS (EEPROM_FIRMWARE_BLOCKS_ADDRESS + SIZE_FIRMWARE_BLOCKS)
/** @brief Address signing requirement table */
#define EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS (EEPROM_FIRMWARE_CRC_ADDRESS + SIZE_FIRMWARE_CRC)
/** @brief Address whitelist requirement table */
#define EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS (EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS + SIZE_SIGNING_REQUIREMENT_TABLE)
/** @brief Address soft signing HMAC key. This is set with @ref SecurityPersonalizer.ino */
#define EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS (EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS + SIZE_WHITELIST_REQUIREMENT_TABLE)
/** @brief Address soft signing serial key. This is set with @ref SecurityPersonalizer.ino */
#define EEPROM_SIGNING_SOFT_SERIAL_ADDRESS (EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS + SIZE_SIGNING_SOFT_HMAC_KEY)
/** @brief Address RF AES encryption key. This is set with @ref SecurityPersonalizer.ino */
#define EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS (EEPROM_SIGNING_SOFT_SERIAL_ADDRESS + SIZE_SIGNING_SOFT_SERIAL)
/** @brief Address node lock counter. This is set with @ref SecurityPersonalizer.ino */
#define EEPROM_NODE_LOCK_COUNTER_ADDRESS (EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS + SIZE_RF_ENCRYPTION_AES_KEY)
/** @brief First free address for sketch static configuration */
#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_NODE_LOCK_COUNTER_ADDRESS + SIZE_NODE_LOCK_COUNTER)
#endif // MyEepromAddresses_h
/** @}*/

View File

@@ -0,0 +1,69 @@
/*
* 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 <thozza@gmail.com>
* Copyright (C) 2015 Tomas Hozza
* Full contributor list: https://github.com/mysensors/MySensors/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"
extern bool transportSendRoute(MyMessage &message);
// global variables
extern MyMessage _msg;
extern MyMessage _msgTmp;
inline void gatewayTransportProcess(void)
{
if (gatewayTransportAvailable()) {
_msg = gatewayTransportReceive();
if (_msg.getDestination() == GATEWAY_ADDRESS) {
// Check if sender requests an echo
if (_msg.getRequestEcho()) {
// Copy message
_msgTmp = _msg;
// Reply without echo flag, otherwise we would end up in an eternal loop
_msgTmp.setRequestEcho(false);
_msgTmp.setEcho(true);
_msgTmp.setSender(getNodeId());
_msgTmp.setDestination(_msg.getSender());
gatewayTransportSend(_msgTmp);
}
if (_msg.getCommand() == C_INTERNAL) {
if (_msg.getType() == I_VERSION) {
// Request for version. Create the response
gatewayTransportSend(buildGw(_msgTmp, I_VERSION).set(MYSENSORS_LIBRARY_VERSION));
#ifdef MY_INCLUSION_MODE_FEATURE
} else if (_msg.getType() == I_INCLUSION_MODE) {
// Request to change inclusion mode
inclusionModeSet(atoi(_msg.data) == 1);
#endif
} else {
(void)_processInternalCoreMessage();
}
} else {
// Call incoming message callback if available
if (receive) {
receive(_msg);
}
}
} else {
#if defined(MY_SENSOR_NETWORK)
transportSendRoute(_msg);
#endif
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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 <thozza@gmail.com>
* Copyright (C) 2015 Tomas Hozza
* Full contributor list: https://github.com/mysensors/MySensors/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.
*/
/**
* @file MyGatewayTransport.h
*
* @defgroup MyGatewayTransportgrp MyGatewayTransport
* @ingroup internals
* @{
*
* Gateway transport-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE
* - [!] Exclamation mark is prepended in case of error
* - SYSTEM:
* - <b>GWT</b>: messages emitted by MyGatewayTransport
* - SUB SYSTEMS:
* - GWT:<b>TIN</b> from @ref gatewayTransportInit()
* - GWT:<b>TPS</b> from @ref gatewayTransportSend()
* - GWT:<b>IMQ</b> from incomingMQTT()
* - GWT:<b>RMQ</b> from reconnectMQTT()
* - GWT:<b>TPC</b> from gatewayTransportConnect()
* - GWT:<b>RFC</b> from _readFromClient()
* - GWT:<b>TSA</b> from @ref gatewayTransportAvailable()
* - GWT:<b>TRC</b> from @ref gatewayTransportReceive()
*
* Gateway transport debug log messages :
*
* |E| SYS | SUB | Message | Comment
* |-|-----|-------|---------------------------|---------------------------------------------------------------------
* | | GWT | TIN | CONNECTING... | Connecting to router
* | | GWT | TIN | IP=%%s | IP address [%%s] obtained
* |!| GWT | TIN | DHCP FAIL | DHCP request failed
* | | GWT | TIN | ETH OK | Connected to network
* |!| GWT | TIN | ETH FAIL | Connection failed
* | | GWT | TPS | TOPIC=%%s,MSG SENT | MQTT message sent on topic [%%s]
* | | GWT | TPS | ETH OK | Connected to network
* |!| GWT | TPS | ETH FAIL | Connection failed
* | | GWT | IMQ | TOPIC=%%s,MSG RECEIVE | MQTT message received on topic [%%s]
* | | GWT | RMQ | CONNECTING... | Connecting to MQTT broker
* | | GWT | RMQ | OK | Connected to MQTT broker
* |!| GWT | RMQ | FAIL | Connection to MQTT broker failed
* | | GWT | TPC | CONNECTING... | Obtaining IP address
* | | GWT | TPC | IP=%%s | IP address [%%s] obtained
* |!| GWT | TPC | DHCP FAIL | DHCP request failed
* | | GWT | RFC | C=%%d,MSG=%%s | Received message [%%s] from client [%%d]
* |!| GWT | RFC | C=%%d,MSG TOO LONG | Received message from client [%%d] too long
* | | GWT | TSA | UDP MSG=%%s | Received UDP message [%%s]
* | | GWT | TSA | ETH OK | Connected to network
* |!| GWT | TSA | ETH FAIL | Connection failed
* | | GWT | TSA | C=%d,DISCONNECTED | Client [%%d] disconnected
* | | GWT | TSA | C=%d,CONNECTED | Client [%%d] connected
* |!| GWT | TSA | NO FREE SLOT | No free slot for client
* |!| GWT | TRC | IP RENEW FAIL | IP renewal failed
*
* @brief API declaration for MyGatewayTransport
*
*/
#ifndef MyGatewayTransport_h
#define MyGatewayTransport_h
#include "MyProtocol.h"
#include "MySensorsCore.h"
#define MSG_GW_STARTUP_COMPLETE "Gateway startup complete." //!< Gateway startup message
#if defined(MY_DEBUG_VERBOSE_GATEWAY)
#define GATEWAY_DEBUG(x,...) DEBUG_OUTPUT(x, ##__VA_ARGS__) //!< debug output
#else
#define GATEWAY_DEBUG(x,...) //!< debug NULL
#endif
/**
* @brief Process gateway-related messages
*/
void gatewayTransportProcess(void);
/**
* @brief Initialize gateway transport driver
* @return true if transport initialized
*/
bool gatewayTransportInit(void);
/**
* @brief Send message to controller
* @param message to send
* @return true if message delivered
*/
bool gatewayTransportSend(MyMessage &message);
/**
* @brief Check if a new message is available from controller
* @return true if message available
*/
bool gatewayTransportAvailable(void);
/**
* @brief Pick up last message received from controller
* @return message
*/
MyMessage& gatewayTransportReceive(void);
#endif /* MyGatewayTransportEthernet_h */
/** @}*/

View File

@@ -0,0 +1,495 @@
/*
* 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 <thozza@gmail.com>
* Copyright (C) 2015 Tomas Hozza
* Full contributor list: https://github.com/mysensors/MySensors/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"
// global variables
extern MyMessage _msgTmp;
// housekeeping, remove for 3.0.0
#ifdef MY_ESP8266_SSID
#warning MY_ESP8266_SSID is deprecated, use MY_WIFI_SSID instead!
#define MY_WIFI_SSID MY_ESP8266_SSID
#undef MY_ESP8266_SSID // cleanup
#endif
#ifdef MY_ESP8266_PASSWORD
#warning MY_ESP8266_PASSWORD is deprecated, use MY_WIFI_PASSWORD instead!
#define MY_WIFI_PASSWORD MY_ESP8266_PASSWORD
#undef MY_ESP8266_PASSWORD // cleanup
#endif
#ifdef MY_ESP8266_BSSID
#warning MY_ESP8266_BSSID is deprecated, use MY_WIFI_BSSID instead!
#define MY_WIFI_BSSID MY_ESP8266_BSSID
#undef MY_ESP8266_BSSID // cleanup
#endif
#ifdef MY_ESP8266_HOSTNAME
#warning MY_ESP8266_HOSTNAME is deprecated, use MY_HOSTNAME instead!
#define MY_HOSTNAME MY_ESP8266_HOSTNAME
#undef MY_ESP8266_HOSTNAME // cleanup
#endif
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
#if !defined(MY_WIFI_SSID)
#error ESP8266/ESP32 gateway: MY_WIFI_SSID not defined!
#endif
#endif
#if defined(MY_CONTROLLER_IP_ADDRESS)
#define _ethernetControllerIP IPAddress(MY_CONTROLLER_IP_ADDRESS)
#endif
#if defined(MY_IP_ADDRESS)
#define _ethernetGatewayIP IPAddress(MY_IP_ADDRESS)
#if defined(MY_IP_GATEWAY_ADDRESS)
#define _gatewayIp IPAddress(MY_IP_GATEWAY_ADDRESS)
#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
// Assume the gateway will be the machine on the same network as the local IP
// but with last octet being '1'
#define _gatewayIp IPAddress(_ethernetGatewayIP[0], _ethernetGatewayIP[1], _ethernetGatewayIP[2], 1)
#endif /* End of MY_IP_GATEWAY_ADDRESS */
#if defined(MY_IP_SUBNET_ADDRESS)
#define _subnetIp IPAddress(MY_IP_SUBNET_ADDRESS)
#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
#define _subnetIp IPAddress(255, 255, 255, 0)
#endif /* End of MY_IP_SUBNET_ADDRESS */
#endif /* End of MY_IP_ADDRESS */
uint8_t _ethernetGatewayMAC[] = { MY_MAC_ADDRESS };
uint16_t _ethernetGatewayPort = MY_PORT;
MyMessage _ethernetMsg;
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
typedef struct {
// Suppress the warning about unused members in this struct because it is used through a complex
// set of preprocessor directives
// cppcheck-suppress unusedStructMember
char string[MY_GATEWAY_MAX_RECEIVE_LENGTH];
// cppcheck-suppress unusedStructMember
uint8_t idx;
} inputBuffer;
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
// Some re-defines to make code more readable below
#define EthernetServer WiFiServer
#define EthernetClient WiFiClient
#define EthernetUDP WiFiUDP
#endif
#if defined(MY_GATEWAY_CLIENT_MODE)
#if defined(MY_USE_UDP)
EthernetUDP _ethernetServer;
#endif /* End of MY_USE_UDP */
#elif defined(MY_GATEWAY_LINUX) /* Elif part of MY_GATEWAY_CLIENT_MODE */
EthernetServer _ethernetServer(_ethernetGatewayPort, MY_GATEWAY_MAX_CLIENTS);
#else /* Else part of MY_GATEWAY_CLIENT_MODE */
EthernetServer _ethernetServer(_ethernetGatewayPort);
#endif /* End of MY_GATEWAY_CLIENT_MODE */
#if defined(MY_GATEWAY_CLIENT_MODE)
static inputBuffer inputString;
#if defined(MY_USE_UDP)
// Nothing to do here
#else
static EthernetClient client = EthernetClient();
#endif /* End of MY_USE_UDP */
#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32) || defined(MY_GATEWAY_LINUX)
static EthernetClient clients[MY_GATEWAY_MAX_CLIENTS];
static bool clientsConnected[MY_GATEWAY_MAX_CLIENTS];
static inputBuffer inputString[MY_GATEWAY_MAX_CLIENTS];
#else /* Else part of MY_GATEWAY_CLIENT_MODE */
static EthernetClient client = EthernetClient();
static inputBuffer inputString;
#endif /* End of MY_GATEWAY_CLIENT_MODE */
// On W5100 boards with SPI_EN exposed we can use the real SPI bus together with radio
// (if we enable it during usage)
void _w5100_spi_en(const bool enable)
{
#if defined(MY_W5100_SPI_EN)
if (enable) {
// Pull up pin
hwPinMode(MY_W5100_SPI_EN, INPUT);
hwDigitalWrite(MY_W5100_SPI_EN, HIGH);
} else {
// Ground pin
hwPinMode(MY_W5100_SPI_EN, OUTPUT);
hwDigitalWrite(MY_W5100_SPI_EN, LOW);
}
#else
(void)enable;
#endif
}
#if !defined(MY_IP_ADDRESS) && defined(MY_GATEWAY_W5100)
void gatewayTransportRenewIP(void)
{
/* renew/rebind IP address
0 - nothing happened
1 - renew failed
2 - renew success
3 - rebind failed
4 - rebind success
*/
static uint32_t _nextIPRenewal = hwMillis() + MY_IP_RENEWAL_INTERVAL_MS;
const uint32_t now = hwMillis();
// http://playground.arduino.cc/Code/TimingRollover
if ((int32_t)(now - _nextIPRenewal) < 0) {
return;
}
if (Ethernet.maintain() & ~(0x06)) {
GATEWAY_DEBUG(PSTR("!GWT:TRC:IP RENEW FAIL\n"));
return;
}
_w5100_spi_en(false);
_nextIPRenewal = now + MY_IP_RENEWAL_INTERVAL_MS;
}
#endif
bool gatewayTransportInit(void)
{
_w5100_spi_en(true);
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
// Turn off access point
WiFi.mode(WIFI_STA);
#if defined(MY_GATEWAY_ESP8266)
WiFi.hostname(MY_HOSTNAME);
#elif defined(MY_GATEWAY_ESP32)
WiFi.setHostname(MY_HOSTNAME);
#endif
#ifdef MY_IP_ADDRESS
WiFi.config(_ethernetGatewayIP, _gatewayIp, _subnetIp);
#endif
(void)WiFi.begin(MY_WIFI_SSID, MY_WIFI_PASSWORD, 0, MY_WIFI_BSSID);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
GATEWAY_DEBUG(PSTR("GWT:TIN:CONNECTING...\n"));
}
GATEWAY_DEBUG(PSTR("GWT:TIN:IP: %s\n"), WiFi.localIP().toString().c_str());
#elif defined(MY_GATEWAY_LINUX)
// Nothing to do here
#else
#if defined(MY_IP_GATEWAY_ADDRESS) && defined(MY_IP_SUBNET_ADDRESS)
// DNS server set to gateway ip
Ethernet.begin(_ethernetGatewayMAC, _ethernetGatewayIP, _gatewayIp, _gatewayIp, _subnetIp);
#elif defined(MY_IP_ADDRESS)
Ethernet.begin(_ethernetGatewayMAC, _ethernetGatewayIP);
#else /* Else part of MY_IP_GATEWAY_ADDRESS && MY_IP_SUBNET_ADDRESS */
// Get IP address from DHCP
if (!Ethernet.begin(_ethernetGatewayMAC)) {
GATEWAY_DEBUG(PSTR("!GWT:TIN:DHCP FAIL\n"));
_w5100_spi_en(false);
return false;
}
#endif /* End of MY_IP_GATEWAY_ADDRESS && MY_IP_SUBNET_ADDRESS */
GATEWAY_DEBUG(PSTR("GWT:TIN:IP=%" PRIu8 ".%" PRIu8 ".%" PRIu8 ".%" PRIu8 "\n"),
Ethernet.localIP()[0],
Ethernet.localIP()[1], Ethernet.localIP()[2], Ethernet.localIP()[3]);
// give the Ethernet interface a second to initialize
delay(1000);
#endif /* MY_GATEWAY_ESP8266 / MY_GATEWAY_ESP32 */
#if defined(MY_GATEWAY_CLIENT_MODE)
#if defined(MY_USE_UDP)
_ethernetServer.begin(_ethernetGatewayPort);
#else /* Else part of MY_USE_UDP */
#if defined(MY_GATEWAY_LINUX) && defined(MY_IP_ADDRESS)
client.bind(_ethernetGatewayIP);
#endif /* End of MY_GATEWAY_LINUX && MY_IP_ADDRESS */
#if defined(MY_CONTROLLER_URL_ADDRESS)
if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
#else
if (client.connect(_ethernetControllerIP, MY_PORT)) {
#endif /* End of MY_CONTROLLER_URL_ADDRESS */
GATEWAY_DEBUG(PSTR("GWT:TIN:ETH OK\n"));
_w5100_spi_en(false);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(F(MSG_GW_STARTUP_COMPLETE)));
_w5100_spi_en(true);
presentNode();
} else {
client.stop();
GATEWAY_DEBUG(PSTR("!GWT:TIN:ETH FAIL\n"));
}
#endif /* End of MY_USE_UDP */
#else /* Else part of MY_GATEWAY_CLIENT_MODE */
#if defined(MY_GATEWAY_LINUX) && defined(MY_IP_ADDRESS)
_ethernetServer.begin(_ethernetGatewayIP);
#else
// we have to use pointers due to the constructor of EthernetServer
_ethernetServer.begin();
#endif /* End of MY_GATEWAY_LINUX && MY_IP_ADDRESS */
#endif /* End of MY_GATEWAY_CLIENT_MODE */
_w5100_spi_en(false);
return true;
}
bool gatewayTransportSend(MyMessage &message)
{
int nbytes = 0;
char *_ethernetMessage = protocolMyMessage2Serial(message);
setIndication(INDICATION_GW_TX);
_w5100_spi_en(true);
#if defined(MY_GATEWAY_CLIENT_MODE)
#if defined(MY_USE_UDP)
#if defined(MY_CONTROLLER_URL_ADDRESS)
_ethernetServer.beginPacket(MY_CONTROLLER_URL_ADDRESS, MY_PORT);
#else
_ethernetServer.beginPacket(_ethernetControllerIP, MY_PORT);
#endif /* End of MY_CONTROLLER_URL_ADDRESS */
_ethernetServer.write((uint8_t *)_ethernetMessage, strlen(_ethernetMessage));
// returns 1 if the packet was sent successfully
nbytes = _ethernetServer.endPacket();
#else /* Else part of MY_USE_UDP */
if (!client.connected()) {
client.stop();
#if defined(MY_CONTROLLER_URL_ADDRESS)
if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
#else
if (client.connect(_ethernetControllerIP, MY_PORT)) {
#endif /* End of MY_CONTROLLER_URL_ADDRESS */
GATEWAY_DEBUG(PSTR("GWT:TPS:ETH OK\n"));
_w5100_spi_en(false);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_STARTUP_COMPLETE));
_w5100_spi_en(true);
presentNode();
} else {
// connecting to the server failed!
GATEWAY_DEBUG(PSTR("!GWT:TPS:ETH FAIL\n"));
_w5100_spi_en(false);
return false;
}
}
nbytes = client.write((const uint8_t *)_ethernetMessage, strlen(_ethernetMessage));
#endif /* End of MY_USE_UDP */
#else /* Else part of MY_GATEWAY_CLIENT_MODE */
// Send message to connected clients
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) {
if (clients[i] && clients[i].connected()) {
nbytes += clients[i].write((uint8_t *)_ethernetMessage, strlen(_ethernetMessage));
}
}
#else /* Else part of MY_GATEWAY_ESPxx*/
nbytes = _ethernetServer.write(_ethernetMessage);
#endif /* End of MY_GATEWAY_ESPxx */
#endif /* End of MY_GATEWAY_CLIENT_MODE */
_w5100_spi_en(false);
return (nbytes > 0);
}
#if defined(MY_USE_UDP)
// Nothing to do here
#else
#if (defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32) || defined(MY_GATEWAY_LINUX)) && !defined(MY_GATEWAY_CLIENT_MODE)
bool _readFromClient(uint8_t i)
{
while (clients[i].connected() && clients[i].available()) {
const char inChar = clients[i].read();
if (inputString[i].idx < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) {
// if newline then command is complete
if (inChar == '\n' || inChar == '\r') {
// Add string terminator and prepare for the next message
inputString[i].string[inputString[i].idx] = 0;
GATEWAY_DEBUG(PSTR("GWT:RFC:C=%" PRIu8 ",MSG=%s\n"), i, inputString[i].string);
inputString[i].idx = 0;
if (protocolSerial2MyMessage(_ethernetMsg, inputString[i].string)) {
return true;
}
} else {
// add it to the inputString:
inputString[i].string[inputString[i].idx++] = inChar;
}
} else {
// Incoming message too long. Throw away
GATEWAY_DEBUG(PSTR("!GWT:RFC:C=%" PRIu8 ",MSG TOO LONG\n"), i);
inputString[i].idx = 0;
// Finished with this client's message. Next loop() we'll see if there's more to read.
break;
}
}
return false;
}
#else /* Else part of MY_GATEWAY_ESP8266 || MY_GATEWAY_LINUX || !MY_GATEWAY_CLIENT_MODE */
bool _readFromClient(void)
{
while (client.connected() && client.available()) {
const char inChar = client.read();
if (inputString.idx < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) {
// if newline then command is complete
if (inChar == '\n' || inChar == '\r') {
// Add string terminator and prepare for the next message
inputString.string[inputString.idx] = 0;
GATEWAY_DEBUG(PSTR("GWT:RFC:MSG=%s\n"), inputString.string);
inputString.idx = 0;
if (protocolSerial2MyMessage(_ethernetMsg, inputString.string)) {
return true;
}
} else {
// add it to the inputString:
inputString.string[inputString.idx++] = inChar;
}
} else {
// Incoming message too long. Throw away
GATEWAY_DEBUG(PSTR("!GWT:RFC:MSG TOO LONG\n"));
inputString.idx = 0;
// Finished with this client's message. Next loop() we'll see if there's more to read.
break;
}
}
return false;
}
#endif /* End of MY_GATEWAY_ESP8266 || MY_GATEWAY_LINUX || !MY_GATEWAY_CLIENT_MODE */
#endif /* End of MY_USE_UDP */
bool gatewayTransportAvailable(void)
{
_w5100_spi_en(true);
#if !defined(MY_IP_ADDRESS) && defined(MY_GATEWAY_W5100)
// renew IP address using DHCP
gatewayTransportRenewIP();
#endif
#if defined(MY_GATEWAY_CLIENT_MODE)
#if defined(MY_USE_UDP)
int packet_size = _ethernetServer.parsePacket();
if (packet_size) {
_ethernetServer.read(inputString.string, MY_GATEWAY_MAX_RECEIVE_LENGTH);
inputString.string[packet_size] = 0;
GATEWAY_DEBUG(PSTR("GWT:TSA:UDP MSG=%s\n"), inputString.string);
_w5100_spi_en(false);
const bool ok = protocolSerial2MyMessage(_ethernetMsg, inputString.string);
if (ok) {
setIndication(INDICATION_GW_RX);
}
return ok;
}
#else /* Else part of MY_USE_UDP */
if (!client.connected()) {
client.stop();
#if defined(MY_CONTROLLER_URL_ADDRESS)
if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
#else
if (client.connect(_ethernetControllerIP, MY_PORT)) {
#endif /* End of MY_CONTROLLER_URL_ADDRESS */
GATEWAY_DEBUG(PSTR("GWT:TSA:ETH OK\n"));
_w5100_spi_en(false);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(F(MSG_GW_STARTUP_COMPLETE)));
_w5100_spi_en(true);
presentNode();
} else {
GATEWAY_DEBUG(PSTR("!GWT:TSA:ETH FAIL\n"));
_w5100_spi_en(false);
return false;
}
}
if (_readFromClient()) {
setIndication(INDICATION_GW_RX);
_w5100_spi_en(false);
return true;
}
#endif /* End of MY_USE_UDP */
#else /* Else part of MY_GATEWAY_CLIENT_MODE */
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32) || defined(MY_GATEWAY_LINUX)
// ESP8266/ESP32: Go over list of clients and stop any that are no longer connected.
// If the server has a new client connection it will be assigned to a free slot.
bool allSlotsOccupied = true;
for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) {
if (!clients[i].connected()) {
if (clientsConnected[i]) {
GATEWAY_DEBUG(PSTR("GWT:TSA:C=%" PRIu8 ",DISCONNECTED\n"), i);
clients[i].stop();
}
//check if there are any new clients
if (_ethernetServer.hasClient()) {
clients[i] = _ethernetServer.available();
inputString[i].idx = 0;
GATEWAY_DEBUG(PSTR("GWT:TSA:C=%" PRIu8 ",CONNECTED\n"), i);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_STARTUP_COMPLETE));
// Send presentation of locally attached sensors (and node if applicable)
presentNode();
}
}
bool connected = clients[i].connected();
clientsConnected[i] = connected;
allSlotsOccupied &= connected;
}
if (allSlotsOccupied && _ethernetServer.hasClient()) {
//no free/disconnected spot so reject
GATEWAY_DEBUG(PSTR("!GWT:TSA:NO FREE SLOT\n"));
EthernetClient c = _ethernetServer.available();
c.stop();
}
// Loop over clients connect and read available data
for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) {
if (_readFromClient(i)) {
setIndication(INDICATION_GW_RX);
_w5100_spi_en(false);
return true;
}
}
#else /* Else part of MY_GATEWAY_ESP8266 || MY_GATEWAY_LINUX */
// W5100/ENC module does not have hasClient-method. We can only serve one client at the time.
EthernetClient newclient = _ethernetServer.available();
// if a new client connects make sure to dispose any previous existing sockets
if (newclient) {
if (client != newclient) {
client.stop();
client = newclient;
GATEWAY_DEBUG(PSTR("GWT:TSA:ETH OK\n"));
_w5100_spi_en(false);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_STARTUP_COMPLETE));
_w5100_spi_en(true);
presentNode();
}
}
if (client) {
if (!client.connected()) {
GATEWAY_DEBUG(PSTR("!GWT:TSA:ETH FAIL\n"));
client.stop();
} else {
if (_readFromClient()) {
setIndication(INDICATION_GW_RX);
_w5100_spi_en(false);
return true;
}
}
}
#endif /* End of MY_GATEWAY_ESP8266 || MY_GATEWAY_LINUX */
#endif /* End of MY_GATEWAY_CLIENT_MODE */
_w5100_spi_en(false);
return false;
}
MyMessage& gatewayTransportReceive(void)
{
// Return the last parsed message
return _ethernetMsg;
}

View File

@@ -0,0 +1,298 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*/
// Topic structure: MY_MQTT_PUBLISH_TOPIC_PREFIX/NODE-ID/SENSOR-ID/CMD-TYPE/ACK-FLAG/SUB-TYPE
#include "MyGatewayTransport.h"
// housekeeping, remove for 3.0.0
#ifdef MY_ESP8266_SSID
#warning MY_ESP8266_SSID is deprecated, use MY_WIFI_SSID instead!
#define MY_WIFI_SSID MY_ESP8266_SSID
#undef MY_ESP8266_SSID // cleanup
#endif
#ifdef MY_ESP8266_PASSWORD
#warning MY_ESP8266_PASSWORD is deprecated, use MY_WIFI_PASSWORD instead!
#define MY_WIFI_PASSWORD MY_ESP8266_PASSWORD
#undef MY_ESP8266_PASSWORD // cleanup
#endif
#ifdef MY_ESP8266_BSSID
#warning MY_ESP8266_BSSID is deprecated, use MY_WIFI_BSSID instead!
#define MY_WIFI_BSSID MY_ESP8266_BSSID
#undef MY_ESP8266_BSSID // cleanup
#endif
#ifdef MY_ESP8266_HOSTNAME
#warning MY_ESP8266_HOSTNAME is deprecated, use MY_HOSTNAME instead!
#define MY_HOSTNAME MY_ESP8266_HOSTNAME
#undef MY_ESP8266_HOSTNAME // cleanup
#endif
#ifndef MY_MQTT_USER
#define MY_MQTT_USER NULL
#endif
#ifndef MY_MQTT_PASSWORD
#define MY_MQTT_PASSWORD NULL
#endif
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
#if !defined(MY_WIFI_SSID)
#error ESP8266/ESP32 MQTT gateway: MY_WIFI_SSID not defined!
#endif
#endif
#if defined MY_CONTROLLER_IP_ADDRESS
#define _brokerIp IPAddress(MY_CONTROLLER_IP_ADDRESS)
#endif
#if defined(MY_IP_ADDRESS)
#define _MQTT_clientIp IPAddress(MY_IP_ADDRESS)
#if defined(MY_IP_GATEWAY_ADDRESS)
#define _gatewayIp IPAddress(MY_IP_GATEWAY_ADDRESS)
#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
// Assume the gateway will be the machine on the same network as the local IP
// but with last octet being '1'
#define _gatewayIp IPAddress(_MQTT_clientIp[0], _MQTT_clientIp[1], _MQTT_clientIp[2], 1)
#endif /* End of MY_IP_GATEWAY_ADDRESS */
#if defined(MY_IP_SUBNET_ADDRESS)
#define _subnetIp IPAddress(MY_IP_SUBNET_ADDRESS)
#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
#define _subnetIp IPAddress(255, 255, 255, 0)
#endif /* End of MY_IP_SUBNET_ADDRESS */
#endif /* End of MY_IP_ADDRESS */
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
#define EthernetClient WiFiClient
#elif defined(MY_GATEWAY_LINUX)
// Nothing to do here
#else
uint8_t _MQTT_clientMAC[] = { MY_MAC_ADDRESS };
#endif /* End of MY_GATEWAY_ESPxy */
#if defined(MY_GATEWAY_TINYGSM)
#if defined(MY_GSM_RX) && defined(MY_GSM_TX)
SoftwareSerial SerialAT(MY_GSM_RX, MY_GSM_TX);
#endif
static TinyGsm modem(SerialAT);
static TinyGsmClient _MQTT_ethClient(modem);
#if defined(MY_GSM_BAUDRATE)
uint32_t rate = MY_GSM_BAUDRATE;
#else /* Else part of MY_GSM_BAUDRATE */
uint32_t rate = 0;
#endif /* End of MY_GSM_BAUDRATE */
#else /* Else part of MY_GATEWAY_TINYGSM */
static EthernetClient _MQTT_ethClient;
#endif /* End of MY_GATEWAY_TINYGSM */
static PubSubClient _MQTT_client(_MQTT_ethClient);
static bool _MQTT_connecting = true;
static bool _MQTT_available = false;
static MyMessage _MQTT_msg;
bool gatewayTransportSend(MyMessage &message)
{
if (!_MQTT_client.connected()) {
return false;
}
setIndication(INDICATION_GW_TX);
char *topic = protocolMyMessage2MQTT(MY_MQTT_PUBLISH_TOPIC_PREFIX, message);
GATEWAY_DEBUG(PSTR("GWT:TPS:TOPIC=%s,MSG SENT\n"), topic);
#if defined(MY_MQTT_CLIENT_PUBLISH_RETAIN)
const bool retain = message.getCommand() == C_SET ||
(message.getCommand() == C_INTERNAL && message.getType() == I_BATTERY_LEVEL);
#else
const bool retain = false;
#endif /* End of MY_MQTT_CLIENT_PUBLISH_RETAIN */
return _MQTT_client.publish(topic, message.getString(_convBuffer), retain);
}
void incomingMQTT(char *topic, uint8_t *payload, unsigned int length)
{
GATEWAY_DEBUG(PSTR("GWT:IMQ:TOPIC=%s, MSG RECEIVED\n"), topic);
_MQTT_available = protocolMQTT2MyMessage(_MQTT_msg, topic, payload, length);
setIndication(INDICATION_GW_RX);
}
bool reconnectMQTT(void)
{
GATEWAY_DEBUG(PSTR("GWT:RMQ:CONNECTING...\n"));
// Attempt to connect
if (_MQTT_client.connect(MY_MQTT_CLIENT_ID, MY_MQTT_USER, MY_MQTT_PASSWORD)) {
GATEWAY_DEBUG(PSTR("GWT:RMQ:OK\n"));
// Send presentation of locally attached sensors (and node if applicable)
presentNode();
// Once connected, publish subscribe
if (__builtin_constant_p(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX)) {
// to save some memory
_MQTT_client.subscribe(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX "/+/+/+/+/+");
} else {
char inTopic[strlen(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) + strlen("/+/+/+/+/+")];
(void)strncpy(inTopic, MY_MQTT_SUBSCRIBE_TOPIC_PREFIX, strlen(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) + 1);
(void)strcat(inTopic, "/+/+/+/+/+");
_MQTT_client.subscribe(inTopic);
}
return true;
}
delay(1000);
GATEWAY_DEBUG(PSTR("!GWT:RMQ:FAIL\n"));
return false;
}
bool gatewayTransportConnect(void)
{
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
if (WiFi.status() != WL_CONNECTED) {
GATEWAY_DEBUG(PSTR("GWT:TPC:CONNECTING...\n"));
delay(1000);
return false;
}
GATEWAY_DEBUG(PSTR("GWT:TPC:IP=%s\n"), WiFi.localIP().toString().c_str());
#elif defined(MY_GATEWAY_LINUX)
#if defined(MY_IP_ADDRESS)
_MQTT_ethClient.bind(_MQTT_clientIp);
#endif /* End of MY_IP_ADDRESS */
#elif defined(MY_GATEWAY_TINYGSM)
GATEWAY_DEBUG(PSTR("GWT:TPC:IP=%s\n"), modem.getLocalIP().c_str());
#else
#if defined(MY_IP_ADDRESS)
Ethernet.begin(_MQTT_clientMAC, _MQTT_clientIp);
#else /* Else part of MY_IP_ADDRESS */
// Get IP address from DHCP
if (!Ethernet.begin(_MQTT_clientMAC)) {
GATEWAY_DEBUG(PSTR("!GWT:TPC:DHCP FAIL\n"));
_MQTT_connecting = false;
return false;
}
#endif /* End of MY_IP_ADDRESS */
GATEWAY_DEBUG(PSTR("GWT:TPC:IP=%" PRIu8 ".%" PRIu8 ".%" PRIu8 ".%" PRIu8 "\n"),
Ethernet.localIP()[0],
Ethernet.localIP()[1], Ethernet.localIP()[2], Ethernet.localIP()[3]);
// give the Ethernet interface a second to initialize
delay(1000);
#endif
return true;
}
bool gatewayTransportInit(void)
{
_MQTT_connecting = true;
#if defined(MY_GATEWAY_TINYGSM)
#if !defined(MY_GSM_BAUDRATE)
rate = TinyGsmAutoBaud(SerialAT);
#endif /* End of MY_GSM_BAUDRATE */
SerialAT.begin(rate);
delay(3000);
modem.restart();
#if defined(MY_GSM_PIN) && !defined(TINY_GSM_MODEM_ESP8266)
modem.simUnlock(MY_GSM_PIN);
#endif /* End of MY_GSM_PIN */
#ifndef TINY_GSM_MODEM_ESP8266
if (!modem.waitForNetwork()) {
GATEWAY_DEBUG(PSTR("!GWT:TIN:ETH FAIL\n"));
while (true);
}
GATEWAY_DEBUG(PSTR("GWT:TIN:ETH OK\n"));
if (!modem.gprsConnect(MY_GSM_APN, MY_GSM_USR, MY_GSM_PSW)) {
GATEWAY_DEBUG(PSTR("!GWT:TIN:ETH FAIL\n"));
while (true);
}
GATEWAY_DEBUG(PSTR("GWT:TIN:ETH OK\n"));
delay(1000);
#else /* Else part of TINY_GSM_MODEM_ESP8266 */
if (!modem.networkConnect(MY_GSM_SSID, MY_GSM_PSW)) {
GATEWAY_DEBUG(PSTR("!GWT:TIN:ETH FAIL\n"));
while (true);
}
GATEWAY_DEBUG(PSTR("GWT:TIN:ETH OK\n"));
delay(1000);
#endif /* End of TINY_GSM_MODEM_ESP8266 */
#endif /* End of MY_GATEWAY_TINYGSM */
#if defined(MY_CONTROLLER_IP_ADDRESS)
_MQTT_client.setServer(_brokerIp, MY_PORT);
#else
_MQTT_client.setServer(MY_CONTROLLER_URL_ADDRESS, MY_PORT);
#endif /* End of MY_CONTROLLER_IP_ADDRESS */
_MQTT_client.setCallback(incomingMQTT);
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
// Turn off access point
WiFi.mode(WIFI_STA);
#if defined(MY_GATEWAY_ESP8266)
WiFi.hostname(MY_HOSTNAME);
#elif defined(MY_GATEWAY_ESP32)
WiFi.setHostname(MY_HOSTNAME);
#endif
#if defined(MY_IP_ADDRESS)
WiFi.config(_MQTT_clientIp, _gatewayIp, _subnetIp);
#endif /* End of MY_IP_ADDRESS */
(void)WiFi.begin(MY_WIFI_SSID, MY_WIFI_PASSWORD, 0, MY_WIFI_BSSID);
#endif
gatewayTransportConnect();
_MQTT_connecting = false;
return true;
}
bool gatewayTransportAvailable(void)
{
if (_MQTT_connecting) {
return false;
}
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
if (WiFi.status() != WL_CONNECTED) {
#if defined(MY_GATEWAY_ESP32)
(void)gatewayTransportInit();
#endif
return false;
}
#endif
if (!_MQTT_client.connected()) {
//reinitialise client
if (gatewayTransportConnect()) {
reconnectMQTT();
}
return false;
}
_MQTT_client.loop();
return _MQTT_available;
}
MyMessage & gatewayTransportReceive(void)
{
// Return the last parsed message
_MQTT_available = false;
return _MQTT_msg;
}

View File

@@ -0,0 +1,82 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MyConfig.h"
#include "MyProtocol.h"
#include "MyGatewayTransport.h"
#include "MyMessage.h"
#include "MyProtocol.h"
// global variables
extern MyMessage _msgTmp;
char _serialInputString[MY_GATEWAY_MAX_RECEIVE_LENGTH]; // A buffer for incoming commands from serial interface
uint8_t _serialInputPos;
MyMessage _serialMsg;
bool gatewayTransportSend(MyMessage &message)
{
setIndication(INDICATION_GW_TX);
MY_SERIALDEVICE.print(protocolMyMessage2Serial(message));
// Serial print is always successful
return true;
}
bool gatewayTransportInit(void)
{
(void)gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_STARTUP_COMPLETE));
// Send presentation of locally attached sensors (and node if applicable)
presentNode();
return true;
}
bool gatewayTransportAvailable(void)
{
while (MY_SERIALDEVICE.available()) {
// get the new byte:
const char inChar = (char)MY_SERIALDEVICE.read();
// if the incoming character is a newline, set a flag
// so the main loop can do something about it:
if (_serialInputPos < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) {
if (inChar == '\n') {
_serialInputString[_serialInputPos] = 0;
const bool ok = protocolSerial2MyMessage(_serialMsg, _serialInputString);
if (ok) {
setIndication(INDICATION_GW_RX);
}
_serialInputPos = 0;
return ok;
} else {
// add it to the inputString:
_serialInputString[_serialInputPos] = inChar;
_serialInputPos++;
}
} else {
// Incoming message too long. Throw away
_serialInputPos = 0;
}
}
return false;
}
MyMessage & gatewayTransportReceive(void)
{
// Return the last parsed message
return _serialMsg;
}

View File

@@ -0,0 +1,41 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MyHelperFunctions.h"
static uint8_t convertH2I(const char c)
{
if (c <= '9') {
return c - '0';
} else if (c >= 'a') {
return c - 'a' + 10;
} else {
return c - 'A' + 10;
}
}
static char convertI2H(const uint8_t i)
{
const uint8_t k = i & 0x0F;
if (k <= 9) {
return '0' + k;
} else {
return 'A' + k - 10;
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 MyHelperFunctions_h
#define MyHelperFunctions_h
/**
* Single character hex conversion
* @param c hex char
* @return byte representation of the paramter
*/
static uint8_t convertH2I(const char c) __attribute__((unused));
/**
* Lower nibble byte to hex conversion
* @param i byte
* @return hex char representation of the parameter
*/
static char convertI2H(const uint8_t i) __attribute__((unused));
#endif

View File

@@ -0,0 +1,72 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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"
// global variables
extern MyMessage _msgTmp;
unsigned long _inclusionStartTime;
bool _inclusionMode;
inline void inclusionInit()
{
_inclusionMode = false;
#if defined(MY_INCLUSION_BUTTON_FEATURE)
// Setup digital in that triggers inclusion mode
hwPinMode(MY_INCLUSION_MODE_BUTTON_PIN, INPUT_PULLUP);
#endif
#if defined (MY_INCLUSION_LED_PIN)
// Setup LED pin that indicates inclusion mode
hwPinMode(MY_INCLUSION_LED_PIN, OUTPUT);
hwDigitalWrite(MY_INCLUSION_LED_PIN, LED_OFF);
#endif
}
void inclusionModeSet(bool newMode)
{
if (newMode != _inclusionMode) {
_inclusionMode = newMode;
// Send back mode change to controller
gatewayTransportSend(buildGw(_msgTmp, I_INCLUSION_MODE).set((uint8_t)(_inclusionMode?1:0)));
if (_inclusionMode) {
_inclusionStartTime = hwMillis();
}
}
#if defined (MY_INCLUSION_LED_PIN)
hwDigitalWrite(MY_INCLUSION_LED_PIN, _inclusionMode ? LED_ON : LED_OFF);
#endif
}
inline void inclusionProcess()
{
#ifdef MY_INCLUSION_BUTTON_FEATURE
if (!_inclusionMode && hwDigitalRead(MY_INCLUSION_MODE_BUTTON_PIN) == MY_INCLUSION_BUTTON_PRESSED) {
// 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);
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MySensorsCore.h"
extern bool gatewayTransportSend(MyMessage &message);
void inclusionInit();
void inclusionModeSet(bool newMode);
void inclusionProcess();
#endif

View File

@@ -0,0 +1,52 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MyIndication.h"
#if defined(MY_DEFAULT_TX_LED_PIN)|| defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
#include "MyLeds.h"
#endif
void setIndication( const indication_t ind )
{
#if defined(MY_DEFAULT_TX_LED_PIN)
if ((INDICATION_TX == ind) || (INDICATION_GW_TX == ind)) {
ledsBlinkTx(1);
} else
#endif
#if defined(MY_DEFAULT_RX_LED_PIN)
if ((INDICATION_RX == ind) || (INDICATION_GW_RX == ind)) {
ledsBlinkRx(1);
} else
#endif
#if defined(MY_DEFAULT_ERR_LED_PIN)
if (ind > INDICATION_ERR_START) {
// Number of blinks indicates which error occurred.
ledsBlinkErr(ind-INDICATION_ERR_START);
}
#endif
indication(ind);
}
#if !defined(MY_INDICATION_HANDLER)
void indication(indication_t)
{
// empty function, resolves AVR-specific GCC optimization bug (<5.5) if handler not used
// see here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77326
}
#endif

View File

@@ -0,0 +1,80 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 MyIndication_h
#define MyIndication_h
/**
* Indication type
*/
typedef enum {
INDICATION_TX = 0, //!< Sent a message.
INDICATION_RX, //!< Received a message.
INDICATION_GW_TX, //!< Gateway transmit message.
INDICATION_GW_RX, //!< Gateway receive message.
INDICATION_FIND_PARENT, //!< Start finding parent node.
INDICATION_GOT_PARENT, //!< Found parent node.
INDICATION_REQ_NODEID, //!< Request node ID.
INDICATION_GOT_NODEID, //!< Got a node ID.
INDICATION_CHECK_UPLINK, //!< Check uplink
INDICATION_REQ_REGISTRATION, //!< Request node registration.
INDICATION_GOT_REGISTRATION, //!< Got registration response.
INDICATION_REBOOT, //!< Rebooting node.
INDICATION_PRESENT, //!< Presenting node to gateway.
INDICATION_CLEAR_ROUTING, //!< Clear routing table requested.
INDICATION_SLEEP, //!< Node goes to sleep.
INDICATION_WAKEUP, //!< Node just woke from sleep.
INDICATION_FW_UPDATE_START, //!< Start of OTA firmware update process.
INDICATION_FW_UPDATE_RX, //!< Received a piece of firmware data.
INDICATION_FW_UPDATE_RX_ERR, //!< Received wrong piece of firmware data.
INDICATION_ERR_START = 100,
INDICATION_ERR_HW_INIT, //!< HW initialization error
INDICATION_ERR_TX, //!< Failed to transmit message.
INDICATION_ERR_TRANSPORT_FAILURE, //!< Transport failure.
INDICATION_ERR_INIT_TRANSPORT, //!< MySensors transport hardware (radio) init failure.
INDICATION_ERR_FIND_PARENT, //!< Failed to find parent node.
INDICATION_ERR_GET_NODEID, //!< Failed to receive node ID.
INDICATION_ERR_CHECK_UPLINK, //!< Failed to check uplink
INDICATION_ERR_SIGN, //!< Error signing.
INDICATION_ERR_LENGTH, //!< Invalid message length.
INDICATION_ERR_VERSION, //!< Protocol version mismatch.
INDICATION_ERR_NET_FULL, //!< Network full. All node ID's are taken.
INDICATION_ERR_INIT_GWTRANSPORT, //!< Gateway transport hardware init failure.
INDICATION_ERR_LOCKED, //!< Node is locked.
INDICATION_ERR_FW_FLASH_INIT, //!< Firmware update flash initialisation failure.
INDICATION_ERR_FW_TIMEOUT, //!< Firmware update timeout.
INDICATION_ERR_FW_CHECKSUM, //!< Firmware update checksum mismatch.
INDICATION_ERR_END
} indication_t;
/**
* Function which is called when something changes about the internal state of MySensors.
* @param ind Event indication of what happened.
*/
void setIndication( const indication_t ind );
/**
* Allow user to define their own indication handler.
*/
void indication( const indication_t );
#endif

View File

@@ -0,0 +1,119 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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"
#define LED_ON_OFF_RATIO (4) // Power of 2 please
#define LED_PROCESS_INTERVAL_MS (MY_DEFAULT_LED_BLINK_PERIOD/LED_ON_OFF_RATIO)
// these variables don't need to be volatile, since we are not using interrupts
static uint8_t countRx;
static uint8_t countTx;
static uint8_t countErr;
static unsigned long prevTime;
inline void ledsInit()
{
// initialize counters
countRx = 0;
countTx = 0;
countErr = 0;
// Setup led pins
#if defined(MY_DEFAULT_RX_LED_PIN)
hwPinMode(MY_DEFAULT_RX_LED_PIN, OUTPUT);
#endif
#if defined(MY_DEFAULT_TX_LED_PIN)
hwPinMode(MY_DEFAULT_TX_LED_PIN, OUTPUT);
#endif
#if defined(MY_DEFAULT_ERR_LED_PIN)
hwPinMode(MY_DEFAULT_ERR_LED_PIN, OUTPUT);
#endif
prevTime = hwMillis() -
LED_PROCESS_INTERVAL_MS; // Subtract some, to make sure leds gets updated on first run.
ledsProcess();
}
void ledsProcess()
{
// Just return if it is not the time...
if ((hwMillis() - prevTime) < LED_PROCESS_INTERVAL_MS) {
return;
}
prevTime = hwMillis();
#if defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
uint8_t state;
#endif
// For an On/Off ratio of 4, the pattern repeated will be [on, on, on, off]
// until the counter becomes 0.
#if defined(MY_DEFAULT_RX_LED_PIN)
if (countRx) {
--countRx;
}
state = (countRx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
hwDigitalWrite(MY_DEFAULT_RX_LED_PIN, state);
#endif
#if defined(MY_DEFAULT_TX_LED_PIN)
if (countTx) {
--countTx;
}
state = (countTx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
hwDigitalWrite(MY_DEFAULT_TX_LED_PIN, state);
#endif
#if defined(MY_DEFAULT_ERR_LED_PIN)
if (countErr) {
--countErr;
}
state = (countErr & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
hwDigitalWrite(MY_DEFAULT_ERR_LED_PIN, state);
#endif
}
void ledsBlinkRx(uint8_t cnt)
{
if (!countRx) {
countRx = cnt*LED_ON_OFF_RATIO;
}
ledsProcess();
}
void ledsBlinkTx(uint8_t cnt)
{
if(!countTx) {
countTx = cnt*LED_ON_OFF_RATIO;
}
ledsProcess();
}
void ledsBlinkErr(uint8_t cnt)
{
if(!countErr) {
countErr = cnt*LED_ON_OFF_RATIO;
}
ledsProcess();
}
bool ledsBlinking()
{
return countRx || countTx || countErr;
}

View File

@@ -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 <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 MY_WITH_LEDS_BLINKING_INVERSE
#define LED_ON 0x1
#define LED_OFF 0x0
#else
#define LED_ON 0x0
#define LED_OFF 0x1
#endif
#if defined(MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
#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
/**
* Test if any LED is currently blinking.
* @return true when one or more LEDs are blinking, false otherwise.
*/
bool ledsBlinking();
#else
// Remove led functions if feature is disabled
#define ledBlinkTx(x,...)
#define ledBlinkRx(x,...)
#define ledBlinkErr(x,...)
#endif
#endif

View File

@@ -0,0 +1,462 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MyMessage.h"
#include "MyHelperFunctions.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
MyMessage::MyMessage(void)
{
this->clear();
}
MyMessage::MyMessage(const uint8_t _sensorId, const mysensors_data_t _dataType)
{
this->clear();
(void)this->setSensor(_sensorId);
(void)this->setType(static_cast<uint8_t>(_dataType));
}
void MyMessage::clear(void)
{
this->last = 0u;
this->sender = 0u;
this->destination = GATEWAY_ADDRESS; // Gateway is default destination
this->version_length = 0u;
this->command_echo_payload = 0u;
this->type = 0u;
this->sensor = 0u;
// clear data buffer
(void)memset((void *)this->data, 0u, sizeof(this->data));
// set message protocol version
(void)this->setVersion();
}
uint8_t MyMessage::getHeaderSize(void) const
{
return (uint8_t)HEADER_SIZE;
}
uint8_t MyMessage::getMaxPayloadSize(void) const
{
return (uint8_t)MAX_PAYLOAD_SIZE;
}
uint8_t MyMessage::getExpectedMessageSize(void) const
{
return this->getHeaderSize() + (this->getSigned() ? this->getMaxPayloadSize() : this->getLength());
}
bool MyMessage::isProtocolVersionValid(void) const
{
return (this->getVersion() == V2_MYS_HEADER_PROTOCOL_VERSION);
}
uint8_t MyMessage::getType(void) const
{
return this->type;
}
MyMessage& MyMessage::setType(const uint8_t messageType)
{
this->type = messageType;
return *this;
}
uint8_t MyMessage::getLast(void) const
{
return this->last;
}
MyMessage& MyMessage::setLast(const uint8_t lastId)
{
this->last = lastId;
return *this;
}
uint8_t MyMessage::getSender(void) const
{
return this->sender;
}
MyMessage& MyMessage::setSender(const uint8_t senderId)
{
this->sender = senderId;
return *this;
}
uint8_t MyMessage::getSensor(void) const
{
return this->sensor;
}
MyMessage& MyMessage::setSensor(const uint8_t sensorId)
{
this->sensor = sensorId;
return *this;
}
uint8_t MyMessage::getDestination(void) const
{
return this->destination;
}
MyMessage& MyMessage::setDestination(const uint8_t destinationId)
{
this->destination = destinationId;
return *this;
}
// TODO: Remove before v3 is released, use isEcho instead
bool MyMessage::isAck(void) const
{
return this->isEcho();
}
bool MyMessage::isEcho(void) const
{
return (bool)BF_GET(this->command_echo_payload, V2_MYS_HEADER_CEP_ECHO_POS,
V2_MYS_HEADER_CEP_ECHO_SIZE);
}
MyMessage& MyMessage::setEcho(const bool echo)
{
BF_SET(this->command_echo_payload, echo, V2_MYS_HEADER_CEP_ECHO_POS,
V2_MYS_HEADER_CEP_ECHO_SIZE);
return *this;
}
bool MyMessage::getRequestEcho(void) const
{
return (bool)BF_GET(this->command_echo_payload, V2_MYS_HEADER_CEP_ECHOREQUEST_POS,
V2_MYS_HEADER_CEP_ECHOREQUEST_SIZE);
}
MyMessage& MyMessage::setRequestEcho(const bool requestEcho)
{
BF_SET(this->command_echo_payload, requestEcho, V2_MYS_HEADER_CEP_ECHOREQUEST_POS,
V2_MYS_HEADER_CEP_ECHOREQUEST_SIZE);
return *this;
}
uint8_t MyMessage::getVersion(void) const
{
return (uint8_t)BF_GET(this->version_length, V2_MYS_HEADER_VSL_VERSION_POS,
V2_MYS_HEADER_VSL_VERSION_SIZE);
}
MyMessage& MyMessage::setVersion(void)
{
BF_SET(this->version_length, V2_MYS_HEADER_PROTOCOL_VERSION, V2_MYS_HEADER_VSL_VERSION_POS,
V2_MYS_HEADER_VSL_VERSION_SIZE);
return *this;
}
mysensors_command_t MyMessage::getCommand(void) const
{
return static_cast<mysensors_command_t>(BF_GET(this->command_echo_payload,
V2_MYS_HEADER_CEP_COMMAND_POS, V2_MYS_HEADER_CEP_COMMAND_SIZE));
}
MyMessage& MyMessage::setCommand(const mysensors_command_t command)
{
BF_SET(this->command_echo_payload, static_cast<uint8_t>(command), V2_MYS_HEADER_CEP_COMMAND_POS,
V2_MYS_HEADER_CEP_COMMAND_SIZE);
return *this;
}
mysensors_payload_t MyMessage::getPayloadType(void) const
{
return static_cast<mysensors_payload_t>(BF_GET(this->command_echo_payload,
V2_MYS_HEADER_CEP_PAYLOADTYPE_POS, V2_MYS_HEADER_CEP_PAYLOADTYPE_SIZE));
}
MyMessage& MyMessage::setPayloadType(const mysensors_payload_t payloadType)
{
BF_SET(this->command_echo_payload, static_cast<uint8_t>(payloadType),
V2_MYS_HEADER_CEP_PAYLOADTYPE_POS, V2_MYS_HEADER_CEP_PAYLOADTYPE_SIZE);
return *this;
}
bool MyMessage::getSigned(void) const
{
return (bool)BF_GET(this->version_length, V2_MYS_HEADER_VSL_SIGNED_POS,
V2_MYS_HEADER_VSL_SIGNED_SIZE);
}
MyMessage& MyMessage::setSigned(const bool signedFlag)
{
BF_SET(this->version_length, signedFlag, V2_MYS_HEADER_VSL_SIGNED_POS,
V2_MYS_HEADER_VSL_SIGNED_SIZE);
return *this;
}
uint8_t MyMessage::getLength(void) const
{
uint8_t length = BF_GET(this->version_length, V2_MYS_HEADER_VSL_LENGTH_POS,
V2_MYS_HEADER_VSL_LENGTH_SIZE);
// limit length
if (length > MAX_PAYLOAD_SIZE) {
length = MAX_PAYLOAD_SIZE;
}
return length;
}
MyMessage& MyMessage::setLength(const uint8_t length)
{
uint8_t finalLength = length;
// limit length
if (finalLength > MAX_PAYLOAD_SIZE) {
finalLength = MAX_PAYLOAD_SIZE;
}
BF_SET(this->version_length, finalLength, V2_MYS_HEADER_VSL_LENGTH_POS,
V2_MYS_HEADER_VSL_LENGTH_SIZE);
return *this;
}
/* Getters for payload converted to desired form */
void* MyMessage::getCustom(void) const
{
return (void *)this->data;
}
const char* MyMessage::getString(void) const
{
if (this->getPayloadType() == P_STRING) {
return this->data;
} else {
return NULL;
}
}
char* MyMessage::getCustomString(char *buffer) const
{
if (buffer != NULL) {
for (uint8_t i = 0; i < this->getLength(); i++) {
buffer[i * 2] = convertI2H(this->data[i] >> 4);
buffer[(i * 2) + 1] = convertI2H(this->data[i]);
}
buffer[this->getLength() * 2] = '\0';
return buffer;
} else {
return NULL;
}
}
char* MyMessage::getStream(char *buffer) const
{
if (buffer != NULL) {
if (this->getCommand() == C_STREAM) {
return this->getCustomString(buffer);
}
return buffer;
} else {
return NULL;
}
}
char* MyMessage::getString(char *buffer) const
{
if (buffer != NULL) {
const uint8_t payloadType = this->getPayloadType();
if (payloadType == P_STRING) {
(void)strncpy(buffer, this->data, this->getLength());
buffer[this->getLength()] = 0;
} else if (payloadType == P_BYTE) {
(void)itoa(bValue, buffer, 10);
} else if (payloadType == P_INT16) {
(void)itoa(iValue, buffer, 10);
} else if (payloadType == P_UINT16) {
(void)utoa(uiValue, buffer, 10);
} else if (payloadType == P_LONG32) {
(void)ltoa(lValue, buffer, 10);
} else if (payloadType == P_ULONG32) {
(void)ultoa(ulValue, buffer, 10);
} else if (payloadType == P_FLOAT32) {
(void)dtostrf(fValue, 2, min(fPrecision, (uint8_t)8u), buffer);
} else if (payloadType == P_CUSTOM) {
return getCustomString(buffer);
}
return buffer;
} else {
return NULL;
}
}
bool MyMessage::getBool(void) const
{
return (bool)this->getByte();
}
uint8_t MyMessage::getByte(void) const
{
if (this->getPayloadType() == P_BYTE) {
return (uint8_t)this->data[0];
} else if (this->getPayloadType() == P_STRING) {
return (uint8_t)atoi(this->data);
} else {
return 0;
}
}
float MyMessage::getFloat(void) const
{
if (this->getPayloadType() == P_FLOAT32) {
return this->fValue;
} else if (this->getPayloadType() == P_STRING) {
return (float)atof(this->data);
} else {
return 0;
}
}
int32_t MyMessage::getLong(void) const
{
if (this->getPayloadType() == P_LONG32) {
return this->lValue;
} else if (this->getPayloadType() == P_STRING) {
return (int32_t)atol(this->data);
} else {
return 0;
}
}
uint32_t MyMessage::getULong(void) const
{
if (this->getPayloadType() == P_ULONG32) {
return this->ulValue;
} else if (this->getPayloadType() == P_STRING) {
return (uint32_t)atol(this->data);
} else {
return 0;
}
}
int16_t MyMessage::getInt(void) const
{
if (this->getPayloadType() == P_INT16) {
return this->iValue;
} else if (this->getPayloadType() == P_STRING) {
return (int16_t)atoi(this->data);
} else {
return 0;
}
}
uint16_t MyMessage::getUInt(void) const
{
if (this->getPayloadType() == P_UINT16) {
return this->uiValue;
} else if (this->getPayloadType() == P_STRING) {
return (uint16_t)atoi(this->data);
} else {
return 0;
}
}
MyMessage& MyMessage::set(const void* value, const size_t _length)
{
(void)this->setLength((value != NULL) ? _length : 0);
(void)this->setPayloadType(P_CUSTOM);
(void)memcpy((void *)this->data, value, this->getLength());
return *this;
}
MyMessage& MyMessage::set(const char* value)
{
(void)this->setLength((value != NULL) ? strlen(value) : 0);
(void)this->setPayloadType(P_STRING);
(void)strncpy(this->data, value, this->getLength());
// null terminate string
this->data[this->getLength()] = 0;
return *this;
}
#if !defined(__linux__)
MyMessage& MyMessage::set(const __FlashStringHelper* value)
{
(void)this->setLength((value != NULL) ? strlen_P(reinterpret_cast<const char *>(value)) : 0);
(void)this->setPayloadType(P_STRING);
(void)strncpy_P(this->data, reinterpret_cast<const char *>(value), this->getLength());
// null terminate string
this->data[this->getLength()] = 0;
return *this;
}
#endif
MyMessage& MyMessage::set(const bool value)
{
return this->set((uint8_t)value);
}
MyMessage& MyMessage::set(const uint8_t value)
{
(void)this->setLength(1u);
(void)this->setPayloadType(P_BYTE);
this->bValue = value;
return *this;
}
MyMessage& MyMessage::set(const float value, const uint8_t decimals)
{
(void)this->setLength(5u); // 32 bit float + persi
(void)this->setPayloadType(P_FLOAT32);
this->fValue = value;
this->fPrecision = decimals;
return *this;
}
MyMessage& MyMessage::set(const uint32_t value)
{
(void)this->setLength(4u);
(void)this->setPayloadType(P_ULONG32);
this->ulValue = value;
return *this;
}
MyMessage& MyMessage::set(const int32_t value)
{
(void)this->setLength(4u);
(void)this->setPayloadType(P_LONG32);
this->lValue = value;
return *this;
}
MyMessage& MyMessage::set(const uint16_t value)
{
(void)this->setLength(2u);
(void)this->setPayloadType(P_UINT16);
this->uiValue = value;
return *this;
}
MyMessage& MyMessage::set(const int16_t value)
{
(void)this->setLength(2u);
(void)this->setPayloadType(P_INT16);
this->iValue = value;
return *this;
}

View File

@@ -0,0 +1,671 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*/
/**
* @file MyMessage.h
*
* @brief API and type declarations for MySensors messages
* @defgroup MyMessagegrp MyMessage
* @ingroup publics
* @{
*
* @brief Here you can find all message types used by the MySensors protocol as well as macros for
* parsing and manipulating messages.
*/
#ifndef MyMessage_h
#define MyMessage_h
#ifdef __cplusplus
#include <Arduino.h>
#include <stdint.h>
#endif
#define V2_MYS_HEADER_PROTOCOL_VERSION (2u) //!< Protocol version
#define V2_MYS_HEADER_SIZE (7u) //!< Header size
#define V2_MYS_HEADER_MAX_MESSAGE_SIZE (32u) //!< Max payload size
#define V2_MYS_HEADER_VSL_VERSION_POS (0) //!< bitfield position version
#define V2_MYS_HEADER_VSL_VERSION_SIZE (2u) //!< size version field
#define V2_MYS_HEADER_VSL_SIGNED_POS (2u) //!< bitfield position signed field
#define V2_MYS_HEADER_VSL_SIGNED_SIZE (1u) //!< size signed field
#define V2_MYS_HEADER_VSL_LENGTH_POS (3u) //!< bitfield position length field
#define V2_MYS_HEADER_VSL_LENGTH_SIZE (5u) //!< size length field
#define V2_MYS_HEADER_CEP_COMMAND_POS (0) //!< bitfield position command field
#define V2_MYS_HEADER_CEP_COMMAND_SIZE (3u) //!< size command field
#define V2_MYS_HEADER_CEP_ECHOREQUEST_POS (3u) //!< bitfield position echo request field
#define V2_MYS_HEADER_CEP_ECHOREQUEST_SIZE (1u) //!< size echo request field
#define V2_MYS_HEADER_CEP_ECHO_POS (4u) //!< bitfield position echo field
#define V2_MYS_HEADER_CEP_ECHO_SIZE (1u) //!< size echo field
#define V2_MYS_HEADER_CEP_PAYLOADTYPE_POS (5u) //!< bitfield position payload type field
#define V2_MYS_HEADER_CEP_PAYLOADTYPE_SIZE (3u) //!< size payload type field
#define MAX_MESSAGE_SIZE V2_MYS_HEADER_MAX_MESSAGE_SIZE //!< The maximum size of a message (including header)
#define HEADER_SIZE V2_MYS_HEADER_SIZE //!< The size of the header
#define MAX_PAYLOAD_SIZE (MAX_MESSAGE_SIZE - HEADER_SIZE) //!< The maximum size of a payload depends on #MAX_MESSAGE_SIZE and #HEADER_SIZE
// deprecated in 3.0.0
#define MAX_PAYLOAD MAX_PAYLOAD_SIZE //!< \deprecated in 3.0.0 The maximum size of a payload depends on #MAX_MESSAGE_SIZE and #HEADER_SIZE
/// @brief The command field (message-type) defines the overall properties of a message
typedef enum {
C_PRESENTATION = 0, //!< Sent by a node when they present attached sensors. This is usually done in presentation() at startup.
C_SET = 1, //!< This message is sent from or to a sensor when a sensor value should be updated.
C_REQ = 2, //!< Requests a variable value (usually from an actuator destined for controller).
C_INTERNAL = 3, //!< Internal MySensors messages (also include common messages provided/generated by the library).
C_STREAM = 4, //!< For firmware and other larger chunks of data that need to be divided into pieces.
C_RESERVED_5 = 5, //!< C_RESERVED_5
C_RESERVED_6 = 6, //!< C_RESERVED_6
C_INVALID_7 = 7 //!< C_INVALID_7
} mysensors_command_t;
#if !DOXYGEN // Hide until we migrate
/// @brief Type of sensor (used when presenting sensors)
typedef enum {
S_DOOR = 0, //!< Door sensor, V_TRIPPED, V_ARMED
S_MOTION = 1, //!< Motion sensor, V_TRIPPED, V_ARMED
S_SMOKE = 2, //!< Smoke sensor, V_TRIPPED, V_ARMED
S_BINARY = 3, //!< Binary light or relay, V_STATUS, V_WATT
S_LIGHT = 3, //!< \deprecated Same as S_BINARY
S_DIMMER = 4, //!< Dimmable light or fan device, V_STATUS (on/off), V_PERCENTAGE (dimmer level 0-100), V_WATT
S_COVER = 5, //!< Blinds or window cover, V_UP, V_DOWN, V_STOP, V_PERCENTAGE (open/close to a percentage)
S_TEMP = 6, //!< Temperature sensor, V_TEMP
S_HUM = 7, //!< Humidity sensor, V_HUM
S_BARO = 8, //!< Barometer sensor, V_PRESSURE, V_FORECAST
S_WIND = 9, //!< Wind sensor, V_WIND, V_GUST
S_RAIN = 10, //!< Rain sensor, V_RAIN, V_RAINRATE
S_UV = 11, //!< Uv sensor, V_UV
S_WEIGHT = 12, //!< Personal scale sensor, V_WEIGHT, V_IMPEDANCE
S_POWER = 13, //!< Power meter, V_WATT, V_KWH, V_VAR, V_VA, V_POWER_FACTOR
S_HEATER = 14, //!< Header device, V_HVAC_SETPOINT_HEAT, V_HVAC_FLOW_STATE, V_TEMP
S_DISTANCE = 15, //!< Distance sensor, V_DISTANCE
S_LIGHT_LEVEL = 16, //!< Light level sensor, V_LIGHT_LEVEL (uncalibrated in percentage), V_LEVEL (light level in lux)
S_ARDUINO_NODE = 17, //!< Used (internally) for presenting a non-repeating Arduino node
S_ARDUINO_REPEATER_NODE = 18, //!< Used (internally) for presenting a repeating Arduino node
S_LOCK = 19, //!< Lock device, V_LOCK_STATUS
S_IR = 20, //!< IR device, V_IR_SEND, V_IR_RECEIVE
S_WATER = 21, //!< Water meter, V_FLOW, V_VOLUME
S_AIR_QUALITY = 22, //!< Air quality sensor, V_LEVEL
S_CUSTOM = 23, //!< Custom sensor
S_DUST = 24, //!< Dust sensor, V_LEVEL
S_SCENE_CONTROLLER = 25, //!< Scene controller device, V_SCENE_ON, V_SCENE_OFF.
S_RGB_LIGHT = 26, //!< RGB light. Send color component data using V_RGB. Also supports V_WATT
S_RGBW_LIGHT = 27, //!< RGB light with an additional White component. Send data using V_RGBW. Also supports V_WATT
S_COLOR_SENSOR = 28, //!< Color sensor, send color information using V_RGB
S_HVAC = 29, //!< Thermostat/HVAC device. V_HVAC_SETPOINT_HEAT, V_HVAC_SETPOINT_COLD, V_HVAC_FLOW_STATE, V_HVAC_FLOW_MODE, V_TEMP
S_MULTIMETER = 30, //!< Multimeter device, V_VOLTAGE, V_CURRENT, V_IMPEDANCE
S_SPRINKLER = 31, //!< Sprinkler, V_STATUS (turn on/off), V_TRIPPED (if fire detecting device)
S_WATER_LEAK = 32, //!< Water leak sensor, V_TRIPPED, V_ARMED
S_SOUND = 33, //!< Sound sensor, V_TRIPPED, V_ARMED, V_LEVEL (sound level in dB)
S_VIBRATION = 34, //!< Vibration sensor, V_TRIPPED, V_ARMED, V_LEVEL (vibration in Hz)
S_MOISTURE = 35, //!< Moisture sensor, V_TRIPPED, V_ARMED, V_LEVEL (water content or moisture in percentage?)
S_INFO = 36, //!< LCD text device / Simple information device on controller, V_TEXT
S_GAS = 37, //!< Gas meter, V_FLOW, V_VOLUME
S_GPS = 38, //!< GPS Sensor, V_POSITION
S_WATER_QUALITY = 39 //!< V_TEMP, V_PH, V_ORP, V_EC, V_STATUS
} mysensors_sensor_t;
/// @brief Type of sensor data (for set/req/echo messages)
typedef enum {
V_TEMP = 0, //!< S_TEMP. Temperature S_TEMP, S_HEATER, S_HVAC
V_HUM = 1, //!< S_HUM. Humidity
V_STATUS = 2, //!< S_BINARY, S_DIMMER, S_SPRINKLER, S_HVAC, S_HEATER. Used for setting/reporting binary (on/off) status. 1=on, 0=off
V_LIGHT = 2, //!< \deprecated Same as V_STATUS
V_PERCENTAGE = 3, //!< S_DIMMER. Used for sending a percentage value 0-100 (%).
V_DIMMER = 3, //!< \deprecated Same as V_PERCENTAGE
V_PRESSURE = 4, //!< S_BARO. Atmospheric Pressure
V_FORECAST = 5, //!< S_BARO. Whether forecast. string of "stable", "sunny", "cloudy", "unstable", "thunderstorm" or "unknown"
V_RAIN = 6, //!< S_RAIN. Amount of rain
V_RAINRATE = 7, //!< S_RAIN. Rate of rain
V_WIND = 8, //!< S_WIND. Wind speed
V_GUST = 9, //!< S_WIND. Gust
V_DIRECTION = 10, //!< S_WIND. Wind direction 0-360 (degrees)
V_UV = 11, //!< S_UV. UV light level
V_WEIGHT = 12, //!< S_WEIGHT. Weight(for scales etc)
V_DISTANCE = 13, //!< S_DISTANCE. Distance
V_IMPEDANCE = 14, //!< S_MULTIMETER, S_WEIGHT. Impedance value
V_ARMED = 15, //!< S_DOOR, S_MOTION, S_SMOKE, S_SPRINKLER. Armed status of a security sensor. 1 = Armed, 0 = Bypassed
V_TRIPPED = 16, //!< S_DOOR, S_MOTION, S_SMOKE, S_SPRINKLER, S_WATER_LEAK, S_SOUND, S_VIBRATION, S_MOISTURE. Tripped status of a security sensor. 1 = Tripped, 0
V_WATT = 17, //!< S_POWER, S_BINARY, S_DIMMER, S_RGB_LIGHT, S_RGBW_LIGHT. Watt value for power meters
V_KWH = 18, //!< S_POWER. Accumulated number of KWH for a power meter
V_SCENE_ON = 19, //!< S_SCENE_CONTROLLER. Turn on a scene
V_SCENE_OFF = 20, //!< S_SCENE_CONTROLLER. Turn of a scene
V_HVAC_FLOW_STATE = 21, //!< S_HEATER, S_HVAC. HVAC flow state ("Off", "HeatOn", "CoolOn", or "AutoChangeOver")
V_HEATER = 21, //!< \deprecated Same as V_HVAC_FLOW_STATE
V_HVAC_SPEED = 22, //!< S_HVAC, S_HEATER. HVAC/Heater fan speed ("Min", "Normal", "Max", "Auto")
V_LIGHT_LEVEL = 23, //!< S_LIGHT_LEVEL. Uncalibrated light level. 0-100%. Use V_LEVEL for light level in lux
V_VAR1 = 24, //!< VAR1
V_VAR2 = 25, //!< VAR2
V_VAR3 = 26, //!< VAR3
V_VAR4 = 27, //!< VAR4
V_VAR5 = 28, //!< VAR5
V_UP = 29, //!< S_COVER. Window covering. Up
V_DOWN = 30, //!< S_COVER. Window covering. Down
V_STOP = 31, //!< S_COVER. Window covering. Stop
V_IR_SEND = 32, //!< S_IR. Send out an IR-command
V_IR_RECEIVE = 33, //!< S_IR. This message contains a received IR-command
V_FLOW = 34, //!< S_WATER. Flow of water (in meter)
V_VOLUME = 35, //!< S_WATER. Water volume
V_LOCK_STATUS = 36, //!< S_LOCK. Set or get lock status. 1=Locked, 0=Unlocked
V_LEVEL = 37, //!< S_DUST, S_AIR_QUALITY, S_SOUND (dB), S_VIBRATION (hz), S_LIGHT_LEVEL (lux)
V_VOLTAGE = 38, //!< S_MULTIMETER
V_CURRENT = 39, //!< S_MULTIMETER
V_RGB = 40, //!< S_RGB_LIGHT, S_COLOR_SENSOR. Sent as ASCII hex: RRGGBB (RR=red, GG=green, BB=blue component)
V_RGBW = 41, //!< S_RGBW_LIGHT. Sent as ASCII hex: RRGGBBWW (WW=white component)
V_ID = 42, //!< Used for sending in sensors hardware ids (i.e. OneWire DS1820b).
V_UNIT_PREFIX = 43, //!< Allows sensors to send in a string representing the unit prefix to be displayed in GUI, not parsed by controller! E.g. cm, m, km, inch.
V_HVAC_SETPOINT_COOL = 44, //!< S_HVAC. HVAC cool setpoint (Integer between 0-100)
V_HVAC_SETPOINT_HEAT = 45, //!< S_HEATER, S_HVAC. HVAC/Heater setpoint (Integer between 0-100)
V_HVAC_FLOW_MODE = 46, //!< S_HVAC. Flow mode for HVAC ("Auto", "ContinuousOn", "PeriodicOn")
V_TEXT = 47, //!< S_INFO. Text message to display on LCD or controller device
V_CUSTOM = 48, //!< Custom messages used for controller/inter node specific commands, preferably using S_CUSTOM device type.
V_POSITION = 49, //!< GPS position and altitude. Payload: latitude;longitude;altitude(m). E.g. "55.722526;13.017972;18"
V_IR_RECORD = 50, //!< Record IR codes S_IR for playback
V_PH = 51, //!< S_WATER_QUALITY, water PH
V_ORP = 52, //!< S_WATER_QUALITY, water ORP : redox potential in mV
V_EC = 53, //!< S_WATER_QUALITY, water electric conductivity μS/cm (microSiemens/cm)
V_VAR = 54, //!< S_POWER, Reactive power: volt-ampere reactive (var)
V_VA = 55, //!< S_POWER, Apparent power: volt-ampere (VA)
V_POWER_FACTOR = 56, //!< S_POWER, Ratio of real power to apparent power: floating point value in the range [-1,..,1]
} mysensors_data_t;
#endif
/// @brief Type of internal messages (for internal messages)
typedef enum {
I_BATTERY_LEVEL = 0, //!< Battery level
I_TIME = 1, //!< Time (request/response)
I_VERSION = 2, //!< Version
I_ID_REQUEST = 3, //!< ID request
I_ID_RESPONSE = 4, //!< ID response
I_INCLUSION_MODE = 5, //!< Inclusion mode
I_CONFIG = 6, //!< Config (request/response)
I_FIND_PARENT_REQUEST = 7, //!< Find parent
I_FIND_PARENT_RESPONSE = 8, //!< Find parent response
I_LOG_MESSAGE = 9, //!< Log message
I_CHILDREN = 10, //!< Children
I_SKETCH_NAME = 11, //!< Sketch name
I_SKETCH_VERSION = 12, //!< Sketch version
I_REBOOT = 13, //!< Reboot request
I_GATEWAY_READY = 14, //!< Gateway ready
I_SIGNING_PRESENTATION = 15, //!< Provides signing related preferences (first byte is preference version)
I_NONCE_REQUEST = 16, //!< Request for a nonce
I_NONCE_RESPONSE = 17, //!< Payload is nonce data
I_HEARTBEAT_REQUEST = 18, //!< Heartbeat request
I_PRESENTATION = 19, //!< Presentation message
I_DISCOVER_REQUEST = 20, //!< Discover request
I_DISCOVER_RESPONSE = 21, //!< Discover response
I_HEARTBEAT_RESPONSE = 22, //!< Heartbeat response
I_LOCKED = 23, //!< Node is locked (reason in string-payload)
I_PING = 24, //!< Ping sent to node, payload incremental hop counter
I_PONG = 25, //!< In return to ping, sent back to sender, payload incremental hop counter
I_REGISTRATION_REQUEST = 26, //!< Register request to GW
I_REGISTRATION_RESPONSE = 27, //!< Register response from GW
I_DEBUG = 28, //!< Debug message
I_SIGNAL_REPORT_REQUEST = 29, //!< Device signal strength request
I_SIGNAL_REPORT_REVERSE = 30, //!< Internal
I_SIGNAL_REPORT_RESPONSE = 31, //!< Device signal strength response (RSSI)
I_PRE_SLEEP_NOTIFICATION = 32, //!< Message sent before node is going to sleep
I_POST_SLEEP_NOTIFICATION = 33 //!< Message sent after node woke up (if enabled)
} mysensors_internal_t;
/// @brief Type of data stream (for streamed message)
typedef enum {
ST_FIRMWARE_CONFIG_REQUEST = 0, //!< Request new FW, payload contains current FW details
ST_FIRMWARE_CONFIG_RESPONSE = 1, //!< New FW details to initiate OTA FW update
ST_FIRMWARE_REQUEST = 2, //!< Request FW block
ST_FIRMWARE_RESPONSE = 3, //!< Response FW block
ST_SOUND = 4, //!< Sound
ST_IMAGE = 5, //!< Image
ST_FIRMWARE_CONFIRM = 6, //!< Mark running firmware as valid (MyOTAFirmwareUpdateNVM + mcuboot)
ST_FIRMWARE_RESPONSE_RLE = 7, //!< Response FW block with run length encoded data
} mysensors_stream_t;
/// @brief Type of payload
typedef enum {
P_STRING = 0, //!< Payload type is string
P_BYTE = 1, //!< Payload type is byte
P_INT16 = 2, //!< Payload type is INT16
P_UINT16 = 3, //!< Payload type is UINT16
P_LONG32 = 4, //!< Payload type is INT32
P_ULONG32 = 5, //!< Payload type is UINT32
P_CUSTOM = 6, //!< Payload type is binary
P_FLOAT32 = 7 //!< Payload type is float32
} mysensors_payload_t;
#ifndef BIT
#define BIT(n) ( 1<<(n) ) //!< Bit indexing macro
#endif
#define BIT_MASK(len) ( BIT(len)-1 ) //!< Create a bitmask of length 'len'
#define BF_MASK(start, len) ( BIT_MASK(len)<<(start) ) //!< Create a bitfield mask of length starting at bit 'start'
#define BF_PREP(x, start, len) ( ((x)&BIT_MASK(len)) << (start) ) //!< Prepare a bitmask for insertion or combining
#define BF_GET(y, start, len) ( ((y)>>(start)) & BIT_MASK(len) ) //!< Extract a bitfield of length 'len' starting at bit 'start' from 'y'
#define BF_SET(y, x, start, len) ( y= ((y) &~ BF_MASK(start, len)) | BF_PREP(x, start, len) ) //!< Insert a new bitfield value 'x' into 'y'
// Getters/setters for special bit fields in header
// deprecated in 3.0.0
#define mSetVersion(_message, _version) _message.setVersion(_version) //!< \deprecated Set version field
#define mGetVersion(_message) _message.getVersion() //!< \deprecated Get version field
#define mSetSigned(_message, _signed) _message.setSigned(_signed) //!< \deprecated Set signed field
#define mGetSigned(_message) _message.getSigned() //!< \deprecated Get signed field
#define mSetLength(_message,_length) _message.setLength(_length) //!< \deprecated Set length field
#define mGetLength(_message) _message.getLength() //!< \deprecated Get length field
#define mSetCommand(_message, _command) _message.setCommand(_command) //!< \deprecated Set command field
#define mGetCommand(_message) _message.getCommand() //!< \deprecated Get command field
#define mSetRequestEcho(_message, _requestEcho) _message.setRequestEcho(_requestEcho) //!< \deprecated Set echo request field
#define mGetRequestEcho(_message) _message.getRequestEcho() //!< \deprecated Get echo request field
#define mSetEcho(_message, _echo) _message.setEcho(_echo) //!< \deprecated Set echo field
#define mGetEcho(_message) _message.getEcho() //!< \deprecated Get echo field
#define mSetPayloadType(_message, _payloadType) _message.setPayloadType(_payloadType) //!< \deprecated Set payload type field
#define mGetPayloadType(_message) _message.getPayloadType() //!< \deprecated Get payload type field
#if defined(__cplusplus) || defined(DOXYGEN)
/**
* @brief MyMessage is used to create, manipulate, send and read MySensors messages
*/
class MyMessage
{
private:
char* getCustomString(char *buffer) const;
public:
/**
* Default constructor
*/
MyMessage(void);
/**
* Constructor
* @param sensorId id of the child sensor for this message
* @param dataType
*/
MyMessage(const uint8_t sensorId, const mysensors_data_t dataType);
/**
* @brief Clear message contents.
*/
void clear(void);
/**
* If payload is something else than P_STRING you can have the payload value converted
* into string representation by supplying a buffer with the minimum size of
* 2 * MAX_PAYLOAD_SIZE + 1. This is to be able to fit hex-conversion of a full binary payload.
* @param buffer pointer to a buffer that's at least 2 * MAX_PAYLOAD_SIZE + 1 bytes large
*/
char* getStream(char *buffer) const;
/**
* @brief Copy the payload into the supplied buffer
*/
char* getString(char *buffer) const;
/**
* @brief Get payload as string
* @return pointer to a char array storing the string
*/
const char* getString(void) const;
/**
* @brief Get custom payload
* @return pointer to the raw payload
*/
void* getCustom(void) const;
/**
* @brief Get bool payload
* @return a bool with the value of the payload (true/false)
*/
bool getBool(void) const;
/**
* @brief Get unsigned 8-bit integer payload
* @return the value of the payload, 0 to 255
*/
uint8_t getByte(void) const;
/**
* @brief Get float payload
* @return the floating-point value of the payload
*/
float getFloat(void) const;
/**
* @brief Get signed 16-bit integer payload
* @return the value of the payload, 32768 to 32767
*/
int16_t getInt(void) const;
/**
* @brief Get unsigned 16-bit integer payload
* @return the value of the payload, 0 to 65535
*/
uint16_t getUInt(void) const;
/**
* @brief Get signed 32-bit integer payload
* @return the value of the payload, 2147483648 to 2147483647
*/
int32_t getLong(void) const;
/**
* @brief Get unsigned 32-bit integer payload
* @return the value of the payload, 0 to 4294967295
*/
uint32_t getULong(void) const;
/**
* @brief getHeaderSize
* @return the size of the header
*/
uint8_t getHeaderSize(void) const;
/**
* @brief getMaxPayloadSize
* @return the max. size of the payload
*/
uint8_t getMaxPayloadSize(void) const;
/**
* @brief getExpectedMessageSize
* @return the expected message size based on header information
*/
uint8_t getExpectedMessageSize(void) const;
/**
* @brief isProtocolVersionValid
* @return true if the protocol version is valid
*/
bool isProtocolVersionValid(void) const;
/**
* @brief Getter for echo request
* @return echo request
*/
bool getRequestEcho(void) const;
/**
* @brief Setter for echo request
* @param requestEcho
*/
MyMessage& setRequestEcho(const bool requestEcho);
/**
* @brief Getter for version
* @return version
*/
uint8_t getVersion(void) const;
/**
* @brief Setter for version
*/
MyMessage& setVersion(void);
/**
* @brief Getter for length
* @return length
*/
uint8_t getLength(void) const;
/**
* @brief Setter for length
* @param length
*/
MyMessage& setLength(const uint8_t length);
/**
* @brief Getter for command type
* @return #mysensors_command_t
*/
mysensors_command_t getCommand(void) const;
/**
* @brief Setter for command type
* @param command
*/
MyMessage& setCommand(const mysensors_command_t command);
/**
* @brief Getter for payload type
* @return payload type
*/
mysensors_payload_t getPayloadType(void) const;
/**
* @brief Setter for payload type
* @param payloadType
*/
MyMessage& setPayloadType(const mysensors_payload_t payloadType);
/**
* @brief Getter for sign field
* @return sign field
*/
bool getSigned(void) const;
/**
* @brief Setter for sign field
* @param signedFlag
*/
MyMessage& setSigned(const bool signedFlag);
/**
* \deprecated use isEcho()
* @brief Getter for echo-flag.
* @return true if this is an echoed message
*/
bool isAck(void) const;
/**
* @brief Getter for echo-flag.
* @return true if this is an echoed message
*/
bool isEcho(void) const;
/**
* @brief Setter for echo-flag.
* @param echo true if this an echo message
*/
MyMessage& setEcho(const bool echo);
/**
* @brief Get message type
* @return messageType
*/
uint8_t getType(void) const;
/**
* @brief Set message type
* @param messageType
*/
MyMessage& setType(const uint8_t messageType);
/**
* @brief Get last ID
* @return lastId
*/
uint8_t getLast(void) const;
/**
* @brief Set last ID
* @param lastId
*/
MyMessage& setLast(const uint8_t lastId);
/**
* @brief Get sender ID
* @return sender
*/
uint8_t getSender(void) const;
/**
* @brief Set sender ID
* @param senderId
*/
MyMessage& setSender(const uint8_t senderId);
/**
* @brief Get sensor ID of message
* @return sensorId
*/
uint8_t getSensor(void) const;
/**
* @brief Set which child sensor this message belongs to
* @param sensorId
*/
MyMessage& setSensor(const uint8_t sensorId);
/**
* @brief Get destination
* @return destinationId
*/
uint8_t getDestination(void) const;
/**
* @brief Set final destination node id for this message
* @param destinationId
*/
MyMessage& setDestination(const uint8_t destinationId);
/**
* @brief Set entire payload
* @param payload pointer to the buffer where the payload is stored
* @param length of the payload
*/
MyMessage& set(const void* payload, const size_t length);
/**
* @brief Set payload to character array
* @param value pointer to the character array. The array must be null-terminated.
*/
MyMessage& set(const char* value);
#if !defined(__linux__)
/**
* @brief Set payload to character array from flash
* @param value pointer to the character array. The array must be null-terminated.
*/
MyMessage& set(const __FlashStringHelper* value);
#endif
/**
* @brief Set payload to decimal number
* @param value float
* @param decimals number of decimals to include
*/
MyMessage& set(const float value, const uint8_t decimals);
/**
* @brief Set payload to bool value
* @param value true or false
*/
MyMessage& set(const bool value);
/**
* @brief Set payload to unsigned 8-bit integer value
* @param value (0 to 255)
*/
MyMessage& set(const uint8_t value);
/**
* @brief Set payload to unsigned 32-bit integer value
* @param value (0 to 4294967295)
*/
MyMessage& set(const uint32_t value);
/**
* @brief Set payload to signed 32-bit integer value
* @param value (2147483648 to 2147483647)
*/
MyMessage& set(const int32_t value);
/**
* @brief Set payload to unsigned 16-bit integer value
* @param value (0 to 65535)
*/
MyMessage& set(const uint16_t value);
/**
* @brief Set payload to signed 16-bit integer value
* @param value (32768 to 32767)
*/
MyMessage& set(const int16_t value);
#else
typedef union {
struct {
#endif
uint8_t last; //!< 8 bit - Id of last node this message passed
uint8_t sender; //!< 8 bit - Id of sender node (origin)
uint8_t destination; //!< 8 bit - Id of destination node
/**
* 2 bit - Protocol version<br>
* 1 bit - Signed flag<br>
* 5 bit - Length of payload
*/
uint8_t version_length;
/**
* 3 bit - Command type<br>
* 1 bit - Request an echo - Indicator that receiver should echo the message back to the sender<br>
* 1 bit - Is echo message - Indicator that this is the echoed message<br>
* 3 bit - Payload data type
*/
uint8_t command_echo_payload;
uint8_t type; //!< 8 bit - Type varies depending on command
uint8_t sensor; //!< 8 bit - Id of sensor that this message concerns.
/*
* Each message can transfer a payload. We add one extra byte for string
* terminator \0 to be "printable" this is not transferred OTA
* This union is used to simplify the construction of the binary data types transferred.
*/
union {
uint8_t bValue; //!< unsigned byte value (8-bit)
uint16_t uiValue; //!< unsigned integer value (16-bit)
int16_t iValue; //!< signed integer value (16-bit)
uint32_t ulValue; //!< unsigned long value (32-bit)
int32_t lValue; //!< signed long value (32-bit)
struct { //!< Float messages
float fValue;
uint8_t fPrecision; //!< Number of decimals when serializing
};
char data[MAX_PAYLOAD_SIZE + 1]; //!< Buffer for raw payload data
} __attribute__((packed)); //!< Doxygen will complain without this comment
#if defined(__cplusplus) || defined(DOXYGEN)
} __attribute__((packed));
#else
};
uint8_t array[HEADER_SIZE + MAX_PAYLOAD_SIZE + 1]; //!< buffer for entire message
} __attribute__((packed)) MyMessage;
#endif
#endif
/** @}*/

View File

@@ -0,0 +1,287 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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);
#elif !defined(MCUBOOT_PRESENT)
SPIFlash _flash(MY_OTA_FLASH_SS, MY_OTA_FLASH_JDECID);
#endif
// Map flash functions
#ifndef MCUBOOT_PRESENT
#define _flash_initialize() _flash.initialize()
#define _flash_readByte(addr) _flash.readByte(addr)
#define _flash_writeBytes( dstaddr, data, size) _flash.writeBytes( dstaddr, data, size)
#define _flash_blockErase32K(num) _flash.blockErase32K(num)
#define _flash_busy() _flash.busy()
#else
#define _flash_initialize() true
#define _flash_readByte(addr) (*((uint8_t *)(addr)))
#define _flash_blockErase32K(num) Flash.erase((uint32_t *)FLASH_AREA_IMAGE_1_OFFSET_0, FLASH_AREA_IMAGE_1_SIZE_0)
#define _flash_busy() false
#endif
LOCAL nodeFirmwareConfig_t _nodeFirmwareConfig;
LOCAL bool _firmwareUpdateOngoing = false;
LOCAL uint32_t _firmwareLastRequest;
LOCAL uint16_t _firmwareBlock;
LOCAL uint8_t _firmwareRetry;
LOCAL bool _firmwareResponse(uint16_t block, uint8_t *data);
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.getType() == 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.getType() == ST_FIRMWARE_RESPONSE) {
// extract FW block
replyFirmwareBlock_t *firmwareResponse = (replyFirmwareBlock_t *)_msg.data;
// Proceed firmware data
return _firmwareResponse(firmwareResponse->block, firmwareResponse->data);
#ifdef FIRMWARE_PROTOCOL_31
} else if (_msg.getType() == ST_FIRMWARE_RESPONSE_RLE) {
// RLE encoded block
// extract FW block
replyFirmwareBlockRLE_t *firmwareResponse = (replyFirmwareBlockRLE_t *)_msg.data;
uint8_t data[FIRMWARE_BLOCK_SIZE];
for (uint8_t i=0; i<FIRMWARE_BLOCK_SIZE; i++) {
data[i]=firmwareResponse->data;
}
while ((_firmwareBlock) && (firmwareResponse->number_of_blocks)) {
_firmwareResponse(firmwareResponse->block, data);
firmwareResponse->number_of_blocks--;
firmwareResponse->block--;
}
return true;
#endif
} else {
#ifdef MCUBOOT_PRESENT
if (_msg.getType() == ST_FIRMWARE_CONFIRM) {
if (*(uint16_t *)MCUBOOT_IMAGE_0_MAGIC_ADDR == ((uint16_t)MCUBOOT_IMAGE_MAGIC)) {
if (*(uint8_t *)(MCUBOOT_IMAGE_0_IMG_OK_ADDR) != MCUBOOT_IMAGE_0_IMG_OK_BYTE) {
// Calculate data word to write
uint32_t *img_ok_base_addr = (uint32_t *)(MCUBOOT_IMAGE_0_IMG_OK_ADDR & ~3); // align word wise
uint32_t img_ok_data = *img_ok_base_addr;
// Set copy of MCUBOOT_IMAGE_0_IMG_OK_ADDR to MCUBOOT_IMAGE_0_IMG_OK_BYTE (0x01)
uint8_t *img_ok_array = (uint8_t *)&img_ok_data;
*(img_ok_array + (MCUBOOT_IMAGE_0_IMG_OK_ADDR % 4)) = MCUBOOT_IMAGE_0_IMG_OK_BYTE;
// Write word back
Flash.write(img_ok_base_addr, img_ok_data);
}
OTA_DEBUG(PSTR("!OTA:FWP:IMAGE CONFIRMED\n"));
} else {
OTA_DEBUG(PSTR("!OTA:FWP:INVALID MCUBOOT MAGIC\n"));
}
}
#endif
}
return false;
}
LOCAL void presentBootloaderInformation(void)
{
requestFirmwareConfig_t *requestFirmwareConfig = (requestFirmwareConfig_t *)_msgTmp.data;
_msgTmp.setLength(sizeof(requestFirmwareConfig_t));
_msgTmp.setCommand(C_STREAM);
_msgTmp.setPayloadType(P_CUSTOM);
// copy node settings to reqFWConfig
(void)memcpy(requestFirmwareConfig, &_nodeFirmwareConfig, sizeof(nodeFirmwareConfig_t));
// add bootloader information
requestFirmwareConfig->BLVersion = MY_OTA_BOOTLOADER_VERSION;
#ifdef FIRMWARE_PROTOCOL_31
requestFirmwareConfig->blockSize = FIRMWARE_BLOCK_SIZE;
#ifndef MCUBOOT_PRESENT
requestFirmwareConfig->img_commited = 0x2;
requestFirmwareConfig->img_revision = 0x00;
requestFirmwareConfig->img_build_num = 0x00;
#else
requestFirmwareConfig->img_commited = *((uint8_t*)(MCUBOOT_IMAGE_0_IMG_OK_ADDR));
requestFirmwareConfig->img_revision = *((uint16_t*)(MCUBOOT_IMAGE_0_IMG_REVISION_ADDR));
requestFirmwareConfig->img_build_num = *((uint16_t*)(MCUBOOT_IMAGE_0_IMG_BUILD_NUM_ADDR));
#endif
#endif
_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 (uint32_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;
}
LOCAL bool _firmwareResponse(uint16_t block, uint8_t *data)
{
if (_firmwareUpdateOngoing) {
OTA_DEBUG(PSTR("OTA:FWP:RECV B=%04" PRIX16 "\n"), block); // received FW block
if (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
#ifdef MCUBOOT_PRESENT
uint32_t addr = ((size_t)(((_firmwareBlock - 1) * FIRMWARE_BLOCK_SIZE)) + (size_t)(
FIRMWARE_START_OFFSET));
if (addr<FLASH_AREA_IMAGE_SCRATCH_OFFSET_0) {
Flash.write_block( (uint32_t *)addr, (uint32_t *)data, FIRMWARE_BLOCK_SIZE>>2);
}
#else
_flash_writeBytes( ((_firmwareBlock - 1) * FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET,
data, FIRMWARE_BLOCK_SIZE);
#endif
// 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));
#ifndef MCUBOOT_PRESENT
// 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()) {}
#endif
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;
}

View File

@@ -0,0 +1,200 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*/
/**
* @file MyOTAFirmwareUpdate.h
*
* @defgroup MyOTAFirmwaregrp MyOTAFirmwareUpdate
* @ingroup internals
* @{
*
* MyOTAFirmwareUpdate-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE
* - [!] Exclamation mark is prepended in case of error or warning
* - SYSTEM:
* - <b>OTA</b> messages emitted by MyOTAFirmwareUpdate
* - SUB SYSTEMS:
* - OTA:<b>FRQ</b> from @ref firmwareOTAUpdateRequest()
* - OTA:<b>FWP</b> from @ref firmwareOTAUpdateProcess()
*
* MyOTAFirmwareUpdate debug log messages:
*
* |E| SYS | SUB | Message | Comment
* |-|-----|-----|-----------------------------|----------------------------------------------------------------------------
* | | OTA | FWP | UPDATE | FW update initiated
* |!| OTA | FWP | UPDO | FW config response received, FW update already ongoing
* |!| OTA | FWP | FLASH INIT FAIL | Failed to initialise flash
* | | OTA | FWP | UPDATE SKIPPED | FW update skipped, no newer version available
* | | OTA | FWP | RECV B=%04X | Received FW block (B)
* |!| OTA | FWP | WRONG FWB | Wrong FW block received
* | | OTA | FWP | FW END | FW received, proceed to CRC verification
* | | OTA | FWP | CRC OK | FW CRC verification OK
* |!| OTA | FWP | CRC FAIL | FW CRC verification failed
* | | OTA | FRQ | FW REQ,T=%04X,V=%04X,B=%04X | Request FW update, FW type (T), version (V), block (B)
* |!| OTA | FRQ | FW UPD FAIL | FW update failed
* | | OTA | CRC | B=%04X,C=%04X,F=%04X | FW CRC verification. FW blocks (B), calculated CRC (C), FW CRC (F)
*
*
* @brief API declaration for MyOTAFirmwareUpdate
*/
#ifndef MyOTAFirmwareUpdate_h
#define MyOTAFirmwareUpdate_h
#include "MySensorsCore.h"
#ifdef MCUBOOT_PRESENT
#include "generated_dts_board.h"
#define FIRMWARE_PROTOCOL_31
#endif
#define LOCAL static //!< static
#if MAX_PAYLOAD_SIZE >= 22
#define FIRMWARE_BLOCK_SIZE (16u) //!< Size of each firmware block
#else
#define FIRMWARE_BLOCK_SIZE (8u) //!< Size of each firmware block
#ifndef FIRMWARE_PROTOCOL_31
#define FIRMWARE_PROTOCOL_31
#endif
#endif
#ifndef MY_OTA_RETRY
#define MY_OTA_RETRY (5u) //!< Number of times to request a fw block before giving up
#endif
#ifndef MY_OTA_RETRY_DELAY
#define MY_OTA_RETRY_DELAY (500u) //!< Number of milliseconds before re-requesting a FW block
#endif
#ifndef MCUBOOT_PRESENT
#define FIRMWARE_START_OFFSET (10u) //!< Start offset for firmware in flash (DualOptiboot wants to keeps a signature first)
#else
#define FIRMWARE_START_OFFSET (FLASH_AREA_IMAGE_1_OFFSET_0) //!< Use offset from generated_dts_board.h (mcuboot)
#endif
#define MY_OTA_BOOTLOADER_MAJOR_VERSION (3u) //!< Bootloader version major
#ifdef FIRMWARE_PROTOCOL_31
#define MY_OTA_BOOTLOADER_MINOR_VERSION (1u) //!< Bootloader version minor
#else
#define MY_OTA_BOOTLOADER_MINOR_VERSION (0u) //!< Bootloader version minor
#endif
#define MY_OTA_BOOTLOADER_VERSION (MY_OTA_BOOTLOADER_MINOR_VERSION * 256 + MY_OTA_BOOTLOADER_MAJOR_VERSION) //!< Bootloader version
#if defined(MY_DEBUG_VERBOSE_OTA_UPDATE)
#define OTA_DEBUG(x,...) DEBUG_OUTPUT(x, ##__VA_ARGS__) //!< debug
//#define OTA_EXTRA_FLASH_DEBUG //!< Dumps flash after each FW block
#else
#define OTA_DEBUG(x,...) //!< debug NULL
#endif
#if defined(DOXYGEN) && !defined(FIRMWARE_PROTOCOL_31)
/**
* @brief Enabled FOTA 3.1 protocol extensions
*
* Supports smaller FIRMWARE_BLOCK_SIZE, RLE and NVM for nRF5 with mcuboot. The
* extension is enabled per default when mcuboot is present or full FIRMWARE_BLOCK_SIZE
* exeeds MAX_PAYLOAD_SIZE.
*/
#define FIRMWARE_PROTOCOL_31
#endif
/**
* @brief FW config structure, stored in eeprom
*/
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t blocks; //!< Number of blocks
uint16_t crc; //!< CRC of block data
} __attribute__((packed)) nodeFirmwareConfig_t;
/**
* @brief FW config request structure
*/
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t blocks; //!< Number of blocks
uint16_t crc; //!< CRC of block data
uint16_t BLVersion; //!< Bootloader version
#ifdef FIRMWARE_PROTOCOL_31
uint8_t blockSize; //!< Blocksize, when protocol version >= 3.1 is reported. Otherwhise the blocksize is 16
uint8_t img_commited; //!< mcuboot image_ok attribute commited firmware=0x01(mcuboot)|0x02(DualOptiboot), when protocol version >= 3.1 is reported
uint16_t img_revision; //!< mcuboot revision attribute, when protocol version >= 3.1 is reported
uint32_t img_build_num; //!< mcuboot build_num attribute, when protocol version >= 3.1 is reported
#endif
} __attribute__((packed)) requestFirmwareConfig_t;
/**
* @brief FW block request structure
*/
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t block; //!< Block index
} __attribute__((packed)) requestFirmwareBlock_t;
/**
* @brief FW block reply structure
*/
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t block; //!< Block index
uint8_t data[FIRMWARE_BLOCK_SIZE]; //!< Block data
} __attribute__((packed)) replyFirmwareBlock_t;
/**
* @brief FW block reply structure (RLE)
*/
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t block; //!< Block index
uint16_t number_of_blocks; //!< Number of blocks to fill with data
uint8_t data; //!< Block data
} __attribute__((packed)) replyFirmwareBlockRLE_t;
/**
* @brief Read firmware settings from EEPROM
*
* Current firmware settings (type, version, crc, blocks) are read into _fc
*/
LOCAL void readFirmwareSettings(void);
/**
* @brief Handle OTA FW update requests
*/
LOCAL void firmwareOTAUpdateRequest(void);
/**
* @brief Handle OTA FW update responses
*
* This function handles incoming OTA FW packets and stores them to external flash (Sensebender)
*/
LOCAL bool firmwareOTAUpdateProcess(void);
/**
* @brief Validate uploaded FW CRC
*
* This function verifies if uploaded FW CRC is valid
*/
LOCAL bool transportIsValidFirmware(void);
/**
* @brief Present bootloader/FW information upon startup
*/
LOCAL void presentBootloaderInformation(void);
#endif
/** @}*/

View File

@@ -0,0 +1,158 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MyOTALogging.h"
#ifdef MY_OTA_LOG_SENDER_FEATURE
// global variables
static bool inOTALog = false;
void OTALog(uint8_t logNode, const bool requestEcho, const char *fmt, ... )
{
// Avoid recursion
if (inOTALog==true) {
return;
}
inOTALog = true;
MyMessage msg(NODE_SENSOR_ID, V_CUSTOM);
char fmtBuffer[MY_SERIAL_OUTPUT_SIZE];
va_list args;
// create message
va_start (args, fmt );
int n = vsnprintf_P(fmtBuffer, sizeof(fmtBuffer), fmt, args);
va_end (args);
// Check number of chars
if (n<1) {
// Nothing to send
inOTALog = false;
return;
}
// Add \n to the end of string
if (n>(int)(sizeof(fmtBuffer)-2)) {
// String is truncated
n = sizeof(fmtBuffer)-2;
}
// add LF if not set
if (fmtBuffer[n-1]!='\n') {
fmtBuffer[n++]='\n';
fmtBuffer[n++]=0;
}
// Configure message
msg.setSender(getNodeId());
msg.setDestination(logNode);
msg.setCommand(C_INTERNAL);
msg.setType(I_LOG_MESSAGE);
msg.setRequestEcho(requestEcho);
// Send package
for (int pos = 0; pos < n; pos += MAX_PAYLOAD_SIZE) {
uint8_t length = strlen(&fmtBuffer[pos]);
if (length > MAX_PAYLOAD_SIZE) {
length = MAX_PAYLOAD_SIZE;
}
(void)_sendRoute(msg.set((char*)&fmtBuffer[pos]));
}
inOTALog = false;
}
#endif
#ifdef MY_OTA_LOG_RECEIVER_FEATURE
// Global variables
char OTALogfmtBuffer[MY_SERIAL_OUTPUT_SIZE];
int OTALogfmtBufferPos = 0;
uint8_t OTALogBufferNode = BROADCAST_ADDRESS;
uint8_t OTALogBufferSensor = 0;
void OTALogPrintPrefix()
{
char prefix[37];
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
snprintf_P(prefix, sizeof(prefix),
PSTR("%" PRId8 ";%" PRId8 ";%" PRId8 ";0;%" PRId8 ";%" PRIu32 " "),
OTALogBufferNode, OTALogBufferSensor, C_INTERNAL, I_LOG_MESSAGE, hwMillis());
MY_SERIALDEVICE.print(prefix);
}
void OTALogFlushBuffer()
{
OTALogfmtBuffer[0] = 0;
OTALogfmtBufferPos = 0;
OTALogBufferNode = BROADCAST_ADDRESS;
}
inline void OTALogPrint(const MyMessage &message)
{
// Ignore log messages via broadcast
if (message.destination == BROADCAST_ADDRESS) {
return;
}
// FLush buffer, when node id changes
if ((OTALogBufferNode!=BROADCAST_ADDRESS) && ((OTALogBufferNode != message.getSender()) ||
(OTALogBufferSensor != message.getSensor()))) {
OTALogPrintPrefix();
MY_SERIALDEVICE.print(OTALogfmtBuffer);
MY_SERIALDEVICE.println("...");
OTALogFlushBuffer();
}
// Add data to buffer
const char *str = message.getString();
strncpy(&OTALogfmtBuffer[OTALogfmtBufferPos], str,
sizeof(OTALogfmtBuffer)-OTALogfmtBufferPos);
OTALogfmtBufferPos += strlen(str);
// Store node ID and sensor ID
OTALogBufferNode = message.getSender();
OTALogBufferSensor = message.getSensor();
// Print out buffered lines ending with \n
char *EOLpos;
while (EOLpos = strchr(OTALogfmtBuffer,'\n'), EOLpos != NULL) {
// Add end of string
EOLpos[0]=0;
// Print out line
OTALogPrintPrefix();
MY_SERIALDEVICE.println(OTALogfmtBuffer);
// Check if more content in buffer
int lenAfterEOL = (size_t)&OTALogfmtBuffer[OTALogfmtBufferPos]-(size_t)EOLpos-2;
if (lenAfterEOL>0) {
// More lines, move string to the beginning of the buffer
strcpy((char*)&OTALogfmtBuffer[0], (char*)&EOLpos[1]);
// calculate OTALogfmtBufferPos
OTALogfmtBufferPos -= (size_t)EOLpos-(size_t)&OTALogfmtBuffer[0]+2;
// Security check
if ((OTALogfmtBufferPos<=0) || (OTALogfmtBufferPos>=(int)sizeof(OTALogfmtBuffer))) {
MY_SERIALDEVICE.print("Sec:");
MY_SERIALDEVICE.println(OTALogfmtBufferPos);
OTALogFlushBuffer();
}
} else {
// End of message, prepare new one
OTALogFlushBuffer();
}
}
}
#endif

View File

@@ -0,0 +1,73 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*/
/**
* @file MyOTALogging.h
*
* @brief API declaration for MyOTALogging
* @defgroup MyOTALogginggrp MyOTALogging
* @ingroup internals
* @{
*
* @brief Enables ending and receiving debug messages over the air.
*/
#ifndef MyOTALogging_h
#define MyOTALogging_h
#include "MySensorsCore.h"
#include "MyTransport.h"
/**
* @brief Send a log message to a node
*
* If MY_OTA_LOG_RECEIVER_FEATURE is enabled on the destination node, the given
* message is printed to the serial port. You have to define MY_OTA_LOG_SENDER_FEATURE
* or MY_DEBUG_OTA to enable the OTALog() function.
*
* Output format of each line:
* Node ID;CHILD_NODE_ID;C_INTERNAL;I_LOG_MESSAGE;hwMillis() MESSAGE
*
* You will see the hwMillis() of the receiving node. After each \n character, a
* new debug message line starts. Incomplete messages are ending with '...'
*
* @param logNode Destination node ID
* @param echo Enable or disable echo flag
* @param fmt printf format string
* @param ... arguments
*/
void OTALog(uint8_t logNode, bool echo, const char *fmt, ... );
/**
* @brief Handles output of OTA log or debug messages
*
* This function is used by MyTransport.cpp
*
* Output format of each line:
* Node ID;CHILD_NODE_ID;C_INTERNAL;I_LOG_MESSAGE;hwMillis() MESSAGE
*
* You will see the hwMillis() of the receiving node. After each \n character, a
* new debug message line starts. Incomplete messages are ending with '...'
*
* @param message Message buffer to use.
*/
inline void OTALogPrint(const MyMessage &message);
#endif /* MyOTALogging_h */
/** @}*/

View File

@@ -0,0 +1,61 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*/
/*
* This file contains settings that for various reasons are unsuitable to store in git
* Rename it by removing the .sample extension and place it in the sketch folder and include
* it in the sketch:
* #include "MyPrivateConfig.h"
* Make sure to include it before
* #include <MySensors.h>
* Then adapt the contents to your personal preference.
*/
#ifndef MyPrivateConfig_h
#define MyPrivateConfig_h
/**********************************
* Node identification Settings
***********************************/
//#define MY_NODE_ID 1
/**********************************
* Message Signing Settings
***********************************/
// Pin used for random generation in soft signing (do not connect anything to this when enabled)
#define MY_SIGNING_SOFT_RANDOMSEED_PIN 7 // A7 -
// Enable node whitelisting
//#define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}}}
/**********************************
* nRF24L01 Driver Defaults
***********************************/
// Enables RF24 encryption (all nodes and gateway must have this enabled)
//#define MY_RF24_ENABLE_ENCRYPTION
/**********************************
* RFM69 Driver Defaults
***********************************/
// Default network id. Use the same for all nodes that will talk to each other
#define MY_RFM69_NETWORKID 100
// Enable this for encryption of packets
//#define MY_RFM69_ENABLE_ENCRYPTION
#endif

View File

@@ -0,0 +1,167 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MyConfig.h"
#include "MyTransport.h"
#include "MyProtocol.h"
#include "MyHelperFunctions.h"
#include <string.h>
char _fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH];
char _convBuffer[MAX_PAYLOAD_SIZE * 2 + 1];
bool protocolSerial2MyMessage(MyMessage &message, char *inputString)
{
char *str, *p;
uint8_t index = 0;
mysensors_command_t command = C_INVALID_7;
message.setSender(GATEWAY_ADDRESS);
message.setLast(GATEWAY_ADDRESS);
message.setEcho(false);
// Extract command data coming on serial line
for (str = strtok_r(inputString, ";", &p); // split using semicolon
str && index < 5; // loop while str is not null an max 4 times
str = strtok_r(NULL, ";", &p), index++ // get subsequent tokens
) {
switch (index) {
case 0: // Radio id (destination)
message.setDestination(atoi(str));
break;
case 1: // Child id
message.setSensor(atoi(str));
break;
case 2: // Message type
command = static_cast<mysensors_command_t>(atoi(str));
message.setCommand(command);
break;
case 3: // Should we request echo from destination?
message.setRequestEcho(atoi(str) ? 1 : 0);
break;
case 4: // Data type
message.setType(atoi(str));
break;
}
}
// payload
if (str == NULL) {
// no payload, set default value
message.set((uint8_t)0);
} else if (command == C_STREAM) {
// stream payload
uint8_t bvalue[MAX_PAYLOAD_SIZE];
uint8_t blen = 0;
while (*str) {
uint8_t val;
val = convertH2I(*str++) << 4;
val += convertH2I(*str++);
bvalue[blen] = val;
blen++;
}
message.set(bvalue, blen);
} else {
// regular payload
char *value = str;
// Remove trailing carriage return and newline character (if it exists)
const uint8_t lastCharacter = strlen(value) - 1;
if (value[lastCharacter] == '\r' || value[lastCharacter] == '\n') {
value[lastCharacter] = '\0';
}
message.set(value);
}
return (index == 5);
}
char *protocolMyMessage2Serial(MyMessage &message)
{
(void)snprintf_P(_fmtBuffer, (uint8_t)MY_GATEWAY_MAX_SEND_LENGTH,
PSTR("%" PRIu8 ";%" PRIu8 ";%" PRIu8 ";%" PRIu8 ";%" PRIu8 ";%s\n"), message.getSender(),
message.getSensor(), message.getCommand(), message.isEcho(), message.getType(),
message.getString(_convBuffer));
return _fmtBuffer;
}
char *protocolMyMessage2MQTT(const char *prefix, MyMessage &message)
{
(void)snprintf_P(_fmtBuffer, (uint8_t)MY_GATEWAY_MAX_SEND_LENGTH,
PSTR("%s/%" PRIu8 "/%" PRIu8 "/%" PRIu8 "/%" PRIu8 "/%" PRIu8 ""), prefix,
message.getSender(), message.getSensor(), message.getCommand(), message.isEcho(),
message.getType());
return _fmtBuffer;
}
bool protocolMQTT2MyMessage(MyMessage &message, char *topic, uint8_t *payload,
const unsigned int length)
{
char *str, *p;
uint8_t index = 0;
message.setSender(GATEWAY_ADDRESS);
message.setLast(GATEWAY_ADDRESS);
message.setEcho(false);
for (str = strtok_r(topic + strlen(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) + 1, "/", &p);
str && index < 5;
str = strtok_r(NULL, "/", &p), index++
) {
switch (index) {
case 0:
// Node id
message.setDestination(atoi(str));
break;
case 1:
// Sensor id
message.setSensor(atoi(str));
break;
case 2: {
// Command type
const mysensors_command_t command = static_cast<mysensors_command_t>(atoi(str));
message.setCommand(command);
// Add payload
if (command == C_STREAM) {
uint8_t bvalue[MAX_PAYLOAD_SIZE];
uint8_t blen = 0;
while (*payload) {
uint8_t val;
val = convertH2I(*payload++) << 4;
val += convertH2I(*payload++);
bvalue[blen] = val;
blen++;
}
message.set(bvalue, blen);
} else {
// terminate string
char *value = (char *)payload;
value[length] = '\0';
message.set((const char*)payload);
}
break;
}
case 3:
// Echo flag
message.setRequestEcho(atoi(str) ? 1 : 0);
break;
case 4:
// Sub type
message.setType(atoi(str));
break;
}
}
// Return true if input valid
return (index == 5);
}

View File

@@ -0,0 +1,33 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 MyProtocol_h
#define MyProtocol_h
#include "MySensorsCore.h"
// parse(message, inputString)
// parse a string into a message element
// returns true if successfully parsed the input string
bool protocolSerial2MyMessage(MyMessage &message, char *inputString);
// Format MyMessage to the protocol representation
char *protocolMyMessage2Serial(MyMessage &message);
#endif

View File

@@ -0,0 +1,833 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MySensorsCore.h"
// debug output
#if defined(MY_DEBUG_VERBOSE_CORE)
#define CORE_DEBUG(x,...) DEBUG_OUTPUT(x, ##__VA_ARGS__) //!< debug
#else
#define CORE_DEBUG(x,...) //!< debug NULL
#endif
// message buffers
MyMessage _msg; // Buffer for incoming messages
MyMessage _msgTmp; // Buffer for temporary messages (acks and nonces among others)
// core configuration
static coreConfig_t _coreConfig;
#if defined(MY_DEBUG_VERBOSE_CORE)
static uint8_t waitLock = 0;
static uint8_t processLock = 0;
#endif
#if defined(DEBUG_OUTPUT_ENABLED)
char _convBuf[MAX_PAYLOAD_SIZE * 2 + 1];
#endif
// Callback for transport=ok transition
void _callbackTransportReady(void)
{
if (!_coreConfig.presentationSent) {
#if !defined(MY_GATEWAY_FEATURE) // GW calls presentNode() when client connected
presentNode();
#endif
_registerNode();
_coreConfig.presentationSent = true;
}
}
void _process(void)
{
#if defined(MY_DEBUG_VERBOSE_CORE)
if (processLock) {
CORE_DEBUG(PSTR("!MCO:PRO:RC=%" PRIu8 "\n"), processLock); // recursive call detected
}
processLock++;
#endif
doYield();
#if defined(MY_INCLUSION_MODE_FEATURE)
inclusionProcess();
#endif
#if defined(MY_GATEWAY_FEATURE)
gatewayTransportProcess();
#endif
#if defined(MY_SENSOR_NETWORK)
transportProcess();
#endif
#if defined(__linux__)
// To avoid high cpu usage
usleep(10000); // 10ms
#endif
#if defined(MY_DEBUG_VERBOSE_CORE)
processLock--;
#endif
}
void _infiniteLoop(void)
{
#if defined(__linux__)
exit(1);
#else
while(1) {
doYield();
}
#endif
}
void _begin(void)
{
#if defined(MY_CORE_ONLY)
// initialize HW and run setup if present
(void)hwInit();
if (setup) {
setup();
}
return;
#endif
// reset wdt
hwWatchdogReset();
if (preHwInit) {
preHwInit();
}
const bool hwInitResult = hwInit();
#if !defined(MY_SPLASH_SCREEN_DISABLED) && !defined(MY_GATEWAY_FEATURE)
displaySplashScreen();
#endif
#if defined(F_CPU)
CORE_DEBUG(PSTR("MCO:BGN:INIT " MY_NODE_TYPE ",CP=" MY_CAPABILITIES ",FQ=%" PRIu16 ",REL=%"
PRIu8 ",VER="
MYSENSORS_LIBRARY_VERSION "\n"), (uint16_t)(F_CPU/1000000UL),
MYSENSORS_LIBRARY_VERSION_PRERELEASE_NUMBER);
#else
CORE_DEBUG(PSTR("MCO:BGN:INIT " MY_NODE_TYPE ",CP=" MY_CAPABILITIES ",FQ=NA,REL=%"
PRIu8 ",VER="
MYSENSORS_LIBRARY_VERSION "\n"), MYSENSORS_LIBRARY_VERSION_PRERELEASE_NUMBER);
#endif
if (!hwInitResult) {
CORE_DEBUG(PSTR("!MCO:BGN:HW ERR\n"));
setIndication(INDICATION_ERR_HW_INIT);
_infiniteLoop();
}
// set defaults
_coreConfig.presentationSent = false;
// Call sketch before() (if defined)
if (before) {
CORE_DEBUG(PSTR("MCO:BGN:BFR\n")); // before callback
before();
}
#if defined(MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
ledsInit();
#endif
signerInit();
// Read latest received controller configuration from EEPROM
// Note: _coreConfig.isMetric is bool, hence empty EEPROM (=0xFF) evaluates to true (default)
hwReadConfigBlock((void *)&_coreConfig.controllerConfig, (void *)EEPROM_CONTROLLER_CONFIG_ADDRESS,
sizeof(controllerConfig_t));
#if defined(MY_OTA_FIRMWARE_FEATURE)
// Read firmware config from EEPROM, i.e. type, version, CRC, blocks
readFirmwareSettings();
#endif
#if defined(MY_SENSOR_NETWORK)
// Save static parent ID in eeprom (used by bootloader)
hwWriteConfig(EEPROM_PARENT_NODE_ID_ADDRESS, MY_PARENT_NODE_ID);
// Initialise transport layer
transportInitialise();
// Register transport=ready callback
transportRegisterReadyCallback(_callbackTransportReady);
// wait until transport is ready
(void)transportWaitUntilReady(MY_TRANSPORT_WAIT_READY_MS);
#endif
_checkNodeLock();
#if defined(MY_GATEWAY_FEATURE)
#if defined(MY_INCLUSION_BUTTON_FEATURE)
inclusionInit();
#endif
// initialise the transport driver
if (!gatewayTransportInit()) {
setIndication(INDICATION_ERR_INIT_GWTRANSPORT);
CORE_DEBUG(PSTR("!MCO:BGN:TSP FAIL\n"));
// Nothing more we can do
_infiniteLoop();
}
#endif
// Call sketch setup() (if defined)
if (setup) {
CORE_DEBUG(PSTR("MCO:BGN:STP\n")); // setup callback
setup();
}
#if defined(MY_SENSOR_NETWORK)
CORE_DEBUG(PSTR("MCO:BGN:INIT OK,TSP=%" PRIu8 "\n"), isTransportReady() &&
transportHALSanityCheck());
#else
// no sensor network defined, call presentation & registration
_callbackTransportReady();
CORE_DEBUG(PSTR("MCO:BGN:INIT OK,TSP=NA\n"));
#endif
// reset wdt before handing over to loop
hwWatchdogReset();
}
void _registerNode(void)
{
#if defined (MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE)
CORE_DEBUG(PSTR("MCO:REG:REQ\n")); // registration request
setIndication(INDICATION_REQ_REGISTRATION);
_coreConfig.nodeRegistered = MY_REGISTRATION_DEFAULT;
uint8_t counter = MY_REGISTRATION_RETRIES;
// only proceed if register response received or retries exceeded
do {
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_REGISTRATION_REQUEST).set(MY_CORE_VERSION));
} while (!wait(2000, C_INTERNAL, I_REGISTRATION_RESPONSE) && counter--);
#else
_coreConfig.nodeRegistered = true;
CORE_DEBUG(PSTR("MCO:REG:NOT NEEDED\n"));
#endif
}
void presentNode(void)
{
setIndication(INDICATION_PRESENT);
// Present node and request config
#if defined(MY_GATEWAY_FEATURE)
// Send presentation for this gateway device
#if defined(MY_REPEATER_FEATURE)
(void)present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE);
#else
(void)present(NODE_SENSOR_ID, S_ARDUINO_NODE);
#endif
#else
#if defined(MY_OTA_FIRMWARE_FEATURE)
presentBootloaderInformation();
#endif
// Send signing preferences for this node to the GW
signerPresentation(_msgTmp, GATEWAY_ADDRESS);
// Send presentation for this radio node
#if defined(MY_REPEATER_FEATURE)
(void)present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE);
#else
(void)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
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_CONFIG).set(getParentNodeId()));
// Wait configuration reply.
(void)wait(2000, C_INTERNAL, I_CONFIG);
#endif
if (presentation) {
presentation();
}
}
uint8_t getNodeId(void)
{
uint8_t result;
#if defined(MY_GATEWAY_FEATURE)
result = GATEWAY_ADDRESS;
#elif defined(MY_SENSOR_NETWORK)
result = transportGetNodeId();
#else
result = VALUE_NOT_DEFINED;
#endif
return result;
}
uint8_t getParentNodeId(void)
{
uint8_t result;
#if defined(MY_GATEWAY_FEATURE)
result = VALUE_NOT_DEFINED; // GW doesn't have a parent
#elif defined(MY_SENSOR_NETWORK)
result = transportGetParentNodeId();
#else
result = VALUE_NOT_DEFINED;
#endif
return result;
}
uint8_t getDistanceGW(void)
{
uint8_t result;
#if defined(MY_GATEWAY_FEATURE)
result = 0;
#elif defined(MY_SENSOR_NETWORK)
result = transportGetDistanceGW();
#else
result = VALUE_NOT_DEFINED;
#endif
return result;
}
controllerConfig_t getControllerConfig(void)
{
return _coreConfig.controllerConfig;
}
bool _sendRoute(MyMessage &message)
{
#if defined(MY_CORE_ONLY)
(void)message;
#endif
#if defined(MY_GATEWAY_FEATURE)
if (message.getDestination() == getNodeId()) {
// This is a message sent from a sensor attached on the gateway node.
// Pass it directly to the gateway transport layer.
return gatewayTransportSend(message);
}
#endif
#if defined(MY_SENSOR_NETWORK)
return transportSendRoute(message);
#else
return false;
#endif
}
bool send(MyMessage &message, const bool requestEcho)
{
message.setSender(getNodeId());
message.setCommand(C_SET);
message.setRequestEcho(requestEcho);
#if defined(MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE)
if (_coreConfig.nodeRegistered) {
return _sendRoute(message);
} else {
CORE_DEBUG(PSTR("!MCO:SND:NODE NOT REG\n")); // node not registered
return false;
}
#else
return _sendRoute(message);
#endif
}
bool sendBatteryLevel(const uint8_t value, const bool requestEcho)
{
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_BATTERY_LEVEL,
requestEcho).set(value));
}
bool sendHeartbeat(const bool requestEcho)
{
#if defined(MY_SENSOR_NETWORK)
const uint32_t heartbeat = transportGetHeartbeat();
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_HEARTBEAT_RESPONSE,
requestEcho).set(heartbeat));
#elif defined(MY_GATEWAY_FEATURE)
const uint32_t heartbeat = hwMillis();
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_HEARTBEAT_RESPONSE,
requestEcho).set(heartbeat));
#else
(void)requestEcho;
return false;
#endif
}
bool present(const uint8_t childSensorId, const mysensors_sensor_t sensorType,
const char *description,
const bool requestEcho)
{
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, childSensorId, C_PRESENTATION,
static_cast<uint8_t>(sensorType),
requestEcho).set(childSensorId == NODE_SENSOR_ID ? MYSENSORS_LIBRARY_VERSION : description));
}
#if !defined(__linux__)
bool present(const uint8_t childSensorId, const mysensors_sensor_t sensorType,
const __FlashStringHelper *description,
const bool requestEcho)
{
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, childSensorId, C_PRESENTATION,
static_cast<uint8_t>(sensorType),
requestEcho).set(childSensorId == NODE_SENSOR_ID ? F(" MYSENSORS_LIBRARY_VERSION "): description));
}
#endif
bool sendSketchInfo(const char *name, const char *version, const bool requestEcho)
{
bool result = true;
if (name) {
result &= _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_NAME,
requestEcho).set(name));
}
if (version) {
result &= _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_VERSION,
requestEcho).set(version));
}
return result;
}
#if !defined(__linux__)
bool sendSketchInfo(const __FlashStringHelper *name, const __FlashStringHelper *version,
const bool requestEcho)
{
bool result = true;
if (name) {
result &= _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_NAME,
requestEcho).set(name));
}
if (version) {
result &= _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_VERSION,
requestEcho).set(version));
}
return result;
}
#endif
bool request(const uint8_t childSensorId, const uint8_t variableType, const uint8_t destination)
{
return _sendRoute(build(_msgTmp, destination, childSensorId, C_REQ, variableType).set(""));
}
bool requestTime(const bool requestEcho)
{
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_TIME,
requestEcho).set(""));
}
// Message delivered through _msg
bool _processInternalCoreMessage(void)
{
const uint8_t type = _msg.getType();
if (_msg.getSender() == GATEWAY_ADDRESS) {
if (type == I_REBOOT) {
#if !defined(MY_DISABLE_REMOTE_RESET)
setIndication(INDICATION_REBOOT);
// WDT fuse should be enabled
hwReboot();
#endif
} else if (type == I_REGISTRATION_RESPONSE) {
#if defined (MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE)
_coreConfig.nodeRegistered = _msg.getBool();
setIndication(INDICATION_GOT_REGISTRATION);
CORE_DEBUG(PSTR("MCO:PIM:NODE REG=%" PRIu8 "\n"), _coreConfig.nodeRegistered); // node registration
#endif
} else if (type == I_CONFIG) {
// Pick up configuration from controller (currently only metric/imperial) and store it in eeprom if changed
_coreConfig.controllerConfig.isMetric = _msg.data[0] == 0x00 ||
_msg.data[0] == 'M'; // metric if null terminated or M
hwWriteConfigBlock((void*)&_coreConfig.controllerConfig, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS,
sizeof(controllerConfig_t));
} else if (type == I_PRESENTATION) {
// Re-send node presentation to controller
presentNode();
} else if (type == I_HEARTBEAT_REQUEST) {
(void)sendHeartbeat();
} else if (type == I_VERSION) {
#if !defined(MY_GATEWAY_FEATURE)
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_VERSION).set(MYSENSORS_LIBRARY_VERSION_INT));
#endif
} else if (type == I_TIME) {
// Deliver time to callback
if (receiveTime) {
receiveTime(_msg.getULong());
}
} else if (type == I_CHILDREN) {
if (_msg.data[0] == 'C') {
#if defined(MY_REPEATER_FEATURE) && defined(MY_SENSOR_NETWORK)
// Clears child relay data for this node
setIndication(INDICATION_CLEAR_ROUTING);
transportClearRoutingTable();
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CHILDREN).set("OK"));
#endif
}
} else if (type == I_DEBUG) {
#if defined(MY_SPECIAL_DEBUG)
const char debug_msg = _msg.data[0];
if (debug_msg == 'R') { // routing table
#if defined(MY_REPEATER_FEATURE) && defined(MY_SENSOR_NETWORK)
transportReportRoutingTable();
#endif
} else if (debug_msg == 'V') { // CPU voltage
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_DEBUG).set(hwCPUVoltage()));
} else if (debug_msg == 'F') { // CPU frequency in 1/10Mhz
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_DEBUG).set(hwCPUFrequency()));
} else if (debug_msg == 'M') { // free memory
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_DEBUG).set(hwFreeMem()));
} else if (debug_msg == 'E') { // clear MySensors eeprom area and reboot
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG).set("OK"));
for (uint16_t i = EEPROM_START; i<EEPROM_LOCAL_CONFIG_ADDRESS; i++) {
hwWriteConfig(i, 0xFF);
}
setIndication(INDICATION_REBOOT);
hwReboot();
}
#endif
} else {
return false; // further processing required
}
} else {
// sender is a node
if (type == I_REGISTRATION_REQUEST) {
#if defined(MY_GATEWAY_FEATURE)
// registration requests are exclusively handled by GW/Controller
#if !defined(MY_REGISTRATION_CONTROLLER)
bool approveRegistration;
#if defined(MY_CORE_COMPATIBILITY_CHECK)
approveRegistration = (_msg.getByte() >= MY_CORE_MIN_VERSION);
#else
// auto registration if version compatible
approveRegistration = true;
#endif
#if (F_CPU>16*1000000ul)
// delay for fast GW and slow nodes
delay(5);
#endif
(void)_sendRoute(build(_msgTmp, _msg.getSender(), NODE_SENSOR_ID, C_INTERNAL,
I_REGISTRATION_RESPONSE).set(approveRegistration));
#else
return false; // processing of this request via controller
#endif
#endif
} else {
return false; // further processing required
}
}
return true; // if not GW or no further processing required
}
void saveState(const uint8_t pos, const uint8_t value)
{
hwWriteConfig(EEPROM_LOCAL_CONFIG_ADDRESS+pos, value);
}
uint8_t loadState(const uint8_t pos)
{
return hwReadConfig(EEPROM_LOCAL_CONFIG_ADDRESS+pos);
}
void wait(const uint32_t waitingMS)
{
#if defined(MY_DEBUG_VERBOSE_CORE)
if (waitLock) {
CORE_DEBUG(PSTR("!MCO:WAI:RC=%" PRIu8 "\n"), waitLock); // recursive call detected
}
waitLock++;
#endif
const uint32_t enteringMS = hwMillis();
while (hwMillis() - enteringMS < waitingMS) {
_process();
}
#if defined(MY_DEBUG_VERBOSE_CORE)
waitLock--;
#endif
}
bool wait(const uint32_t waitingMS, const mysensors_command_t cmd)
{
#if defined(MY_DEBUG_VERBOSE_CORE)
if (waitLock) {
CORE_DEBUG(PSTR("!MCO:WAI:RC=%" PRIu8 "\n"), waitLock); // recursive call detected
}
waitLock++;
#endif
const uint32_t enteringMS = hwMillis();
// invalidate cmd
//_msg.setCommand(!cmd);
_msg.setCommand(C_INVALID_7);
bool expectedResponse = false;
while ((hwMillis() - enteringMS < waitingMS) && !expectedResponse) {
_process();
expectedResponse = (_msg.getCommand() == cmd);
}
#if defined(MY_DEBUG_VERBOSE_CORE)
waitLock--;
#endif
return expectedResponse;
}
bool wait(const uint32_t waitingMS, const mysensors_command_t cmd, const uint8_t msgType)
{
#if defined(MY_DEBUG_VERBOSE_CORE)
if (waitLock) {
CORE_DEBUG(PSTR("!MCO:WAI:RC=%" PRIu8 "\n"), waitLock); // recursive call detected
}
waitLock++;
#endif
const uint32_t enteringMS = hwMillis();
// invalidate cmd
//_msg.setCommand(!cmd);
_msg.setCommand(C_INVALID_7);
bool expectedResponse = false;
while ( (hwMillis() - enteringMS < waitingMS) && !expectedResponse ) {
_process();
expectedResponse = (_msg.getCommand() == cmd && _msg.getType() == msgType);
}
#if defined(MY_DEBUG_VERBOSE_CORE)
waitLock--;
#endif
return expectedResponse;
}
void doYield(void)
{
hwWatchdogReset();
yield();
#if defined (MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
ledsProcess();
#endif
}
int8_t _sleep(const uint32_t sleepingMS, const bool smartSleep, const uint8_t interrupt1,
const uint8_t mode1, const uint8_t interrupt2, const uint8_t mode2)
{
CORE_DEBUG(PSTR("MCO:SLP:MS=%" PRIu32 ",SMS=%" PRIu8 ",I1=%" PRIu8 ",M1=%" PRIu8 ",I2=%" PRIu8
",M2=%" PRIu8 "\n"), sleepingMS, smartSleep,
interrupt1, mode1, interrupt2, mode2);
// repeater feature: sleeping not possible
#if defined(MY_REPEATER_FEATURE)
(void)smartSleep;
(void)interrupt1;
(void)mode1;
(void)interrupt2;
(void)mode2;
CORE_DEBUG(PSTR("!MCO:SLP:REP\n")); // sleeping not possible, repeater feature enabled
wait(sleepingMS);
return MY_SLEEP_NOT_POSSIBLE;
#else
uint32_t sleepingTimeMS = sleepingMS;
#if defined(MY_SENSOR_NETWORK)
// Do not sleep if transport not ready
if (!isTransportReady()) {
CORE_DEBUG(PSTR("!MCO:SLP:TNR\n")); // sleeping not possible, transport not ready
const uint32_t sleepEnterMS = hwMillis();
uint32_t sleepDeltaMS = 0;
while (!isTransportReady() && (sleepDeltaMS < sleepingTimeMS) &&
(sleepDeltaMS < MY_SLEEP_TRANSPORT_RECONNECT_TIMEOUT_MS)) {
_process();
sleepDeltaMS = hwMillis() - sleepEnterMS;
}
// sleep remainder
if (sleepDeltaMS < sleepingTimeMS) {
sleepingTimeMS -= sleepDeltaMS; // calculate remaining sleeping time
CORE_DEBUG(PSTR("MCO:SLP:MS=%" PRIu32 "\n"), sleepingTimeMS);
} else {
// no sleeping time left
return MY_SLEEP_NOT_POSSIBLE;
}
}
// OTA FW feature: do not sleep if FW update ongoing
#if defined(MY_OTA_FIRMWARE_FEATURE)
while (isFirmwareUpdateOngoing() && sleepingTimeMS) {
CORE_DEBUG(PSTR("!MCO:SLP:FWUPD\n")); // sleeping not possible, FW update ongoing
wait(1000ul);
sleepingTimeMS = sleepingTimeMS >= 1000ul ? sleepingTimeMS - 1000ul : 1000ul;
}
#endif // MY_OTA_FIRMWARE_FEATURE
if (smartSleep) {
// sleeping time left?
if (sleepingTimeMS > 0 && sleepingTimeMS < ((uint32_t)MY_SMART_SLEEP_WAIT_DURATION_MS)) {
wait(sleepingMS);
CORE_DEBUG(PSTR("!MCO:SLP:NTL\n")); // sleeping not possible, no time left
return MY_SLEEP_NOT_POSSIBLE;
}
// notify controller about going to sleep, payload indicates smartsleep waiting time in MS
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_PRE_SLEEP_NOTIFICATION).set((uint32_t)MY_SMART_SLEEP_WAIT_DURATION_MS));
wait(MY_SMART_SLEEP_WAIT_DURATION_MS); // listen for incoming messages
#if defined(MY_OTA_FIRMWARE_FEATURE)
// check if during smart sleep waiting period a FOTA request was received
if (isFirmwareUpdateOngoing()) {
CORE_DEBUG(PSTR("!MCO:SLP:FWUPD\n")); // sleeping not possible, FW update ongoing
return MY_SLEEP_NOT_POSSIBLE;
}
#endif // MY_OTA_FIRMWARE_FEATURE
}
#else
(void)smartSleep;
#endif // MY_SENSOR_NETWORK
#if defined(MY_SENSOR_NETWORK)
transportDisable();
#endif
setIndication(INDICATION_SLEEP);
#if defined (MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
// Wait until leds finish their blinking pattern
while (ledsBlinking()) {
doYield();
}
#endif
int8_t result = MY_SLEEP_NOT_POSSIBLE; // default
if (interrupt1 != INTERRUPT_NOT_DEFINED && interrupt2 != INTERRUPT_NOT_DEFINED) {
// both IRQs
result = hwSleep(interrupt1, mode1, interrupt2, mode2, sleepingTimeMS);
} else if (interrupt1 != INTERRUPT_NOT_DEFINED && interrupt2 == INTERRUPT_NOT_DEFINED) {
// one IRQ
result = hwSleep(interrupt1, mode1, sleepingTimeMS);
} else if (interrupt1 == INTERRUPT_NOT_DEFINED && interrupt2 == INTERRUPT_NOT_DEFINED) {
// no IRQ
result = hwSleep(sleepingTimeMS);
}
setIndication(INDICATION_WAKEUP);
CORE_DEBUG(PSTR("MCO:SLP:WUP=%" PRIi8 "\n"), result); // sleep wake-up
#if defined(MY_SENSOR_NETWORK)
transportReInitialise();
#endif
if (smartSleep) {
// notify controller about waking up, payload indicates sleeping time in MS
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_POST_SLEEP_NOTIFICATION).set(sleepingTimeMS));
}
return result;
#endif
}
// sleep functions
int8_t sleep(const uint32_t sleepingMS, const bool smartSleep)
{
return _sleep(sleepingMS, smartSleep);
}
int8_t sleep(const uint8_t interrupt, const uint8_t mode, const uint32_t sleepingMS,
const bool smartSleep)
{
return _sleep(sleepingMS, smartSleep, interrupt, mode);
}
int8_t sleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2,
const uint8_t mode2, const uint32_t sleepingMS, const bool smartSleep)
{
return _sleep(sleepingMS, smartSleep, interrupt1, mode1, interrupt2, mode2);
}
// deprecated smartSleep() functions
int8_t smartSleep(const uint32_t sleepingMS)
{
// compatibility
return _sleep(sleepingMS, true);
}
int8_t smartSleep(const uint8_t interrupt, const uint8_t mode, const uint32_t sleepingMS)
{
// compatibility
return _sleep(sleepingMS, true, interrupt, mode);
}
int8_t smartSleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2,
const uint8_t mode2, const uint32_t sleepingMS)
{
// compatibility
return _sleep(sleepingMS, true, interrupt1, mode1, interrupt2, mode2);
}
uint32_t getSleepRemaining(void)
{
return hwGetSleepRemaining();
}
void _nodeLock(const char *str)
{
#ifdef MY_NODE_LOCK_FEATURE
// Make sure EEPROM is updated to locked status
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER_ADDRESS, 0);
while (1) {
setIndication(INDICATION_ERR_LOCKED);
CORE_DEBUG(PSTR("MCO:NLK:NODE LOCKED. TO UNLOCK, GND PIN %" PRIu8 " AND RESET\n"),
MY_NODE_UNLOCK_PIN);
doYield();
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID,C_INTERNAL, I_LOCKED).set(str));
#if defined(MY_SENSOR_NETWORK)
transportSleep();
CORE_DEBUG(PSTR("MCO:NLK:TSL\n")); // sleep transport
#endif
setIndication(INDICATION_SLEEP);
(void)hwSleep((uint32_t)1000*60*30); // Sleep for 30 min before resending LOCKED message
setIndication(INDICATION_WAKEUP);
}
#else
(void)str;
#endif
}
void _checkNodeLock(void)
{
#ifdef MY_NODE_LOCK_FEATURE
// Check if node has been locked down
if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER_ADDRESS) == 0) {
// Node is locked, check if unlock pin is asserted, else hang the node
hwPinMode(MY_NODE_UNLOCK_PIN, INPUT_PULLUP);
// Make a short delay so we are sure any large external nets are fully pulled
uint32_t enter = hwMillis();
while (hwMillis() - enter < 2) {}
if (hwDigitalRead(MY_NODE_UNLOCK_PIN) == 0) {
// Pin is grounded, reset lock counter
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER_ADDRESS, MY_NODE_LOCK_COUNTER_MAX);
// Disable pullup
hwPinMode(MY_NODE_UNLOCK_PIN, INPUT);
setIndication(INDICATION_ERR_LOCKED);
CORE_DEBUG(PSTR("MCO:BGN:NODE UNLOCKED\n"));
} else {
// Disable pullup
hwPinMode(MY_NODE_UNLOCK_PIN, INPUT);
_nodeLock("LDB"); //Locked during boot
}
} else if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER_ADDRESS) == 0xFF) {
// Reset value
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER_ADDRESS, MY_NODE_LOCK_COUNTER_MAX);
}
#endif
}
#if DOXYGEN
#endif

View File

@@ -0,0 +1,509 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*/
/**
* @file MySensorsCore.h
*
* @defgroup MySensorsCoregrp MySensorsCore
* @ingroup internals
* @{
*
* MySensorsCore-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE
* - [!] Exclamation mark is prepended in case of error or warning
* - SYSTEM:
* - <b>MCO</b> messages emitted by MySensorsCore
* - SUB SYSTEMS:
* - MCO:<b>BGN</b> from @ref _begin()
* - MCO:<b>REG</b> from @ref _registerNode()
* - MCO:<b>SND</b> from @ref send()
* - MCO:<b>PIM</b> from @ref _processInternalCoreMessage()
* - MCO:<b>NLK</b> from @ref _nodeLock()
*
* MySensorsCore debug log messages:
*
* |E| SYS | SUB | Message | Comment
* |-|-----|-----|---------------------------------------------|-----------------------------------------------------------------------------------------------------------------
* |!| MCO | BGN | HW ERR | Error HW initialization (e.g. ext. EEPROM)
* | | MCO | BGN | INIT %%s,CP=%%s,FQ=%%d,REL=%%d,VER=%%s | Core initialization, capabilities (CP), CPU frequency [Mhz] (FQ), release number (REL), library version (VER)
* | | MCO | BGN | BFR | Callback before()
* | | MCO | BGN | STP | Callback setup()
* | | MCO | BGN | INIT OK,TSP=%%d | Core initialised, transport status (TSP): 0=not initialised, 1=initialised, NA=not available
* | | MCO | BGN | NODE UNLOCKED | Node successfully unlocked (see signing chapter)
* |!| MCO | BGN | TSP FAIL | Transport initialization failed
* | | MCO | REG | REQ | Registration request
* | | MCO | REG | NOT NEEDED | No registration needed (i.e. GW)
* |!| MCO | SND | NODE NOT REG | Node is not registered, cannot send message
* | | MCO | PIM | NODE REG=%%d | Registration response received, registration status (REG)
* |!| MCO | WAI | RC=%%d | Recursive call detected in wait(), level (RC)
* | | MCO | SLP | MS=%%lu,SMS=%%d,I1=%%d,M1=%%d,I2=%%d,M2=%%d | Sleep node, time (MS), smartSleep (SMS), Int1 (I1), Mode1 (M1), Int2 (I2), Mode2 (M2)
* | | MCO | SLP | WUP=%%d | Node woke-up, reason/IRQ (WUP)
* |!| MCO | SLP | NTL | Sleeping not possible, no time left
* |!| MCO | SLP | FWUPD | Sleeping not possible, FW update ongoing
* |!| MCO | SLP | REP | Sleeping not possible, repeater feature enabled
* |!| MCO | SLP | TNR | Transport not ready, attempt to reconnect until timeout (@ref MY_SLEEP_TRANSPORT_RECONNECT_TIMEOUT_MS)
* | | MCO | NLK | NODE LOCKED. UNLOCK: GND PIN %%d AND RESET | Node locked during booting, see signing chapter for additional information
* | | MCO | NLK | TSL | Set transport to sleep
*
* @brief API declaration for MySensorsCore
*/
#ifndef MySensorsCore_h
#define MySensorsCore_h
#include "Version.h"
#include "MyConfig.h"
#include "MyEepromAddresses.h"
#include "MyMessage.h"
#include <stddef.h>
#include <stdarg.h>
#define GATEWAY_ADDRESS ((uint8_t)0) //!< Node ID for GW sketch
#define NODE_SENSOR_ID ((uint8_t)255) //!< Node child is always created/presented when a node is started
#define MY_CORE_VERSION ((uint8_t)2) //!< core version
#define MY_CORE_MIN_VERSION ((uint8_t)2) //!< min core version required for compatibility
#define MY_WAKE_UP_BY_TIMER ((int8_t)-1) //!< Sleeping wake up by timer
#define MY_SLEEP_NOT_POSSIBLE ((int8_t)-2) //!< Sleeping not possible
#define INTERRUPT_NOT_DEFINED ((uint8_t)255) //!< _sleep() param: no interrupt defined
#define MODE_NOT_DEFINED ((uint8_t)255) //!< _sleep() param: no mode defined
#define VALUE_NOT_DEFINED ((uint8_t)255) //!< Value not defined
#define FUNCTION_NOT_SUPPORTED ((uint16_t)0) //!< Function not supported
/**
* @brief Controller configuration
*
* This structure stores controller-related configurations
*/
typedef struct {
uint8_t isMetric; //!< Flag indicating if metric or imperial measurements are used
} controllerConfig_t;
/**
* @brief Node core configuration
*/
typedef struct {
controllerConfig_t controllerConfig; //!< Controller config
// 8 bit
bool nodeRegistered : 1; //!< Flag node registered
bool presentationSent : 1; //!< Flag presentation sent
uint8_t reserved : 6; //!< reserved
} coreConfig_t;
// **** public functions ********
/**
* Return this nodes id.
*/
uint8_t getNodeId(void);
/**
* Return the parent node id.
*/
uint8_t getParentNodeId(void);
/**
* Sends node information to the gateway.
*/
void presentNode(void);
/**
* Each node must present all attached sensors before any values can be handled correctly by the controller.
* It is usually good to present all attached sensors after power-up in setup().
*
* @param sensorId Select a unique sensor id for this sensor. Choose a number between 0-254.
* @param sensorType The sensor type. See sensor typedef in MyMessage.h.
* @param description A textual description of the sensor.
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool present(const uint8_t sensorId, const mysensors_sensor_t sensorType,
const char *description = "",
const bool requestEcho = false);
#if !defined(__linux__)
bool present(const uint8_t childSensorId, const mysensors_sensor_t sensorType,
const __FlashStringHelper *description,
const bool requestEcho = false);
#endif
/**
* Sends sketch meta information to the gateway. Not mandatory but a nice thing to do.
* @param name String containing a short Sketch name or NULL if not applicable
* @param version String containing a short Sketch version or NULL if not applicable
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool sendSketchInfo(const char *name, const char *version, const bool requestEcho = false);
#if !defined(__linux__)
bool sendSketchInfo(const __FlashStringHelper *name, const __FlashStringHelper *version,
const bool requestEcho = false);
#endif
/**
* Sends a message to gateway or one of the other nodes in the radio network
* @param msg Message to send
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool send(MyMessage &msg, const bool requestEcho = false);
/**
* Send this nodes battery level to gateway.
* @param level Level between 0-100(%)
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool sendBatteryLevel(const uint8_t level, const bool requestEcho = false);
/**
* Send a heartbeat message (I'm alive!) to the gateway/controller.
* The payload will be an incremental 16 bit integer value starting at 1 when sensor is powered on.
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool sendHeartbeat(const bool requestEcho = false);
/**
* Send this nodes signal strength to gateway.
* @param level Signal strength can be RSSI if the radio provide it, or another kind of calculation
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool sendSignalStrength(const int16_t level, const bool requestEcho = false);
/**
* Send this nodes TX power level to gateway.
* @param level For instance, can be TX power level in dbm
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool sendTXPowerLevel(const uint8_t level, const bool requestEcho = false);
/**
* Requests a value from gateway or some other sensor in the radio network.
* Make sure to add callback-method in begin-method to handle request responses.
*
* @param childSensorId The unique child id for the different sensors connected to this Arduino. 0-254.
* @param variableType The variableType to fetch
* @param destination The nodeId of other node in radio network. Default is gateway
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool request(const uint8_t childSensorId, const uint8_t variableType,
const uint8_t destination = GATEWAY_ADDRESS);
/**
* Requests time from controller. Answer will be delivered to receiveTime function in sketch.
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool requestTime(const bool requestEcho = false);
/**
* Returns the most recent node configuration received from controller
*/
controllerConfig_t getControllerConfig(void);
/**
* Save a state (in local EEPROM). Good for actuators to "remember" state between
* power cycles.
*
* You have 256 bytes to play with. Note that there is a limitation on the number
* of writes the EEPROM can handle (~100 000 cycles on ATMega328).
*
* @param pos The position to store value in (0-255)
* @param value to store in position
*/
void saveState(const uint8_t pos, const uint8_t value);
/**
* Load a state (from local EEPROM).
*
* @param pos The position to fetch value from (0-255)
* @return Value to store in position
*/
uint8_t loadState(const uint8_t pos);
/**
* Wait for a specified amount of time to pass. Keeps process()ing.
* This does not power-down the radio nor the Arduino.
* Because this calls process() in a loop, it is a good way to wait
* in your loop() on a repeater node or sensor that listens to messages.
* @param waitingMS Number of milliseconds to wait.
*/
void wait(const uint32_t waitingMS);
/**
* Wait for a specified amount of time to pass or until specified message received. Keeps process()ing.
* This does not power-down the radio nor the Arduino.
* Because this calls process() in a loop, it is a good way to wait
* in your loop() on a repeater node or sensor that listens to messages.
* @param waitingMS Number of milliseconds to wait.
* @param cmd Command of incoming message.
* @return True if specified message received
*/
bool wait(const uint32_t waitingMS, const mysensors_command_t cmd);
/**
* Wait for a specified amount of time to pass or until specified message received. Keeps process()ing.
* This does not power-down the radio nor the Arduino.
* Because this calls process() in a loop, it is a good way to wait
* in your loop() on a repeater node or sensor that listens to messages.
* @param waitingMS Number of milliseconds to wait.
* @param cmd Command of incoming message.
* @param msgtype Message type.
* @return True if specified message received
*/
bool wait(const uint32_t waitingMS, const mysensors_command_t cmd, const uint8_t msgtype);
/**
* Function to allow scheduler to do some work.
* @remark Internally it will call yield, kick the watchdog and update led states.
*/
void doYield(void);
/**
* Sleep (PowerDownMode) the MCU and radio. Wake up on timer.
* @param sleepingMS Number of milliseconds to sleep.
* @param smartSleep Set True if sending heartbeat and process incoming messages before going to sleep.
* @return @ref MY_WAKE_UP_BY_TIMER if timer woke it up, @ref MY_SLEEP_NOT_POSSIBLE if not possible (e.g. ongoing FW update)
*/
int8_t sleep(const uint32_t sleepingMS, const bool smartSleep = false);
/**
* Sleep (PowerDownMode) the MCU and radio. Wake up on timer or pin change.
* See: http://arduino.cc/en/Reference/attachInterrupt for details on modes and which pin
* is assigned to what interrupt. On Nano/Pro Mini: 0=Pin2, 1=Pin3
* @param interrupt Interrupt that should trigger the wakeup
* @param mode RISING, FALLING, CHANGE
* @param sleepingMS Number of milliseconds to sleep or 0 to sleep forever
* @param smartSleep Set True if sending heartbeat and process incoming messages before going to sleep
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t sleep(const uint8_t interrupt, const uint8_t mode, const uint32_t sleepingMS = 0,
const bool smartSleep = false);
/**
* Sleep (PowerDownMode) the MCU and radio. Wake up on timer or pin change for two separate interrupts.
* See: http://arduino.cc/en/Reference/attachInterrupt for details on modes and which pin
* is assigned to what interrupt. On Nano/Pro Mini: 0=Pin2, 1=Pin3
* @param interrupt1 First interrupt that should trigger the wakeup
* @param mode1 Mode for first interrupt (RISING, FALLING, CHANGE)
* @param interrupt2 Second interrupt that should trigger the wakeup
* @param mode2 Mode for second interrupt (RISING, FALLING, CHANGE)
* @param sleepingMS Number of milliseconds to sleep or 0 to sleep forever
* @param smartSleep Set True if sending heartbeat and process incoming messages before going to sleep.
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t sleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2,
const uint8_t mode2, const uint32_t sleepingMS = 0, const bool smartSleep = false);
/**
* \deprecated Use sleep(ms, true) instead
* Same as sleep(), send heartbeat and process incoming messages before going to sleep.
* Specify the time to wait for incoming messages by defining @ref MY_SMART_SLEEP_WAIT_DURATION_MS to a time (ms).
* @param sleepingMS Number of milliseconds to sleep.
* @return @ref MY_WAKE_UP_BY_TIMER if timer woke it up, @ref MY_SLEEP_NOT_POSSIBLE if not possible (e.g. ongoing FW update)
*/
int8_t smartSleep(const uint32_t sleepingMS);
/**
* \deprecated Use sleep(interrupt, mode, ms, true) instead
* Same as sleep(), send heartbeat and process incoming messages before going to sleep.
* Specify the time to wait for incoming messages by defining @ref MY_SMART_SLEEP_WAIT_DURATION_MS to a time (ms).
* @param interrupt Interrupt that should trigger the wakeup
* @param mode RISING, FALLING, CHANGE
* @param sleepingMS Number of milliseconds to sleep or 0 to sleep forever
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t smartSleep(const uint8_t interrupt, const uint8_t mode, const uint32_t sleepingMS = 0);
/**
* \deprecated Use sleep(interrupt1, mode1, interrupt2, mode2, ms, true) instead
* Same as sleep(), send heartbeat and process incoming messages before going to sleep.
* Specify the time to wait for incoming messages by defining @ref MY_SMART_SLEEP_WAIT_DURATION_MS to a time (ms).
* @param interrupt1 First interrupt that should trigger the wakeup
* @param mode1 Mode for first interrupt (RISING, FALLING, CHANGE)
* @param interrupt2 Second interrupt that should trigger the wakeup
* @param mode2 Mode for second interrupt (RISING, FALLING, CHANGE)
* @param sleepingMS Number of milliseconds to sleep or 0 to sleep forever
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t smartSleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2,
const uint8_t mode2, const uint32_t sleepingMS = 0);
/**
* Sleep (PowerDownMode) the MCU and radio. Wake up on timer or pin change for two separate interrupts.
* See: http://arduino.cc/en/Reference/attachInterrupt for details on modes and which pin
* is assigned to what interrupt. On Nano/Pro Mini: 0=Pin2, 1=Pin3
* @param sleepingMS Number of milliseconds to sleep or 0 to sleep forever
* @param interrupt1 (optional) First interrupt that should trigger the wakeup
* @param mode1 (optional) Mode for first interrupt (RISING, FALLING, CHANGE)
* @param interrupt2 (optional) Second interrupt that should trigger the wakeup
* @param mode2 (optional) Mode for second interrupt (RISING, FALLING, CHANGE)
* @param smartSleep (optional) Set True if sending heartbeat and process incoming messages before going to sleep.
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t _sleep(const uint32_t sleepingMS, const bool smartSleep = false,
const uint8_t interrupt1 = INTERRUPT_NOT_DEFINED, const uint8_t mode1 = MODE_NOT_DEFINED,
const uint8_t interrupt2 = INTERRUPT_NOT_DEFINED, const uint8_t mode2 = MODE_NOT_DEFINED);
/**
* Return the sleep time remaining after waking up from sleep.
* Depending on the CPU architecture, the remaining time can be seconds off (e.g. upto roughly 8 seconds on AVR).
* @return Time remaining, in ms, when wake from sleep by an interrupt, 0 by timer (@ref MY_WAKE_UP_BY_TIMER), undefined otherwise.
*/
uint32_t getSleepRemaining(void);
// **** private functions ********
/**
* @defgroup MyLockgrp MyNodeLock
* @ingroup internals
* @brief API declaration for MyNodeLock
* @{
*/
/**
* @brief Lock a node and transmit provided message with 30m intervals
*
* This function is called if suspicious activity has exceeded the threshold (see
* @ref MY_NODE_LOCK_COUNTER_MAX). Unlocking with a normal Arduino bootloader require erasing the EEPROM
* while unlocking with a custom bootloader require holding @ref MY_NODE_UNLOCK_PIN low during power on/reset.
*
* @param str The string to transmit.
*/
void _nodeLock(const char *str);
/**
* @brief Check node lock status and prevent node execution if locked.
*/
void _checkNodeLock(void);
/** @}*/ // Node lock group
/**
* @brief Node initialisation
*/
void _begin(void);
/**
* @brief Main framework process
*/
void _process(void);
/**
* @brief Processes internal core message
* @return True if no further processing required
*/
bool _processInternalCoreMessage(void);
/**
* @brief Puts node to a infinite loop if unrecoverable situation detected
*/
void _infiniteLoop(void);
/**
* @brief Handles registration request
*/
void _registerNode(void);
/**
* @brief Sends message according to routing table
* @param message
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool _sendRoute(MyMessage &message);
/**
* @brief Callback for incoming messages
*/
void receive(const MyMessage&) __attribute__((weak));
/**
* @brief Callback for incoming time messages
*/
void receiveTime(uint32_t) __attribute__((weak));
/**
* @brief Node presentation
*/
void presentation(void) __attribute__((weak));
/**
* @brief Called before node initialises
*/
void before(void) __attribute__((weak));
/**
* @brief Called before any hardware initialisation is done
*/
void preHwInit(void) __attribute__((weak));
/**
* @brief Called after node initialises but before main loop
*/
void setup(void) __attribute__((weak));
/**
* @brief Main loop
*/
void loop(void) __attribute__((weak));
// Inline function and macros
static inline MyMessage& build(MyMessage &msg, const uint8_t destination, const uint8_t sensor,
const mysensors_command_t command, const uint8_t type, const bool requestEcho = false)
{
msg.setSender(getNodeId());
msg.setDestination(destination);
msg.setSensor(sensor);
msg.setType(type);
msg.setCommand(command);
msg.setRequestEcho(requestEcho);
msg.setEcho(false);
return msg;
}
static inline MyMessage& buildGw(MyMessage &msg, const uint8_t type)
{
msg.setSender(GATEWAY_ADDRESS);
msg.setDestination(GATEWAY_ADDRESS);
msg.setSensor(NODE_SENSOR_ID);
msg.setType(type);
msg.setCommand(C_INTERNAL);
msg.setRequestEcho(false);
msg.setEcho(false);
return msg;
}
#endif
/** @}*/

View File

@@ -0,0 +1,587 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MySigning.h"
#define SIGNING_PRESENTATION_VERSION_1 1
#define SIGNING_PRESENTATION_REQUIRE_SIGNATURES (1 << 0)
#define SIGNING_PRESENTATION_REQUIRE_WHITELISTING (1 << 1)
#if defined(MY_DEBUG_VERBOSE_SIGNING)
#define SIGN_DEBUG(x,...) DEBUG_OUTPUT(x, ##__VA_ARGS__)
#else
#define SIGN_DEBUG(x,...)
#endif
#if defined(MY_SIGNING_REQUEST_SIGNATURES) &&\
(!defined(MY_SIGNING_ATSHA204) && !defined(MY_SIGNING_SOFT))
#error You have to pick either MY_SIGNING_ATSHA204 or MY_SIGNING_SOFT to reqire signatures!
#endif
#if defined(MY_SIGNING_SOFT) && defined(MY_SIGNING_ATSHA204)
#error You have to pick one and only one signing backend
#endif
#ifdef MY_SIGNING_FEATURE
static uint8_t _doSign[32]; // Bitfield indicating which sensors require signed communication
static uint8_t _doWhitelist[32]; // Bitfield indicating which sensors require salted signatures
static MyMessage _msgSign; // Buffer for message to sign.
static uint8_t _signingNonceStatus;
static bool stateValid = false;
#ifdef MY_NODE_LOCK_FEATURE
static uint8_t nof_nonce_requests = 0;
static uint8_t nof_failed_verifications = 0;
#endif
// Status when waiting for signing nonce in signerSignMsg
enum { SIGN_WAITING_FOR_NONCE = 0, SIGN_OK = 1 };
// Macros for manipulating signing requirement tables
#define DO_SIGN(node) (~_doSign[node>>3]&(1<<node%8))
#define SET_SIGN(node) (_doSign[node>>3]&=~(1<<node%8))
#if defined(MY_SIGNING_WEAK_SECURITY)
#define CLEAR_SIGN(node) (_doSign[node>>3]|=(1<<node%8))
#endif
#define DO_WHITELIST(node) (~_doWhitelist[node>>3]&(1<<node%8))
#define SET_WHITELIST(node) (_doWhitelist[node>>3]&=~(1<<node%8))
#if defined(MY_SIGNING_WEAK_SECURITY)
#define CLEAR_WHITELIST(node) (_doWhitelist[node>>3]|=(1<<node%8))
#endif
#if defined(MY_SIGNING_SOFT)
extern bool signerAtsha204SoftInit(void);
extern bool signerAtsha204SoftCheckTimer(void);
extern bool signerAtsha204SoftGetNonce(MyMessage &msg);
extern void signerAtsha204SoftPutNonce(MyMessage &msg);
extern bool signerAtsha204SoftVerifyMsg(MyMessage &msg);
extern bool signerAtsha204SoftSignMsg(MyMessage &msg);
#define signerBackendInit signerAtsha204SoftInit
#define signerBackendCheckTimer signerAtsha204SoftCheckTimer
#define signerBackendGetNonce signerAtsha204SoftGetNonce
#define signerBackendPutNonce signerAtsha204SoftPutNonce
#define signerBackendVerifyMsg signerAtsha204SoftVerifyMsg
#define signerBackendSignMsg signerAtsha204SoftSignMsg
#elif defined(MY_SIGNING_ATSHA204)
extern bool signerAtsha204Init(void);
extern bool signerAtsha204CheckTimer(void);
extern bool signerAtsha204GetNonce(MyMessage &msg);
extern void signerAtsha204PutNonce(MyMessage &msg);
extern bool signerAtsha204VerifyMsg(MyMessage &msg);
extern bool signerAtsha204SignMsg(MyMessage &msg);
#define signerBackendInit signerAtsha204Init
#define signerBackendCheckTimer signerAtsha204CheckTimer
#define signerBackendGetNonce signerAtsha204GetNonce
#define signerBackendPutNonce signerAtsha204PutNonce
#define signerBackendVerifyMsg signerAtsha204VerifyMsg
#define signerBackendSignMsg signerAtsha204SignMsg
#endif
static bool skipSign(MyMessage &msg);
#else // not MY_SIGNING_FEATURE
#define signerBackendCheckTimer() true
#endif // MY_SIGNING_FEATURE
static void prepareSigningPresentation(MyMessage &msg, uint8_t destination);
static bool signerInternalProcessPresentation(MyMessage &msg);
static bool signerInternalProcessNonceRequest(MyMessage &msg);
static bool signerInternalProcessNonceResponse(MyMessage &msg);
#if (defined(MY_ENCRYPTION_FEATURE) || defined(MY_SIGNING_FEATURE)) &&\
!defined(MY_SIGNING_SIMPLE_PASSWD)
static bool signerInternalValidatePersonalization(void);
#endif
void signerInit(void)
{
#if defined(MY_SIGNING_FEATURE)
stateValid = true;
#endif
#if (defined (MY_ENCRYPTION_FEATURE) || defined (MY_SIGNING_FEATURE)) &&\
!defined (MY_SIGNING_SIMPLE_PASSWD)
// Suppress this warning since it is only fixed on Linux builds and this keeps the code more tidy
// cppcheck-suppress knownConditionTrueFalse
if (!signerInternalValidatePersonalization()) {
SIGN_DEBUG(PSTR("!SGN:PER:TAMPERED\n"));
#if defined(MY_SIGNING_FEATURE)
stateValid = false;
#endif
} else {
SIGN_DEBUG(PSTR("SGN:PER:OK\n"));
}
#endif
#if defined(MY_SIGNING_FEATURE)
// Read out the signing requirements from EEPROM
hwReadConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS,
sizeof(_doSign));
// Read out the whitelist requirements from EEPROM
hwReadConfigBlock((void*)_doWhitelist, (void*)EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS,
sizeof(_doWhitelist));
if (!signerBackendInit()) {
SIGN_DEBUG(PSTR("!SGN:INI:BND FAIL\n"));
} else {
SIGN_DEBUG(PSTR("SGN:INI:BND OK\n"));
}
#endif
}
void signerPresentation(MyMessage &msg, uint8_t destination)
{
prepareSigningPresentation(msg, destination);
#if defined(MY_SIGNING_REQUEST_SIGNATURES)
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES;
SIGN_DEBUG(PSTR("SGN:PRE:SGN REQ\n")); // Signing required
#else
SIGN_DEBUG(PSTR("SGN:PRE:SGN NREQ\n")); // Signing not required
#endif
#if defined(MY_SIGNING_NODE_WHITELISTING)
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_WHITELISTING;
SIGN_DEBUG(PSTR("SGN:PRE:WHI REQ\n")); // Whitelisting required
#else
SIGN_DEBUG(PSTR("SGN:PRE:WHI NREQ\n")); // Whitelisting not required
#endif
if (!_sendRoute(msg)) {
SIGN_DEBUG(PSTR("!SGN:PRE:XMT,TO=%" PRIu8 " FAIL\n"),
destination); // Failed to transmit presentation!
} else {
SIGN_DEBUG(PSTR("SGN:PRE:XMT,TO=%" PRIu8 "\n"), destination); // Transmitted signing presentation!
}
if (destination == GATEWAY_ADDRESS) {
SIGN_DEBUG(PSTR("SGN:PRE:WAIT GW\n")); // Waiting for GW to send signing preferences...
wait(2000, C_INTERNAL, I_SIGNING_PRESENTATION);
}
}
bool signerProcessInternal(MyMessage &msg)
{
bool ret;
switch (msg.getType()) {
case I_SIGNING_PRESENTATION:
ret = signerInternalProcessPresentation(msg);
break;
case I_NONCE_REQUEST:
ret = signerInternalProcessNonceRequest(msg);
break;
case I_NONCE_RESPONSE:
ret = signerInternalProcessNonceResponse(msg);
break;
default:
ret = false; // Let the transport process this message further as it is not related to signing
break;
}
return ret;
}
bool signerCheckTimer(void)
{
return signerBackendCheckTimer();
}
bool signerSignMsg(MyMessage &msg)
{
#if defined(MY_SIGNING_FEATURE)
bool ret;
// If destination is known to require signed messages and we are the sender,
// sign this message unless it is identified as an exception
if (DO_SIGN(msg.getDestination()) && msg.getSender() == getNodeId()) {
if (skipSign(msg)) {
ret = true;
} else {
// Before starting, validate that our state is good, or signing will fail
if (!stateValid) {
SIGN_DEBUG(PSTR("!SGN:SGN:STATE\n")); // Signing system is not in a valid state
ret = false;
} else {
// Send nonce-request
_signingNonceStatus=SIGN_WAITING_FOR_NONCE;
if (!_sendRoute(build(_msgSign, msg.getDestination(), msg.getSensor(), C_INTERNAL,
I_NONCE_REQUEST).set(""))) {
SIGN_DEBUG(PSTR("!SGN:SGN:NCE REQ,TO=%" PRIu8 " FAIL\n"),
msg.getDestination()); // Failed to transmit nonce request!
ret = false;
} else {
SIGN_DEBUG(PSTR("SGN:SGN:NCE REQ,TO=%" PRIu8 "\n"), msg.getDestination()); // Nonce requested
// 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 = msg; // Copy the message to sign since buffer might be touched in _process()
while (hwMillis() - enter < MY_VERIFICATION_TIMEOUT_MS &&
_signingNonceStatus==SIGN_WAITING_FOR_NONCE) {
_process();
}
if (hwMillis() - enter > MY_VERIFICATION_TIMEOUT_MS) {
SIGN_DEBUG(PSTR("!SGN:SGN:NCE TMO\n")); // Timeout waiting for nonce!
ret = false;
} else {
if (_signingNonceStatus == SIGN_OK) {
// process() received a nonce and signerProcessInternal successfully signed the message
msg = _msgSign; // Write the signed message back
SIGN_DEBUG(PSTR("SGN:SGN:SGN\n")); // Message to send has been signed
ret = true;
// 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 {
SIGN_DEBUG(PSTR("!SGN:SGN:SGN FAIL\n")); // Message to send could not be signed!
ret = false;
}
}
}
}
}
} else if (getNodeId() == msg.getSender()) {
msg.setSigned(false); // Message is not supposed to be signed, make sure it is marked unsigned
SIGN_DEBUG(PSTR("SGN:SGN:NREQ=%" PRIu8 "\n"),
msg.getDestination()); // Do not sign message as it is not req
ret = true;
} else {
SIGN_DEBUG(PSTR("SGN:SGN:%" PRIu8 "!=%" PRIu8 " NUS\n"), msg.getSender(),
getNodeId()); // Will not sign message since it was from someone else
ret = true;
}
return ret;
#else
(void)msg;
return true;
#endif // MY_SIGNING_FEATURE
}
bool signerVerifyMsg(MyMessage &msg)
{
bool verificationResult = true;
// 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
#if defined(MY_SIGNING_FEATURE) && defined(MY_SIGNING_REQUEST_SIGNATURES)
// If we are a node, or we are a gateway and the sender require signatures (or just a strict gw)
// and we are the destination...
#if defined(MY_SIGNING_WEAK_SECURITY)
if ((!MY_IS_GATEWAY || DO_SIGN(msg.getSender())) && msg.getDestination() == getNodeId()) {
#else
if (msg.getDestination() == getNodeId()) {
#endif
// Internal messages of certain types are not verified
if (skipSign(msg)) {
verificationResult = true;
} else if (!msg.getSigned()) {
// Got unsigned message that should have been signed
SIGN_DEBUG(PSTR("!SGN:VER:NSG\n")); // Message is not signed, but it should have been!
verificationResult = false;
} else {
// Before starting, validate that our state is good, or signing will fail
if (!stateValid) {
SIGN_DEBUG(PSTR("!SGN:VER:STATE\n")); // Signing system is not in a valid state
verificationResult = false;
} else {
if (!signerBackendVerifyMsg(msg)) {
SIGN_DEBUG(PSTR("!SGN:VER:FAIL\n")); // Signature verification failed!
verificationResult = false;
} else {
SIGN_DEBUG(PSTR("SGN:VER:OK\n"));
}
}
#if defined(MY_NODE_LOCK_FEATURE)
if (verificationResult) {
// On successful verification, clear lock counters
nof_nonce_requests = 0;
nof_failed_verifications = 0;
} else {
nof_failed_verifications++;
SIGN_DEBUG(PSTR("SGN:VER:LEFT=%" PRIu8 "\n"), MY_NODE_LOCK_COUNTER_MAX-nof_failed_verifications);
if (nof_failed_verifications >= MY_NODE_LOCK_COUNTER_MAX) {
_nodeLock("TMFV"); // Too many failed verifications
}
}
#endif
msg.setSigned(false); // Clear the sign-flag now as verification is completed
}
}
#else
(void)msg;
#endif // MY_SIGNING_REQUEST_SIGNATURES
return verificationResult;
}
int signerMemcmp(const void* a, const void* b, size_t sz)
{
int retVal;
size_t i;
int done = 0;
const uint8_t* ptrA = (const uint8_t*)a;
const uint8_t* ptrB = (const uint8_t*)b;
for (i=0; i < sz; i++) {
if (ptrA[i] == ptrB[i]) {
if (done > 0) {
done = 1;
} else {
done = 0;
}
} else {
if (done > 0) {
done = 2;
} else {
done = 3;
}
}
}
if (done > 0) {
retVal = -1;
} else {
retVal = 0;
}
return retVal;
}
#if defined(MY_SIGNING_FEATURE)
// Helper function to centralize signing/verification exceptions
static bool skipSign(MyMessage &msg)
{
bool ret = false;
if (msg.isEcho()) {
ret = true;
} else if (msg.getCommand() == C_INTERNAL &&
(msg.getType() == I_SIGNING_PRESENTATION ||
msg.getType() == I_REGISTRATION_REQUEST ||
msg.getType() == I_NONCE_REQUEST || msg.getType() == I_NONCE_RESPONSE ||
msg.getType() == I_ID_REQUEST || msg.getType() == I_ID_RESPONSE ||
msg.getType() == I_FIND_PARENT_REQUEST || msg.getType() == I_FIND_PARENT_RESPONSE ||
msg.getType() == I_HEARTBEAT_REQUEST || msg.getType() == I_HEARTBEAT_RESPONSE ||
msg.getType() == I_PING || msg.getType() == I_PONG ||
msg.getType() == I_DISCOVER_REQUEST || msg.getType() == I_DISCOVER_RESPONSE ||
msg.getType() == I_LOG_MESSAGE)) {
ret = true;
} else if (msg.getCommand() == C_STREAM &&
(msg.getType() == ST_SOUND ||
msg.getType() == ST_IMAGE ||
msg.getType() == ST_FIRMWARE_REQUEST || msg.getType() == ST_FIRMWARE_RESPONSE )) {
ret = true;
}
if (ret) {
SIGN_DEBUG(PSTR("SGN:SKP:%s CMD=%" PRIu8 ",TYPE=%" PRIu8 "\n"), msg.isEcho() ? "ECHO" : "MSG",
msg.getCommand(),
msg.getType()); //Skip signing/verification of this message
}
return ret;
}
#endif
// Helper to prepare a signing presentation message
static void prepareSigningPresentation(MyMessage &msg, uint8_t destination)
{
// Only supports version 1 for now
(void)build(msg, destination, NODE_SENSOR_ID, C_INTERNAL, I_SIGNING_PRESENTATION).set("");
msg.setLength(2);
msg.setPayloadType(P_CUSTOM); // displayed as hex
msg.data[0] = SIGNING_PRESENTATION_VERSION_1;
msg.data[1] = 0;
}
// Helper to process presentation messages
static bool signerInternalProcessPresentation(MyMessage &msg)
{
const uint8_t sender = msg.getSender();
#if defined(MY_SIGNING_FEATURE)
if (msg.data[0] != SIGNING_PRESENTATION_VERSION_1) {
SIGN_DEBUG(PSTR("!SGN:PRE:VER=%" PRIu8 "\n"),
msg.data[0]); // Unsupported signing presentation version
return true; // Just drop this presentation message
}
// We only handle version 1 here...
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_SIGNATURES) {
// We received an indicator that the sender require us to sign all messages we send to it
SIGN_DEBUG(PSTR("SGN:PRE:SGN REQ,FROM=%" PRIu8 "\n"), sender); // Node require signatures
SET_SIGN(sender);
} else {
#if defined(MY_SIGNING_WEAK_SECURITY)
// We received an indicator that the sender does not require us to sign messages we send to it
SIGN_DEBUG(PSTR("SGN:PRE:SGN NREQ,FROM=%" PRIu8 "\n"), sender); // Node does not require signatures
CLEAR_SIGN(sender);
#else
if (DO_SIGN(sender)) {
SIGN_DEBUG(PSTR("!SGN:PRE:SGN NREQ,FROM=%" PRIu8 " REJ\n"),
sender); // Node does not require signatures but used to do so
}
#endif
}
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_WHITELISTING) {
// We received an indicator that the sender require us to salt signatures with serial
SIGN_DEBUG(PSTR("SGN:PRE:WHI REQ,FROM=%" PRIu8 "\n"), sender); // Node require whitelisting
SET_WHITELIST(sender);
} else {
#if defined(MY_SIGNING_WEAK_SECURITY)
// We received an indicator that the sender does not require us to sign messages we send to it
SIGN_DEBUG(PSTR("SGN:PRE:WHI NREQ,FROM=%" PRIu8 "\n"),
sender); // Node does not require whitelisting
CLEAR_WHITELIST(sender);
#else
if (DO_WHITELIST(sender)) {
SIGN_DEBUG(PSTR("!SGN:PRE:WHI NREQ,FROM=%" PRIu8 " REJ\n"),
sender); // Node does not require whitelisting but used to do so
}
#endif
}
// Save updated tables
hwWriteConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS,
sizeof(_doSign));
hwWriteConfigBlock((void*)_doWhitelist, (void*)EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS,
sizeof(_doWhitelist));
// Inform sender about our preference if we are a gateway, but only require signing if the sender
// required signing unless we explicitly configure it to
#if defined(MY_GATEWAY_FEATURE)
prepareSigningPresentation(msg, sender);
#if defined(MY_SIGNING_REQUEST_SIGNATURES)
#if defined(MY_SIGNING_WEAK_SECURITY)
if (DO_SIGN(sender)) {
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES;
}
#else
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES;
#endif
#endif // MY_SIGNING_REQUEST_SIGNATURES
#if defined(MY_SIGNING_NODE_WHITELISTING)
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_WHITELISTING;
#endif // MY_SIGNING_NODE_WHITELISTING
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_SIGNATURES) {
SIGN_DEBUG(PSTR("SGN:PRE:SGN REQ,TO=%" PRIu8 "\n"),
sender); // Inform node that we require signatures
} else {
SIGN_DEBUG(PSTR("SGN:PRE:SGN NREQ,TO=%" PRIu8 "\n"),
sender); // Inform node that we do not require signatures
}
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_WHITELISTING) {
SIGN_DEBUG(PSTR("SGN:PRE:WHI REQ,TO=%" PRIu8 "\n"),
sender); // Inform node that we require whitelisting
} else {
SIGN_DEBUG(PSTR("SGN:PRE:WHI NREQ,TO=%" PRIu8 "\n"),
sender); // Inform node that we do not require whitelisting
}
if (!_sendRoute(msg)) {
SIGN_DEBUG(PSTR("!SGN:PRE:XMT,TO=%" PRIu8 " FAIL\n"),
sender); // Failed to transmit signing presentation!
} else {
SIGN_DEBUG(PSTR("SGN:PRE:XMT,TO=%" PRIu8 "\n"), sender);
}
#endif // MY_GATEWAY_FEATURE
#else // not MY_SIGNING_FEATURE
#if defined(MY_GATEWAY_FEATURE)
// If we act as gateway and do not have the signing feature and receive a signing request we still
// need to do make sure the requester does not believe the gateway still require signatures
prepareSigningPresentation(msg, sender);
SIGN_DEBUG(PSTR("SGN:PRE:NSUP,TO=%" PRIu8 "\n"),
sender); // Informing node that we do not support signing
if (!_sendRoute(msg)) {
SIGN_DEBUG(PSTR("!SGN:PRE:XMT,TO=%" PRIu8 " FAIL\n"),
sender); // Failed to transmit signing presentation!
} else {
SIGN_DEBUG(PSTR("SGN:PRE:XMT,TO=%" PRIu8 "\n"), sender);
}
#else // not MY_GATEWAY_FEATURE
// If we act as a node and do not have the signing feature then we just silently drop any signing
// presentation messages received
(void)msg;
(void)sender;
// Received signing presentation, but signing is not supported (message ignored)
SIGN_DEBUG(
PSTR("SGN:PRE:NSUP\n"));
#endif // not MY_GATEWAY_FEATURE
#endif // not MY_SIGNING_FEATURE
return true; // No need to further process I_SIGNING_PRESENTATION
}
// Helper to process nonce request messages
static bool signerInternalProcessNonceRequest(MyMessage &msg)
{
#if defined(MY_SIGNING_FEATURE)
#if defined(MY_NODE_LOCK_FEATURE)
nof_nonce_requests++;
SIGN_DEBUG(PSTR("SGN:NCE:LEFT=%" PRIu8 "\n"),
MY_NODE_LOCK_COUNTER_MAX-nof_nonce_requests); // Nonce requests left until lockdown
if (nof_nonce_requests >= MY_NODE_LOCK_COUNTER_MAX) {
_nodeLock("TMNR"); // Too many nonces requested
}
#endif // MY_NODE_LOCK_FEATURE
if (signerBackendGetNonce(msg)) {
if (!_sendRoute(build(msg, msg.getSender(), NODE_SENSOR_ID, C_INTERNAL, I_NONCE_RESPONSE))) {
SIGN_DEBUG(PSTR("!SGN:NCE:XMT,TO=%" PRIu8 " FAIL\n"), msg.getSender()); // Failed to transmit nonce!
} else {
SIGN_DEBUG(PSTR("SGN:NCE:XMT,TO=%" PRIu8 "\n"), msg.getSender());
}
} else {
SIGN_DEBUG(PSTR("!SGN:NCE:GEN\n")); // Failed to generate nonce!
}
#else // not MY_SIGNING_FEATURE
(void)msg;
SIGN_DEBUG(
PSTR("SGN:NCE:NSUP (DROPPED)\n")); // Received nonce request/response without signing support
#endif // MY_SIGNING_FEATURE
return true; // No need to further process I_NONCE_REQUEST
}
// Helper to process nonce response messages
static bool signerInternalProcessNonceResponse(MyMessage &msg)
{
#if defined(MY_SIGNING_FEATURE)
// Proceed with signing if nonce has been received
SIGN_DEBUG(PSTR("SGN:NCE:FROM=%" PRIu8 "\n"), msg.getSender());
if (msg.getSender() != _msgSign.getDestination()) {
SIGN_DEBUG(PSTR("SGN:NCE:%" PRIu8 "!=%" PRIu8 " (DROPPED)\n"), _msgSign.getDestination(),
msg.getSender());
} else {
signerBackendPutNonce(msg);
if (signerBackendSignMsg(_msgSign)) {
// _msgSign now contains the signed message pending transmission
_signingNonceStatus = SIGN_OK;
}
}
#else
(void)msg;
SIGN_DEBUG(
PSTR("SGN:NCE:NSUP (DROPPED)\n")); // Received nonce request/response without signing support
#endif
return true; // No need to further process I_NONCE_RESPONSE
}
#if (defined(MY_ENCRYPTION_FEATURE) || defined(MY_SIGNING_FEATURE)) &&\
!defined(MY_SIGNING_SIMPLE_PASSWD)
static bool signerInternalValidatePersonalization(void)
{
#ifdef __linux__
// Personalization on linux is a bit more crude
return true;
#else
uint8_t buffer[SIZE_SIGNING_SOFT_HMAC_KEY + SIZE_RF_ENCRYPTION_AES_KEY + SIZE_SIGNING_SOFT_SERIAL];
uint8_t hash[32];
uint8_t checksum;
hwReadConfigBlock((void*)buffer, (void*)EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS,
SIZE_SIGNING_SOFT_HMAC_KEY);
hwReadConfigBlock((void*)&buffer[SIZE_SIGNING_SOFT_HMAC_KEY],
(void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, SIZE_RF_ENCRYPTION_AES_KEY);
hwReadConfigBlock((void*)&buffer[SIZE_SIGNING_SOFT_HMAC_KEY + SIZE_RF_ENCRYPTION_AES_KEY],
(void*)EEPROM_SIGNING_SOFT_SERIAL_ADDRESS, SIZE_SIGNING_SOFT_SERIAL);
hwReadConfigBlock((void*)&checksum, (void*)EEPROM_PERSONALIZATION_CHECKSUM_ADDRESS,
SIZE_PERSONALIZATION_CHECKSUM);
SHA256(hash, buffer, sizeof(buffer));
return (checksum == hash[0]);
#endif
}
#endif

View File

@@ -0,0 +1,907 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*/
/**
* @defgroup MySigninggrpPub Message signing
* @ingroup publics
* @{
*
* @brief The message signing infrastructure provides message authenticity to users by signing
* MySensors messages.
*
* Signing support created by Patrick "Anticimex" Fallberg.
*
* @section MySigninggrptoc Table of contents
*
* @ref MySigninggrphowuse <br>
* @ref MySigningwhitelisting <br>
* @ref MySigninglimitations <br>
* @ref MySigningusecases <br>
* @ref MySigningtechnical <br>
* @ref MySigninggrpbackground <br>
* @ref MySigninggrphow <br>
* @ref MySigninggrpencryption <br>
*
* @section MySigninggrphowuse How to use this
*
* Before we begin with the details, I just want to emphasize that signing is completely optional
* and not enabled by default.
*
* If you do want the additional security layer signing provides, you pick the backend of your
* choice in your sketch. Currently, two compatible backends are supported; @ref MY_SIGNING_ATSHA204
* (hardware backed) and @ref MY_SIGNING_SOFT (software backed). There also exist a simplified
* variant (@ref MY_SIGNING_SIMPLE_PASSWD) of the software backend which only require one setting
* to activate.
*
* If you use hardware backed signing, then connect the device as follows:
* @image html MySigning/wiring.png
* @note The pull-up resistor is optional but recommended.
* @note If you change the default pin (A3) make sure you use a pin that supports input/output
* (ex. A6 & A7 on a Pro Mini are input only pins).
*
* To use signing, you need to perform three major steps which are described below.
*
* <b>Firstly</b>, you need to make sure to pick a backend to use.
* @code{.cpp}
* //#define MY_SIGNING_SOFT
* //#define MY_SIGNING_SIMPLE_PASSWD
* #define MY_SIGNING_ATSHA204
* #include <MySensors.h>
* ...
* @endcode
* Make sure to set the define before the inclusion of MySensors.h.
* It is ok to mix @ref MY_SIGNING_SOFT and @ref MY_SIGNING_ATSHA204 in a network.
* They are fully compatible. It is however not recommended to use @ref MY_SIGNING_SOFT on nodes
* that are publicly accessible (more on that later).
*
* If you use @ref MY_SIGNING_SOFT or @ref MY_SIGNING_ATSHA204 you also need to decide if the node
* (or gateway) in question require messages to be signed in addition to the ability to generate
* signatures for other nodes.
* This has to be set by at least one of the nodes in a "pair" or nobody will actually start
* calculating a signature for a message.
* Just set the flag @ref MY_SIGNING_REQUEST_SIGNATURES and the node will inform the gateway that it
* expects the gateway to sign all messages sent to the node. Note that when set in a gateway, the
* gateway will require ALL nodes in the network to sign messages.
* If this behaviour is undesired, enable the flag @ref MY_SIGNING_WEAK_SECURITY which will allow
* the gateway to only require signatures from nodes that in turn require signatures. It will also
* allow the gateway (and all nodes) to "downgrade" security by clearing the signing/whitelisting
* requirements (whitelisting is described later on in the @ref MySigningwhitelisting section) in
* the EEPROM if a node presents itself as not having any security requirements.
* If @ref MY_SIGNING_WEAK_SECURITY is not set, any node that has presented itself with
* signing/whitelisting requirements will be permanently marked as such by the receiver
* (typically the gateway). The only way then to reset/revert this requirement is to clear the
* EEPROM at the receiver (or disable @ref MY_SIGNING_REQUEST_SIGNATURES, but the preference will be
* remembered if the request flag is re-enabled before EEPROM is cleared).<br>
* If you want to have two nodes communicate securely directly with each other, the nodes that
* require signatures must send a presentation message to all nodes it expect signed messages from
* (only the gateway is informed automatically). See @ref signerPresentation().<br>
* A node can have three "states" with respect to signing:
* 1. Node does not support signing in any way (neither @ref MY_SIGNING_ATSHA204,
* @ref MY_SIGNING_SOFT nor @ref MY_SIGNING_SIMPLE_PASSWD is set)
* 2. Node does support signing but don't require messages sent to it to be signed (neither
* @ref MY_SIGNING_REQUEST_SIGNATURES nor @ref MY_SIGNING_SIMPLE_PASSWD is set)
* 3. Node does support signing and require messages sent to it to be signed (@ref
* MY_SIGNING_SOFT or @ref MY_SIGNING_ATSHA204 together with @ref MY_SIGNING_REQUEST_SIGNATURES or
* @ref MY_SIGNING_SIMPLE_PASSWD are set)
*
* <b>Secondly</b>, you need to verify the configuration for the backend.<br>
* For hardware backed signing it is the pin the device is connected to. In MyConfig.h there are
* defaults which you might need to adjust to match your personal build. The setting is defined
* using @ref MY_SIGNING_ATSHA204_PIN.<br>
* If you use an official MySensors board (like the SenseBender GW) you do not need to set the pin,
* this is configured automatically by the Arduino board definition files.
*
* Similar to picking your backend, this can also be set in your sketch:
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #define MY_SIGNING_ATSHA204_PIN 4
* #define MY_SIGNING_REQUEST_SIGNATURES
* #include <MySensors.h>
* ...
* @endcode
* For the software backed signing backend, an unconnected analog pin is required on boards that
* does not provide a hardware based random generator unit to set a random seed for the
* pseudo-random generator.
* It is important that the pin is floating, or the output of the pseudo-random generator will be
* predictable, and thus compromise the signatures. The setting is defined using
* @ref MY_SIGNING_SOFT_RANDOMSEED_PIN. The same configuration possibilities exist as with the other
* configuration options.
*
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #include <MySensors.h>
* ...
* @endcode
*
* An example of a node that require signatures is available in @ref SecureActuator.ino.
*
* <b>Thirdly</b>, if you use a signing backend and you don't use @ref MY_SIGNING_SIMPLE_PASSWD, you
* need to personalize the node.
*
* @anchor personalization If you use the ATSHA204A (@ref MY_SIGNING_ATSHA204), before any signing
* operations can be done, the device needs to be <i>personalized</i>.
* This can be a daunting process as it involves irreversibly writing configurations to the device,
* which cannot be undone. I have however tried to simplify the process as much as possibly by
* creating a helper-sketch specifically for this purpose in @ref SecurityPersonalizer.ino
* Note that you also need to do personalization for @ref MY_SIGNING_SOFT, but then the values are
* stored in EEPROM.
*
* To personalize a ATSHA204A do the following procedure:
* 1. Enable @ref GENERATE_KEYS_ATSHA204A<br>
* This will lock the ATSHA204A and generate random keys for HMAC (signing) and %AES (encryption).
* Copy the keys generated and replace the corresponding definitions under
* "User defined key data", specifically @ref MY_HMAC_KEY and @ref MY_AES_KEY.
* 2. Disable @ref GENERATE_KEYS_ATSHA204A and enable @ref PERSONALIZE_ATSHA204A<br>
* This will store the HMAC key to the ATSHA204A and the %AES key to EEPROM. It will also write
* a checksum of the personalization data in EEPROM to be able to detect if the data is
* altered.<br>
* Personalization is now complete.
*
* To personalize for software signing do the following procedure:
* 1. Enable @ref GENERATE_KEYS_SOFT<br>
* This will generate random keys for HMAC (signing) and %AES (encryption).
* Copy the keys generated and replace the corresponding definitions under
* "User defined key data", specifically @ref MY_HMAC_KEY and @ref MY_AES_KEY.
* 2. Disable @ref GENERATE_KEYS_SOFT and enable @ref PERSONALIZE_SOFT<br>
* This will store the HMAC key and the %AES key to EEPROM. It will also write
* a checksum of the personalization data in EEPROM to be able to detect if the data is
* altered.<br>
* Personalization is now complete.
*
* If you want to use soft signing and you want to use whitelisting (the ability to revoke/ban
* compromised nodes in the network) and your target does not provide a unique device ID, you have
* to generate a unique serial and store it in EEPROM. This can be done by replacing
* @ref PERSONALIZE_SOFT in step 2 above with @ref PERSONALIZE_SOFT_RANDOM_SERIAL. See the output
* under "Hardware security peripherals" to determine if this is necessary.
*
* When you have personalized your first device after step 2 above, you can run the same sketch on
* all devices in your network that needs to be personalized in a compatible manner. Pick
* @ref PERSONALIZE_ATSHA204A or @ref PERSONALIZE_SOFT as needed by the hardware. When the
* personalization has finished, you just program the sketch you plan to use (with the appropriate
* signing flags set).
*
* If you are using a Raspberry PI-based gateway, personalizaion is done slightly differently:
* 1. Generate keys, execute @c mysgw with arguments
* * To generate HMAC key @verbatim --gen-soft-hmac-key @endverbatim
* * To generate %AES key @verbatim --gen-aes-key @endverbatim
* * To generate a soft serial number @verbatim --gen-soft-serial @endverbatim
* 2. Update the gateway config file with the generated keys/valeus
* * For HMAC key @verbatim soft_hmac_key=<DATA> @endverbatim
* * For %AES key @verbatim aes_key=<DATA> @endverbatim
* * For soft serial number @verbatim soft_serial_key=<DATA> @endverbatim
*
* You are now set and ready to use message signing in your network.
* As of now, the following restrictions will be applied to your nodes:
* * If a node does require signing, any unsigned message sent to the node will be rejected.
* This also applies to the gateway.
* * Your radio communication quality is expected to work fine (if any NACK happen on a signing
* related message, it will fail, and enabling signing will put maximum strain on your RF link as
* maximum sized packets are transmitted in the network). See @ref MySigningTroubleshootinggrp.
* * All nodes and gateways in a network maintain a table where the signing preferences of all nodes
* are stored. This is also stored in EEPROM so if a node or gateway reboots, the other nodes does
* not have to retransmit a signing presentation to the node for the node to start expecting signed
* messages from other nodes.<br>
* * By default, the signing preferences are not "downgradeable". That is, any node that at any
* point in time has indicated a signing requirement will not be able to revert this requirement at
* the receiving end (except by manual erase of the EEPROM).<br>
* If you for some reason need to be able to downgrade the security requirements, you can set
* @ref MY_SIGNING_WEAK_SECURITY at the receiver to allow it to downgrade the security expectations
* of the node in question.<br>
* You then need to reset your transmitting node, to force it to transmit updated signing
* preferences.
*
* @section MySigningwhitelisting Whitelisting and node revocation
*
* Consider the situation when you have set up your secure topology. We use the remotely operated
* garage door as an example:
* * You have a node inside your garage (considered physically inaccessible) that controls your
* garage door motor.<br>
* This node requires signing since you do not want an unauthorized person sending it orders to
* open the door.
* * You have a keyfob node with a signing backend that uses the same PSK as your door opener node.
*
* In this setup, your keyfob can securely transmit messages to your door node since the keyfob will
* sign the messages it sends and the door node will verify that these were sent from a trusted node
* (since it used the correct PSK). If the keyfob does not sign the messages, the door node
* will not accept them. Optionally, your keyfob sends a signed message to your gateway (which
* require signatures) and the gateway in turn sends a signed message to your garage door.
*
* One day your keyfob gets stolen or you lost it or it simply broke down.
*
* You now end up with a problem; you need some way of telling your door node that the keyfob in
* question cannot be trusted any more. You could now repersonalize all your node to switch to a
* different PSK but this obviously is a hassle. How do you make sure that the "rogue" keyfob can be
* removed from the "trusted chain"?
*
* The answer to this is whitelisting. You let your door node keep a whitelist of all nodes it
* trusts. If you stop trusting a particular node, you remove it from the nodes whitelist
* (by uploading a new sketch), and it will no longer be able to communicate signed messages to the
* door node.
*
* In case you want to be able to "whitelist" trusted nodes (in order to be able to revoke them in
* case they are lost) you also need to take note of the serial number of the ATSHA device or the
* software value stored in EEPROM. This is unique for each device. The serial number is printed
* in a copy+paste friendly format by the personalizer for this purpose.<br>
* The whitelist is stored on the node that require signatures. When a received message is
* verified, the serial of the sender is looked up in a list stored on the receiving node, and the
* corresponding serial stored in the list for that sender is then included in the signature
* verification process. The list is stored as the value of the flag that enables whitelisting,
* @ref MY_SIGNING_NODE_WHITELISTING.<br>
*
* Whitelisting is achieved by 'salting' the signature with some node-unique information known to
* the receiver. In the case of @ref MY_SIGNING_ATSHA204 this is the unique serial number programmed
* into the circuit. This unique number is never transmitted over the air in clear text, so Eve will
* not be able to figure out a "trusted" serial by snooping on the traffic.<br>
* Instead the value is hashed together with the senders NodeId into the HMAC signature to produce
* the final signature. The receiver will then take the originating NodeId of the signed message and
* do the corresponding calculation with the serial it has stored in it's whitelist if it finds a
* matching entry in it's whitelist.
*
* Whitelisting is an optional alternative because it adds some code and configuration options which
* might not be desirable for every user. So if you want the ability to use whitelists, you need to
* enable @ref MY_SIGNING_NODE_WHITELISTING. You need to remember that the gateway will remember if
* a node has presented it with a whitelisting requirement as described above, if you at some point
* decide to remove the whitelist requirement.<br>
* The whitelist is provided as value of the flag that enable it as follows (example is a node that
* require signing as well):
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #define MY_SIGNING_REQUEST_SIGNATURES
* #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}},{.nodeId = 2,.serial = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09}}}
* #include <MySensors.h>
* ...
* @endcode
* In this example, there are two nodes in the whitelist; the gateway, and a separate node that
* communicates directly with this node (with signed messages). You do not need to do anything
* special for the sending nodes, apart from making sure they support signing.
*
* The "soft" backend of course also support whitelisting. Example:
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}},{.nodeId = 2,.serial = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09}}}
* #include <MySensors.h>
* ...
* @endcode
*
* For a node that should transmit whitelisted messages but not receive whitelisted messages, you do
* not need any special configurations:
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* @endcode
* Remember that you always need to select a signing backend for all nodes that communicate to a
* node that require whitelisting. Also, note that a node that use whitelisting will not accept
* messages from nodes that are not present in it's whitelist.
* And you have to personalize all nodes that use signing with a common HMAC key but different
* serial numbers (@ref MY_SIGNING_ATSHA204 always has unique serials).
*
* @section MySigninglimitations Known limitations
*
* Due to the limiting factor of our cheapest Arduino nodes, the use of diversified keys is not
* implemented. That mean that all nodes in your network share the same PSK (at least the ones that
* are supposed to exchange signed data). It is important to understand the implications of
* this, and that is covered in the "Typical use cases" chapter below.<br>
* Most importantly, if you use @ref MY_SIGNING_SOFT your PSK will be stored in EEPROM and will
* therefore be accessible by anyone with physical access to your node. Therefore it is <b>NOT</b>
* recommended to use @ref MY_SIGNING_SOFT on nodes that are placed in a public place or worn on
* on your person (like a keyfob).<br>
* Also be reminded that the strength of the signature is inversely proportional to the size of the
* message. The larger the message, the weaker the signature.
*
* @section MySigningusecases Typical use cases
*
* "Securely located" in this context mean a node which is not physically publicly accessible.
* Typically at least your gateway.<br>
* "Public" in this context mean a node that is located outside your "trusted environment". This
* includes sensors located outdoors, keyfobs etc.
*
* @subsection MySigninglock Securely located lock
*
* You have a securely located gateway and a lock somewhere inside your "trusted environment" (e.g.
* inside your house door, the door to your dungeon or similar).<br>
* You need to make your node require signed messages but you do not necessarily need to make your
* gateway require signed messages (unless you are concerned that someone might spoof the lock
* status of your lock).<br>
* Configuration example for the secure lock node:<br>
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #define MY_SIGNING_REQUEST_SIGNATURES
* #include <MySensors.h>
* ...
* @endcode
* If you do also want your gateway to require signatures from your lock you just enable the same
* (or similar if using software signing) settings in the gateway.
*
* @subsection MySigningpatio Patio motion sensor
*
* Your gateway is securely located inside your house, but your motion sensor is located outside
* your house. You have for some reason elected that this node should sign the messages it send to
* your gateway. You should lock the data (PSK) in this node then, because if someone were to steal
* your patio motion sensor, they could rewrite the firmware and spoof your gateway to use it to
* transmit a correctly signed message to your secure lock inside your house. But if you revoke your
* gateway (and lock) PSK the outside sensor cannot be used for this anymore. Nor can it be changed
* in order to do it in the future. You can also use whitelisting to revoke your lost node.<br>
* This is an unlikely use case because there really is no reason to sign sensor values. If you for
* some reason want to obfuscate sensor data, encryption is a better alternative.<br>
* Configuration example for a motion sensor:<br>
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #include <MySensors.h>
* ...
* @endcode
*
* The gateway needs to be configured with a whitelist (and it has to have an entry for all nodes
* that send and/or require signed messages):<br>
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = MOTION_SENSOR_ID,.serial = {0x12,0x34,0x56,0x78,0x90,0x12,0x34,0x56,0x78}}}
* #include <MySensors.h>
* ...
* @endcode
* @subsection MySigningkeyfob Keyfob for garage door opener
*
* Perhaps the most typical usecase for signed messages. Your keyfob should be totally locked down.
* If the garage door opener is secured (and it should be) it can be unlocked. That way, if you
* loose your keyfob, you can revoke the PSK in both the opener and your gateway,
* thus rendering the keyfob useless without having to replace your nodes. You can also use
* whitelisting to revoke your lost keyfob.<br>
* Configuration example for the keyfob (keyfob will only transmit to another node and not receive
* anything):<br>
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #include <MySensors.h>
* ...
* @endcode
*
* Configuration example for the door controller node (should require signing from anyone who wants
* to control it):<br>
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88}},{.nodeId = KEYFOB_ID,.serial = {<FROM ATSHA ON KEYFOB>}}}
* #include <MySensors.h>
* ...
* @endcode
*
* @subsection MySigningsketches Relevant sketches
*
* - @ref SecureActuator.ino
* - @ref SecurityPersonalizer.ino
*
* @section MySigningtechnical The technical stuff
*
* The following sequence diagram illustrate how messages are passed in a MySensors network with
* respect to signing:
* @image html MySigning/signingsequence.png
*
* None of this activity is “visible” to you (as the sensor sketch implementor). All you need to do
* is to set your preferences in your sketch and personalize accordingly.
* That is enough to enable protection from both Eve and Mallory in your network
* although if you do not also enable encryption, Eve can eavesdrop, but not do anything about,
* your messages (except possibly preventing them from arriving).
*
* How are the messages actually affected by the signing?<br>
* The following illustration shows what part of the message is signed, and where the signature is
* stored:
* @image html MySigning/signingillustrated1.png
*
* The first byte of the header is not covered by the signature, because in the network, this byte
* is used to track hops in the network and therefore might change if the message is passing a relay
* node. So it cannot be part of the signature, or the signature would be invalid when it arrives to
* its destination. The signature also carries a byte with a signing identifier to prevent false
* results from accidental mixing of incompatible signing backends in the network. Thus, the maximum
* size for a payload is 29-7 bytes. Larger payloads are not possible to sign at the moment. Another
* thing to consider is that the strength of the signature is inversely proportional to the payload
* size.
*
* As for the software backend, it turns out that the ATSHA does not do “vanilla” HMAC processing.
* Fortunately, Atmel has documented exactly how the circuit processes the data and hashes thus
* making it possible to generate signatures that are identical to signatures generated by the
* circuit.
*
* The signatures are calculates in the following way:
* @image html MySigning/signingillustrated2.png
*
* Exactly how this is done can be reviewed in the source for the software backend
* (MySigningAtsha204Soft.cpp) and the ATSHA204A
* <a href="http://www.atmel.com/Images/Atmel-8885-CryptoAuth-ATSHA204A-Datasheet.pdf">datasheet
* </a>. In the MySensors protocol, the following internal messagetypes handles signature
* requirements and nonce requests:<br>
* @ref I_SIGNING_PRESENTATION <br>
* @ref I_NONCE_REQUEST <br>
* @ref I_NONCE_RESPONSE <br>
*
* Also, the version field in the header has been reduced from 3 to 2 bits in order to fit a single
* bit to indicate that a message is signed.
*
* @section MySigninggrpbackground Background and concepts
*
* Suppose two participants, Alice and Bob, wants to exchange a message. Alice sends a message to
* Bob. In MySensors “language” Alice could be a gateway and Bob an actuator (light switch,
* electronic lock, etc). But to be generic, we will substitute the term “gateway” with Alice and a
* “node” with Bob (although the reverse relationship is also supported).
*
* Alice sends a message to Bob. This message can be heard by anyone who wants to listen (and also
* by anyone that is within “hearing” distance). Normally, this is perhaps not a big issue. Nothing
* Alice says to Bob may be secret or sensitive in any way. However, sometimes (or perhaps always)
* Bob want to be sure that the message Bob receives actually came from Alice. In cryptography,
* this is known as <i>authenticity</i>. Bob needs some way of determining that the message is
* authentic from Alice, when Bob receives it. This prevents an eavesdropper, Eve, to trick Bob into
* thinking it was Alice that sent a message Eve in fact transmitted. Bob also needs to know how to
* determine if the message has been repeated. Eve could record a message sent by Alice that Bob
* accepted and then send the same message again. Eve could also in some way prevent Bob from
* receiving the message and delay it in order to permit the message to arrive to Bob at a time Eve
* chooses, instead of Alice. Such an attack is known as a <b>replay attack</b>.<br>
* <i>Authenticity</i> permits Bob to determine if Alice is the true sender of a message.
* @image html MySigning/alicenfriends.png
*
* It can also be interesting for Bob to know that the message Alice sent has not been tampered with
* in any way. This is the <i>integrity</i> of the message. We now introduce Mallory, who could be
* intercepting the communication between Alice and Bob and replace some parts of the message but
* keeping the parts that authenticate the message. That way, Bob still trusts Alice to be the
* source, but the contents of the message was not the content Alice sent. Bob needs to be able to
* determine that the contents of the message was not altered after Alice sent it.<br>
* Mallory would in this case be a <b>man-in-the-middle</b> attacker.<br>
* <i>Integrity</i> permits Bob to verify that the messages received from Alice has not been
* tampered with.<br>
* This is achieved by adding a <i>signature</i> to the message, which Bob can inspect to validate
* that Alice is the author.
* @image html MySigning/alicenfriends2.png
*
* The signing scheme used, needs to address both these attack scenarios. Neither Eve nor Mallory
* must be permitted to interfere with the message exchange between Alice and Bob.
*
* The key challenge to implementing a secure signing scheme is to ensure that every signature is
* different, even if the message is not. If not, <b>replay attacks</b> would be very hard to
* prevent.<br>
* One way of doing this is to increment some counter on the sender side and include it in the
* signature. This is however predictable.<br>
* A better option would be to introduce a random number to the signature. That way, it is
* impossible to predict what the signature will be. The problem is, that also makes it impossible
* for the receiver (Bob) to verify that the signature is valid.<br>
* A solution to this is to let Bob generate the random number, keep it in memory and send it to
* Alice. Alice can then use the random number in the signature calculation and send the signed
* message back to Bob who can validate the signature with the random number used.
* This random number is in cryptography known as a
* <a href="https://en.wikipedia.org/wiki/Cryptographic_nonce">nonce</a> or
* <a href="https://en.wikipedia.org/wiki/Salt_%28cryptography%29">salt</a>.
*
* However, Mallory might be eavesdropping on the communication and snoop up the nonce in order to
* generate a new valid signature for a different message. To counter this, both Alice and Bob keep
* a secret that only they know. This secret is never transmitted over the air,
* nor is it revealed to anybody. This secret is known as a
* <a href="https://en.wikipedia.org/wiki/Pre-shared_key"> pre-shared key</a> (PSK).
*
* If Eve or Mallory are really sophisticated, he/she might use a <b>delayed replay attack</b>.
* This can be done by allowing Bob to transmit a nonce to Alice. But when Alice transmits the
* uniquely signed message, Mallory prevents Bob from receiving it, to a point when Mallory
* decides Bob should receive it. An example of such an attack is described
* <a href="http://spencerwhyte.blogspot.se/2014/03/delay-attack-jam-intercept-and-replay.html">
* here</a>.<br>
* This needs to be addressed as well, and one way of doing this is to have Bob keep track of time
* between a transmitted nonce and a signed message to verify. If Bob is asked for a nonce, Bob
* knows that a signed message is going to arrive “soon”. Bob can then decide that if the signed
* message does not arrive within a predefined timeframe, Bob throws away the generated nonce and
* thus makes it impossible to verify the message if it arrives late.
*
* The flow can be described like this:
* @image html MySigning/alicenbob.png
* The benefits for MySensors to support this are obvious. Nobody wants others to be able to control
* or manipulate any actuators in their home.
*
* @section MySigninggrphow How this is done
*
* There exist many forms of message signature solutions to combat Eve and Mallory.<br>
* Most of these solutions are quite complex in term of computations, so I elected to use an
* algorithm that an external circuit is able to process. This has the added benefit of protecting
* any keys and intermediate data used for calculating the signature so that even if someone were to
* actually steal a sensor and disassembled it, they would not be able to extract the keys and other
* information from the device.<br>
* A common scheme for message signing (authenticity and integrity) is implemented using
* <a href="http://en.wikipedia.org/wiki/Hash-based_message_authentication_code">HMAC</a> which in
* combination with a strong <a href="http://en.wikipedia.org/wiki/Hash_function"> hash function</a>
* provides a very strong level of protection.<br>
* The <a href="http://www.atmel.com/devices/ATSHA204A.aspx">Atmel ATSHA204A</a> is a low-cost,
* low-voltage/current circuit that provides HMAC calculation capabilities with SHA256 hashing which
* is a (currently) virtually unbreakable combination. If SHA256 were to be hacked, a certain
* <a href="http://en.wikipedia.org/wiki/Bitcoin">cryptocurrency</a> would immediately be rendered
* worthless.<br>
* The ATSHA device also contain a random number generator (RNG) which enables the generation of a
* good nonce, as in, <i>non-predictable</i>.<br>
* As I acknowledge that some might not want to use an additional external circuit, I have also
* implemented a software version of the ATSHA device, capable of generating the same signatures as
* the ATSHA device does. Because it is pure-software however, it does not provide as good nonces
* (it uses the <a href="http://arduino.cc/en/reference/random">Arduino pseudo-random generator</a>)
* and the HMAC key is stored in SW and is therefore readable if the memory is dumped. It also
* naturally claims more flash space due to the more complex software. But for indoor
* sensors/actuators this might be good enough for most people.
*
* @section MySigninggrpencryption Why encryption is not part of this
*
* Well, some could be uncomfortable with somebody being able to snoop temperatures, motion or the
* state changes of locks in the environment.
* Signing does <b>not</b> address these issues. Encryption is needed to prevent this.<br>
* It is my personal standpoint that encryption should not be part of the MySensors “protocol”. The
* reason is that a gateway and a node does not really care about messages being readable or not by
* “others”. It makes more sense that such guarantees are provided by the underlying transmission
* layer (RF solution in this case). It is the information transmitted over the air that needs to be
* secret (if user so desires). The “trust” level on the other hand needs to go all the way into the
* sketches (who might have different requirements of trust depending on the message participant),
* and for this reason, it is more important (and less complicated) to ensure authenticity and
* <i>integrity</i> at protocol-level as message contents is still readable throughout the protocol
* stack. But as soon as the message leaves the “stack” it can be scramble into “garbage” when
* transmitted over the air and then reassembled by a receiving node before being fed in “the clear”
* up the stack at the receiving end.
*
* There are methods and possibilities to provide encryption also in software, but if this is done,
* it is my recommendation that this is done after integrity- and authentication information has
* been provided to the message (if this is desired). Integrity and authentication is of course not
* mandatory and some might be happy with only having encryption to cover their need for security.
* I, however, have only focused on <i>integrity</i> and <i>authenticity</i> while at the same time
* keeping the current message routing mechanisms intact and therefore leave the matter of
* <i>secrecy</i> to be implemented in the “physical” transport layer. With the <i>integrity</i> and
* <i>authenticity</i> handled in the protocol it ought to be enough for a simple encryption
* (nonce-less %AES with a PSK for instance) on the message as it is sent to the RF backend. Atmel
* does provide such circuits as well but I have not investigated the matter further as it given the
* current size of the ethernet gateway sketch is close to the size limit on an Arduino Nano, so it
* will be difficult to fit this into some existing gateway designs.<br>
* Also it is worth to consider that the state of a lock can just as readily be determined by simply
* looking at the door in question or attempting to open it, so obfuscating this information will
* not necessarily deter an attacker in any way.<br>
* Nevertheless, I do acknowledge that people find the fact that all information is sent “in the
* clear” even if it require some technical effort for an intruder to obtain and inspect this
* information. So I do encourage the use of encrypting transport layers.<br>
* This is however not covered by this implementation.<br>
* This might change in the future as more powerful platforms emerge which permit more complex
* security schemes and better hardware acceleration.
*/
/** @}*/
/**
* @defgroup MySigninggrp MySigning
* @ingroup internals
* @{
*
* @brief API declaration for MySigning signing backend
*
* @see MySigninggrpPub
*/
/**
* @file MySigning.h
*
* @brief API declaration for MySigning signing backend
*/
#ifndef MySigning_h
#define MySigning_h
#include "MySensorsCore.h"
#include "drivers/ATSHA204/ATSHA204.h"
#ifdef MY_SIGNING_NODE_WHITELISTING
typedef struct {
uint8_t nodeId; /**< @brief The ID of the node */
uint8_t serial[SHA204_SERIAL_SZ]; /**< @brief Node specific serial number */
} whitelist_entry_t;
#endif
/** @brief Helper macro to determine the number of elements in a array */
#define NUM_OF(x) (sizeof(x)/sizeof(x[0]))
/**
* @brief Initializes signing infrastructure and associated backend.
*
* This function makes sure that the internal states of the signing infrastructure
* is set to a known initial state.
* \n@b Usage: This function should be called before any signing related operations take place.
*/
void signerInit(void);
/**
* @brief Does signing specific presentation for a node.
*
* This function makes sure any signing related presentation info is shared with the other part.
* The presentation of the gateways signing preferences is done in @ref signerProcessInternal().
* \n@b Usage: This function should be called by the presentation routine of the MySensors library.
* You only need to call this directly from a sketch to set up a node to node signed message exchange.
* If you do call this directly from a sketch, and you at some point change your sketch to go from
* requiring signing to not requiring signatures, you need to present this change to the node at least
* once, so it can update its requirements tables accordingly. Or it will keep believing that this node
* require signatures and attempt to send signed messages to it.
*
* @param msg Message buffer to use.
* @param destination Node ID of the destination.
*/
void signerPresentation(MyMessage &msg, uint8_t destination);
/**
* @brief Manages internal signing message handshaking.
*
* This function takes care of signing related message handshaking such as nonce exchange.
* \n@b Usage: This function should be called by the incoming message handler before any further message
* processing is performed on internal messages. This function should only be called for @ref C_INTERNAL class
* messages.
*
* @param msg Message buffer to process.
* @returns @c true if caller should stop further message processing.
*/
bool signerProcessInternal(MyMessage &msg);
/**
* @brief Check timeout of verification session.
*
* Nonce will be purged if it takes too long for a signed message to be sent to the receiver.
* \n@b Usage: This function should be called on regular intervals, typically within some process loop.
*
* @returns @c true if session is still valid.
*/
bool signerCheckTimer(void);
/**
* @brief Get nonce from provided message and store for signing operations.
*
* Returns @c false if subsystem is busy processing an ongoing signing operation.<br>
* Returns @c false if signing identifier found in message is not supported by the used backend.<br>
* If successful, this marks the start of a signing operation at the sending side so
* implementation is expected to do any necessary initializations within this call.
* \n@b Usage: This function is typically called as action when receiving a @ref I_NONCE_RESPONSE
* message.
*
* @param msg The message to get the nonce from.
* @returns @c true if successful, else @c false.
*/
bool signerPutNonce(MyMessage &msg);
/**
* @brief Signs provided message. All remaining space in message payload buffer is used for
* signing identifier and signature.
*
* Nonce used for signature calculation is the one generated previously within @ref signerProcessInternal().<br>
* Nonce will be cleared when this function is called to prevent re-use of nonce.<br>
* Returns @c false if subsystem is busy processing an ongoing signing operation.<br>
* Returns @c false if not two bytes or more of free payload space is left in provided message.<br>
* This ends a signing operation at the sending side so implementation is expected to do any
* deinitializations and enter a power saving state within this call.
* \n@b Usage: This function is typically called as action when receiving a @ref I_NONCE_RESPONSE
* message and after @ref signerPutNonce() has successfully been executed.
*
* @param msg The message to sign.
* @returns @c true if successful, else @c false.
*/
bool signerSignMsg(MyMessage &msg);
/**
* @brief Verifies signature in provided message.
*
* Nonce used for verification is the one previously set using @ref signerPutNonce().<br>
* Nonce will be cleared when this function is called to prevent re-use of nonce.<br>
* Returns @c false if subsystem is busy processing an ongoing signing operation.<br>
* Returns @c false if signing identifier found in message is not supported by the used backend.<br>
* This ends a signing operation at the receiving side so implementation is expected to do any
* deinitializations and enter a power saving state within this call.
* \n@b Usage: This function is typically called when receiving a message that is flagged as signed
* and @ref MY_SIGNING_REQUEST_SIGNATURES is activated.
*
* @param msg The message to verify.
* @returns @c true if successful, else @c false.
*/
bool signerVerifyMsg(MyMessage &msg);
/**
* @brief Do a timing neutral memory comparison.
*
* The function behaves similar to memcmp with the difference that it will
* always use the same number of instructions for a given number of bytes,
* no matter how the two buffers differ and the response is either 0 or -1.
*
* @param a First buffer for comparison.
* @param b Second buffer for comparison.
* @param sz The number of bytes to compare.
* @returns 0 if buffers match, -1 if they do not.
*/
int signerMemcmp(const void* a, const void* b, size_t sz);
#endif
/** @}*/
/**
* @defgroup MySigningDebugMessages Signing related debug messages
* @ingroup MySigninggrpPub
* @{
*
* @brief Explanation of the abstract signing related debug messages
*
* MySigning-related log messages, format: [!]SYSTEM:SUB SYSTEM:MESSAGE
* - [!] Exclamation mark is prepended in case of error or warning
* - SYSTEM:
* - <b>SGN</b> messages emitted by MySigning
* - SUB SYSTEMS:
* - SGN:<b>INI</b> from @ref signerInit
* - SGN:<b>PER</b> from @ref signerInit
* - SGN:<b>PRE</b> from @ref signerPresentation
* - SGN:<b>SGN</b> from @ref signerSignMsg
* - SGN:<b>VER</b> from @ref signerVerifyMsg
* - SGN:<b>SKP</b> from @ref signerSignMsg or @ref signerVerifyMsg (skipSign)
* - SGN:<b>NCE</b> from @ref signerProcessInternal (signerInternalProcessNonceRequest)
* - SGN:<b>BND</b> from the signing backends
*
* MySigning debug log messages:
*
* |E| SYS | SUB | Message | Comment
* |-|-----|-----|--------------------------|----------------------------------------------------------------------------
* | | SGN | INI | BND OK | Backend has initialized ok
* |!| SGN | INI | BND FAIL | Backend has not initialized ok
* | | SGN | PER | OK | Personalization data is ok
* |!| SGN | PER | TAMPERED | Personalization data has been tampered
* | | SGN | PRE | SGN REQ | Signing required
* | | SGN | PRE | SGN REQ,TO='node' | Tell 'node' that we require signing
* | | SGN | PRE | SGN REQ,FROM='node' | Node 'node' require signing
* | | SGN | PRE | SGN NREQ | Signing not required
* | | SGN | PRE | SGN REQ,TO='node' | Tell 'node' that we do not require signing
* | | SGN | PRE | SGN NREQ,FROM='node' | Node 'node' does not require signing
* |!| SGN | PRE | SGN NREQ,FROM='node' REJ | Node 'node' does not require signing but used to (requirement remain unchanged)
* | | SGN | PRE | WHI REQ | Whitelisting required
* | | SGN | PRE | WHI REQ,TO='node' | Tell 'node' that we require whitelisting
* | | SGN | PRE | WHI REQ,FROM='node' | Node 'node' require whitelisting
* | | SGN | PRE | WHI NREQ | Whitelisting not required
* | | SGN | PRE | WHI NREQ,TO='node' | Tell 'node' that we do not require whitelisting
* | | SGN | PRE | WHI NREQ,FROM='node' | Node 'node' does not require whitelisting
* |!| SGN | PRE | WHI NREQ,FROM='node' REJ | Node 'node' does not require whitelisting but used to (requirement remain unchanged)
* | | SGN | PRE | XMT,TO='node' | Presentation data transmitted to 'node'
* |!| SGN | PRE | XMT,TO='node' FAIL | Presentation data not properly transmitted to 'node'
* | | SGN | PRE | WAIT GW | Waiting for gateway presentation data
* |!| SGN | PRE | VER='version' | Presentation version 'version' is not supported
* | | SGN | PRE | NSUP | Received signing presentation but signing is not supported
* | | SGN | PRE | NSUP,TO='node' | Informing 'node' that we do not support signing
* | | SGN | SGN | NCE REQ,TO='node' | Nonce request transmitted to 'node'
* |!| SGN | SGN | NCE REQ,TO='node' FAIL | Nonce request not properly transmitted to 'node'
* |!| SGN | SGN | NCE TMO | Timeout waiting for nonce
* | | SGN | SGN | SGN | Message signed
* |!| SGN | SGN | SGN FAIL | Message failed to be signed
* | | SGN | SGN | NREQ='node' | 'node' does not require signed messages
* | | SGN | SGN | 'sender'!='us' NUS | Will not sign because 'sender' is not 'us' (repeater)
* |!| SGN | SGN | STATE | Security system in a invalid state (personalization data tampered)
* |!| SGN | VER | NSG | Message was not signed, but it should have been
* |!| SGN | VER | FAIL | Verification failed
* | | SGN | VER | OK | Verification succeeded
* | | SGN | VER | LEFT='number' | 'number' of failed verifications left in a row before node is locked
* |!| SGN | VER | STATE | Security system in a invalid state (personalization data tampered)
* | | SGN | SKP | MSG CMD='cmd',TYPE='type'| Message with command 'cmd' and type 'type' does not need to be signed
* | | SGN | SKP | ECHO CMD='cmd',TYPE='type'| ECHO messages does not need to be signed
* | | SGN | NCE | LEFT='number' | 'number' of nonce requests between successful verifications left before node is locked
* | | SGN | NCE | XMT,TO='node' | Nonce data transmitted to 'node'
* |!| SGN | NCE | XMT,TO='node' FAIL | Nonce data not properly transmitted to 'node'
* |!| SGN | NCE | GEN | Failed to generate nonce
* | | SGN | NCE | NSUP (DROPPED) | Ignored nonce/request for nonce (signing not supported)
* | | SGN | NCE | FROM='node' | Received nonce from 'node'
* | | SGN | NCE | 'sender'!='dst' (DROPPED)| Ignoring nonce as it did not come from the designation of the message to sign
* |!| SGN | BND | INIT FAIL | Failed to initialize signing backend
* |!| SGN | BND | PWD<8 | Signing password too short
* |!| SGN | BND | PER | Backend not personalized
* |!| SGN | BND | SER | Could not get device unique serial from backend
* |!| SGN | BND | TMR | Backend timed out
* |!| SGN | BND | SIG,SIZE,'message'>'max' | Refusing to sign 'message' because it is bigger than 'max' allowed size
* | | SGN | BND | SIG WHI,ID='id' | Salting message with our 'id'
* | | SGN | BND | SIG WHI,SERIAL='serial' | Salting message with our 'serial'
* |!| SGN | BND | VER ONGOING | Verification failed, no ongoing session
* |!| SGN | BND | VER,IDENT='identifier' | Verification failed, 'identifier' is unknown
* | | SGN | BND | VER WHI,ID='sender' | 'sender' found in whitelist
* | | SGN | BND | VER WHI,SERIAL='serial' | Expecting 'serial' for this sender
* |!| SGN | BND | VER WHI,ID='id' MISSING | 'id' not found in whitelist
* | | SGN | BND | NONCE='nonce' | Calculating signature using 'nonce'
* | | SGN | BND | HMAC='hmac' | Calculated signature is 'hmac'
*/
/** @}*/
/**
* @defgroup MySigningTroubleshootinggrp Signing troubleshooting
* @ingroup MySigninggrpPub
* @{
*
* @brief Typical signing related failure cases and how to solve them
*
* @section MySigningTroubleshootingSymptoms Symptoms and solutions
*
* The first thing to do if you suspect signing is causing problems, is to enable the verbose debug
* flag for the signing backend. @see MY_DEBUG_VERBOSE_SIGNING
*
* If you are having trouble getting signing to work, please see the following troubleshooting tips.
*
* @subsection MySigningTroubleshootingSymptomStFail Signing fails and logs show st=fail on transmissions
*
* This is actually not a signing problem, although often st=fail becomes st=ok when signing is disabled.
* This is by far the most commonly reported problem with signing, but the problems is not with signing,
* it is with radio performance.<br>
* This is a typical log which might look like a signing related issue but isn't:
* @code{.unparsed}
* 0;255;3;0;9;Skipping security for command 3 type 16
* 0;255;3;0;9;read: 3-3-0 s=255,c=3,t=16,pt=0,l=0,sg=0:
* 0;255;3;0;9;Signing backend: ATSHA204Soft
* 0;255;3;0;9;SHA256: 86DEAE1DAF50D577A4E2262B33ABF9DEE05DD8FAF84F94F50900000000000000
* 0;255;3;0;9;Skipping security for command 3 type 17
* 0;255;3;0;9;send: 0-0-3-3 s=255,c=3,t=17,pt=6,l=25,sg=0,st=fail:86DEAE1DAF50D577A4E2262B33ABF9DEE05DD8FAF84F94F509
* 0;255;3;0;9;Failed to transmit nonce!
* 0;255;3;0;9;Message is not signed, but it should have been!
* 0;255;3;0;9;verify fail
* @endcode
*
* The reason for this is that when signing is used, the messages transmitted become relatively large.<br>
* Because of this, the message is more sensitive to noise, and the chance for a message to get scrambled
* increase with the message size. Please refer to the troubleshooting section at the MySensors forum for
* information on how to improve radio performance.<br>
* This is a good place to start: https://forum.mysensors.org/topic/666/debug-faq-and-how-ask-for-help
*
* @subsection MySigningTroubleshootingSymptomNonce Failed to generate nonce
*
* The signing backend failed to generate the nonce needed to sign a message. This indicate a hardware
* problem. Please post the debug info on the forum together with a description of your hardware setup.
*
* @subsection MySigningTroubleshootingSymptomSign Failed to sign message
*
* The signing backend failed to sign the message. Typically this happens if your message is so large,
* that there is no room left in the buffer to store a signature.
*
* @subsection MySigningTroubleshootingSymptomWrongSource Nonce did not come from the destination (XX) of the message to be signed! It came from YY
*
* This should not really happen. The reason for this message is that the signing backend is only capable
* of handling one signed message session at any time. If for some reason multiple nodes send a nonce message to
* the same node, only the nonce from a node that is the destination of the current message signing session will be
* accepted. Any other nonces will be dropped. This should not happen as no node should send a nonce unless asked to,
* and a node will only ask for a nonce to one destination for every signing session.<br>
* If you see this message, please post the debugging details on the MySensors forum so it can be investigated further
* together with a description of your setup.
*
* @subsection MySigningTroubleshootingSymptomNotSigned Message is not signed, but it should have been
*
* A node has failed to comply with the signing preferences of this node. Check that the node has received a
* signing presentation message from this node. This is automatically transmitted to gateways. For other nodes,
* you need to transmit the presentation from the sketch. @see signerPresentation
*
* @subsection MySigningTroubleshootingSymptomNotSignedGeneral "Messages do not appear to be signed but I think they should be..."
*
* Make sure you have enabled the flag to require signatures to require signatures and have enabled one of the signing
* backends. @see MY_SIGNING_REQUEST_SIGNATURES @see MY_SIGNING_ATSHA204 @see MY_SIGNING_SOFT
*
* @subsection MySigningTroubleshootingSymptomNotWorkingWhitelisting Signature verification failed!
*
* Make sure both source and destination of the signed message has undergone @ref personalization with the same HMAC key.<br>
* Also, if whitelisting is used, make sure the proper serial is paired with the proper node ID at the destination.
* Whitelisting preferences are communicated with the signing presentation (done automatically from nodes to gateway but
* has to be explicitly done by sketch for node to node communication). @see signerPresentation
*
* @subsection MySigningTroubleshootingSymptomStTampered Signing backend reports tampered even after personalization
*
* The signing backend validates that the secure elements in EEPROM remain unmodified after personalization using a checksum. If the check fails,
* the backend reports
* @code
* !SGN:PER:Tampered
* @endcode
* This usually indicate that the sketch has modified the secure elements in EEPROM, but if you experience this even after a node is freshly
* personalized on a atmega device, it could be that the EESAVE fuse bit is not set which mean that the EEPROM is erased when a new firmware is flashed.
* You will need to enable the EESAVE bit in order to have the security personalization persist in the node.
*/
/** @}*/

View File

@@ -0,0 +1,357 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*
*******************************
*
* DESCRIPTION
* Signing support created by Patrick "Anticimex" Fallberg <patrick@fallberg.net>
* ATSHA204 signing backend. The Atmel ATSHA204 offers true random number generation and
* HMAC-SHA256 authentication with a readout-protected key.
*
*/
#include "MySigning.h"
#include "MyHelperFunctions.h"
#ifdef MY_SIGNING_ATSHA204
#define SIGNING_IDENTIFIER (1) //HMAC-SHA256
#if defined(MY_DEBUG_VERBOSE_SIGNING)
#define SIGN_DEBUG(x,...) DEBUG_OUTPUT(x, ##__VA_ARGS__)
#else
#define SIGN_DEBUG(x,...)
#endif
static unsigned long _signing_timestamp;
static bool _signing_verification_ongoing = false;
static uint8_t _signing_verifying_nonce[32+9+1];
static uint8_t _signing_signing_nonce[32+9+1];
static uint8_t _signing_temp_message[SHA_MSG_SIZE];
static uint8_t _signing_rx_buffer[SHA204_RSP_SIZE_MAX];
static uint8_t _signing_tx_buffer[SHA204_CMD_SIZE_MAX];
static uint8_t* const _signing_hmac = &_signing_rx_buffer[SHA204_BUFFER_POS_DATA];
static uint8_t _signing_node_serial_info[9];
#ifdef MY_SIGNING_NODE_WHITELISTING
static const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING;
#endif
static bool init_ok = false;
static void signerCalculateSignature(MyMessage &msg, bool signing);
static uint8_t* signerAtsha204AHmac(const uint8_t* nonce, const uint8_t* data);
static uint8_t* signerSha256(const uint8_t* data, size_t sz);
bool signerAtsha204Init(void)
{
init_ok = true;
atsha204_init(MY_SIGNING_ATSHA204_PIN);
(void)atsha204_wakeup(_signing_temp_message);
// Read the configuration lock flag to determine if device is personalized or not
if (atsha204_read(_signing_tx_buffer, _signing_rx_buffer,
SHA204_ZONE_CONFIG, 0x15<<2) != SHA204_SUCCESS) {
SIGN_DEBUG(PSTR("!SGN:BND:INIT FAIL\n")); //Could not read ATSHA204A lock config
init_ok = false;
} else if (_signing_rx_buffer[SHA204_BUFFER_POS_DATA+3] != 0x00) {
SIGN_DEBUG(PSTR("!SGN:BND:PER\n")); //ATSHA204A not personalized
init_ok = false;
}
if (init_ok) {
// Get and cache the serial of the ATSHA204A
if (atsha204_getSerialNumber(_signing_node_serial_info) != SHA204_SUCCESS) {
SIGN_DEBUG(PSTR("!SGN:BND:SER\n")); //Could not get ATSHA204A serial
init_ok = false;
}
}
return init_ok;
}
bool signerAtsha204CheckTimer(void)
{
if (!init_ok) {
return false;
}
if (_signing_verification_ongoing) {
unsigned long time_now = hwMillis();
// If timestamp is taken so late a rollover can take place during the timeout,
// offset both timestamp and current time to make sure no rollover takes place during the
// timeout
if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < _signing_timestamp) {
_signing_timestamp += MY_VERIFICATION_TIMEOUT_MS;
time_now += MY_VERIFICATION_TIMEOUT_MS;
}
if (time_now > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) {
SIGN_DEBUG(PSTR("!SGN:BND:TMR\n")); //Verification timeout
// Purge nonce
memset(_signing_signing_nonce, 0xAA, 32);
memset(_signing_verifying_nonce, 0xAA, 32);
_signing_verification_ongoing = false;
return false;
}
}
return true;
}
bool signerAtsha204GetNonce(MyMessage &msg)
{
if (!init_ok) {
return false;
}
// We used a basic whitening technique that XORs each byte in a 32byte random value with current
// hwMillis() counter. This 32-byte random value is then hashed (SHA256) to produce the resulting
// nonce
(void)atsha204_wakeup(_signing_temp_message);
if (atsha204_execute(SHA204_RANDOM, RANDOM_SEED_UPDATE, 0, 0, NULL,
RANDOM_COUNT, _signing_tx_buffer, RANDOM_RSP_SIZE, _signing_rx_buffer) !=
SHA204_SUCCESS) {
return false;
}
for (int i = 0; i < 32; i++) {
_signing_verifying_nonce[i] = _signing_rx_buffer[SHA204_BUFFER_POS_DATA+i] ^ (hwMillis()&0xFF);
}
(void)memcpy((void *)_signing_verifying_nonce, (const void *)signerSha256(_signing_verifying_nonce,
32),
min((uint8_t)MAX_PAYLOAD_SIZE, 32u));
// We just idle the chip now since we expect to use it soon when the signed message arrives
atsha204_idle();
if (MAX_PAYLOAD_SIZE < 32) {
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
(void)memset((void *)&_signing_verifying_nonce[MAX_PAYLOAD_SIZE], 0xAA, 32u - MAX_PAYLOAD_SIZE);
}
// Transfer the first part of the nonce to the message
msg.set(_signing_verifying_nonce, min((uint8_t)MAX_PAYLOAD_SIZE, 32u));
_signing_verification_ongoing = true;
_signing_timestamp = hwMillis(); // Set timestamp to determine when to purge nonce
return true;
}
void signerAtsha204PutNonce(MyMessage &msg)
{
if (!init_ok) {
return;
}
(void)memcpy((void *)_signing_signing_nonce, (const void *)msg.getCustom(),
min((uint8_t)MAX_PAYLOAD_SIZE, 32u));
if (MAX_PAYLOAD_SIZE < 32u) {
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
(void)memset((void *)&_signing_signing_nonce[MAX_PAYLOAD_SIZE], 0xAA, 32u - MAX_PAYLOAD_SIZE);
}
}
bool signerAtsha204SignMsg(MyMessage &msg)
{
// If we cannot fit any signature in the message, refuse to sign it
if (msg.getLength() > MAX_PAYLOAD_SIZE - 2) {
SIGN_DEBUG(PSTR("!SGN:BND:SIG,SIZE,%" PRIu8 ">%" PRIu8 "\n"), msg.getLength(),
MAX_PAYLOAD_SIZE - 2); //Message too large
return false;
}
// Calculate signature of message
msg.setSigned(true); // make sure signing flag is set before signature is calculated
signerCalculateSignature(msg, true);
#if defined(MY_SIGNING_NODE_WHITELISTING)
if (DO_WHITELIST(msg.destination)) {
// Salt the signature with the senders nodeId and the unique serial of the ATSHA device
// We can reuse the nonce buffer now since it is no longer needed
memcpy(_signing_signing_nonce, _signing_hmac, 32);
_signing_signing_nonce[32] = msg.getSender();
memcpy(&_signing_signing_nonce[33], _signing_node_serial_info, 9);
// We can 'void' sha256 because the hash is already put in the correct place
(void)signerSha256(_signing_signing_nonce, 32+1+9);
SIGN_DEBUG(PSTR("SGN:BND:SIG WHI,ID=%" PRIu8 "\n"), msg.getSender());
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(_signing_node_serial_info, 9);
SIGN_DEBUG(PSTR("SGN:BND:SIG WHI,SERIAL=%s\n"), hwDebugPrintStr);
#endif
}
#endif
// Put device back to sleep
atsha204_sleep();
// Overwrite the first byte in the signature with the signing identifier
_signing_hmac[0] = SIGNING_IDENTIFIER;
// Transfer as much signature data as the remaining space in the message permits
(void)memcpy((void *)&msg.data[msg.getLength()], (const void *)_signing_hmac,
min(MAX_PAYLOAD_SIZE - msg.getLength(), 32));
return true;
}
bool signerAtsha204VerifyMsg(MyMessage &msg)
{
if (!_signing_verification_ongoing) {
SIGN_DEBUG(PSTR("!SGN:BND:VER ONGOING\n"));
return false;
} else {
// Make sure we have not expired
if (!signerCheckTimer()) {
return false;
}
_signing_verification_ongoing = false;
if (msg.data[msg.getLength()] != SIGNING_IDENTIFIER) {
SIGN_DEBUG(PSTR("!SGN:BND:VER,IDENT=%" PRIu8 "\n"), msg.data[msg.getLength()]);
return false;
}
signerCalculateSignature(msg, false); // Get signature of message
#ifdef MY_SIGNING_NODE_WHITELISTING
// Look up the senders nodeId in our whitelist and salt the signature with that data
size_t j;
for (j=0; j < NUM_OF(_signing_whitelist); j++) {
if (_signing_whitelist[j].nodeId == msg.getSender()) {
// We can reuse the nonce buffer now since it is no longer needed
memcpy(_signing_verifying_nonce, _signing_hmac, 32);
_signing_verifying_nonce[32] = msg.getSender();
memcpy(&_signing_verifying_nonce[33], _signing_whitelist[j].serial, 9);
// We can 'void' sha256 because the hash is already put in the correct place
(void)signerSha256(_signing_verifying_nonce, 32+1+9);
SIGN_DEBUG(PSTR("SGN:BND:VER WHI,ID=%" PRIu8 "\n"), msg.getSender());
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(_signing_whitelist[j].serial, 9);
SIGN_DEBUG(PSTR("SGN:BND:VER WHI,SERIAL=%s\n"), hwDebugPrintStr);
#endif
break;
}
}
if (j == NUM_OF(_signing_whitelist)) {
SIGN_DEBUG(PSTR("!SGN:BND:VER WHI,ID=%" PRIu8 " MISSING\n"), msg.getSender());
// Put device back to sleep
atsha204_sleep();
return false;
}
#endif
// Put device back to sleep
atsha204_sleep();
// Overwrite the first byte in the signature with the signing identifier
_signing_hmac[0] = SIGNING_IDENTIFIER;
// Compare the calculated signature with the provided signature
if (signerMemcmp(&msg.data[msg.getLength()], _signing_hmac,
min(MAX_PAYLOAD_SIZE - msg.getLength(), 32))) {
return false;
} else {
return true;
}
}
}
// Helper to calculate signature of msg (returned in _signing_rx_buffer[SHA204_BUFFER_POS_DATA])
// (=_signing_hmac)
static void signerCalculateSignature(MyMessage &msg, bool signing)
{
// Signature is calculated on everything expect the first byte in the header
uint16_t bytes_left = msg.getLength()+HEADER_SIZE-1;
int16_t current_pos = 1-(int16_t)HEADER_SIZE; // Start at the second byte in the header
uint8_t* nonce = signing ? _signing_signing_nonce : _signing_verifying_nonce;
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(nonce, 32);
SIGN_DEBUG(PSTR("SGN:BND:NONCE=%s\n"), hwDebugPrintStr);
#endif
while (bytes_left) {
uint16_t bytes_to_include = min(bytes_left, 32);
(void)atsha204_wakeup(_signing_temp_message); // Issue wakeup to reset watchdog
memset(_signing_temp_message, 0, 32);
memcpy(_signing_temp_message, (uint8_t*)&msg.data[current_pos], bytes_to_include);
// We can 'void' signerAtsha204AHmac because the HMAC is already put in the correct place
(void)signerAtsha204AHmac(nonce, _signing_temp_message);
// Purge nonce when used
memset(nonce, 0xAA, 32);
bytes_left -= bytes_to_include;
current_pos += bytes_to_include;
if (bytes_left > 0) {
// We will do another pass, use current HMAC as nonce for the next HMAC
memcpy(nonce, _signing_hmac, 32);
atsha204_idle(); // Idle the chip to allow the wakeup call to reset the watchdog
}
}
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(_signing_hmac, 32);
SIGN_DEBUG(PSTR("SGN:BND:HMAC=%s\n"), hwDebugPrintStr);
#endif
}
// Helper to calculate a ATSHA204A specific HMAC-SHA256 using provided 32 byte nonce and data
// (zero padded to 32 bytes)
// The pointer to the HMAC is returned, but the HMAC is also stored in
// _signing_rx_buffer[SHA204_BUFFER_POS_DATA] (=_signing_hmac)
static uint8_t* signerAtsha204AHmac(const uint8_t* nonce, const uint8_t* data)
{
// Program the data to sign into the ATSHA204
(void)atsha204_execute(SHA204_WRITE, SHA204_ZONE_DATA | SHA204_ZONE_COUNT_FLAG, 8 << 3, 32,
(uint8_t*)data,
WRITE_COUNT_LONG, _signing_tx_buffer, WRITE_RSP_SIZE, _signing_rx_buffer);
// Program the nonce to use for the signature (has to be done just before GENDIG
// due to chip limitations)
(void)atsha204_execute(SHA204_NONCE, NONCE_MODE_PASSTHROUGH, 0, 32, (uint8_t*)nonce,
NONCE_COUNT_LONG, _signing_tx_buffer, NONCE_RSP_SIZE_SHORT,
_signing_rx_buffer);
// Generate digest of data and nonce
(void)atsha204_execute(SHA204_GENDIG, GENDIG_ZONE_DATA, 8, 0, NULL,
GENDIG_COUNT_DATA, _signing_tx_buffer, GENDIG_RSP_SIZE,
_signing_rx_buffer);
// Calculate HMAC of message+nonce digest and secret key
(void)atsha204_execute(SHA204_HMAC, HMAC_MODE_SOURCE_FLAG_MATCH, 0, 0, NULL,
HMAC_COUNT, _signing_tx_buffer, HMAC_RSP_SIZE, _signing_rx_buffer);
return &_signing_rx_buffer[SHA204_BUFFER_POS_DATA];
}
// Helper to calculate a generic SHA256 digest of provided buffer (only supports one block)
// The pointer to the hash is returned, but the hash is also stored in
// _signing_rx_buffer[SHA204_BUFFER_POS_DATA])
static uint8_t* signerSha256(const uint8_t* data, size_t sz)
{
// Initiate SHA256 calculator
(void)atsha204_execute(SHA204_SHA, SHA_INIT, 0, 0, NULL,
SHA_COUNT_SHORT, _signing_tx_buffer, SHA_RSP_SIZE_SHORT,
_signing_rx_buffer);
// Calculate a hash
memset(_signing_temp_message, 0x00, SHA_MSG_SIZE);
memcpy(_signing_temp_message, data, sz);
_signing_temp_message[sz] = 0x80;
// Write length data to the last bytes
_signing_temp_message[SHA_MSG_SIZE-2] = (sz >> 5);
_signing_temp_message[SHA_MSG_SIZE-1] = (sz << 3);
(void)atsha204_execute(SHA204_SHA, SHA_CALC, 0, SHA_MSG_SIZE, _signing_temp_message,
SHA_COUNT_LONG, _signing_tx_buffer, SHA_RSP_SIZE_LONG, _signing_rx_buffer);
return &_signing_rx_buffer[SHA204_BUFFER_POS_DATA];
}
#endif //MY_SIGNING_ATSHA204

View File

@@ -0,0 +1,374 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*
*******************************
*
* DESCRIPTION
* Signing support created by Patrick "Anticimex" Fallberg <patrick@fallberg.net>
* ATSHA204 emulated signing backend. The emulated ATSHA204 implementation offers pseudo random
* number generation and HMAC-SHA256 authentication compatible with a "physical" ATSHA204.
* NOTE: Key is stored in clear text in the Arduino firmware. Therefore, the use of this back-end
* could compromise the key used in the signed message infrastructure if device is lost and its
* memory dumped.
*
*/
#include "MySigning.h"
#include "MyHelperFunctions.h"
#ifdef MY_SIGNING_SOFT
#define SIGNING_IDENTIFIER (1) //HMAC-SHA256
#if defined(MY_DEBUG_VERBOSE_SIGNING)
#define SIGN_DEBUG(x,...) DEBUG_OUTPUT(x, ##__VA_ARGS__)
#else
#define SIGN_DEBUG(x,...)
#endif
static unsigned long _signing_timestamp;
static bool _signing_verification_ongoing = false;
static bool _signing_init_ok = false;
static uint8_t _signing_verifying_nonce[32+9+1];
static uint8_t _signing_nonce[32+9+1];
static uint8_t _signing_hmac_key[SIZE_SIGNING_SOFT_HMAC_KEY];
static uint8_t _signing_hmac[32];
static uint8_t _signing_node_serial_info[SIZE_SIGNING_SOFT_SERIAL];
#ifdef MY_SIGNING_NODE_WHITELISTING
static const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING;
#endif
static void signerCalculateSignature(MyMessage &msg, const bool signing);
static void signerAtsha204AHmac(uint8_t *dest, const uint8_t *nonce, const uint8_t *data);
bool signerAtsha204SoftInit(void)
{
_signing_init_ok = true;
// initialize pseudo-RNG
hwRandomNumberInit();
// Set secrets
#ifdef MY_SIGNING_SIMPLE_PASSWD
if (strnlen(MY_SIGNING_SIMPLE_PASSWD, 32) < 8) {
SIGN_DEBUG(PSTR("!SGN:BND:PWD<8\n")); //Password is too short to be acceptable
_signing_init_ok = false;
} else {
(void)memset((void *)_signing_hmac_key, 0x00, sizeof(_signing_hmac_key));
(void)memcpy((void *)_signing_hmac_key, MY_SIGNING_SIMPLE_PASSWD, strnlen(MY_SIGNING_SIMPLE_PASSWD,
32));
(void)memset((void *)_signing_node_serial_info, 0x00, sizeof(_signing_node_serial_info));
(void)memcpy((void *)_signing_node_serial_info, MY_SIGNING_SIMPLE_PASSWD,
strnlen(MY_SIGNING_SIMPLE_PASSWD, 8));
_signing_node_serial_info[8] = getNodeId();
}
#else
hwReadConfigBlock((void *)_signing_hmac_key, (void *)EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS,
SIZE_SIGNING_SOFT_HMAC_KEY);
hwReadConfigBlock((void *)_signing_node_serial_info, (void *)EEPROM_SIGNING_SOFT_SERIAL_ADDRESS,
SIZE_SIGNING_SOFT_SERIAL);
#endif
uint16_t chk = 0;
for (uint8_t i = 0; i < SIZE_SIGNING_SOFT_SERIAL; i++) {
chk += _signing_node_serial_info[i];
}
if (chk==SIZE_SIGNING_SOFT_SERIAL *
0xFF) { // test if == { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }
unique_id_t uniqueID;
// There is no serial, attempt to get unique ID for serial instead
if (hwUniqueID(&uniqueID)) {
// There is a unique serial, use that
(void)memcpy((void *)_signing_node_serial_info, (const void *)uniqueID, SIZE_SIGNING_SOFT_SERIAL);
}
}
return _signing_init_ok;
}
bool signerAtsha204SoftCheckTimer(void)
{
if (!_signing_init_ok) {
return false;
}
if (_signing_verification_ongoing) {
unsigned long time_now = hwMillis();
// If timestamp is taken so late a rollover can take place during the timeout,
// offset both timestamp and current time to make sure no rollover takes place during the
// timeout
if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < _signing_timestamp) {
_signing_timestamp += MY_VERIFICATION_TIMEOUT_MS;
time_now += MY_VERIFICATION_TIMEOUT_MS;
}
if (time_now > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) {
SIGN_DEBUG(PSTR("!SGN:BND:TMR\n")); //Verification timeout
// Purge nonce
(void)memset((void *)_signing_nonce, 0xAA, sizeof(_signing_nonce));
(void)memset((void *)_signing_verifying_nonce, 0xAA, sizeof(_signing_verifying_nonce));
_signing_verification_ongoing = false;
return false;
}
}
return true;
}
bool signerAtsha204SoftGetNonce(MyMessage &msg)
{
if (!_signing_init_ok) {
return false;
}
#ifdef MY_HW_HAS_GETENTROPY
// Try to get MAX_PAYLOAD_SIZE random bytes
while (hwGetentropy(&_signing_verifying_nonce, MAX_PAYLOAD_SIZE) != MAX_PAYLOAD_SIZE);
#else
// We used a basic whitening technique that XORs a random byte with the current hwMillis() counter
// and then the byte is hashed (SHA256) to produce the resulting nonce
uint8_t randBuffer[32];
for (uint8_t i = 0; i < sizeof(randBuffer); i++) {
randBuffer[i] = random(256) ^ (hwMillis() & 0xFF);
}
SHA256(_signing_verifying_nonce, randBuffer, sizeof(randBuffer));
#endif
if (MAX_PAYLOAD_SIZE < 32) {
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
(void)memset((void *)&_signing_verifying_nonce[MAX_PAYLOAD_SIZE], 0xAA, 32u - MAX_PAYLOAD_SIZE);
}
// Transfer the first part of the nonce to the message
msg.set(_signing_verifying_nonce, MIN((uint8_t)MAX_PAYLOAD_SIZE, (uint8_t)32));
_signing_verification_ongoing = true;
_signing_timestamp = hwMillis(); // Set timestamp to determine when to purge nonce
// Be a little fancy to handle turnover (prolong the time allowed to timeout after turnover)
// Note that if message is "too" quick, and arrives before turnover, it will be rejected
// but this is consider such a rare case that it is accepted and rejects are 'safe'
if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < hwMillis()) {
_signing_timestamp = 0;
}
return true;
}
void signerAtsha204SoftPutNonce(MyMessage &msg)
{
if (!_signing_init_ok) {
return;
}
(void)memcpy((void *)_signing_nonce, (const void *)msg.getCustom(), MIN((uint8_t)MAX_PAYLOAD_SIZE,
(uint8_t)32));
if (MAX_PAYLOAD_SIZE < 32) {
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
(void)memset((void *)&_signing_nonce[MAX_PAYLOAD_SIZE], 0xAA, 32u - MAX_PAYLOAD_SIZE);
}
}
bool signerAtsha204SoftSignMsg(MyMessage &msg)
{
// If we cannot fit any signature in the message, refuse to sign it
if (msg.getLength() > MAX_PAYLOAD_SIZE - 2u) {
SIGN_DEBUG(PSTR("!SGN:BND:SIG,SIZE,%" PRIu8 ">%" PRIu8 "\n"), msg.getLength(),
MAX_PAYLOAD_SIZE - 2); //Message too large
return false;
}
// Calculate signature of message
msg.setSigned(true); // make sure signing flag is set before signature is calculated
signerCalculateSignature(msg, true);
#if defined(MY_SIGNING_NODE_WHITELISTING)
if (DO_WHITELIST(msg.getDestination())) {
// Salt the signature with the senders nodeId and the (hopefully) unique serial The Creator has
// provided. We can reuse the nonce buffer now since it is no longer needed
(void)memcpy((void *)_signing_nonce, (const void *)_signing_hmac, 32);
_signing_nonce[32] = msg.getSender();
(void)memcpy((void *)&_signing_nonce[33], (const void *)_signing_node_serial_info, 9);
SHA256(_signing_hmac, _signing_nonce, 32+1+9);
SIGN_DEBUG(PSTR("SGN:BND:SIG WHI,ID=%" PRIu8 "\n"), msg.getSender());
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(_signing_node_serial_info, 9);
SIGN_DEBUG(PSTR("SGN:BND:SIG WHI,SERIAL=%s\n"), hwDebugPrintStr);
#endif
}
#endif
// Overwrite the first byte in the signature with the signing identifier
_signing_hmac[0] = SIGNING_IDENTIFIER;
// Transfer as much signature data as the remaining space in the message permits
(void)memcpy((void *)&msg.data[msg.getLength()], (const void *)_signing_hmac,
MIN((uint8_t)(MAX_PAYLOAD_SIZE - msg.getLength()), (uint8_t)32));
return true;
}
bool signerAtsha204SoftVerifyMsg(MyMessage &msg)
{
if (!_signing_verification_ongoing) {
SIGN_DEBUG(PSTR("!SGN:BND:VER ONGOING\n"));
return false;
} else {
// Make sure we have not expired
if (!signerCheckTimer()) {
return false;
}
_signing_verification_ongoing = false;
if (msg.data[msg.getLength()] != SIGNING_IDENTIFIER) {
SIGN_DEBUG(PSTR("!SGN:BND:VER,IDENT=%" PRIu8 "\n"), msg.data[msg.getLength()]);
return false;
}
signerCalculateSignature(msg, false); // Get signature of message
#ifdef MY_SIGNING_NODE_WHITELISTING
// Look up the senders nodeId in our whitelist and salt the signature with that data
size_t j;
for (j = 0; j < NUM_OF(_signing_whitelist); j++) {
if (_signing_whitelist[j].nodeId == msg.getSender()) {
// We can reuse the nonce buffer now since it is no longer needed
(void)memcpy((void *)_signing_verifying_nonce, (const void *)_signing_hmac, 32);
_signing_verifying_nonce[32] = msg.getSender();
(void)memcpy((void *)&_signing_verifying_nonce[33], (const void *)_signing_whitelist[j].serial, 9);
SHA256(_signing_hmac, _signing_verifying_nonce, 32+1+9);
SIGN_DEBUG(PSTR("SGN:BND:VER WHI,ID=%" PRIu8 "\n"), msg.getSender());
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(_signing_whitelist[j].serial, 9);
SIGN_DEBUG(PSTR("SGN:BND:VER WHI,SERIAL=%s\n"), hwDebugPrintStr);
#endif
break;
}
}
if (j == NUM_OF(_signing_whitelist)) {
SIGN_DEBUG(PSTR("!SGN:BND:VER WHI,ID=%" PRIu8 " MISSING\n"), msg.getSender());
return false;
}
#endif
// Overwrite the first byte in the signature with the signing identifier
_signing_hmac[0] = SIGNING_IDENTIFIER;
// Compare the calculated signature with the provided signature
if (signerMemcmp(&msg.data[msg.getLength()], _signing_hmac,
MIN((uint8_t)(MAX_PAYLOAD_SIZE - msg.getLength()), (uint8_t)32))) {
return false;
} else {
return true;
}
}
}
// Helper to calculate signature of msg (returned in _signing_hmac)
static void signerCalculateSignature(MyMessage &msg, const bool signing)
{
// Signature is calculated on everything expect the first byte in the header
uint8_t bytes_left = msg.getLength()+HEADER_SIZE-1;
int16_t current_pos = 1-(int16_t)HEADER_SIZE; // Start at the second byte in the header
uint8_t* nonce = signing ? _signing_nonce : _signing_verifying_nonce;
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(nonce, 32);
SIGN_DEBUG(PSTR("SGN:BND:NONCE=%s\n"), hwDebugPrintStr);
#endif
uint8_t _signing_temp_message[32];
while (bytes_left) {
uint8_t bytes_to_include = MIN(bytes_left, (uint8_t)32);
(void)memset((void *)_signing_temp_message, 0x00, sizeof(_signing_temp_message));
(void)memcpy((void *)_signing_temp_message, (const void *)&msg.data[current_pos], bytes_to_include);
signerAtsha204AHmac(_signing_hmac, nonce, _signing_temp_message);
// Purge nonce when used
(void)memset((void *)nonce, 0xAA, 32);
bytes_left -= bytes_to_include;
current_pos += bytes_to_include;
if (bytes_left) {
// We will do another pass, use current HMAC as nonce for the next HMAC
(void)memcpy((void *)nonce, (const void *)_signing_hmac, 32);
}
}
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(_signing_hmac, 32);
SIGN_DEBUG(PSTR("SGN:BND:HMAC=%s\n"), hwDebugPrintStr);
#endif
}
// Helper to calculate a ATSHA204A specific HMAC-SHA256 using provided 32 byte nonce and data
// (zero padded to 32 bytes)
// The pointer to the HMAC is returned, but the HMAC is also stored in _signing_hmac
static void signerAtsha204AHmac(uint8_t *dest, const uint8_t *nonce, const uint8_t *data)
{
// ATSHA204 calculates the HMAC with a PSK and a SHA256 digest of the following data:
// 32 bytes zeroes
// 32 bytes digest,
// 1 byte OPCODE (0x11)
// 1 byte Mode (0x04)
// 2 bytes SlotID (0x0000)
// 11 bytes zeroes
// SN[8] (0xEE)
// 4 bytes zeroes
// SN[0:1] (0x0123)
// 2 bytes zeroes
// The digest is calculated as a SHA256 digest of the following:
// 32 bytes message
// 1 byte OPCODE (0x15)
// 1 byte param1 (0x02)
// 2 bytes param2 (0x0800)
// SN[8] (0xEE)
// SN[0:1] (0x0123)
// 25 bytes zeroes
// 32 bytes nonce
#if defined(MY_CRYPTO_SHA256_ASM)
static uint8_t _signing_buffer[96]; // static for AVR ASM SHA256
#else
uint8_t _signing_buffer[96];
#endif
// Calculate message digest first
(void)memset((void *)_signing_buffer, 0x00, sizeof(_signing_buffer));
(void)memcpy((void *)_signing_buffer, (const void *)data, 32);
_signing_buffer[0 + 32] = 0x15; // OPCODE
_signing_buffer[1 + 32] = 0x02; // param1
_signing_buffer[2 + 32] = 0x08; // param2(1)
//_signing_buffer[3 + 32] = 0x00; // param2(2)
_signing_buffer[4 + 32] = 0xEE; // SN[8]
_signing_buffer[5 + 32] = 0x01; // SN[0]
_signing_buffer[6 + 32] = 0x23; // SN[1]
// _signing_buffer[7 + 32..31 + 32] => 0x00;
(void)memcpy((void *)&_signing_buffer[64], (const void *)nonce, 32);
SHA256(_signing_hmac, _signing_buffer, 96);
// Feed "message" to HMAC calculator
(void)memset((void *)_signing_buffer, 0x00, sizeof(_signing_buffer));
(void)memcpy((void *)&_signing_buffer[32], (const void *)_signing_hmac, 32);
_signing_buffer[0 + 64] = 0x11; // OPCODE
_signing_buffer[1 + 64] = 0x04; // Mode
//_signing_buffer[2 + 64] = 0x00; // SlotID(1)
//_signing_buffer[3 + 64] = 0x00; // SlotID(2)
//_signing_buffer[4 + 64..14 + 64] => 0x00; // 11 bytes zeroes
_signing_buffer[15 + 64] = 0xEE; // SN[8]
//_signing_buffer[16 + 64..19 + 64] => 0x00; // 4 bytes zeroes
_signing_buffer[20 + 64] = 0x01;
_signing_buffer[21 + 64] = 0x23;
//_signing_buffer[22 + 64] = 0x00; // SN[0]
//_signing_buffer[23 + 64] = 0x00; // SN[1]
SHA256HMAC(dest, _signing_hmac_key, 32, _signing_buffer, 88);
}
#endif //MY_SIGNING_SOFT

View File

@@ -0,0 +1,52 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MySensorsCore.h"
void displaySplashScreen(void)
{
#if !defined(MY_DISABLED_SERIAL) && defined(MY_DEBUGDEVICE)
static const uint8_t splashScreen[] PROGMEM = {
0x20, 0x7C, 0x5F, 0x5c, 0x2F, 0x60, 0x2C, 0x0A, // substitution matrix
0x07, 0x02, 0x20, 0x02, 0x20, 0xB2, 0x87, 0x10, 0x03, 0x40, 0x01, 0x20, 0x00, 0x24, 0x02, 0x22,
0x10, 0x02, 0x22, 0x02, 0x02, 0x20, 0x02, 0x22, 0x00, 0x22, 0x20, 0x02, 0x02, 0x20, 0x22, 0x27,
0x10, 0x13, 0x41, 0x01, 0x01, 0x01, 0x03, 0x22, 0x20, 0x30, 0x40, 0x20, 0x30, 0x52, 0x03, 0x40,
0x22, 0x14, 0x02, 0x03, 0x10, 0x52, 0x24, 0x02, 0x21, 0x71, 0x01, 0x00, 0x10, 0x10, 0x12, 0x10,
0x12, 0x22, 0x10, 0x10, 0x02, 0x24, 0x01, 0x01, 0x03, 0x22, 0x03, 0x00, 0x20, 0x01, 0x01, 0x00,
0x32, 0x20, 0x37, 0x12, 0x10, 0x01, 0x21, 0x32, 0x26, 0x01, 0x28, 0x40, 0x32, 0x22, 0x12, 0x10,
0x12, 0x12, 0x22, 0x43, 0x22, 0x24, 0x12, 0x10, 0x01, 0x22, 0x24, 0x70, 0xC1, 0x22, 0x24,
0x0F, 0x0F
};
uint8_t pos = 16;
char display = 0;
while (pos<sizeof(splashScreen)*2) {
uint8_t val = pgm_read_byte(&(splashScreen[pos>>1]));
val = (pos % 2) ? val & 0xF : val >> 4;
pos++;
if (val<8) {
display = pgm_read_byte(&(splashScreen[val]));
}
const uint8_t rep = val > 7 ? val - 5 : 1;
for (uint8_t c = 0; c<rep; c++) {
MY_DEBUGDEVICE.print(display);
}
}
MY_DEBUGDEVICE.println(F(MYSENSORS_LIBRARY_VERSION "\n"));
#endif
}

View File

@@ -0,0 +1,28 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 MySplashScreen_h
#define MySplashScreen_h
/**
* Display MySensors splash screen.
*/
void displaySplashScreen(void);
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,573 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*/
/**
* @file MyTransport.h
*
* @defgroup MyTransportgrp MyTransport
* @ingroup internals
* @{
*
* Transport-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE
* - [!] Exclamation mark is prepended in case of error
* - SYSTEM:
* - <b>TSM</b>: messages emitted by the transport state machine
* - <b>TSF</b>: messages emitted by transport support functions
* - SUB SYSTEMS:
* - Transport state machine (<b>TSM</b>)
* - TSM:<b>INIT</b> from <b>stInit</b> Initialize transport and radio
* - TSM:<b>FPAR</b> from <b>stParent</b> Find parent
* - TSM:<b>ID</b> from <b>stID</b> Check/request node ID, if dynamic node ID set
* - TSM:<b>UPL</b> from <b>stUplink</b> Verify uplink connection by pinging GW
* - TSM:<b>READY</b> from <b>stReady</b> Transport is ready and fully operational
* - TSM:<b>FAIL</b> from <b>stFailure</b> Failure in transport link or transport HW
* - Transport support function (<b>TSF</b>)
* - TSF:<b>CKU</b> from @ref transportCheckUplink(), checks connection to GW
* - TSF:<b>SID</b> from @ref transportAssignNodeID(), assigns node ID
* - TSF:<b>PNG</b> from @ref transportPingNode(), pings a node
* - TSF:<b>WUR</b> from @ref transportWaitUntilReady(), waits until transport is ready
* - TSF:<b>CRT</b> from @ref transportClearRoutingTable(), clears routing table stored in EEPROM
* - TSF:<b>LRT</b> from @ref transportLoadRoutingTable(), loads RAM routing table from EEPROM (only GW/repeaters)
* - TSF:<b>SRT</b> from @ref transportSaveRoutingTable(), saves RAM routing table to EEPROM (only GW/repeaters)
* - TSF:<b>MSG</b> from @ref transportProcessMessage(), processes incoming message
* - TSF:<b>SAN</b> from @ref transportInvokeSanityCheck(), calls transport-specific sanity check
* - TSF:<b>RTE</b> from @ref transportRouteMessage(), sends message
* - TSF:<b>SND</b> from @ref transportSendRoute(), sends message if transport is ready (exposed)
* - TSF:<b>TDI</b> from @ref transportDisable()
* - TSF:<b>TRI</b> from @ref transportReInitialise()
* - TSF:<b>SIR</b> from @ref transportSignalReport()
*
* Transport debug log messages:
*
* |E| SYS | SUB | Message | Comment
* |-|-----|-------|---------------------------|---------------------------------------------------------------------
* | | TSM | INIT | | <b>Transition to stInit state</b>
* | | TSM | INIT | STATID=%%d | Node ID is static
* | | TSM | INIT | TSP OK | Transport device configured and fully operational
* | | TSM | INIT | TSP PSM | Transport passive mode set
* | | TSM | INIT | GW MODE | Node is set up as GW, thus omitting ID and findParent states
* |!| TSM | INIT | TSP FAIL | Transport device initialization failed
* | | TSM | FPAR | | <b>Transition to stParent state</b>
* | | TSM | FPAR | STATP=%%d | Static parent set, skip finding parent
* | | TSM | FPAR | OK | Parent node identified
* |!| TSM | FPAR | NO REPLY | No potential parents replied to find parent request
* |!| TSM | FPAR | FAIL | Finding parent failed
* | | TSM | ID | | <b>Transition to stID state</b>
* | | TSM | ID | OK,ID=%%d | Node ID is valid
* | | TSM | ID | REQ | Request node ID from controller
* |!| TSM | ID | FAIL,ID=%%d | ID verification failed, ID invalid, no ID received from controller
* | | TSM | UPL | | <b>Transition to stUplink state</b>
* | | TSM | UPL | OK | Uplink OK, GW returned ping
* | | TSF | UPL | DGWC,O=%%d,N=%%d | Uplink check revealed changed network topology, old distance (O), new distance (N)
* |!| TSM | UPL | FAIL | Uplink check failed, i.e. GW could not be pinged
* | | TSM | READY | SRT | Save routing table
* | | TSM | READY | ID=%%d,PAR=%%d,DIS=%%d | <b>Transition to stReady</b> Transport ready, node ID (ID), parent node ID (PAR), distance to GW (DIS)
* |!| TSM | READY | UPL FAIL,SNP | Too many failed uplink transmissions, search new parent
* |!| TSM | READY | FAIL,STATP | Too many failed uplink transmissions, static parent enforced
* | | TSM | FAIL | CNT=%%d | <b>Transition to stFailure state</b>, consecutive failure counter (CNT)
* | | TSM | FAIL | DIS | Disable transport
* | | TSM | FAIL | RE-INIT | Attempt to re-initialize transport
* | | TSF | CKU | OK | Uplink OK
* | | TSF | CKU | OK,FCTRL | Uplink OK, flood control prevents pinging GW in too short intervals
* | | TSF | CKU | DGWC,O=%%d,N=%%d | Uplink check revealed changed network topology, old distance (O), new distance (N)
* | | TSF | CKU | FAIL | No reply received when checking uplink
* | | TSF | SID | OK,ID=%%d | Node ID assigned
* |!| TSF | SID | FAIL,ID=%%d | Assigned ID is invalid
* | | TSF | PNG | SEND,TO=%%d | Send ping to destination (TO)
* | | TSF | WUR | MS=%%lu | Wait until transport ready, timeout (MS)
* | | TSF | MSG | ECHO REQ | ECHO message requested
* | | TSF | MSG | ECHO | ECHO message, do not proceed but forward to callback
* | | TSF | MSG | FPAR RES,ID=%%d,D=%%d | Response to find parent received from node (ID) with distance (D) to GW
* | | TSF | MSG | FPAR PREF FOUND | Preferred parent found, i.e. parent defined via MY_PARENT_NODE_ID
* | | TSF | MSG | FPAR OK,ID=%%d,D=%%d | Find parent response from node (ID) is valid, distance (D) to GW
* | | TSF | MSG | FPAR INACTIVE | Find parent response received, but no find parent request active, skip response
* | | TSF | MSG | FPAR REQ,ID=%%d | Find parent request from node (ID)
* | | TSF | MSG | PINGED,ID=%%d,HP=%%d | Node pinged by node (ID) with (HP) hops
* | | TSF | MSG | PONG RECV,HP=%%d | Pinged node replied with (HP) hops
* | | TSF | MSG | BC | Broadcast message received
* | | TSF | MSG | GWL OK | Link to GW ok
* | | TSF | MSG | FWD BC MSG | Controlled broadcast message forwarding
* | | TSF | MSG | RCV CB | Hand over message to @ref receive() callback function
* | | TSF | MSG | REL MSG | Relay message
* | | TSF | MSG | REL PxNG,HP=%%d | Relay PING/PONG message, increment hop counter (HP)
* |!| TSF | MSG | SIGN VERIFY FAIL | Signing verification failed
* |!| TSF | MSG | REL MSG,NORP | Node received a message for relaying, but node is not a repeater, message skipped
* |!| TSF | MSG | SIGN FAIL | Signing message failed
* |!| TSF | MSG | GWL FAIL | GW uplink failed
* |!| TSF | MSG | ID TK INVALID | Token for ID request invalid
* | | TSF | SAN | OK | Sanity check passed
* |!| TSF | SAN | FAIL | Sanity check failed, attempt to re-initialize radio
* | | TSF | CRT | OK | Clearing routing table successful
* | | TSF | LRT | OK | Loading routing table successful
* | | TSF | SRT | OK | Saving routing table successful
* |!| TSF | RTE | FPAR ACTIVE | Finding parent active, message not sent
* |!| TSF | RTE | DST %%d UNKNOWN | Routing for destination (DST) unknown, send message to parent
* | | TSF | RTE | N2N OK | Node-to-node communication succeeded
* |!| TSF | RTE | N2N FAIL | Node-to-node communication failed, handing over to parent for re-routing
* | | TSF | RRT | ROUTE N=%%d,R=%%d | Routing table, messages to node (N) are routed via node (R)
* |!| TSF | SND | TNR | Transport not ready, message cannot be sent
* | | TSF | TDI | TSL | Set transport to sleep
* | | TSF | TDI | TPD | Power down transport
* | | TSF | TRI | TRI | Reinitialise transport
* | | TSF | TRI | TSB | Set transport to standby
* | | TSF | SIR | CMD=%d,VAL=%d | Get signal report
*
*
* Incoming / outgoing messages:
*
* See <a href="https://www.mysensors.org/download/serial_api_20">here</a> for more detail on the format and definitions.
*
* Receiving a message
* - TSF:MSG:READ,sender-last-destination,s=%%d,c=%%d,t=%%d,pt=%%d,l=%%d,sg=%%d:%%s
*
* Sending a message
* - [!/?]TSF:MSG:SEND,sender-last-next-destination,s=%%d,c=%%d,t=%%d,pt=%%d,l=%%d,sg=%%d,ft=%%d,st=%%s:%%s
*
* Prepended char:
* - <b>none</b>=sending OK
* - <b>!</b>=error sending
* - <b>?</b>=sending status unknown
* Message fields:
* - <b>s</b>=sensor ID
* - <b>c</b>=command
* - <b>t</b>=msg type
* - <b>pt</b>=payload type
* - <b>l</b>=length
* - <b>sg</b>=signing flag
* - <b>ft</b>=failed uplink transmission counter
* - <b>st</b>=send status, OK=success, NACK=no radio ACK received
*
* @startuml
* state top as "Transport" {
* state Init
* state Failure
* state Ready
* state Parent
* state ID
* state Uplink
* }
*
* [*] --> Init
* Init : entry / Read config from eeprom
* Init --> Failure : [! transportInit()\n|| ID == 0\n|| ID == 255 ]
* Init --> Ready : [MY_GATEWAY_FEATURE]
* Init --> Parent : [else]
*
* Parent : entry / Broadcast Find Parent
* Parent --> ID : [MY_PARENT_NODE_IS_STATIC\n|| MY_PASSIVE_NODE\n|| Parent found]
* Parent --> Parent : [timeout\n&& retries left]
* Parent --> Failure : [timeout\n&& no retries left]
*
* ID : entry / Request Node ID
* ID --> Uplink : [ID valid]
* ID --> ID : [timeout\n&& retries left]
* ID --> Failure : [timeout\n&& no retries left]
*
* Uplink : entry / Check uplink (PING)
* Uplink --> Uplink : [timeout\n&& retries left]
* Uplink --> Parent : [timeout\n&& no retries left]
* Uplink --> Ready : [MY_TRANSPORT_UPLINK_CHECK_DISABLED\n|| Uplink ok (PONG)]
*
* Ready : entry / Transport ready callback
* Ready : MY_GATEWAY_FEATURE && Network discovery required / Send discovery
* Ready --> Parent : [!MY_PARENT_NODE_IS_STATIC\n&& Uplink failure overflow]
*
* Failure : entry / Disable transport
* Failure --> Init : [timeout]
* top --> Failure : [MY_TRANSPORT_SANITY_CHECK\n&& !transportSanityCheck]
* @enduml
*
* @brief API declaration for MyTransport
*
*/
#ifndef MyTransport_h
#define MyTransport_h
#include "hal/transport/MyTransportHAL.h"
#ifndef MY_TRANSPORT_MAX_TX_FAILURES
#if defined(MY_REPEATER_FEATURE)
#define MY_TRANSPORT_MAX_TX_FAILURES (10u) //!< search for a new parent node after this many transmission failures, higher threshold for repeating nodes
#else
#define MY_TRANSPORT_MAX_TX_FAILURES (5u) //!< search for a new parent node after this many transmission failures, lower threshold for non-repeating nodes
#endif
#endif
#ifndef MY_TRANSPORT_MAX_TSM_FAILURES
#define MY_TRANSPORT_MAX_TSM_FAILURES (7u) //!< Max. number of consecutive TSM failure state entries (3bits)
#endif
#ifndef MY_TRANSPORT_TIMEOUT_FAILURE_STATE_MS
#define MY_TRANSPORT_TIMEOUT_FAILURE_STATE_MS (10*1000ul) //!< Duration failure state (in ms)
#endif
#ifndef MY_TRANSPORT_TIMEOUT_EXT_FAILURE_STATE_MS
#define MY_TRANSPORT_TIMEOUT_EXT_FAILURE_STATE_MS (60*1000ul) //!< Duration extended failure state (in ms)
#endif
#ifndef MY_TRANSPORT_STATE_TIMEOUT_MS
#define MY_TRANSPORT_STATE_TIMEOUT_MS (2*1000ul) //!< general state timeout (in ms)
#endif
#ifndef MY_TRANSPORT_CHKUPL_INTERVAL_MS
#define MY_TRANSPORT_CHKUPL_INTERVAL_MS (10*1000ul) //!< Interval to re-check uplink (in ms)
#endif
#ifndef MY_TRANSPORT_STATE_RETRIES
#define MY_TRANSPORT_STATE_RETRIES (3u) //!< retries before switching to FAILURE
#endif
#define AUTO (255u) //!< ID 255 is reserved
#define BROADCAST_ADDRESS (255u) //!< broadcasts are addressed to ID 255
#define DISTANCE_INVALID (255u) //!< invalid distance when searching for parent
#define MAX_HOPS (254u) //!< maximal number of hops for ping/pong
#define INVALID_HOPS (255u) //!< invalid hops
#define MAX_SUBSEQ_MSGS (5u) //!< Maximum number of subsequently processed messages in FIFO (to prevent transport deadlock if HW issue)
#define UPLINK_QUALITY_WEIGHT (0.05f) //!< UPLINK_QUALITY_WEIGHT
// parent node check
#if defined(MY_PARENT_NODE_IS_STATIC) && !defined(MY_PARENT_NODE_ID)
#error MY_PARENT_NODE_IS_STATIC but no MY_PARENT_NODE_ID defined!
#endif
#define _autoFindParent (bool)(MY_PARENT_NODE_ID == AUTO) //!< returns true if static parent id is undefined
#define isValidDistance(_distance) (bool)(_distance!=DISTANCE_INVALID) //!< returns true if distance is valid
#define isValidParent(_parent) (bool)(_parent != AUTO) //!< returns true if parent is valid
/**
* @brief Callback type
*/
typedef void(*transportCallback_t)(void);
/**
* @brief Node configuration
*
* This structure stores node-related configurations
*/
typedef struct {
uint8_t nodeId; //!< Current node id
uint8_t parentNodeId; //!< Where this node sends its messages
uint8_t distanceGW; //!< This nodes distance to sensor net gateway (number of hops)
uint8_t passiveMode : 1; //!< Passive mode
uint8_t reserved : 7; //!< Reserved
} transportConfig_t;
/**
* @brief SM state
*
* This structure stores SM state definitions
*/
typedef struct {
void(*Transition)(void); //!< state transition function
void(*Update)(void); //!< state update function
} transportState_t;
/**
* @brief Datatype for internal RSSI storage
*/
typedef int16_t transportRSSI_t; //!< Datatype for internal RSSI storage
// helper macro for conversion
#define transportInternalToRSSI(__value) ((int16_t)__value >> 4) //!< Convert internal RSSI to RSSI
#define transportRSSItoInternal(__value) ((transportRSSI_t)__value << 4) //!< Convert RSSI to internal RSSI
/**
* @brief Status variables and SM state
*
* This structure stores transport status and SM variables
*/
typedef struct {
// SM variables
transportState_t *currentState; //!< pointer to current FSM state
uint32_t stateEnter; //!< state enter timepoint
// general transport variables
uint32_t lastUplinkCheck; //!< last uplink check, required to prevent GW flooding
// 8 bits
bool findingParentNode : 1; //!< flag finding parent node is active
bool preferredParentFound : 1; //!< flag preferred parent found
bool uplinkOk : 1; //!< flag uplink ok
bool pingActive : 1; //!< flag ping active
bool transportActive : 1; //!< flag transport active
uint8_t stateRetries : 3; //!< retries / state re-enter (max 7)
// 8 bits
uint8_t failedUplinkTransmissions : 4; //!< counter failed uplink transmissions (max 15)
uint8_t failureCounter : 3; //!< counter for TSM failures (max 7)
bool msgReceived : 1; //!< flag message received
uint8_t pingResponse; //!< stores I_PONG hops
#if defined(MY_SIGNAL_REPORT_ENABLED)
transportRSSI_t uplinkQualityRSSI; //!< Uplink quality, internal RSSI representation
#endif
} transportSM_t;
/**
* @brief RAM routing table
*/
typedef struct {
uint8_t route[SIZE_ROUTES]; //!< route for node
} routingTable_t;
// PRIVATE functions
/**
* @brief Initialize SM variables and transport HW
*/
void stInitTransition(void);
/**
* @brief Initialize transport
*/
void stInitUpdate(void);
/**
* @brief Find parent
*/
void stParentTransition(void);
/**
* @brief Verify find parent responses
*/
void stParentUpdate(void);
/**
* @brief Send ID request
*/
void stIDTransition(void);
/**
* @brief Verify ID response and GW link
*/
void stIDUpdate(void);
/**
* @brief Send uplink ping request
*/
void stUplinkTransition(void);
/**
* @brief Verify uplink response
*/
void stUplinkUpdate(void);
/**
* @brief Set transport OK
*/
void stReadyTransition(void);
/**
* @brief Monitor transport link
*/
void stReadyUpdate(void);
/**
* @brief Transport failure and power down radio
*/
void stFailureTransition(void);
/**
* @brief Re-initialize transport after timeout
*/
void stFailureUpdate(void);
/**
* @brief Switch SM state
* @param newState New state to switch SM to
*/
void transportSwitchSM(transportState_t &newState);
/**
* @brief Update SM state
*/
void transportUpdateSM(void);
/**
* @brief Request time in current SM state
* @return ms in current state
*/
uint32_t transportTimeInState(void);
/**
* @brief Call transport driver sanity check
*/
void transportInvokeSanityCheck(void);
/**
* @brief Process all pending messages in RX FIFO
*/
void transportProcessFIFO(void);
/**
* @brief Receive message from RX FIFO and process
*/
void transportProcessMessage(void);
/**
* @brief Assign node ID
* @param newNodeId New node ID
* @return true if node ID is valid and successfully assigned
*/
bool transportAssignNodeID(const uint8_t newNodeId);
/**
* @brief Wait and process messages for a defined amount of time until specified message received
* @param waitingMS Time to wait and process incoming messages in ms
* @param cmd Specific command
* @param msgType Specific message type
* @return true if specified command received within waiting time
*/
bool transportWait(const uint32_t waitingMS, const uint8_t cmd, const uint8_t msgType);
/**
* @brief Ping node
* @param targetId Node to be pinged
* @return hops from pinged node or 255 if no answer received within 2000ms
*/
uint8_t transportPingNode(const uint8_t targetId);
/**
* @brief Send and route message according to destination
*
* This function is used in MyTransport and omits the transport state check, i.e. message can be sent even if transport is not ready
*
* @param message
* @return true if message sent successfully
*/
bool transportRouteMessage(MyMessage &message);
/**
* @brief Send and route message according to destination with transport state check
* @param message
* @return true if message sent successfully and false if sending error or transport !OK
*/
bool transportSendRoute(MyMessage &message);
/**
* @brief Send message to recipient
* @param to Recipient of message
* @param message
* @return true if message sent successfully
*/
bool transportSendWrite(const uint8_t to, MyMessage &message);
/**
* @brief Check uplink to GW, includes flooding control
* @param force to override flood control timer
* @return true if uplink ok
*/
bool transportCheckUplink(const bool force = false);
// PUBLIC functions
/**
* @brief Wait until transport is ready
* @param waitingMS timeout in MS, set 0 (default) for no timeout, i.e. wait indefinitely. For a node in standalone mode (optional network connection) set >0 to allow a node entering the main loop() function.
* @return true if transport is ready
*/
bool transportWaitUntilReady(const uint32_t waitingMS = 0);
/**
* @brief Initialize transport and SM
*/
void transportInitialise(void);
/**
* @brief Process FIFO msg and update SM
*/
void transportProcess(void);
/**
* @brief Flag transport ready
* @return true if transport is initialized and ready
*/
bool isTransportReady(void);
/**
* @brief Flag searching parent ongoing
* @return true if transport is searching for parent
*/
bool isTransportSearchingParent(void);
/**
* @brief Flag TSM extended failure
* @return true if TSM had too many consecutive failure state entries
*/
bool isTransportExtendedFailure(void);
/**
* @brief Flag valid message received
* @return true if valid message received, needs to be reset if used
*/
bool isMessageReceived(void);
/**
* @brief Reset message received flag
*/
void resetMessageReceived(void);
/**
* @brief Clear routing table
*/
void transportClearRoutingTable(void);
/**
* @brief Return heart beat
* @return MS in current state
*/
uint32_t transportGetHeartbeat(void);
/**
* @brief Load routing table from EEPROM to RAM.
* Only for GW devices with enough RAM, i.e. ESP8266, RPI Sensebender GW, etc.
* Atmega328 has only limited amount of RAM
*/
void transportLoadRoutingTable(void);
/**
* @brief Save routing table to EEPROM.
*/
void transportSaveRoutingTable(void);
/**
* @brief Update routing table
* @param node
* @param route
*/
void transportSetRoute(const uint8_t node, const uint8_t route);
/**
* @brief Load route to node
* @param node
* @return route to node
*/
uint8_t transportGetRoute(const uint8_t node);
/**
* @brief Reports content of routing table
*/
void transportReportRoutingTable(void);
/**
* @brief Get node ID
* @return node ID
*/
uint8_t transportGetNodeId(void);
/**
* @brief Get parent node ID
* @return parent node ID
*/
uint8_t transportGetParentNodeId(void);
/**
* @brief Get distance to GW
* @return distance (=hops) to GW
*/
uint8_t transportGetDistanceGW(void);
/**
* @brief Toggle passive mode, i.e. transport does not wait for ACK
* @param OnOff
*/
void transportTogglePassiveMode(const bool OnOff);
/**
* @brief Disable transport, if xxx_POWER_PIN is defined, transport is powered down, else send to sleep
*/
void transportDisable(void);
/**
* @brief Reinitialise transport. Put transport to standby - If xxx_POWER_PIN set, power up and go to standby
*/
void transportReInitialise(void);
/**
* @brief Get transport signal report
* @param command:
* R = RSSI (if available) of incoming @ref I_SIGNAL_REPORT_REQUEST message (from last hop)
* R! = RSSI (if available) of ACK to @ref I_SIGNAL_REPORT_REVERSE message received from last hop
* S = SNR (if available) of incoming @ref I_SIGNAL_REPORT_REQUEST message (from last hop)
* S! = SNR (if available) of ACK to @ref I_SIGNAL_REPORT_REVERSE message received from last hop
* P = TX powerlevel in %
* T = TX powerlevel in dBm
* U = Uplink quality (via ACK from parent node), avg. RSSI
* @return Signal report (if report is not available, INVALID_RSSI, INVALID_SNR, INVALID_PERCENT, or INVALID_LEVEL is sent instead)
*/
int16_t transportSignalReport(const char command) __attribute__((unused));
/**
* @brief Get transport signal report
* @param signalReport
* @return report
*/
int16_t transportGetSignalReport(const signalReport_t signalReport) __attribute__((unused));
#endif // MyTransport_h
/** @}*/

View File

@@ -0,0 +1,65 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*/
/**
* @file Version.h
*
* @defgroup Versiongrp Version
* @ingroup internals
* @{
*
* This file defines the MySensors library version number
* Please adjust for new releases.
* These helper macros generate a numerical and alphanumerical (see http://www.semver.org) representation of the library version number, i.e
*
* | SemVer | Numerical | Comments
* |-------------|-------------|------------------
* | 2.1.0 | 0x020100FF | final
* | 2.1.1-beta | 0x02010100 | first pre-release
* | 2.1.1 | 0x020101FF | final
* | 2.2.0-beta | 0x02020000 | first pre-release
* | 2.2.0-rc.1 | 0x02020001 |
* | 2.2.0-rc.2 | 0x02020002 |
* | 2.2.0 | 0x020200FF | final
*/
#ifndef Version_h
#define Version_h
#define STR_HELPER(x) #x //!< Helper macro, STR_HELPER()
#define STR(x) STR_HELPER(x) //!< Helper macro, STR()
#define MYSENSORS_LIBRARY_VERSION_MAJOR 2 //!< Major release version
#define MYSENSORS_LIBRARY_VERSION_MINOR 3 //!< Minor release version
#define MYSENSORS_LIBRARY_VERSION_PATCH 2 //!< Patch version
#define MYSENSORS_LIBRARY_VERSION_PRERELEASE "" //!< Pre-release suffix, i.e. alpha, beta, rc.1, etc
#define MYSENSORS_LIBRARY_VERSION_PRERELEASE_NUMBER 0xFF //!< incremental counter, starting at 0x00. 0xFF for final release
#if (MYSENSORS_LIBRARY_VERSION_PRERELEASE_NUMBER != 0xFF)
#define MYSENSORS_LIBRARY_VERSION STR(MYSENSORS_LIBRARY_VERSION_MAJOR) "." STR(MYSENSORS_LIBRARY_VERSION_MINOR) "." STR(MYSENSORS_LIBRARY_VERSION_PATCH) "-" MYSENSORS_LIBRARY_VERSION_PRERELEASE //!< pre-release versioning
#else
#define MYSENSORS_LIBRARY_VERSION STR(MYSENSORS_LIBRARY_VERSION_MAJOR) "." STR(MYSENSORS_LIBRARY_VERSION_MINOR) "." STR(MYSENSORS_LIBRARY_VERSION_PATCH) //!< final release versioning
#endif
#define MYSENSORS_LIBRARY_VERSION_INT ( ((uint32_t)MYSENSORS_LIBRARY_VERSION_MAJOR) << 24 | ((uint32_t)MYSENSORS_LIBRARY_VERSION_MINOR) << 16 | ((uint32_t)MYSENSORS_LIBRARY_VERSION_PATCH) << 8 | ((uint32_t)MYSENSORS_LIBRARY_VERSION_PRERELEASE_NUMBER) ) //!< numerical versioning
#endif // Version_h
/** @}*/