* Signing support with MAX_PAYLOAD>32 Support in the signing backends to handle configurations where MAX_PAYLOAD is more than 32 bytes. Fixes #748 * Support big message signatures Support for signing messages larger than 32 bytes. This is not fully testable with the current version of the library but the change would add theoretical support for signing any sized messages. Fixes #749 * Have gw properly indicate whitelisting preferences If the gateway hold a signing whitelist, inform all nodes of this requirement and not just nodes that hold whitelists themselves. Fixes #806 * Harden security The flag MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL has been removed. Gateway will now request signatures from everyone by default. In addition to this, no nodes in the network will allow clearing of signing/whitelisting preferences by OTA commands by default. If the old behaviour is needed (suitable for gradual signing roll out and development purposes) a new flag, MY_SIGNING_WEAK_SECURITY can be set. Fixes #807 * Signing debug messages rewritten All signing related debug has been rewritten to better match other core debug printouts. Also, backends are updated to have a smaller implementation delta. * Simplified signing option implemented Enable by MY_SIGNING_SIMPLE_PASSWD. Signing, and signing requirements will be enabled, as will encryption. Whitelisting is optional. The value provided to MY_SIGNING_SIMPLE_PASSWD is used as HMAC and AES key. Whitelists use the first 8 bytes of the password and the ninth byte from the node ID as serial. Password is required to be at least 8 characters wide. It is zero padded if it is not long enough to cover the AES (16 bytes) and HMAC (32 bytes) keys. It is not recommended to have a password shorter than 32 bytes, and there is no need for a password longer than 32 bytes. * Add documentation section for signing debug * Add nRF5 encryption to simple signing option * Restructure MySigning documentation * Minor fixes for Linux
703 lines
20 KiB
C++
703 lines
20 KiB
C++
/*
|
|
* The MySensors Arduino library handles the wireless radio link and protocol
|
|
* between your home built sensors/actuators and HA controller of choice.
|
|
* The sensors forms a self healing radio network with optional repeaters. Each
|
|
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
|
|
* network topology allowing messages to be routed to nodes.
|
|
*
|
|
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
|
|
* Copyright (C) 2013-2017 Sensnology AB
|
|
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
|
|
*
|
|
* Documentation: http://www.mysensors.org
|
|
* Support Forum: http://forum.mysensors.org
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* version 2 as published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include "MySensorsCore.h"
|
|
|
|
#if defined(__linux__)
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
// 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(DEBUG_OUTPUT_ENABLED)
|
|
char _convBuf[MAX_PAYLOAD*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)
|
|
{
|
|
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
|
|
}
|
|
|
|
void _infiniteLoop(void)
|
|
{
|
|
while(1) {
|
|
doYield();
|
|
#if defined(__linux__)
|
|
exit(1);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void _begin(void)
|
|
{
|
|
// reset wdt
|
|
hwWatchdogReset();
|
|
|
|
if (preHwInit) {
|
|
preHwInit();
|
|
}
|
|
|
|
if (!hwInit()) {
|
|
CORE_DEBUG(PSTR("!MCO:BGN:HW ERR\n"));
|
|
setIndication(INDICATION_ERR_HW_INIT);
|
|
_infiniteLoop();
|
|
}
|
|
|
|
CORE_DEBUG(PSTR("MCO:BGN:INIT " MY_NODE_TYPE ",CP=" MY_CAPABILITIES ",VER="
|
|
MYSENSORS_LIBRARY_VERSION "\n"));
|
|
|
|
// 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=%d\n"), isTransportReady());
|
|
#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.destination == 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 enableAck)
|
|
{
|
|
message.sender = getNodeId();
|
|
mSetCommand(message, C_SET);
|
|
mSetRequestAck(message, enableAck);
|
|
|
|
#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 ack)
|
|
{
|
|
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_BATTERY_LEVEL,
|
|
ack).set(value));
|
|
}
|
|
|
|
bool sendHeartbeat(const bool ack)
|
|
{
|
|
#if defined(MY_SENSOR_NETWORK)
|
|
const uint32_t heartbeat = transportGetHeartbeat();
|
|
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_HEARTBEAT_RESPONSE,
|
|
ack).set(heartbeat));
|
|
#else
|
|
(void)ack;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
bool present(const uint8_t childSensorId, const uint8_t sensorType, const char *description,
|
|
const bool ack)
|
|
{
|
|
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, childSensorId, C_PRESENTATION, sensorType,
|
|
ack).set(childSensorId==NODE_SENSOR_ID?MYSENSORS_LIBRARY_VERSION:description));
|
|
}
|
|
|
|
bool sendSketchInfo(const char *name, const char *version, const bool ack)
|
|
{
|
|
bool result = true;
|
|
if (name) {
|
|
result &= _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_NAME,
|
|
ack).set(name));
|
|
}
|
|
if (version) {
|
|
result &= _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_VERSION,
|
|
ack).set(version));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
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 ack)
|
|
{
|
|
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_TIME, ack).set(""));
|
|
}
|
|
|
|
// Message delivered through _msg
|
|
bool _processInternalMessages(void)
|
|
{
|
|
const uint8_t type = _msg.type;
|
|
|
|
if (_msg.sender == GATEWAY_ADDRESS) {
|
|
if (type == I_REBOOT) {
|
|
#if !defined(MY_DISABLE_REMOTE_RESET)
|
|
// Requires MySensors or other bootloader with watchdogs enabled
|
|
setIndication(INDICATION_REBOOT);
|
|
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=%d\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_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;
|
|
}
|
|
} else {
|
|
// sender is a node
|
|
if (type == I_REGISTRATION_REQUEST) {
|
|
#if defined(MY_GATEWAY_FEATURE)
|
|
// registeration 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>16000000)
|
|
// delay for fast GW and slow nodes
|
|
delay(5);
|
|
#endif
|
|
(void)_sendRoute(build(_msgTmp, _msg.sender, NODE_SENSOR_ID, C_INTERNAL,
|
|
I_REGISTRATION_RESPONSE).set(approveRegistration));
|
|
#else
|
|
return false; // processing of this request via controller
|
|
#endif
|
|
#endif
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
const uint32_t enteringMS = hwMillis();
|
|
while (hwMillis() - enteringMS < waitingMS) {
|
|
_process();
|
|
}
|
|
}
|
|
|
|
bool wait(const uint32_t waitingMS, const uint8_t cmd, const uint8_t msgType)
|
|
{
|
|
const uint32_t enteringMS = hwMillis();
|
|
// invalidate msg type
|
|
_msg.type = !msgType;
|
|
bool expectedResponse = false;
|
|
while ( (hwMillis() - enteringMS < waitingMS) && !expectedResponse ) {
|
|
_process();
|
|
expectedResponse = (mGetCommand(_msg) == cmd && _msg.type == msgType);
|
|
}
|
|
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=%lu,SMS=%d,I1=%d,M1=%d,I2=%d,M2=%d\n"), sleepingMS, smartSleep,
|
|
interrupt1, mode1, interrupt2, mode2);
|
|
// OTA FW feature: do not sleep if FW update ongoing
|
|
#if defined(MY_OTA_FIRMWARE_FEATURE)
|
|
if (isFirmwareUpdateOngoing()) {
|
|
CORE_DEBUG(PSTR("!MCO:SLP:FWUPD\n")); // sleeping not possible, FW update ongoing
|
|
wait(sleepingMS);
|
|
return MY_SLEEP_NOT_POSSIBLE;
|
|
}
|
|
#endif
|
|
// 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=%lu\n"), sleepingTimeMS);
|
|
} else {
|
|
// no sleeping time left
|
|
return MY_SLEEP_NOT_POSSIBLE;
|
|
}
|
|
}
|
|
if (smartSleep) {
|
|
// 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
|
|
}
|
|
#else
|
|
(void)smartSleep;
|
|
#endif
|
|
|
|
#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=%d\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);
|
|
}
|
|
|
|
|
|
|
|
void _nodeLock(const char* str)
|
|
{
|
|
#ifdef MY_NODE_LOCK_FEATURE
|
|
// Make sure EEPROM is updated to locked status
|
|
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, 0);
|
|
while (1) {
|
|
setIndication(INDICATION_ERR_LOCKED);
|
|
CORE_DEBUG(PSTR("MCO:NLK:NODE LOCKED. TO UNLOCK, GND PIN %d 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((unsigned long)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) == 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
|
|
unsigned long enter = hwMillis();
|
|
while (hwMillis() - enter < 2) {}
|
|
if (hwDigitalRead(MY_NODE_UNLOCK_PIN) == 0) {
|
|
// Pin is grounded, reset lock counter
|
|
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, 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) == 0xFF) {
|
|
// Reset walue
|
|
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, MY_NODE_LOCK_COUNTER_MAX);
|
|
}
|
|
#endif
|
|
}
|
|
#if DOXYGEN
|
|
|
|
#endif |