mirror of
https://github.com/IoTManagerProject/IoTManager.git
synced 2026-03-30 11:59:12 +03:00
добавление mysensors в процессе не рабочая версия
This commit is contained in:
244
lib/MySensors/core/MyCapabilities.h
Normal file
244
lib/MySensors/core/MyCapabilities.h
Normal 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 */
|
||||
92
lib/MySensors/core/MyEepromAddresses.h
Normal file
92
lib/MySensors/core/MyEepromAddresses.h
Normal 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
|
||||
|
||||
/** @}*/
|
||||
69
lib/MySensors/core/MyGatewayTransport.cpp
Normal file
69
lib/MySensors/core/MyGatewayTransport.cpp
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
121
lib/MySensors/core/MyGatewayTransport.h
Normal file
121
lib/MySensors/core/MyGatewayTransport.h
Normal 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 */
|
||||
|
||||
/** @}*/
|
||||
|
||||
495
lib/MySensors/core/MyGatewayTransportEthernet.cpp
Normal file
495
lib/MySensors/core/MyGatewayTransportEthernet.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
298
lib/MySensors/core/MyGatewayTransportMQTTClient.cpp
Normal file
298
lib/MySensors/core/MyGatewayTransportMQTTClient.cpp
Normal 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;
|
||||
}
|
||||
82
lib/MySensors/core/MyGatewayTransportSerial.cpp
Normal file
82
lib/MySensors/core/MyGatewayTransportSerial.cpp
Normal 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;
|
||||
}
|
||||
41
lib/MySensors/core/MyHelperFunctions.cpp
Normal file
41
lib/MySensors/core/MyHelperFunctions.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
38
lib/MySensors/core/MyHelperFunctions.h
Normal file
38
lib/MySensors/core/MyHelperFunctions.h
Normal 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
|
||||
72
lib/MySensors/core/MyInclusionMode.cpp
Normal file
72
lib/MySensors/core/MyInclusionMode.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
33
lib/MySensors/core/MyInclusionMode.h
Normal file
33
lib/MySensors/core/MyInclusionMode.h
Normal 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
|
||||
52
lib/MySensors/core/MyIndication.cpp
Normal file
52
lib/MySensors/core/MyIndication.cpp
Normal 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
|
||||
80
lib/MySensors/core/MyIndication.h
Normal file
80
lib/MySensors/core/MyIndication.h
Normal 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
|
||||
119
lib/MySensors/core/MyLeds.cpp
Normal file
119
lib/MySensors/core/MyLeds.cpp
Normal 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;
|
||||
}
|
||||
59
lib/MySensors/core/MyLeds.h
Normal file
59
lib/MySensors/core/MyLeds.h
Normal 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
|
||||
462
lib/MySensors/core/MyMessage.cpp
Normal file
462
lib/MySensors/core/MyMessage.cpp
Normal 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;
|
||||
}
|
||||
671
lib/MySensors/core/MyMessage.h
Normal file
671
lib/MySensors/core/MyMessage.h
Normal 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
|
||||
/** @}*/
|
||||
287
lib/MySensors/core/MyOTAFirmwareUpdate.cpp
Normal file
287
lib/MySensors/core/MyOTAFirmwareUpdate.cpp
Normal 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;
|
||||
}
|
||||
200
lib/MySensors/core/MyOTAFirmwareUpdate.h
Normal file
200
lib/MySensors/core/MyOTAFirmwareUpdate.h
Normal 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
|
||||
|
||||
/** @}*/
|
||||
158
lib/MySensors/core/MyOTALogging.cpp
Normal file
158
lib/MySensors/core/MyOTALogging.cpp
Normal 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
|
||||
73
lib/MySensors/core/MyOTALogging.h
Normal file
73
lib/MySensors/core/MyOTALogging.h
Normal 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 */
|
||||
|
||||
/** @}*/
|
||||
61
lib/MySensors/core/MyPrivateConfig.h.sample
Normal file
61
lib/MySensors/core/MyPrivateConfig.h.sample
Normal 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
|
||||
167
lib/MySensors/core/MyProtocol.cpp
Normal file
167
lib/MySensors/core/MyProtocol.cpp
Normal 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);
|
||||
}
|
||||
33
lib/MySensors/core/MyProtocol.h
Normal file
33
lib/MySensors/core/MyProtocol.h
Normal 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
|
||||
833
lib/MySensors/core/MySensorsCore.cpp
Normal file
833
lib/MySensors/core/MySensorsCore.cpp
Normal 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
|
||||
509
lib/MySensors/core/MySensorsCore.h
Normal file
509
lib/MySensors/core/MySensorsCore.h
Normal 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
|
||||
|
||||
/** @}*/
|
||||
587
lib/MySensors/core/MySigning.cpp
Normal file
587
lib/MySensors/core/MySigning.cpp
Normal 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
|
||||
907
lib/MySensors/core/MySigning.h
Normal file
907
lib/MySensors/core/MySigning.h
Normal 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.
|
||||
*/
|
||||
/** @}*/
|
||||
357
lib/MySensors/core/MySigningAtsha204.cpp
Normal file
357
lib/MySensors/core/MySigningAtsha204.cpp
Normal 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
|
||||
374
lib/MySensors/core/MySigningAtsha204Soft.cpp
Normal file
374
lib/MySensors/core/MySigningAtsha204Soft.cpp
Normal 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
|
||||
52
lib/MySensors/core/MySplashScreen.cpp
Normal file
52
lib/MySensors/core/MySplashScreen.cpp
Normal 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
|
||||
}
|
||||
28
lib/MySensors/core/MySplashScreen.h
Normal file
28
lib/MySensors/core/MySplashScreen.h
Normal 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
|
||||
1173
lib/MySensors/core/MyTransport.cpp
Normal file
1173
lib/MySensors/core/MyTransport.cpp
Normal file
File diff suppressed because it is too large
Load Diff
573
lib/MySensors/core/MyTransport.h
Normal file
573
lib/MySensors/core/MyTransport.h
Normal 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
|
||||
/** @}*/
|
||||
65
lib/MySensors/core/Version.h
Normal file
65
lib/MySensors/core/Version.h
Normal 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
|
||||
/** @}*/
|
||||
Reference in New Issue
Block a user