* 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
1164 lines
37 KiB
C++
1164 lines
37 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 "MyTransport.h"
|
|
|
|
// debug
|
|
#if defined(MY_DEBUG_VERBOSE_TRANSPORT)
|
|
#define TRANSPORT_DEBUG(x,...) DEBUG_OUTPUT(x, ##__VA_ARGS__) //!< debug
|
|
extern char _convBuf[MAX_PAYLOAD * 2 + 1];
|
|
#else
|
|
#define TRANSPORT_DEBUG(x,...) //!< debug NULL
|
|
#endif
|
|
|
|
|
|
// SM: transitions and update states
|
|
static transportState_t stInit = { stInitTransition, stInitUpdate };
|
|
static transportState_t stParent = { stParentTransition, stParentUpdate };
|
|
static transportState_t stID = { stIDTransition, stIDUpdate };
|
|
static transportState_t stUplink = { stUplinkTransition, stUplinkUpdate };
|
|
static transportState_t stReady = { stReadyTransition, stReadyUpdate };
|
|
static transportState_t stFailure = { stFailureTransition, stFailureUpdate };
|
|
|
|
// transport SM variables
|
|
static transportSM_t _transportSM;
|
|
|
|
// transport configuration
|
|
static transportConfig_t _transportConfig;
|
|
|
|
// callback transportOk
|
|
transportCallback_t _transportReady_cb = NULL;
|
|
|
|
// enhanced ID assignment
|
|
#if !defined(MY_GATEWAY_FEATURE) && (MY_NODE_ID == AUTO)
|
|
static uint8_t _transportToken = AUTO;
|
|
#endif
|
|
|
|
// global variables
|
|
extern MyMessage _msg; // incoming message
|
|
extern MyMessage _msgTmp; // outgoing message
|
|
|
|
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
|
|
static routingTable_t _transportRoutingTable; //!< routing table
|
|
static uint32_t _lastRoutingTableSave; //!< last routing table dump
|
|
#endif
|
|
|
|
// regular sanity check, activated by default on GW and repeater nodes
|
|
#if defined(MY_TRANSPORT_SANITY_CHECK)
|
|
static uint32_t _lastSanityCheck; //!< last sanity check
|
|
#endif
|
|
|
|
// regular network discovery, sends I_DISCOVER_REQUESTS to update routing table
|
|
// sufficient to have GW triggering requests to also update repeater nodes
|
|
#if defined(MY_GATEWAY_FEATURE)
|
|
static uint32_t _lastNetworkDiscovery; //! last network discovery
|
|
#endif
|
|
|
|
// stInit: initialise transport HW
|
|
void stInitTransition(void)
|
|
{
|
|
TRANSPORT_DEBUG(PSTR("TSM:INIT\n"));
|
|
// initialise status variables
|
|
_transportSM.pingActive = false;
|
|
_transportSM.transportActive = false;
|
|
_transportSM.lastUplinkCheck = 0ul;
|
|
|
|
#if defined(MY_TRANSPORT_SANITY_CHECK)
|
|
_lastSanityCheck = hwMillis();
|
|
#endif
|
|
#if defined(MY_GATEWAY_FEATURE)
|
|
_lastNetworkDiscovery = 0ul;
|
|
#endif
|
|
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
|
|
_lastRoutingTableSave = hwMillis();
|
|
#endif
|
|
|
|
// Read node settings (ID, parent ID, GW distance) from EEPROM
|
|
hwReadConfigBlock((void*)&_transportConfig, (void*)EEPROM_NODE_ID_ADDRESS,
|
|
sizeof(transportConfig_t));
|
|
}
|
|
|
|
void stInitUpdate(void)
|
|
{
|
|
// initialise radio
|
|
if (!transportInit()) {
|
|
TRANSPORT_DEBUG(PSTR("!TSM:INIT:TSP FAIL\n"));
|
|
setIndication(INDICATION_ERR_INIT_TRANSPORT);
|
|
transportSwitchSM(stFailure);
|
|
} else {
|
|
TRANSPORT_DEBUG(PSTR("TSM:INIT:TSP OK\n"));
|
|
_transportSM.transportActive = true;
|
|
#if defined (MY_PASSIVE_NODE)
|
|
_transportConfig.passiveMode = true;
|
|
TRANSPORT_DEBUG(PSTR("TSM:INIT:TSP PSM\n")); // transport passive mode
|
|
#else
|
|
_transportConfig.passiveMode = false;
|
|
#endif
|
|
#if defined(MY_GATEWAY_FEATURE)
|
|
// Set configuration for gateway
|
|
TRANSPORT_DEBUG(PSTR("TSM:INIT:GW MODE\n"));
|
|
_transportConfig.parentNodeId = GATEWAY_ADDRESS;
|
|
_transportConfig.distanceGW = 0u;
|
|
_transportConfig.nodeId = GATEWAY_ADDRESS;
|
|
transportSetAddress(GATEWAY_ADDRESS);
|
|
// GW mode: skip FPAR,ID,UPL states
|
|
transportSwitchSM(stReady);
|
|
#else
|
|
if (MY_NODE_ID != AUTO) {
|
|
TRANSPORT_DEBUG(PSTR("TSM:INIT:STATID=%d\n"),(uint8_t)MY_NODE_ID);
|
|
// Set static ID
|
|
_transportConfig.nodeId = (uint8_t)MY_NODE_ID;
|
|
// Save static ID to eeprom (for bootloader)
|
|
hwWriteConfig(EEPROM_NODE_ID_ADDRESS, (uint8_t)MY_NODE_ID);
|
|
}
|
|
// assign ID if set
|
|
if (_transportConfig.nodeId == AUTO || transportAssignNodeID(_transportConfig.nodeId)) {
|
|
// if node ID valid (>0 and <255), proceed to next state
|
|
transportSwitchSM(stParent);
|
|
} else {
|
|
// ID invalid (0 or 255)
|
|
transportSwitchSM(stFailure);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// stParent: find parent
|
|
void stParentTransition(void)
|
|
{
|
|
TRANSPORT_DEBUG(PSTR("TSM:FPAR\n")); // find parent
|
|
setIndication(INDICATION_FIND_PARENT);
|
|
_transportSM.uplinkOk = false;
|
|
_transportSM.preferredParentFound = false;
|
|
#if defined(MY_PARENT_NODE_IS_STATIC) || defined(MY_PASSIVE_NODE)
|
|
TRANSPORT_DEBUG(PSTR("TSM:FPAR:STATP=%d\n"), (uint8_t)MY_PARENT_NODE_ID); // static parent
|
|
_transportSM.findingParentNode = false;
|
|
_transportConfig.distanceGW = 1u; // assumption, CHKUPL:GWDC will update this variable
|
|
_transportConfig.parentNodeId = (uint8_t)MY_PARENT_NODE_ID;
|
|
// save parent ID to eeprom (for bootloader)
|
|
hwWriteConfig(EEPROM_PARENT_NODE_ID_ADDRESS, (uint8_t)MY_PARENT_NODE_ID);
|
|
#else
|
|
_transportSM.findingParentNode = true;
|
|
_transportConfig.distanceGW = DISTANCE_INVALID; // Set distance to max and invalidate parent node ID
|
|
_transportConfig.parentNodeId = AUTO;
|
|
// Broadcast find parent request
|
|
(void)transportRouteMessage(build(_msgTmp, BROADCAST_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
|
|
I_FIND_PARENT_REQUEST).set(""));
|
|
#endif
|
|
}
|
|
|
|
// stParentUpdate
|
|
void stParentUpdate(void)
|
|
{
|
|
#if defined(MY_PARENT_NODE_IS_STATIC) || defined(MY_PASSIVE_NODE)
|
|
// skipping find parent
|
|
setIndication(INDICATION_GOT_PARENT);
|
|
transportSwitchSM(stID);
|
|
#else
|
|
if (transportTimeInState() > MY_TRANSPORT_STATE_TIMEOUT_MS || _transportSM.preferredParentFound) {
|
|
// timeout or preferred parent found
|
|
if (_transportConfig.parentNodeId != AUTO) {
|
|
// parent assigned
|
|
TRANSPORT_DEBUG(PSTR("TSM:FPAR:OK\n")); // find parent ok
|
|
_transportSM.findingParentNode = false;
|
|
setIndication(INDICATION_GOT_PARENT);
|
|
// go to next state
|
|
transportSwitchSM(stID);
|
|
} else {
|
|
// timeout w/o reply or valid parent
|
|
if (_transportSM.stateRetries < MY_TRANSPORT_STATE_RETRIES) {
|
|
// retries left
|
|
TRANSPORT_DEBUG(PSTR("!TSM:FPAR:NO REPLY\n")); // find parent, no reply
|
|
// reenter state
|
|
transportSwitchSM(stParent);
|
|
} else {
|
|
// no retries left, finding parent failed
|
|
TRANSPORT_DEBUG(PSTR("!TSM:FPAR:FAIL\n"));
|
|
setIndication(INDICATION_ERR_FIND_PARENT);
|
|
transportSwitchSM(stFailure);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// stID: verify and request ID if necessary
|
|
void stIDTransition(void)
|
|
{
|
|
TRANSPORT_DEBUG(PSTR("TSM:ID\n")); // verify/request node ID
|
|
if (_transportConfig.nodeId == AUTO) {
|
|
// send ID request
|
|
setIndication(INDICATION_REQ_NODEID);
|
|
#if !defined(MY_GATEWAY_FEATURE) && (MY_NODE_ID == AUTO)
|
|
_transportToken = (uint8_t)(hwMillis() & 0xFF);
|
|
if (_transportToken == AUTO) {
|
|
_transportToken++; // AUTO as token not allowed
|
|
}
|
|
const uint8_t sensorID = _transportToken;
|
|
#else
|
|
const uint8_t sensorID = NODE_SENSOR_ID;
|
|
#endif
|
|
TRANSPORT_DEBUG(PSTR("TSM:ID:REQ\n")); // request node ID
|
|
(void)transportRouteMessage(build(_msgTmp, GATEWAY_ADDRESS, sensorID, C_INTERNAL,
|
|
I_ID_REQUEST).set(""));
|
|
}
|
|
}
|
|
|
|
void stIDUpdate(void)
|
|
{
|
|
if (_transportConfig.nodeId != AUTO) {
|
|
// current node ID is valid
|
|
TRANSPORT_DEBUG(PSTR("TSM:ID:OK\n"));
|
|
setIndication(INDICATION_GOT_NODEID);
|
|
// proceed to next state
|
|
transportSwitchSM(stUplink);
|
|
} else if (transportTimeInState() > MY_TRANSPORT_STATE_TIMEOUT_MS) {
|
|
// timeout
|
|
if (_transportSM.stateRetries < MY_TRANSPORT_STATE_RETRIES) {
|
|
// retries left: reenter state
|
|
transportSwitchSM(stID);
|
|
} else {
|
|
// no retries left
|
|
TRANSPORT_DEBUG(PSTR("!TSM:ID:FAIL\n"));
|
|
setIndication(INDICATION_ERR_GET_NODEID);
|
|
transportSwitchSM(stFailure);
|
|
}
|
|
}
|
|
}
|
|
|
|
void stUplinkTransition(void)
|
|
{
|
|
#if !defined(MY_TRANSPORT_UPLINK_CHECK_DISABLED)
|
|
TRANSPORT_DEBUG(PSTR("TSM:UPL\n"));
|
|
setIndication(INDICATION_CHECK_UPLINK);
|
|
_transportSM.pingResponse = INVALID_HOPS;
|
|
_transportSM.pingActive = true;
|
|
(void)transportRouteMessage(build(_msgTmp,GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
|
|
I_PING).set((uint8_t)0x01));
|
|
#endif
|
|
}
|
|
|
|
void stUplinkUpdate(void)
|
|
{
|
|
#if !defined(MY_TRANSPORT_UPLINK_CHECK_DISABLED)
|
|
if (_transportSM.pingResponse != INVALID_HOPS) {
|
|
_transportSM.lastUplinkCheck = hwMillis();
|
|
// uplink ok, i.e. GW replied
|
|
TRANSPORT_DEBUG(PSTR("TSM:UPL:OK\n")); // uplink ok
|
|
if (_transportSM.pingResponse != _transportConfig.distanceGW) {
|
|
TRANSPORT_DEBUG(PSTR("TSM:UPL:DGWC,O=%d,N=%d\n"), _transportConfig.distanceGW,
|
|
_transportSM.pingResponse); // distance to GW changed
|
|
_transportConfig.distanceGW = _transportSM.pingResponse;
|
|
}
|
|
transportSwitchSM(stReady); // proceed to next state
|
|
} else if (transportTimeInState() > MY_TRANSPORT_STATE_TIMEOUT_MS) {
|
|
// timeout
|
|
if (_transportSM.stateRetries < MY_TRANSPORT_STATE_RETRIES) {
|
|
// retries left: reenter state
|
|
transportSwitchSM(stUplink);
|
|
} else {
|
|
// no retries left
|
|
TRANSPORT_DEBUG(PSTR("!TSM:UPL:FAIL\n")); // uplink check failed
|
|
_transportSM.pingActive = false;
|
|
setIndication(INDICATION_ERR_CHECK_UPLINK);
|
|
transportSwitchSM(stParent); // go back to stParent
|
|
}
|
|
}
|
|
#else
|
|
TRANSPORT_DEBUG(PSTR("TSM:UPL:DISABLED\n")); // uplink check disabled
|
|
transportSwitchSM(stReady);
|
|
#endif
|
|
}
|
|
|
|
void stReadyTransition(void)
|
|
{
|
|
// transport is ready and fully operational
|
|
TRANSPORT_DEBUG(PSTR("TSM:READY:ID=%d,PAR=%d,DIS=%d\n"), _transportConfig.nodeId,
|
|
_transportConfig.parentNodeId, _transportConfig.distanceGW);
|
|
_transportSM.uplinkOk = true;
|
|
_transportSM.failureCounter = 0u; // reset failure counter
|
|
_transportSM.failedUplinkTransmissions = 0u; // reset failed uplink TX counter
|
|
// callback
|
|
if (_transportReady_cb) {
|
|
_transportReady_cb();
|
|
}
|
|
}
|
|
|
|
// stReadyUpdate: monitors link
|
|
void stReadyUpdate(void)
|
|
{
|
|
#if defined(MY_GATEWAY_FEATURE)
|
|
if (hwMillis() - _lastNetworkDiscovery > MY_TRANSPORT_DISCOVERY_INTERVAL_MS) {
|
|
_lastNetworkDiscovery = hwMillis();
|
|
TRANSPORT_DEBUG(PSTR("TSM:READY:NWD REQ\n")); // send transport network discovery
|
|
(void)transportRouteMessage(build(_msgTmp, BROADCAST_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
|
|
I_DISCOVER_REQUEST).set(""));
|
|
}
|
|
#else
|
|
if (_transportSM.failedUplinkTransmissions > MY_TRANSPORT_MAX_TX_FAILURES) {
|
|
// too many uplink transmissions failed, find new parent (if non-static)
|
|
#if !defined(MY_PARENT_NODE_IS_STATIC)
|
|
TRANSPORT_DEBUG(PSTR("!TSM:READY:UPL FAIL,SNP\n")); // uplink failed, search new parent
|
|
transportSwitchSM(stParent);
|
|
#else
|
|
TRANSPORT_DEBUG(PSTR("!TSM:READY:UPL FAIL,STATP\n")); // uplink failed, static parent
|
|
// reset counter
|
|
_transportSM.failedUplinkTransmissions = 0u;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
|
|
if (hwMillis() - _lastRoutingTableSave > MY_ROUTING_TABLE_SAVE_INTERVAL_MS) {
|
|
_lastRoutingTableSave = hwMillis();
|
|
transportSaveRoutingTable();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// stFailure: entered upon HW init failure or max retries exceeded
|
|
void stFailureTransition(void)
|
|
{
|
|
if (_transportSM.failureCounter < MY_TRANSPORT_MAX_TSM_FAILURES) {
|
|
_transportSM.failureCounter++; // increment consecutive TSM failure counter
|
|
}
|
|
TRANSPORT_DEBUG(PSTR("TSM:FAIL:CNT=%d\n"),_transportSM.failureCounter);
|
|
_transportSM.uplinkOk = false; // uplink nok
|
|
_transportSM.transportActive = false; // transport inactive
|
|
setIndication(INDICATION_ERR_INIT_TRANSPORT);
|
|
#if defined(MY_SENSOR_NETWORK)
|
|
TRANSPORT_DEBUG(PSTR("TSM:FAIL:DIS\n")); // disable transport, no need until re-init
|
|
transportDisable(); // sleep
|
|
#endif
|
|
}
|
|
|
|
void stFailureUpdate(void)
|
|
{
|
|
if (transportTimeInState() > ( isTransportExtendedFailure()?
|
|
MY_TRANSPORT_TIMEOUT_EXT_FAILURE_STATE_MS:
|
|
MY_TRANSPORT_TIMEOUT_FAILURE_STATE_MS) ) {
|
|
TRANSPORT_DEBUG(PSTR("TSM:FAIL:RE-INIT\n")); // attempt to re-initialise transport
|
|
transportSwitchSM(stInit);
|
|
}
|
|
}
|
|
|
|
void transportSwitchSM(transportState_t& newState)
|
|
{
|
|
if (_transportSM.currentState != &newState) {
|
|
_transportSM.stateRetries = 0u; // state change, reset retry counter
|
|
_transportSM.currentState = &newState; // change state
|
|
} else {
|
|
_transportSM.stateRetries++; // increment retries
|
|
}
|
|
if (_transportSM.currentState->Transition) {
|
|
_transportSM.currentState->Transition(); // State transition
|
|
}
|
|
_transportSM.stateEnter = hwMillis(); // save time
|
|
}
|
|
|
|
uint32_t transportTimeInState(void)
|
|
{
|
|
return hwMillis() - _transportSM.stateEnter;
|
|
}
|
|
|
|
void transportUpdateSM(void)
|
|
{
|
|
if (_transportSM.currentState->Update) {
|
|
_transportSM.currentState->Update();
|
|
}
|
|
}
|
|
|
|
bool isTransportReady(void)
|
|
{
|
|
return _transportSM.uplinkOk;
|
|
}
|
|
|
|
bool isTransportExtendedFailure(void)
|
|
{
|
|
return _transportSM.failureCounter == MY_TRANSPORT_MAX_TSM_FAILURES;
|
|
}
|
|
|
|
bool isTransportSearchingParent(void)
|
|
{
|
|
return _transportSM.findingParentNode;
|
|
}
|
|
|
|
bool isMessageReceived(void)
|
|
{
|
|
return _transportSM.msgReceived;
|
|
}
|
|
|
|
void resetMessageReceived(void)
|
|
{
|
|
_transportSM.msgReceived = false;
|
|
}
|
|
|
|
|
|
void transportInitialise(void)
|
|
{
|
|
_transportSM.failureCounter = 0u; // reset failure counter
|
|
transportLoadRoutingTable(); // load routing table to RAM (if feature enabled)
|
|
// intial state
|
|
_transportSM.currentState = NULL;
|
|
transportSwitchSM(stInit);
|
|
}
|
|
|
|
void transportDisable(void)
|
|
{
|
|
if (RADIO_CAN_POWER_OFF == true) {
|
|
TRANSPORT_DEBUG(PSTR("TSF:TDI:TPD\n")); // power down transport
|
|
transportPowerDown();
|
|
} else {
|
|
TRANSPORT_DEBUG(PSTR("TSF:TDI:TSL\n")); // send transport to sleep
|
|
transportSleep();
|
|
}
|
|
}
|
|
void transportReInitialise(void)
|
|
{
|
|
if (RADIO_CAN_POWER_OFF == true) {
|
|
TRANSPORT_DEBUG(PSTR("TSF:TRI:TPU\n")); // transport power up
|
|
transportPowerUp();
|
|
transportSetAddress(_transportConfig.nodeId);
|
|
} else {
|
|
TRANSPORT_DEBUG(PSTR("TSF:TRI:TSB\n")); // transport standby
|
|
transportStandBy();
|
|
}
|
|
}
|
|
|
|
|
|
bool transportWaitUntilReady(const uint32_t waitingMS)
|
|
{
|
|
// check if transport ready
|
|
TRANSPORT_DEBUG(PSTR("TSF:WUR:MS=%lu\n"), waitingMS); // timeout
|
|
uint32_t enterMS = hwMillis();
|
|
bool result = false;
|
|
while (!result && ( hwMillis() - enterMS < waitingMS || !waitingMS)) {
|
|
transportProcess();
|
|
result = isTransportReady();
|
|
doYield();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// update TSM and process incoming messages
|
|
void transportProcess(void)
|
|
{
|
|
// update state machine
|
|
transportUpdateSM();
|
|
// process transport FIFO
|
|
transportProcessFIFO();
|
|
}
|
|
|
|
|
|
bool transportCheckUplink(const bool force)
|
|
{
|
|
if (!force && (hwMillis() - _transportSM.lastUplinkCheck) < MY_TRANSPORT_CHKUPL_INTERVAL_MS) {
|
|
TRANSPORT_DEBUG(PSTR("TSF:CKU:OK,FCTRL\n")); // flood control
|
|
return true;
|
|
}
|
|
// ping GW
|
|
const uint8_t hopsCount = transportPingNode(GATEWAY_ADDRESS);
|
|
// verify hops
|
|
if (hopsCount != INVALID_HOPS) {
|
|
// update
|
|
_transportSM.lastUplinkCheck = hwMillis();
|
|
TRANSPORT_DEBUG(PSTR("TSF:CKU:OK\n"));
|
|
// did distance to GW change upstream, eg. re-routing of uplink nodes
|
|
if (hopsCount != _transportConfig.distanceGW) {
|
|
TRANSPORT_DEBUG(PSTR("TSF:CKU:DGWC,O=%d,N=%d\n"), _transportConfig.distanceGW,
|
|
hopsCount); // distance to GW changed
|
|
_transportConfig.distanceGW = hopsCount;
|
|
}
|
|
return true;
|
|
} else {
|
|
TRANSPORT_DEBUG(PSTR("TSF:CKU:FAIL\n"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool transportAssignNodeID(const uint8_t newNodeId)
|
|
{
|
|
// verify if ID valid
|
|
if (newNodeId != GATEWAY_ADDRESS && newNodeId != AUTO) {
|
|
_transportConfig.nodeId = newNodeId;
|
|
transportSetAddress(newNodeId);
|
|
// Write ID to EEPROM
|
|
hwWriteConfig(EEPROM_NODE_ID_ADDRESS, newNodeId);
|
|
TRANSPORT_DEBUG(PSTR("TSF:SID:OK,ID=%d\n"),newNodeId); // Node ID assigned
|
|
return true;
|
|
} else {
|
|
TRANSPORT_DEBUG(PSTR("!TSF:SID:FAIL,ID=%d\n"),newNodeId); // ID is invalid, cannot assign ID
|
|
setIndication(INDICATION_ERR_NET_FULL);
|
|
_transportConfig.nodeId = AUTO;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool transportRouteMessage(MyMessage &message)
|
|
{
|
|
const uint8_t destination = message.destination;
|
|
uint8_t route = _transportConfig.parentNodeId; // by default, all traffic is routed via parent node
|
|
|
|
if (_transportSM.findingParentNode && destination != BROADCAST_ADDRESS) {
|
|
TRANSPORT_DEBUG(PSTR("!TSF:RTE:FPAR ACTIVE\n")); // find parent active, message not sent
|
|
// request to send a non-BC message while finding parent active, abort
|
|
return false;
|
|
}
|
|
|
|
if (destination == GATEWAY_ADDRESS) {
|
|
route = _transportConfig.parentNodeId; // message to GW always routes via parent
|
|
} else if (destination == BROADCAST_ADDRESS) {
|
|
route = BROADCAST_ADDRESS; // message to BC does not require routing
|
|
} else {
|
|
#if defined(MY_REPEATER_FEATURE)
|
|
// destination not GW & not BC, get route
|
|
route = transportGetRoute(destination);
|
|
if (route == AUTO) {
|
|
TRANSPORT_DEBUG(PSTR("!TSF:RTE:%d UNKNOWN\n"), destination); // route unknown
|
|
#if !defined(MY_GATEWAY_FEATURE)
|
|
if (message.last != _transportConfig.parentNodeId) {
|
|
// message not from parent, i.e. child node - route it to parent
|
|
route = _transportConfig.parentNodeId;
|
|
} else {
|
|
// route unknown and msg received from parent, send it to destination assuming in rx radius
|
|
route = destination;
|
|
}
|
|
#else
|
|
// if GW, all unknown destinations are directly addressed
|
|
route = destination;
|
|
#endif
|
|
}
|
|
#else
|
|
route = _transportConfig.parentNodeId; // not a repeater, all traffic routed via parent
|
|
#endif
|
|
}
|
|
// send message, HAL
|
|
const bool result = transportSendWrite(route, message);
|
|
#if !defined(MY_GATEWAY_FEATURE)
|
|
// update counter
|
|
if (route == _transportConfig.parentNodeId) {
|
|
|
|
if (!result) {
|
|
setIndication(INDICATION_ERR_TX);
|
|
_transportSM.failedUplinkTransmissions++;
|
|
} else {
|
|
_transportSM.failedUplinkTransmissions = 0u;
|
|
#if defined(MY_SIGNAL_REPORT_ENABLED)
|
|
// update uplink quality monitor
|
|
const int16_t signalStrengthRSSI = transportGetSignalReport(SR_TX_RSSI);
|
|
_transportSM.uplinkQualityRSSI = static_cast<transportRSSI_t>((1 - UPLINK_QUALITY_WEIGHT) *
|
|
_transportSM.uplinkQualityRSSI
|
|
+ (UPLINK_QUALITY_WEIGHT * transportRSSItoInternal(signalStrengthRSSI)));
|
|
#endif
|
|
}
|
|
}
|
|
#else
|
|
if(!result) {
|
|
setIndication(INDICATION_ERR_TX);
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
bool transportSendRoute(MyMessage &message)
|
|
{
|
|
bool result = false;
|
|
if (isTransportReady()) {
|
|
result = transportRouteMessage(message);
|
|
} else {
|
|
// TNR: transport not ready
|
|
TRANSPORT_DEBUG(PSTR("!TSF:SND:TNR\n"));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// only be used inside transport
|
|
bool transportWait(const uint32_t waitingMS, const uint8_t cmd, const uint8_t msgType)
|
|
{
|
|
const uint32_t enterMS = hwMillis();
|
|
// invalidate msg type
|
|
_msg.type = !msgType;
|
|
bool expectedResponse = false;
|
|
while ((hwMillis() - enterMS < waitingMS) && !expectedResponse) {
|
|
// process incoming messages
|
|
transportProcessFIFO();
|
|
doYield();
|
|
expectedResponse = (mGetCommand(_msg) == cmd && _msg.type == msgType);
|
|
}
|
|
return expectedResponse;
|
|
}
|
|
|
|
uint8_t transportPingNode(const uint8_t targetId)
|
|
{
|
|
if(!_transportSM.pingActive) {
|
|
TRANSPORT_DEBUG(PSTR("TSF:PNG:SEND,TO=%d\n"), targetId);
|
|
if(targetId == _transportConfig.nodeId) {
|
|
// pinging self
|
|
_transportSM.pingResponse = 0u;
|
|
} else {
|
|
_transportSM.pingActive = true;
|
|
_transportSM.pingResponse = INVALID_HOPS;
|
|
(void)transportRouteMessage(build(_msgTmp, targetId, NODE_SENSOR_ID, C_INTERNAL,
|
|
I_PING).set((uint8_t)0x01));
|
|
// Wait for ping reply or timeout
|
|
(void)transportWait(2000, C_INTERNAL, I_PONG);
|
|
}
|
|
// make sure missing I_PONG msg does not block pinging function by leaving pingActive=true
|
|
_transportSM.pingActive = false;
|
|
return _transportSM.pingResponse;
|
|
} else {
|
|
TRANSPORT_DEBUG(PSTR("!TSF:PNG:ACTIVE\n")); // ping active, cannot start new ping
|
|
return INVALID_HOPS;
|
|
}
|
|
}
|
|
|
|
uint32_t transportGetHeartbeat(void)
|
|
{
|
|
return transportTimeInState();
|
|
}
|
|
|
|
void transportProcessMessage(void)
|
|
{
|
|
// Manage signing timeout
|
|
(void)signerCheckTimer();
|
|
// receive message
|
|
setIndication(INDICATION_RX);
|
|
uint8_t payloadLength = transportReceive((uint8_t *)
|
|
&_msg.last); // last is the first byte of the payload buffer
|
|
// get message length and limit size
|
|
const uint8_t msgLength = min(mGetLength(_msg), (uint8_t)MAX_PAYLOAD);
|
|
// calculate expected length
|
|
const uint8_t expectedMessageLength = HEADER_SIZE + (mGetSigned(_msg) ? MAX_PAYLOAD : msgLength);
|
|
#if defined(MY_RF24_ENABLE_ENCRYPTION) || defined(MY_NRF5_ESB_ENABLE_ENCRYPTION) || defined(MY_RFM95_ENABLE_ENCRYPTION)
|
|
// payload length = a multiple of blocksize length for decrypted messages, i.e. cannot be used for payload length check
|
|
payloadLength = expectedMessageLength;
|
|
#endif
|
|
const uint8_t command = mGetCommand(_msg);
|
|
const uint8_t type = _msg.type;
|
|
const uint8_t sender = _msg.sender;
|
|
const uint8_t last = _msg.last;
|
|
const uint8_t destination = _msg.destination;
|
|
|
|
TRANSPORT_DEBUG(PSTR("TSF:MSG:READ,%d-%d-%d,s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d:%s\n"),
|
|
sender, last, destination, _msg.sensor, command, type, mGetPayloadType(_msg), msgLength,
|
|
mGetSigned(_msg), ((command == C_INTERNAL &&
|
|
type == I_NONCE_RESPONSE) ? "<NONCE>" : _msg.getString(_convBuf)));
|
|
|
|
// Reject payloads with incorrect length
|
|
if (payloadLength != expectedMessageLength) {
|
|
setIndication(INDICATION_ERR_LENGTH);
|
|
TRANSPORT_DEBUG(PSTR("!TSF:MSG:LEN,%d!=%d\n"), payloadLength,
|
|
expectedMessageLength); // invalid payload length
|
|
return;
|
|
}
|
|
|
|
// Reject messages with incorrect protocol version
|
|
if (mGetVersion(_msg) != PROTOCOL_VERSION) {
|
|
setIndication(INDICATION_ERR_VERSION);
|
|
TRANSPORT_DEBUG(PSTR("!TSF:MSG:PVER,%d=%d\n"), mGetVersion(_msg),
|
|
PROTOCOL_VERSION); // protocol version mismatch
|
|
return;
|
|
}
|
|
|
|
// Reject messages that do not pass verification
|
|
if (!signerVerifyMsg(_msg)) {
|
|
setIndication(INDICATION_ERR_SIGN);
|
|
TRANSPORT_DEBUG(PSTR("!TSF:MSG:SIGN VERIFY FAIL\n"));
|
|
return;
|
|
}
|
|
|
|
// update routing table if msg not from parent
|
|
#if defined(MY_REPEATER_FEATURE)
|
|
#if !defined(MY_GATEWAY_FEATURE)
|
|
if (last != _transportConfig.parentNodeId) {
|
|
#else
|
|
// GW doesn't have parent
|
|
{
|
|
#endif
|
|
// Message is from one of the child nodes and not sent from this node. Add it to routing table.
|
|
if (sender != _transportConfig.nodeId)
|
|
{
|
|
transportSetRoute(sender, last);
|
|
}
|
|
}
|
|
#endif // MY_REPEATER_FEATURE
|
|
|
|
// set message received flag
|
|
_transportSM.msgReceived = true;
|
|
|
|
// Is message addressed to this node?
|
|
if (destination == _transportConfig.nodeId) {
|
|
// prevent buffer overflow by limiting max. possible message length (5 bits=31 bytes max) to MAX_PAYLOAD (25 bytes)
|
|
mSetLength(_msg, min(mGetLength(_msg),(uint8_t)MAX_PAYLOAD));
|
|
// null terminate data
|
|
_msg.data[msgLength] = 0u;
|
|
// Check if sender requests an ack back.
|
|
if (mGetRequestAck(_msg)) {
|
|
TRANSPORT_DEBUG(PSTR("TSF:MSG:ACK REQ\n")); // ACK requested
|
|
_msgTmp = _msg; // Copy message
|
|
mSetRequestAck(_msgTmp,
|
|
false); // Reply without ack flag (otherwise we would end up in an eternal loop)
|
|
mSetAck(_msgTmp, true); // set ACK flag
|
|
_msgTmp.sender = _transportConfig.nodeId;
|
|
_msgTmp.destination = sender;
|
|
// send ACK, use transportSendRoute since ACK reply is not internal, i.e. if !transportOK do not reply
|
|
(void)transportSendRoute(_msgTmp);
|
|
}
|
|
if(!mGetAck(_msg)) {
|
|
// only process if not ACK
|
|
if (command == C_INTERNAL) {
|
|
// Process signing related internal messages
|
|
if (signerProcessInternal(_msg)) {
|
|
return; // Signer processing indicated no further action needed
|
|
}
|
|
#if !defined(MY_GATEWAY_FEATURE)
|
|
if (type == I_ID_RESPONSE) {
|
|
#if (MY_NODE_ID == AUTO)
|
|
// only active if node ID dynamic
|
|
if ((_msg.sensor == _transportToken) || (_msg.sensor == AUTO)) {
|
|
(void)transportAssignNodeID(_msg.getByte());
|
|
} else {
|
|
TRANSPORT_DEBUG(PSTR("!TSF:MSG:ID TK INVALID\n"));
|
|
}
|
|
#endif
|
|
return; // no further processing required
|
|
}
|
|
if (type == I_FIND_PARENT_RESPONSE) {
|
|
#if !defined(MY_GATEWAY_FEATURE) && !defined(MY_PARENT_NODE_IS_STATIC)
|
|
if (_transportSM.findingParentNode) { // only process if find parent active
|
|
// Reply to a I_FIND_PARENT_REQUEST message. Check if the distance is shorter than we already have.
|
|
uint8_t distance = _msg.getByte();
|
|
if (isValidDistance(distance)) {
|
|
distance++; // Distance to gateway is one more for us w.r.t. parent
|
|
// update settings if distance shorter or preferred parent found
|
|
if (((isValidDistance(distance) && distance < _transportConfig.distanceGW) || (!_autoFindParent &&
|
|
sender == (uint8_t)MY_PARENT_NODE_ID)) && !_transportSM.preferredParentFound) {
|
|
// Found a neighbor closer to GW than previously found
|
|
if (!_autoFindParent && sender == (uint8_t)MY_PARENT_NODE_ID) {
|
|
_transportSM.preferredParentFound = true;
|
|
TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR PREF\n")); // find parent, preferred parent found
|
|
}
|
|
_transportConfig.distanceGW = distance;
|
|
_transportConfig.parentNodeId = sender;
|
|
TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR OK,ID=%d,D=%d\n"), _transportConfig.parentNodeId,
|
|
_transportConfig.distanceGW);
|
|
}
|
|
}
|
|
} else {
|
|
TRANSPORT_DEBUG(PSTR("!TSF:MSG:FPAR INACTIVE\n")); // find parent response received, but inactive
|
|
}
|
|
return;
|
|
#endif
|
|
}
|
|
#endif // !defined(MY_GATEWAY_FEATURE)
|
|
// general
|
|
if (type == I_PING) {
|
|
TRANSPORT_DEBUG(PSTR("TSF:MSG:PINGED,ID=%d,HP=%d\n"), sender, _msg.getByte()); // node pinged
|
|
#if defined(MY_GATEWAY_FEATURE) && (F_CPU>16000000)
|
|
// delay for fast GW and slow nodes
|
|
delay(5);
|
|
#endif
|
|
(void)transportRouteMessage(build(_msgTmp, sender, NODE_SENSOR_ID, C_INTERNAL,
|
|
I_PONG).set((uint8_t)1));
|
|
return; // no further processing required
|
|
}
|
|
if (type == I_PONG) {
|
|
if (_transportSM.pingActive) {
|
|
_transportSM.pingActive = false;
|
|
_transportSM.pingResponse = _msg.getByte();
|
|
TRANSPORT_DEBUG(PSTR("TSF:MSG:PONG RECV,HP=%d\n"), _transportSM.pingResponse); // pong received
|
|
} else {
|
|
TRANSPORT_DEBUG(PSTR("!TSF:MSG:PONG RECV,INACTIVE\n")); // pong received, but !pingActive
|
|
}
|
|
return; // no further processing required
|
|
}
|
|
if (_processInternalMessages()) {
|
|
return; // no further processing required
|
|
}
|
|
if (type == I_SIGNAL_REPORT_REVERSE) {
|
|
return; // no further processing required
|
|
}
|
|
if (type == I_SIGNAL_REPORT_REQUEST) {
|
|
const char command = _msg.data[0];
|
|
int16_t value = INVALID_RSSI;
|
|
#if defined(MY_SIGNAL_REPORT_ENABLED)
|
|
if (_msg.data[1] != '!') {
|
|
value = transportSignalReport(command);
|
|
} else {
|
|
// send request
|
|
if (transportRouteMessage(build(_msgTmp, _msg.last, NODE_SENSOR_ID, C_INTERNAL,
|
|
I_SIGNAL_REPORT_REVERSE).set((uint8_t)255))) {
|
|
// S>s, R>r, ascii delta = 32
|
|
value = transportSignalReport(command + 32); // reverse
|
|
};
|
|
}
|
|
#else
|
|
(void)command;
|
|
#endif
|
|
(void)transportRouteMessage(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
|
|
I_SIGNAL_REPORT_RESPONSE).set(value));
|
|
return;
|
|
}
|
|
} else if (command == C_STREAM) {
|
|
#if defined(MY_OTA_FIRMWARE_FEATURE)
|
|
if(firmwareOTAUpdateProcess()) {
|
|
return; // OTA FW update processing indicated no further action needed
|
|
}
|
|
#endif
|
|
}
|
|
} else {
|
|
TRANSPORT_DEBUG(
|
|
PSTR("TSF:MSG:ACK\n")); // received message is ACK, no internal processing, handover to msg callback
|
|
}
|
|
#if defined(MY_GATEWAY_FEATURE)
|
|
// Hand over message to controller
|
|
(void)gatewayTransportSend(_msg);
|
|
#endif
|
|
// Call incoming message callback if available
|
|
if (receive) {
|
|
receive(_msg);
|
|
}
|
|
} else if (destination == BROADCAST_ADDRESS) {
|
|
TRANSPORT_DEBUG(PSTR("TSF:MSG:BC\n")); // broadcast msg
|
|
if (command == C_INTERNAL) {
|
|
if (isTransportReady()) {
|
|
// only reply if node is fully operational
|
|
if (type == I_FIND_PARENT_REQUEST) {
|
|
#if defined(MY_REPEATER_FEATURE)
|
|
if (sender != _transportConfig.parentNodeId) { // no circular reference
|
|
TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR REQ,ID=%d\n"), sender); // FPAR: find parent request
|
|
// 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 (transportCheckUplink()) {
|
|
_transportSM.lastUplinkCheck = hwMillis();
|
|
TRANSPORT_DEBUG(PSTR("TSF:MSG:GWL OK\n")); // GW uplink ok
|
|
// random delay minimizes collisions
|
|
delay(hwMillis() & 0x3ff);
|
|
(void)transportRouteMessage(build(_msgTmp, sender, NODE_SENSOR_ID, C_INTERNAL,
|
|
I_FIND_PARENT_RESPONSE).set(_transportConfig.distanceGW));
|
|
} else {
|
|
TRANSPORT_DEBUG(PSTR("!TSF:MSG:GWL FAIL\n")); // GW uplink fail, do not respond to parent request
|
|
}
|
|
}
|
|
#endif
|
|
return; // no further processing required, do not forward
|
|
}
|
|
} // isTransportReady
|
|
if (type == I_FIND_PARENT_RESPONSE) {
|
|
return; // no further processing required, do not forward
|
|
}
|
|
#if !defined(MY_GATEWAY_FEATURE)
|
|
if (type == I_DISCOVER_REQUEST) {
|
|
if (last == _transportConfig.parentNodeId) {
|
|
// random wait to minimize collisions
|
|
delay(hwMillis() & 0x3ff);
|
|
(void)transportRouteMessage(build(_msgTmp, sender, NODE_SENSOR_ID, C_INTERNAL,
|
|
I_DISCOVER_RESPONSE).set(_transportConfig.parentNodeId));
|
|
// no return here (for fwd if repeater)
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
// controlled BC relay
|
|
#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 == _transportConfig.parentNodeId && sender != _transportConfig.nodeId &&
|
|
isTransportReady()) {
|
|
TRANSPORT_DEBUG(PSTR("TSF:MSG:FWD BC MSG\n")); // controlled broadcast msg forwarding
|
|
(void)transportRouteMessage(_msg);
|
|
}
|
|
#endif
|
|
|
|
// Callback for BC, only for non-internal messages
|
|
if (command != C_INTERNAL) {
|
|
#if !defined(MY_GATEWAY_FEATURE)
|
|
// only proceed if message received from parent
|
|
if (last != _transportConfig.parentNodeId) {
|
|
return;
|
|
}
|
|
#endif
|
|
#if defined(MY_GATEWAY_FEATURE)
|
|
// Hand over message to controller
|
|
(void)gatewayTransportSend(_msg);
|
|
#endif
|
|
if (receive) {
|
|
receive(_msg);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// msg not to us and not BC, relay msg
|
|
#if defined(MY_REPEATER_FEATURE)
|
|
if (isTransportReady()) {
|
|
TRANSPORT_DEBUG(PSTR("TSF:MSG:REL MSG\n")); // relay msg
|
|
if (command == C_INTERNAL) {
|
|
if (type == I_PING || type == I_PONG) {
|
|
uint8_t hopsCnt = _msg.getByte();
|
|
if (hopsCnt != MAX_HOPS) {
|
|
TRANSPORT_DEBUG(PSTR("TSF:MSG:REL PxNG,HP=%d\n"), hopsCnt);
|
|
hopsCnt++;
|
|
_msg.set(hopsCnt);
|
|
}
|
|
}
|
|
}
|
|
// Relay this message to another node
|
|
(void)transportRouteMessage(_msg);
|
|
}
|
|
#else
|
|
TRANSPORT_DEBUG(PSTR("!TSF:MSG:REL MSG,NREP\n")); // message relaying request, but not a repeater
|
|
#endif
|
|
}
|
|
(void)last; //avoid cppcheck warning
|
|
}
|
|
|
|
void transportInvokeSanityCheck(void)
|
|
{
|
|
if (!transportSanityCheck()) {
|
|
TRANSPORT_DEBUG(PSTR("!TSF:SAN:FAIL\n")); // sanity check fail
|
|
transportSwitchSM(stFailure);
|
|
} else {
|
|
TRANSPORT_DEBUG(PSTR("TSF:SAN:OK\n")); // sanity check ok
|
|
}
|
|
}
|
|
|
|
void transportProcessFIFO(void)
|
|
{
|
|
if (!_transportSM.transportActive) {
|
|
// transport not active, no further processing required
|
|
return;
|
|
}
|
|
|
|
#if defined(MY_TRANSPORT_SANITY_CHECK)
|
|
if (hwMillis() - _lastSanityCheck > MY_TRANSPORT_SANITY_CHECK_INTERVAL_MS) {
|
|
_lastSanityCheck = hwMillis();
|
|
transportInvokeSanityCheck();
|
|
}
|
|
#endif
|
|
|
|
uint8_t _processedMessages = MAX_SUBSEQ_MSGS;
|
|
// process all msgs in FIFO or counter exit
|
|
while (transportAvailable() && _processedMessages--) {
|
|
transportProcessMessage();
|
|
}
|
|
#if defined(MY_OTA_FIRMWARE_FEATURE)
|
|
if (isTransportReady()) {
|
|
// only process if transport ok
|
|
firmwareOTAUpdateRequest();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool transportSendWrite(const uint8_t to, MyMessage &message)
|
|
{
|
|
message.last = _transportConfig.nodeId; // Update last
|
|
// sign message if required
|
|
if (!signerSignMsg(message)) {
|
|
TRANSPORT_DEBUG(PSTR("!TSF:MSG:SIGN FAIL\n"));
|
|
setIndication(INDICATION_ERR_SIGN);
|
|
return false;
|
|
}
|
|
|
|
// msg length changes if signed
|
|
const uint8_t totalMsgLength = HEADER_SIZE + ( mGetSigned(message) ? MAX_PAYLOAD : mGetLength(
|
|
message) );
|
|
|
|
// send
|
|
setIndication(INDICATION_TX);
|
|
bool result = transportSend(to, &message, min((uint8_t)MAX_MESSAGE_LENGTH, totalMsgLength),
|
|
_transportConfig.passiveMode);
|
|
// broadcasting (workaround counterfeits)
|
|
result |= (to == BROADCAST_ADDRESS);
|
|
|
|
TRANSPORT_DEBUG(PSTR("%sTSF:MSG:SEND,%d-%d-%d-%d,s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d,ft=%d,st=%s:%s\n"),
|
|
(_transportConfig.passiveMode ? "?" : result ? "" : "!"), message.sender, message.last, to,
|
|
message.destination,
|
|
message.sensor,
|
|
mGetCommand(message), message.type,
|
|
mGetPayloadType(message), mGetLength(message), mGetSigned(message),
|
|
_transportSM.failedUplinkTransmissions,
|
|
(result ? "OK" : "NACK"),
|
|
((mGetCommand(message) == C_INTERNAL &&
|
|
message.type == I_NONCE_RESPONSE) ? "<NONCE>" : message.getString(_convBuf)));
|
|
|
|
return result;
|
|
}
|
|
|
|
void transportRegisterReadyCallback(transportCallback_t cb)
|
|
{
|
|
_transportReady_cb = cb;
|
|
}
|
|
|
|
uint8_t transportGetNodeId(void)
|
|
{
|
|
return _transportConfig.nodeId;
|
|
}
|
|
uint8_t transportGetParentNodeId(void)
|
|
{
|
|
return _transportConfig.parentNodeId;
|
|
}
|
|
uint8_t transportGetDistanceGW(void)
|
|
{
|
|
return _transportConfig.distanceGW;
|
|
}
|
|
|
|
|
|
void transportClearRoutingTable(void)
|
|
{
|
|
for (uint16_t i = 0; i < SIZE_ROUTES; i++) {
|
|
transportSetRoute((uint8_t)i, BROADCAST_ADDRESS);
|
|
}
|
|
transportSaveRoutingTable(); // save cleared routing table to EEPROM (if feature enabled)
|
|
TRANSPORT_DEBUG(PSTR("TSF:CRT:OK\n")); // clear routing table
|
|
}
|
|
|
|
void transportLoadRoutingTable(void)
|
|
{
|
|
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
|
|
hwReadConfigBlock((void*)&_transportRoutingTable.route, (void*)EEPROM_ROUTES_ADDRESS, SIZE_ROUTES);
|
|
TRANSPORT_DEBUG(PSTR("TSF:LRT:OK\n")); // load routing table
|
|
#endif
|
|
}
|
|
|
|
void transportSaveRoutingTable(void)
|
|
{
|
|
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
|
|
hwWriteConfigBlock((void*)&_transportRoutingTable.route, (void*)EEPROM_ROUTES_ADDRESS, SIZE_ROUTES);
|
|
TRANSPORT_DEBUG(PSTR("TSF:SRT:OK\n")); // save routing table
|
|
#endif
|
|
}
|
|
|
|
void transportSetRoute(const uint8_t node, const uint8_t route)
|
|
{
|
|
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
|
|
_transportRoutingTable.route[node] = route;
|
|
#else
|
|
hwWriteConfig(EEPROM_ROUTES_ADDRESS + node, route);
|
|
#endif
|
|
}
|
|
|
|
uint8_t transportGetRoute(const uint8_t node)
|
|
{
|
|
uint8_t result;
|
|
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
|
|
result = _transportRoutingTable.route[node];
|
|
#else
|
|
result = hwReadConfig(EEPROM_ROUTES_ADDRESS + node);
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
void transportReportRoutingTable(void)
|
|
{
|
|
#if defined(MY_REPEATER_FEATURE)
|
|
for (uint16_t cnt = 0; cnt < SIZE_ROUTES; cnt++) {
|
|
const uint8_t route = transportGetRoute(cnt);
|
|
if (route != BROADCAST_ADDRESS) {
|
|
TRANSPORT_DEBUG(PSTR("TSF:RRT:ROUTE N=%d,R=%d\n"), cnt, route);
|
|
uint8_t outBuf[2] = { (uint8_t)cnt,route };
|
|
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG).set(outBuf,
|
|
2));
|
|
wait(200);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void transportTogglePassiveMode(const bool OnOff)
|
|
{
|
|
#if !defined (MY_PASSIVE_NODE)
|
|
_transportConfig.passiveMode = OnOff;
|
|
#else
|
|
(void)OnOff;
|
|
#endif
|
|
}
|
|
|
|
int16_t transportGetSignalReport(const signalReport_t signalReport)
|
|
{
|
|
int16_t result;
|
|
switch (signalReport) {
|
|
case SR_RX_RSSI:
|
|
result = transportGetReceivingRSSI();
|
|
break;
|
|
case SR_TX_RSSI:
|
|
result = transportGetSendingRSSI();
|
|
break;
|
|
case SR_RX_SNR:
|
|
result = transportGetReceivingSNR();
|
|
break;
|
|
case SR_TX_SNR:
|
|
result = transportGetSendingSNR();
|
|
break;
|
|
case SR_TX_POWER_LEVEL:
|
|
result = transportGetTxPowerLevel();
|
|
break;
|
|
case SR_TX_POWER_PERCENT:
|
|
result = transportGetTxPowerPercent();
|
|
break;
|
|
case SR_UPLINK_QUALITY:
|
|
result = transportInternalToRSSI(_transportSM.uplinkQualityRSSI);
|
|
break;
|
|
default:
|
|
result = 0;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int16_t transportSignalReport(const char command)
|
|
{
|
|
signalReport_t reportCommand;
|
|
switch (command) {
|
|
case 'S':
|
|
// SNR (if available) of incoming message
|
|
reportCommand = SR_RX_SNR;
|
|
break;
|
|
case 's':
|
|
// SNR (if available) of incoming ACK message
|
|
reportCommand = SR_TX_SNR;
|
|
break;
|
|
case 'R':
|
|
// RSSI (if available) of incoming message
|
|
reportCommand = SR_RX_RSSI;
|
|
break;
|
|
case 'r':
|
|
// RSSI (if available) of incoming ACK message
|
|
reportCommand = SR_TX_RSSI;
|
|
break;
|
|
case 'P':
|
|
// TX powerlevel in %
|
|
reportCommand = SR_TX_POWER_PERCENT;
|
|
break;
|
|
case 'T':
|
|
// TX powerlevel in dBm
|
|
reportCommand = SR_TX_POWER_LEVEL;
|
|
break;
|
|
case 'U':
|
|
// Uplink quality
|
|
reportCommand = SR_UPLINK_QUALITY;
|
|
break;
|
|
default:
|
|
reportCommand = SR_NOT_DEFINED;
|
|
break;
|
|
}
|
|
const uint16_t result = transportGetSignalReport(reportCommand);
|
|
TRANSPORT_DEBUG(PSTR("TSF:SIR:CMD=%d,VAL=%d\n"), reportCommand, result);
|
|
return result;
|
|
}
|