diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index 2e135c0..48dd130 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -81,6 +81,18 @@ //#define MY_RADIO_RFM69 //#define MY_RS485 +/** +* @def MY_TRANSPORT_SANITY_CHECK +* @brief If enabled, node will check transport in regular intervals to detect HW issues and re-initialize in case of failure. This feature is enabled for all repeater nodes (incl. GW) +*/ +//#define MY_TRANSPORT_SANITY_CHECK +/** +* @def MY_TRANSPORT_SANITY_CHECK_INTERVAL +* @brief Interval (in ms) of transport sanity checks +*/ +#ifndef MY_TRANSPORT_SANITY_CHECK_INTERVAL +#define MY_TRANSPORT_SANITY_CHECK_INTERVAL ((uint32_t)60000) +#endif /** * @def MY_REGISTRATION_FEATURE * @brief If enabled, node has to register to gateway/controller before allowed to send sensor data. @@ -687,5 +699,6 @@ #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}}} #define MY_IS_RFM69HW #define MY_PARENT_NODE_IS_STATIC +#define MY_REGISTRATION_CONTROLLER #define MY_DEBUG_VERBOSE_RF24 #endif diff --git a/libraries/MySensors/core/MyIndication.h b/libraries/MySensors/core/MyIndication.h index fe3f192..ab2bbbc 100644 --- a/libraries/MySensors/core/MyIndication.h +++ b/libraries/MySensors/core/MyIndication.h @@ -46,6 +46,7 @@ typedef enum { INDICATION_ERR_START = 100, 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. diff --git a/libraries/MySensors/core/MyTransport.cpp b/libraries/MySensors/core/MyTransport.cpp index 305ef97..f3b50da 100644 --- a/libraries/MySensors/core/MyTransport.cpp +++ b/libraries/MySensors/core/MyTransport.cpp @@ -20,6 +20,13 @@ #include "MyTransport.h" +static uint32_t _transport_lastUplinkCheck; //!< last uplink check, to prevent GW flooding + +// repeaters (this includes GW) should perform regular sanity checks for network reliability +#if defined(MY_TRANSPORT_SANITY_CHECK) || defined(MY_REPEATER_FEATURE) + uint32_t _transport_lastSanityCheck; //!< last sanity check +#endif + // SM: transitions and update states static State stInit = { stInitTransition, NULL }; static State stParent = { stParentTransition, stParentUpdate }; @@ -37,18 +44,21 @@ void stInitTransition() { _transportSM.failedUplinkTransmissions = 0; _transportSM.pingActive = false; _transportSM.transportActive = false; - + #if defined(MY_TRANSPORT_SANITY_CHECK) || defined(MY_GATEWAY_FEATURE) || defined(MY_REPEATER_FEATURE) + _transport_lastSanityCheck = hwMillis(); + #endif + _transport_lastUplinkCheck = 0; // Read node settings (ID, parentId, GW distance) from EEPROM hwReadConfigBlock((void*)&_nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(NodeConfig)); // initialize radio if (!transportInit()) { - debug(PSTR("!RADIO:fail\n")); + debug(PSTR("!TSM:RADIO:FAIL\n")); setIndication(INDICATION_ERR_INIT_TRANSPORT); transportSwitchSM(stFailure); } else { - debug(PSTR("RADIO:OK\n")); + debug(PSTR("TSM:RADIO:OK\n")); _transportSM.transportActive = true; #if defined(MY_GATEWAY_FEATURE) // Set configuration for gateway @@ -74,7 +84,7 @@ void stInitTransition() { // stParent: find parent void stParentTransition() { - debug(PSTR("TSM:PARENT\n")); + debug(PSTR("TSM:FPAR\n")); // find parent _transportSM.preferredParentFound = false; _transportSM.findingParentNode = true; _transportSM.failedUplinkTransmissions = 0; @@ -92,6 +102,7 @@ void stParentUpdate() { if (transportTimeInState() > STATE_TIMEOUT || _transportSM.preferredParentFound) { // timeout or preferred parent found if (_nc.parentNodeId != AUTO) { + debug(PSTR("TSM:FPAR:OK\n")); // find parent ok _transportSM.findingParentNode = false; setIndication(INDICATION_GOT_PARENT); transportSwitchSM(stID); @@ -102,6 +113,7 @@ void stParentUpdate() { transportSwitchSM(stParent); } else { + debug(PSTR("!TSM:FPAR:FAIL\n")); // find parent fail setIndication(INDICATION_ERR_FIND_PARENT); transportSwitchSM(stFailure); } @@ -122,7 +134,7 @@ void stIDTransition() { void stIDUpdate() { if (_nc.nodeId != AUTO) { // current node ID is valid, proceed to uplink check - debug(PSTR("node ID=%d\n"), _nc.nodeId); + debug(PSTR("TSM:CHKID:OK (ID=%d)\n"), _nc.nodeId); setIndication(INDICATION_GOT_NODEID); // check uplink transportSwitchSM(stUplink); @@ -134,6 +146,7 @@ void stIDUpdate() { } else { // fail + debug(PSTR("!TSM:CHKID:FAIL (ID=%d)\n"), _nc.nodeId); setIndication(INDICATION_ERR_GET_NODEID); transportSwitchSM(stFailure); } @@ -141,14 +154,19 @@ void stIDUpdate() { } void stUplinkTransition() { - if (transportPingNode(GATEWAY_ADDRESS) == _nc.distance) { + debug(PSTR("TSM:UPL\n")); + if(transportCheckUplink(true)) { + debug(PSTR("TSM:UPL:OK\n")); transportSwitchSM(stOK); } - else transportSwitchSM(stParent); + else { + debug(PSTR("!TSM:UPL:FAIL\n")); + transportSwitchSM(stParent); + } } void stOKTransition() { - debug(PSTR("TSM:OK\n")); + debug(PSTR("TSM:READY\n")); // transport is ready _transportSM.uplinkOk = true; } @@ -158,21 +176,22 @@ void stOKUpdate() { if (_transportSM.failedUplinkTransmissions > TRANSMISSION_FAILURES) { // too many uplink transmissions failed, find new parent #if !defined(MY_PARENT_NODE_IS_STATIC) - debug(PSTR("!TSM:UPL fail, SNP\n")); + debug(PSTR("!TSM:UPL FAIL, SNP\n")); transportSwitchSM(stParent); #else - debug(PSTR("!TSM:UPL fail, STATP\n")); + debug(PSTR("!TSM:UPL FAIL, STATP\n")); _transportSM.failedUplinkTransmissions = 0; #endif } - #endif + #endif } -// stFailure: ented upon HW init failure or max retries exceeded +// stFailure: entered upon HW init failure or max retries exceeded void stFailureTransition() { debug(PSTR("!TSM:FAILURE\n")); _transportSM.uplinkOk = false; _transportSM.transportActive = false; + setIndication(INDICATION_ERR_INIT_TRANSPORT); // power down transport, no need until re-init debug(PSTR("TSM:PDT\n")); transportPowerDown(); @@ -224,10 +243,35 @@ void transportProcess() { // update state machine transportUpdateSM(); // process transport FIFO - if (_transportSM.transportActive) transportProcessFIFO(); + transportProcessFIFO(); } +bool transportCheckUplink(bool force) { + if (!force && (hwMillis() - _transport_lastUplinkCheck) < CHKUPL_INTERVAL) { + debug(PSTR("TSP:CHKUPL:OK (FLDCTRL)\n")); // flood control + return true; + } + // ping GW + uint8_t hopsCount = transportPingNode(GATEWAY_ADDRESS); + // verify hops + if (hopsCount != INVALID_HOPS) { + // update + _transport_lastUplinkCheck = hwMillis(); + debug(PSTR("TSP:CHKUPL:OK\n")); + // did distance to GW change upstream, i.e. re-routing of uplink nodes? + if (hopsCount != _nc.distance) { + debug(PSTR("TSP:CHKUPL:DGWC (old=%d,new=%d)"), _nc.distance, hopsCount); // distance to GW changed + _nc.distance = hopsCount; + } + return true; + } + else { + debug(PSTR("TSP:CHKUPL:FAIL (hops=%d)\n"), hopsCount); + return false; + } +} + void transportAssignNodeID(uint8_t newNodeId) { // verify if ID valid if (newNodeId != GATEWAY_ADDRESS && newNodeId != AUTO) { @@ -235,10 +279,10 @@ void transportAssignNodeID(uint8_t newNodeId) { transportSetAddress(newNodeId); // Write ID to EEPROM hwWriteConfig(EEPROM_NODE_ID_ADDRESS, newNodeId); - debug(PSTR("assign node ID=%d\n"),newNodeId); + debug(PSTR("TSP:ASSIGNID:OK (ID=%d)\n"),newNodeId); } else { - debug(PSTR("node ID invalid (%d)\n"),newNodeId); + debug(PSTR("!TSP:ASSIGNID:FAIL (ID=%d)\n"),newNodeId); setIndication(INDICATION_ERR_NET_FULL); // Nothing else we can do... transportSwitchSM(stFailure); @@ -250,7 +294,7 @@ bool transportRouteMessage(MyMessage &message) { uint8_t route; if (_transportSM.findingParentNode && destination != BROADCAST_ADDRESS) { - debug(PSTR("finding parent active, msg not send\n")); + debug(PSTR("!TSP:FPAR:ACTIVE (msg not send)\n")); // request to send a non-BC message while finding parent active, abort return false; } @@ -269,7 +313,7 @@ bool transportRouteMessage(MyMessage &message) { // route unknown if (message.last != _nc.parentNodeId) { // message not from parent, i.e. child node - route it to parent - debug(PSTR("Destination %d unknown, send to parent %d\n"), destination, _nc.parentNodeId); + debug(PSTR("!TSP:ROUTING:DEST UNKNOWN (dest=%d, STP=%d)\n"), destination, _nc.parentNodeId); route = _nc.parentNodeId; } else { @@ -306,7 +350,7 @@ bool transportSendRoute(MyMessage &message) { } else { // TNR: transport not ready - debug(PSTR("!TNR\n")); + debug(PSTR("!TSP:SEND:TNR\n")); return false; } } @@ -318,7 +362,8 @@ bool transportWait(uint32_t ms, uint8_t cmd, uint8_t msgtype){ _msg.type = !msgtype; bool expectedResponse = false; while ((hwMillis() - enter < ms) && !expectedResponse) { - transportProcess(); + // process incoming messages + transportProcessFIFO(); #if defined(ARDUINO_ARCH_ESP8266) yield(); #endif @@ -330,15 +375,17 @@ bool transportWait(uint32_t ms, uint8_t cmd, uint8_t msgtype){ uint8_t transportPingNode(uint8_t targetId) { if(!_transportSM.pingActive){ if(targetId == _nc.nodeId) { - // ping to ourself + // ping to ourself, pingActive remains false return 0; } _transportSM.pingActive = true; _transportSM.pingResponse = INVALID_HOPS; - debug(PSTR("pinging node %d\n"), targetId); + debug(PSTR("TSP:PING:SEND (dest=%d)\n"), targetId); transportRouteMessage(build(_msgTmp, _nc.nodeId, targetId, NODE_SENSOR_ID, C_INTERNAL, I_PING, false).set((uint8_t)0x01)); // Wait for ping reply or timeout transportWait(2000, C_INTERNAL, I_PONG); + // make sure missing I_PONG msg does not block pinging function by leaving pignActive=true + _transportSM.pingActive = false; return _transportSM.pingResponse; } else { @@ -347,9 +394,10 @@ uint8_t transportPingNode(uint8_t targetId) { } void transportClearRoutingTable() { - debug(PSTR("CRT\n")); // clear routing table - for(uint8_t i = 0; i!=255; i++) - hwWriteConfig(EEPROM_ROUTES_ADDRESS+i, BROADCAST_ADDRESS); + for (uint8_t i = 0; i != 255; i++) { + hwWriteConfig(EEPROM_ROUTES_ADDRESS + i, BROADCAST_ADDRESS); + } + debug(PSTR("TSP:CRT:OK\n")); // clear routing table } uint32_t transportGetHeartbeat() { @@ -361,31 +409,32 @@ void transportProcessMessage() { uint8_t payloadLength = transportReceive((uint8_t *)&_msg); (void)payloadLength; // currently not used, but good to test for CRC-ok but corrupt msgs + setIndication(INDICATION_RX); - // verify protocol version - if(mGetVersion(_msg) != PROTOCOL_VERSION) { - setIndication(INDICATION_ERR_VERSION); - debug(PSTR("!PVER mismatch\n")); - return; - } - - // Reject massages that do not pass verification - if (!signerVerifyMsg(_msg)) { - setIndication(INDICATION_ERR_SIGN); - debug(PSTR("!SIGN verify fail\n")); - return; - } - uint8_t command = mGetCommand(_msg); uint8_t type = _msg.type; uint8_t sender = _msg.sender; uint8_t last = _msg.last; uint8_t destination = _msg.destination; - debug(PSTR("read: %d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d:%s\n"), + debug(PSTR("TSP:MSG:READ %d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d:%s\n"), sender, last, destination, _msg.sensor, mGetCommand(_msg), type, mGetPayloadType(_msg), mGetLength(_msg), mGetSigned(_msg), _msg.getString(_convBuf)); + // verify protocol version + if(mGetVersion(_msg) != PROTOCOL_VERSION) { + setIndication(INDICATION_ERR_VERSION); + debug(PSTR("!TSP:MSG:PVER mismatch\n")); // protocol version + return; + } + + // Reject massages that do not pass verification + if (!signerVerifyMsg(_msg)) { + setIndication(INDICATION_ERR_SIGN); + debug(PSTR("!TSP:MSG:SIGN verify fail\n")); + return; + } + if (destination == _nc.nodeId) { // This message is addressed to this node @@ -410,7 +459,7 @@ void transportProcessMessage() { _msgTmp.sender = _nc.nodeId; _msgTmp.destination = sender; // send ACK - debug(PSTR("ACK msg\n")); + debug(PSTR("TSP:MSG:ACK msg\n")); // use transportSendRoute since ACK reply is not internal, i.e. if !transportOK do not reply transportSendRoute(_msgTmp); } @@ -433,7 +482,7 @@ void transportProcessMessage() { #if !defined(MY_GATEWAY_FEATURE) // Reply to a I_FIND_PARENT message. Check if the distance is shorter than we already have. uint8_t distance = _msg.getByte(); - debug(PSTR("node %d replied, d=%d, _nc.distance=%d\n"), sender, distance,_nc.distance); + debug(PSTR("TSP:MSG:FPAR RES (ID=%d, dist=%d)\n"), sender, distance); if (isValidDistance(distance)) { // Distance to gateway is one more for us w.r.t. parent distance++; @@ -442,11 +491,11 @@ void transportProcessMessage() { // Found a neighbor closer to GW than previously found if (!_autoFindParent && sender == MY_PARENT_NODE_ID) { _transportSM.preferredParentFound = true; - debug(PSTR("preferred parent found\n")); + debug(PSTR("TSP:MSG:FPAR (PPAR FOUND)\n")); // preferred parent found } _nc.distance = distance; _nc.parentNodeId = sender; - debug(PSTR("parent=%d, d=%d\n"), _nc.parentNodeId, _nc.distance); + debug(PSTR("TSP:MSG:PAR OK (ID=%d, dist=%d)\n"), _nc.parentNodeId, _nc.distance); } } return; @@ -455,7 +504,7 @@ void transportProcessMessage() { #endif // general if (type == I_PING) { - debug(PSTR("node pinged by %d, hops=%d\n"), sender, _msg.getByte()); + debug(PSTR("TSP:MSG:PINGED (ID=%d, hops=%d)\n"), sender, _msg.getByte()); transportRouteMessage(build(_msgTmp, _nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_PONG, false).set((uint8_t)0x01)); return; // no further processing required } @@ -463,7 +512,7 @@ void transportProcessMessage() { if (_transportSM.pingActive) { _transportSM.pingActive = false; _transportSM.pingResponse = _msg.getByte(); - debug(PSTR("ping replied, hops=%d\n"), _transportSM.pingResponse); + debug(PSTR("TSP:MSG:PONG RECV (hops=%d)\n"), _transportSM.pingResponse); } return; // no further processing required } @@ -492,22 +541,24 @@ void transportProcessMessage() { } else if (destination == BROADCAST_ADDRESS) { // broadcast - debug(PSTR("MSG:BC\n")); + debug(PSTR("TSP:MSG:BC\n")); if (command == C_INTERNAL) { if (isTransportOK()) { // only reply if node is fully operational if (type == I_FIND_PARENT) { #if defined(MY_REPEATER_FEATURE) if (sender != _nc.parentNodeId) { // no circular reference - debug(PSTR("FPREQ from %d\n"), sender); // FPR: find parent request + debug(PSTR("TSP:MSG:FPAR REQ (sender=%d)\n"), sender); // FPR: find parent request // node is in our range, update routing table - important if node has new repeater as parent hwWriteConfig(EEPROM_ROUTES_ADDRESS + sender, sender); // check if uplink functional - node can only be parent node if link to GW functional // this also prevents circular references in case GW ooo - if (transportPingNode(GATEWAY_ADDRESS) != INVALID_HOPS) { // eventually use _nc.distance instead of INVALID_HOPS - // random wait to minimize collisions - debug(PSTR("GWL ok, FPRES to %d\n"), sender); // GW link ok, respond to request - delay(hwMillis() & 0x3ff); + if(transportCheckUplink(false)){ + #if defined(MY_REPEATER_FEATURE) + _transport_lastUplinkCheck = hwMillis(); + #endif + debug(PSTR("TSP:MSG:GWL OK\n")); // GW uplink ok + //delay(hwMillis() & 0x3ff); transportRouteMessage(build(_msgTmp, _nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT_RESPONSE, false).set(_nc.distance)); } } @@ -529,7 +580,7 @@ void transportProcessMessage() { #if defined(MY_REPEATER_FEATURE) // controlled BC repeating: forward only if message received from parent and sender not self to prevent circular fwds if(last == _nc.parentNodeId && sender != _nc.nodeId && isTransportOK()){ - debug(PSTR("FWD BC msg\n")); // forward BC msg + debug(PSTR("TSP:MSG:FWD BC MSG\n")); // forward BC msg transportRouteMessage(_msg); } #endif @@ -544,8 +595,8 @@ void transportProcessMessage() { // msg not no us and not BC, relay msg #if defined(MY_REPEATER_FEATURE) if (isTransportOK()) { - debug(PSTR("REL MSG\n")); // relay msg - // only update routing table to uplink + debug(PSTR("TSP:MSG:REL MSG\n")); // relay msg + // update routing table if message not received from parent if (last != _nc.parentNodeId) { hwWriteConfig(EEPROM_ROUTES_ADDRESS + sender, last); } @@ -553,7 +604,7 @@ void transportProcessMessage() { if (type == I_PING || type == I_PONG) { uint8_t hopsCnt = _msg.getByte(); if (hopsCnt != MAX_HOPS) { - debug(PSTR("relay ping/pong, hops=%d\n"), hopsCnt); + debug(PSTR("TSP:MSG:REL PxNG (hops=%d)\n"), hopsCnt); _msg.set((uint8_t)(hopsCnt + 1)); } } @@ -562,14 +613,34 @@ void transportProcessMessage() { transportRouteMessage(_msg); } #else - debug(PSTR("REL MSG, but not a repeater\n")); + debug(PSTR("!TSM:MSG:REL MSG, but not a repeater\n")); #endif } } inline void transportProcessFIFO() { - // process all msgs in FIFO - while (transportAvailable()) { + if (_transportSM.transportActive) { + #if defined(MY_TRANSPORT_SANITY_CHECK) || defined(MY_REPEATER_FEATURE) + if (hwMillis() > (_transport_lastSanityCheck + MY_TRANSPORT_SANITY_CHECK_INTERVAL)) { + _transport_lastSanityCheck = hwMillis(); + if (!transportSanityCheck()) { + debug(PSTR("!TSP:SANCHK:FAIL\n")); // sanity check fail + transportSwitchSM(stFailure); + return; + } + else { + debug(PSTR("TSP:SANCHK:OK\n")); // sanity check ok + } + } + #endif + } + else { + // transport not active, nothing to be done + return; + } + uint8_t _processedMessages = MAX_SUBSEQ_MSGS; + // process all msgs in FIFO or counter exit + while (transportAvailable() && _processedMessages--) { transportProcessMessage(); } #if defined(MY_OTA_FIRMWARE_FEATURE) @@ -587,7 +658,7 @@ bool transportSendWrite(uint8_t to, MyMessage &message) { // sign message if required if (!signerSignMsg(message)) { - debug(PSTR("!sign fail\n")); + debug(PSTR("!TSP:MSG:SIGN fail\n")); setIndication(INDICATION_ERR_SIGN); } @@ -598,8 +669,8 @@ bool transportSendWrite(uint8_t to, MyMessage &message) { setIndication(INDICATION_TX); bool ok = transportSend(to, &message, min(MAX_MESSAGE_LENGTH, HEADER_SIZE + length)); - debug(PSTR("send: %d-%d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d,st=%s:%s\n"), - message.sender,message.last, to, message.destination, message.sensor, mGetCommand(message), message.type, + debug(PSTR("%sTSP:MSG:SEND %d-%d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d,st=%s:%s\n"), + (ok || to == BROADCAST_ADDRESS ? "" : "!"),message.sender,message.last, to, message.destination, message.sensor, mGetCommand(message), message.type, mGetPayloadType(message), mGetLength(message), mGetSigned(message), to==BROADCAST_ADDRESS ? "bc" : (ok ? "ok":"fail"), message.getString(_convBuf)); return (ok || to==BROADCAST_ADDRESS); diff --git a/libraries/MySensors/core/MyTransport.h b/libraries/MySensors/core/MyTransport.h index 15eed5d..e4304bc 100644 --- a/libraries/MySensors/core/MyTransport.h +++ b/libraries/MySensors/core/MyTransport.h @@ -27,15 +27,17 @@ #include "MySensorsCore.h" -#define TRANSMISSION_FAILURES 5 //!< search for a new parent node after this many transmission failures -#define TIMEOUT_FAILURE_STATE 10000 //!< duration failure state -#define STATE_TIMEOUT 2000 //!< general state timeout -#define STATE_RETRIES 3 //!< retries before switching to FAILURE -#define AUTO 255 //!< ID 255 is reserved for auto initialization of nodeId. -#define BROADCAST_ADDRESS ((uint8_t)255) //!< broadcasts are addressed to ID 255 -#define DISTANCE_INVALID ((uint8_t)255) //!< invalid distance when searching for parent -#define MAX_HOPS ((uint8_t)254) //!< maximal mumber of hops for ping/pong -#define INVALID_HOPS ((uint8_t)255) //!< invalid hops +#define TRANSMISSION_FAILURES 5 //!< search for a new parent node after this many transmission failures +#define TIMEOUT_FAILURE_STATE 10000 //!< duration failure state +#define STATE_TIMEOUT 2000 //!< general state timeout +#define STATE_RETRIES 3 //!< retries before switching to FAILURE +#define AUTO 255 //!< ID 255 is reserved for auto initialization of nodeId. +#define BROADCAST_ADDRESS ((uint8_t)255) //!< broadcasts are addressed to ID 255 +#define DISTANCE_INVALID ((uint8_t)255) //!< invalid distance when searching for parent +#define MAX_HOPS ((uint8_t)254) //!< maximal mumber of hops for ping/pong +#define INVALID_HOPS ((uint8_t)255) //!< invalid hops +#define MAX_SUBSEQ_MSGS 5 //!< Maximum number of subsequentially processed messages in FIFO (to prevent transport deadlock if HW issue) +#define CHKUPL_INTERVAL ((uint32_t)5000) //!< Minimum time interval to re-check uplink #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 @@ -177,6 +179,12 @@ bool transportSendRoute(MyMessage &message); * @return true if message sent successfully */ bool transportSendWrite(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(bool force); // PUBLIC functions @@ -231,6 +239,11 @@ bool transportSend(uint8_t to, const void* data, uint8_t len); */ bool transportAvailable(); /** +* @brief Sanity check for transport: is transport still responsive? +* @return true transport ok +*/ +bool transportSanityCheck(); +/** * @brief Receive message from FIFO * @return length of recevied message (header + payload) */ diff --git a/libraries/MySensors/core/MyTransportNRF24.cpp b/libraries/MySensors/core/MyTransportNRF24.cpp index 3db404c..e38ecf4 100644 --- a/libraries/MySensors/core/MyTransportNRF24.cpp +++ b/libraries/MySensors/core/MyTransportNRF24.cpp @@ -75,6 +75,10 @@ bool transportAvailable() { return avail; } +bool transportSanityCheck() { + return RF24_sanityCheck(); +} + uint8_t transportReceive(void* data) { uint8_t len = RF24_readMessage(data); #if defined(MY_RF24_ENABLE_ENCRYPTION) diff --git a/libraries/MySensors/core/MyTransportRFM69.cpp b/libraries/MySensors/core/MyTransportRFM69.cpp index 776b2ae..b5aec33 100644 --- a/libraries/MySensors/core/MyTransportRFM69.cpp +++ b/libraries/MySensors/core/MyTransportRFM69.cpp @@ -57,6 +57,11 @@ bool transportAvailable() { return _radio.receiveDone(); } +bool transportSanityCheck() { + // not implemented yet + return true; +} + uint8_t transportReceive(void* data) { memcpy(data,(const void *)_radio.DATA, _radio.DATALEN); // Send ack back if this message wasn't a broadcast diff --git a/libraries/MySensors/core/MyTransportRS485.cpp b/libraries/MySensors/core/MyTransportRS485.cpp index d488ab9..814f721 100644 --- a/libraries/MySensors/core/MyTransportRS485.cpp +++ b/libraries/MySensors/core/MyTransportRS485.cpp @@ -337,6 +337,11 @@ bool transportAvailable() { return _packet_received; } +bool transportSanityCheck() { + // not implemented yet + return true; +} + uint8_t transportReceive(void* data) { if (_packet_received) { memcpy(data,_data,_packet_len); diff --git a/libraries/MySensors/drivers/RF24/RF24.cpp b/libraries/MySensors/drivers/RF24/RF24.cpp index 0378107..9af162f 100644 --- a/libraries/MySensors/drivers/RF24/RF24.cpp +++ b/libraries/MySensors/drivers/RF24/RF24.cpp @@ -63,22 +63,22 @@ LOCAL uint8_t RF24_spiByteTransfer(uint8_t cmd) { LOCAL uint8_t RF24_RAW_readByteRegister(uint8_t cmd) { uint8_t value = RF24_spiMultiByteTransfer( cmd, NULL, 1, true); - RF24_DEBUG(PSTR("read register, reg=%d, value=%d\n"), cmd & (R_REGISTER ^ 0xFF), value); + RF24_DEBUG(PSTR("RF24:read register, reg=%d, value=%d\n"), cmd & (R_REGISTER ^ 0xFF), value); return value; } LOCAL uint8_t RF24_RAW_writeByteRegister(uint8_t cmd, uint8_t value) { - RF24_DEBUG(PSTR("write register, reg=%d, value=%d\n"), cmd & (W_REGISTER ^ 0xFF), value); + RF24_DEBUG(PSTR("RF24:write register, reg=%d, value=%d\n"), cmd & (W_REGISTER ^ 0xFF), value); return RF24_spiMultiByteTransfer( cmd , &value, 1, false); } LOCAL void RF24_flushRX(void) { - RF24_DEBUG(PSTR("RF24_flushRX\n")); + RF24_DEBUG(PSTR("RF24:flushRX\n")); RF24_spiByteTransfer( FLUSH_RX ); } LOCAL void RF24_flushTX(void) { - RF24_DEBUG(PSTR("RF24_flushTX\n")); + RF24_DEBUG(PSTR("RF24:flushTX\n")); RF24_spiByteTransfer( FLUSH_TX ); } @@ -138,14 +138,14 @@ LOCAL void RF24_enableFeatures(void) { } LOCAL void RF24_openWritingPipe(uint8_t recipient) { - RF24_DEBUG(PSTR("open writing pipe, recipient=%d\n"), recipient); + RF24_DEBUG(PSTR("RF24:open writing pipe, recipient=%d\n"), recipient); // only write LSB of RX0 and TX pipe RF24_setPipeLSB(RX_ADDR_P0, recipient); RF24_setPipeLSB(TX_ADDR, recipient); } LOCAL void RF24_startListening(void) { - RF24_DEBUG(PSTR("start listening\n")); + RF24_DEBUG(PSTR("RF24:start listening\n")); // toggle PRX RF24_setRFConfiguration(MY_RF24_CONFIGURATION | _BV(PWR_UP) | _BV(PRIM_RX) ); // all RX pipe addresses must be unique, therefore skip if node ID is 0xFF @@ -155,7 +155,7 @@ LOCAL void RF24_startListening(void) { } LOCAL void RF24_stopListening(void) { - RF24_DEBUG(PSTR("stop listening\n")); + RF24_DEBUG(PSTR("RF24:stop listening\n")); RF24_ce(LOW); // timing delayMicroseconds(130); @@ -167,7 +167,7 @@ LOCAL void RF24_stopListening(void) { LOCAL void RF24_powerDown(void) { RF24_ce(LOW); RF24_setRFConfiguration(MY_RF24_CONFIGURATION); - RF24_DEBUG(PSTR("power down\n")); + RF24_DEBUG(PSTR("RF24:power down\n")); } LOCAL bool RF24_sendMessage( uint8_t recipient, const void* buf, uint8_t len ) { @@ -175,40 +175,41 @@ LOCAL bool RF24_sendMessage( uint8_t recipient, const void* buf, uint8_t len ) { RF24_stopListening(); RF24_openWritingPipe( recipient ); - RF24_DEBUG(PSTR("send message to %d, len=%d\n"),recipient,len); + RF24_DEBUG(PSTR("RF24:send message to %d, len=%d\n"),recipient,len); // flush TX FIFO RF24_flushTX(); // this command is affected in clones (e.g. Si24R1): flipped NoACK bit when using W_TX_PAYLOAD_NO_ACK / W_TX_PAYLOAD // RF24_spiMultiByteTransfer( recipient==BROADCAST_ADDRESS ? W_TX_PAYLOAD_NO_ACK : W_TX_PAYLOAD, (uint8_t*)buf, len, false ); // we are safe by disabling AutoACK on the broadcasting pipe RF24_spiMultiByteTransfer( W_TX_PAYLOAD, (uint8_t*)buf, len, false ); - // go + // go, TX starts after ~10us RF24_ce(HIGH); - // TX starts after ~10us + // timeout counter to detect HW issues + uint16_t timeout = 0xFFFF; do { status = RF24_getStatus(); - } while (!(status & ( _BV(MAX_RT) | _BV(TX_DS) ))); - + } while (!(status & ( _BV(MAX_RT) | _BV(TX_DS) )) && timeout--); + // timeout value after successful TX on 16Mhz AVR ~ 65500, i.e. msg is transmitted after ~36 loop cycles RF24_ce(LOW); // reset interrupts RF24_setStatus(_BV(TX_DS) | _BV(MAX_RT) ); // Max retries exceeded if( status & _BV(MAX_RT)){ // flush packet - RF24_DEBUG(PSTR("MAX_RT\n")); + RF24_DEBUG(PSTR("RF24:MAX_RT\n")); RF24_flushTX(); } RF24_startListening(); - - return (status & _BV(TX_DS)); + // true if message sent and not timeout + return (status & _BV(TX_DS) && timeout); } LOCAL uint8_t RF24_getDynamicPayloadSize(void) { uint8_t result = RF24_spiMultiByteTransfer(R_RX_PL_WID, NULL, 1, true); // check if payload size invalid if(result > 32) { - RF24_DEBUG(PSTR("invalid payload length = %d\n"),result); + RF24_DEBUG(PSTR("RF24:invalid payload length = %d\n"),result); RF24_flushRX(); result = 0; } @@ -217,18 +218,14 @@ LOCAL uint8_t RF24_getDynamicPayloadSize(void) { LOCAL bool RF24_isDataAvailable() { - uint8_t pipe_num = ( RF24_getStatus() >> RX_P_NO ) & 0b0111; - #if defined(MY_DEBUG_VERBOSE_RF24) && defined(MY_DEBUG) - if(pipe_num <= 5) - RF24_DEBUG(PSTR("Data available on pipe %d\n"),pipe_num); - #endif - return (pipe_num <= 5); + uint8_t status = RF24_getStatus(); + return (((status >> RX_P_NO) & 0b0111) <= 5); } LOCAL uint8_t RF24_readMessage( void* buf) { uint8_t len = RF24_getDynamicPayloadSize(); - RF24_DEBUG(PSTR("read message, len=%d\n"), len); + RF24_DEBUG(PSTR("RF24:read message, len=%d\n"), len); RF24_spiMultiByteTransfer( R_RX_PAYLOAD , (uint8_t*)buf, len, true ); // clear RX interrupt RF24_setStatus(_BV(RX_DR) ); @@ -249,6 +246,12 @@ LOCAL uint8_t RF24_getNodeID(void) { return MY_RF24_NODE_ADDRESS; } +LOCAL bool RF24_sanityCheck(void) { + // detect HW defect ot interrupted SPI line, CE disconnect cannot be detected + bool status = RF24_readByteRegister(RF_SETUP) == MY_RF24_RF_SETUP; + status &= RF24_readByteRegister(RF_CH) == MY_RF24_CHANNEL; + return status; +} LOCAL bool RF24_initialize(void) { // Initialize pins @@ -272,11 +275,10 @@ LOCAL bool RF24_initialize(void) { RF24_setRFSetup(MY_RF24_RF_SETUP); // sanity check #if defined(MY_RF24_SANITY_CHECK) - uint8_t _rf_setup = RF24_readByteRegister(RF_SETUP); - if(_rf_setup!=MY_RF24_RF_SETUP) { - RF24_DEBUG(PSTR("Sanity check failed: RF_SETUP register=%d instead of %d, check wiring, replace module or non-P version\n"), _rf_setup, MY_RF24_RF_SETUP); - return false; - } + if (!RF24_sanityCheck()) { + RF24_DEBUG(PSTR("RF24:Sanity check failed: configuration mismatch! Check wiring, replace module or non-P version\n")); + return false; + } #endif // toggle features (necessary on some clones) RF24_enableFeatures(); diff --git a/libraries/MySensors/drivers/RF24/RF24.h b/libraries/MySensors/drivers/RF24/RF24.h index 744020f..e0f81ba 100644 --- a/libraries/MySensors/drivers/RF24/RF24.h +++ b/libraries/MySensors/drivers/RF24/RF24.h @@ -232,6 +232,7 @@ LOCAL bool RF24_isDataAvailable(); LOCAL uint8_t RF24_readMessage(void* buf); LOCAL void RF24_setNodeAddress(uint8_t address); LOCAL uint8_t RF24_getNodeID(void); +LOCAL bool RF24_sanityCheck(void); LOCAL bool RF24_initialize(void); LOCAL void RF24_setChannel(uint8_t channel); LOCAL void RF24_setRetries(uint8_t retransmitDelay, uint8_t retransmitCount); diff --git a/libraries/MySensors/keywords.txt b/libraries/MySensors/keywords.txt index d8ca7d5..09acb99 100644 --- a/libraries/MySensors/keywords.txt +++ b/libraries/MySensors/keywords.txt @@ -45,6 +45,8 @@ MY_REGISTRATION_FEATURE LITERAL1 MY_REGISTRATION_DEFAULT LITERAL1 MY_REGISTRATION_CONTROLLER LITERAL1 MY_CORE_COMPATIBILITY_CHECK LITERAL1 +MY_TRANSPORT_SANITY_CHECK LITERAL1 +MY_TRANSPORT_SANITY_CHECK_INTERVAL LITERAL1 MY_NODE_ID LITERAL1 MY_PARENT_NODE_ID LITERAL1 MY_PARENT_NODE_IS_STATIC LITERAL1