mirror of
https://github.com/mysensors/MySensors.git
synced 2026-03-05 15:54:13 +01:00
834 lines
25 KiB
C++
834 lines
25 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-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
|