Files
MySensors/libraries/MySensors/MySensor.cpp
T
Yveaux e5bd891a77 Improved parent node search
Retry parent node search with each transmit when parent has not been
found.
Transmit does not block on transmit when no parent has been found as
this depletes batteries on battery powered nodes.
Fix nRF24 configuration for dynamic ACK's. This bug caused broadcasts to
be sent with auto-ACK's.
To not break existing functionality parent search can be started when
node ID is still unknown. In this case node ID is set to broadcast ID,
which can cause funky stuff to happen...
2014-09-11 09:37:42 +02:00

608 lines
19 KiB
C++
Executable File

/*
The MySensors library adds a new layer on top of the RF24 library.
It handles radio network routing, relaying and ids.
Created by Henrik Ekblad <henrik.ekblad@gmail.com>
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 "MySensor.h"
#include "utility/LowPower.h"
#include "utility/RF24.h"
#include "utility/RF24_config.h"
#define DISTANCE_INVALID (0xFF)
// Inline function and macros
static inline MyMessage& build (MyMessage &msg, uint8_t sender, uint8_t destination, uint8_t sensor, uint8_t command, uint8_t type, bool enableAck) {
msg.sender = sender;
msg.destination = destination;
msg.sensor = sensor;
msg.type = type;
mSetCommand(msg,command);
mSetRequestAck(msg,enableAck);
mSetAck(msg,false);
return msg;
}
static inline bool isValidParent( const uint8_t parent ) {
return parent != AUTO;
}
static inline bool isValidDistance( const uint8_t distance ) {
return distance != DISTANCE_INVALID;
}
MySensor::MySensor(uint8_t _cepin, uint8_t _cspin) : RF24(_cepin, _cspin) {
}
void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, boolean _repeaterMode, uint8_t _parentNodeId, rf24_pa_dbm_e paLevel, uint8_t channel, rf24_datarate_e dataRate) {
Serial.begin(BAUD_RATE);
isGateway = false;
repeaterMode = _repeaterMode;
msgCallback = _msgCallback;
failedTransmissions = 0;
if (repeaterMode) {
setupRepeaterMode();
}
setupRadio(paLevel, channel, dataRate);
// Read settings from EEPROM
eeprom_read_block((void*)&nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(NodeConfig));
// Read latest received controller configuration from EEPROM
eeprom_read_block((void*)&cc, (void*)EEPROM_LOCAL_CONFIG_ADDRESS, sizeof(ControllerConfig));
if (cc.isMetric == 0xff) {
// Eeprom empty, set default to metric
cc.isMetric = 0x01;
}
autoFindParent = _parentNodeId == AUTO;
if (!autoFindParent) {
nc.parentNodeId = _parentNodeId;
nc.distance = 0;
} else if (!isValidParent(nc.parentNodeId)) {
// Auto find parent, but parent in eeprom is invalid. Force parent search on first transmit.
nc.distance = DISTANCE_INVALID;
}
if (_nodeId != AUTO) {
// Set static id
nc.nodeId = _nodeId;
}
// Try to fetch node-id from gateway
if (nc.nodeId == AUTO) {
requestNodeId();
}
debug(PSTR("%s started, id=%d, parent=%d, distance=%d\n"), repeaterMode?"repeater":"sensor", nc.nodeId, nc.parentNodeId, nc.distance);
// Open reading pipe for messages directed to this node (set write pipe to same)
RF24::openReadingPipe(WRITE_PIPE, TO_ADDR(nc.nodeId));
RF24::openReadingPipe(CURRENT_NODE_PIPE, TO_ADDR(nc.nodeId));
// Send presentation for this radio node (attach
present(NODE_SENSOR_ID, repeaterMode? S_ARDUINO_REPEATER_NODE : S_ARDUINO_NODE);
// Send a configuration exchange request to controller
// Node sends parent node. Controller answers with latest node configuration
// which is picked up in process()
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CONFIG, false).set(nc.parentNodeId));
// Wait configuration reply.
waitForReply();
}
void MySensor::setupRadio(rf24_pa_dbm_e paLevel, uint8_t channel, rf24_datarate_e dataRate) {
// Start up the radio library
RF24::begin();
if (!RF24::isPVariant()) {
debug(PSTR("check wires\n"));
while(1);
}
RF24::setAutoAck(1);
RF24::setAutoAck(BROADCAST_PIPE,false); // Turn off auto ack for broadcast
RF24::setChannel(channel);
RF24::setPALevel(paLevel);
RF24::setDataRate(dataRate);
RF24::setRetries(5,15);
RF24::setCRCLength(RF24_CRC_16);
RF24::enableDynamicPayloads();
RF24::enableDynamicAck(); // Required to disable ack-sending for broadcast messages
// All nodes listen to broadcast pipe (for FIND_PARENT_RESPONSE messages)
RF24::openReadingPipe(BROADCAST_PIPE, TO_ADDR(BROADCAST_ADDRESS));
}
void MySensor::setupRepeaterMode(){
childNodeTable = new uint8_t[256];
eeprom_read_block((void*)childNodeTable, (void*)EEPROM_ROUTES_ADDRESS, 256);
}
uint8_t MySensor::getNodeId() {
return nc.nodeId;
}
ControllerConfig MySensor::getConfig() {
return cc;
}
void MySensor::requestNodeId() {
debug(PSTR("req node id\n"));
RF24::openReadingPipe(CURRENT_NODE_PIPE, TO_ADDR(nc.nodeId));
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_ID_REQUEST, false).set(""));
waitForReply();
}
void MySensor::findParentNode() {
// Send ping message to BROADCAST_ADDRESS (to which all relaying nodes and gateway listens and should reply to)
debug(PSTR("find parent\n"));
build(msg, nc.nodeId, BROADCAST_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT, false).set("");
// Write msg, but suppress recursive parent search
sendWrite(BROADCAST_ADDRESS, msg, false);
// Wait for ping response.
waitForReply();
}
void MySensor::waitForReply() {
unsigned long enter = millis();
// Wait a couple of seconds for response
while (millis() - enter < 2000) {
process();
}
}
boolean MySensor::sendRoute(MyMessage &message) {
// Make sure to process any incoming messages before sending (could this end up in recursive loop?)
// process();
bool isInternal = mGetCommand(message) == C_INTERNAL;
// If we still don't have any node id, re-request and skip this message.
if (nc.nodeId == AUTO && !(isInternal && message.type == I_ID_REQUEST)) {
requestNodeId();
return false;
}
if (repeaterMode) {
uint8_t dest = message.destination;
uint8_t route = getChildRoute(dest);
if (route>GATEWAY_ADDRESS && route<BROADCAST_ADDRESS && dest != GATEWAY_ADDRESS) {
// --- debug(PSTR("route %d.\n"), route);
// Message destination is not gateway and is in routing table for this node.
// Send it downstream
return sendWrite(route, message);
} else if (isInternal && message.type == I_ID_RESPONSE && dest==BROADCAST_ADDRESS) {
// Node has not yet received any id. We need to send it
// by doing a broadcast sending,
return sendWrite(BROADCAST_ADDRESS, message);
}
}
if (!isGateway) {
// Should be routed back to gateway.
return sendWrite(nc.parentNodeId, message);
}
return false;
}
boolean MySensor::sendWrite(uint8_t next, MyMessage &message, const bool allowFindParent) {
bool ok = true;
const bool broadcast = next == BROADCAST_ADDRESS;
const bool toParent = next == nc.parentNodeId;
// With current implementation parent node Id can equal the broadcast address when
// starting with empty eeprom and AUTO node Id is active.
// This behavior is undesired, as possible parents will report back to broadcast address.
// debug(PSTR("sendWrite next=%d, parent=%d, distance=%d\n"), next, nc.parentNodeId, nc.distance);
// If sending directly to parent node and distance is not set, then try to find parent now.
if ( allowFindParent && toParent && !isValidDistance(nc.distance) ) {
findParentNode();
// Known distance indicates parent has been found
ok = isValidDistance(nc.distance);
}
if (ok) {
uint8_t length = mGetLength(message);
message.last = nc.nodeId;
mSetVersion(message, PROTOCOL_VERSION);
// Make sure radio has powered up
RF24::powerUp();
RF24::stopListening();
RF24::openWritingPipe(TO_ADDR(next));
// Send message. Disable auto-ack for broadcasts.
ok = RF24::write(&message, min(MAX_MESSAGE_LENGTH, HEADER_SIZE + length), broadcast);
RF24::startListening();
debug(PSTR("send: %d-%d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,st=%s:%s\n"),
message.sender,message.last, next, message.destination, message.sensor, mGetCommand(message), message.type,
mGetPayloadType(message), mGetLength(message), broadcast ? "bc" : (ok ? "ok":"fail"), message.getString(convBuf));
// If many successive transmissions to parent failed, the parent node might be down and we
// need to find another route to gateway.
if (toParent) {
if (ok) {
failedTransmissions = 0;
} else {
failedTransmissions++;
if ( autoFindParent && (failedTransmissions >= SEARCH_FAILURES)) {
debug(PSTR("lost parent\n"));
// Set distance invalid to trigger parent search on next write.
nc.distance = DISTANCE_INVALID;
failedTransmissions = 0;
}
}
}
}
return ok;
}
bool MySensor::send(MyMessage &message, bool enableAck) {
message.sender = nc.nodeId;
mSetCommand(message,C_SET);
mSetRequestAck(message,enableAck);
return sendRoute(message);
}
void MySensor::sendBatteryLevel(uint8_t value, bool enableAck) {
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_BATTERY_LEVEL, enableAck).set(value));
}
void MySensor::present(uint8_t childSensorId, uint8_t sensorType, bool enableAck) {
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, childSensorId, C_PRESENTATION, sensorType, enableAck).set(LIBRARY_VERSION));
}
void MySensor::sendSketchInfo(const char *name, const char *version, bool enableAck) {
if (name != NULL) {
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_NAME, enableAck).set(name));
}
if (version != NULL) {
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_VERSION, enableAck).set(version));
}
}
void MySensor::request(uint8_t childSensorId, uint8_t variableType, uint8_t destination) {
sendRoute(build(msg, nc.nodeId, destination, childSensorId, C_REQ, variableType, false));
}
void MySensor::requestTime(void (* _timeCallback)(unsigned long)) {
timeCallback = _timeCallback;
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_TIME, false));
}
boolean MySensor::process() {
uint8_t pipe;
boolean available = RF24::available(&pipe);
if (!available || pipe>6)
return false;
uint8_t len = RF24::getDynamicPayloadSize();
RF24::read(&msg, len);
// Add string termination, good if we later would want to print it.
msg.data[mGetLength(msg)] = '\0';
debug(PSTR("read: %d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d:%s\n"),
msg.sender, msg.last, msg.destination, msg.sensor, mGetCommand(msg), msg.type, mGetPayloadType(msg), mGetLength(msg), msg.getString(convBuf));
if(!(mGetVersion(msg) == PROTOCOL_VERSION)) {
debug(PSTR("version mismatch\n"));
return false;
}
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;
if (repeaterMode && command == C_INTERNAL && type == I_FIND_PARENT) {
// Relaying nodes should always answer ping messages
// Wait a random delay of 0-1.023 seconds to minimize collision
// between ping ack messages from other relaying nodes
delay(millis() & 0x3ff);
sendWrite(sender, build(msg, nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT_RESPONSE, false).set(nc.distance));
return false;
} else if (destination == nc.nodeId) {
// Check if sender requests an ack back.
if (mGetRequestAck(msg)) {
// Copy message
ack = msg;
mSetRequestAck(ack,false); // Reply without ack flag (otherwise we would end up in an eternal loop)
mSetAck(ack,true);
ack.sender = nc.nodeId;
ack.destination = msg.sender;
sendRoute(ack);
}
// This message is addressed to this node
if (repeaterMode && last != nc.parentNodeId) {
// Message is from one of the child nodes. Add it to routing table.
addChildRoute(sender, last);
}
if (command == C_INTERNAL) {
if (type == I_FIND_PARENT_RESPONSE && !isGateway) {
// We've received a reply to a FIND_PARENT message. Check if the distance is
// shorter than we already have.
uint8_t distance = msg.getByte();
if (isValidDistance(distance))
{
// Distance to gateway is one more for us w.r.t. parent
distance++;
if (isValidDistance(distance) && (distance < nc.distance)) {
// Found a neighbor closer to GW than previously found
nc.distance = distance;
nc.parentNodeId = msg.sender;
eeprom_write_byte((uint8_t*)EEPROM_PARENT_NODE_ID_ADDRESS, nc.parentNodeId);
eeprom_write_byte((uint8_t*)EEPROM_DISTANCE_ADDRESS, nc.distance);
debug(PSTR("new parent=%d, d=%d\n"), nc.parentNodeId, nc.distance);
}
}
return false;
} else if (sender == GATEWAY_ADDRESS) {
bool isMetric;
if (type == I_REBOOT) {
wdt_enable(WDTO_15MS);
for (;;);
} else if (type == I_ID_RESPONSE) {
if (nc.nodeId == AUTO) {
nc.nodeId = msg.getByte();
// Write id to EEPROM
if (nc.nodeId == AUTO) {
// sensor net gateway will return max id if all sensor id are taken
debug(PSTR("full\n"));
while (1); // Wait here. Nothing else we can do...
} else {
RF24::openReadingPipe(CURRENT_NODE_PIPE, TO_ADDR(nc.nodeId));
eeprom_write_byte((uint8_t*)EEPROM_NODE_ID_ADDRESS, nc.nodeId);
}
debug(PSTR("id=%d\n"), nc.nodeId);
}
} else if (type == I_CONFIG) {
// Pick up configuration from controller (currently only metric/imperial)
// and store it in eeprom if changed
isMetric = msg.getByte() == 'M' ;
if (cc.isMetric != isMetric) {
cc.isMetric = isMetric;
eeprom_write_byte((uint8_t*)EEPROM_CONTROLLER_CONFIG_ADDRESS, isMetric);
//eeprom_write_block((const void*)&cc, (uint8_t*)EEPROM_CONTROLLER_CONFIG_ADDRESS, sizeof(ControllerConfig));
}
} else if (type == I_CHILDREN) {
if (repeaterMode && msg.getByte() == 'C') {
// Clears child relay data for this node
debug(PSTR("rd=clear\n"));
for (uint8_t i=0;i< sizeof(childNodeTable); i++) {
removeChildRoute(i);
}
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CHILDREN,false).set(""));
}
} else if (type == I_TIME) {
if (timeCallback != NULL) {
// Deliver time to callback
timeCallback(msg.getULong());
}
}
return false;
}
}
// Call incoming message callback if available
if (msgCallback != NULL) {
msgCallback(msg);
}
// Return true if message was addressed for this node...
return true;
} else if (repeaterMode && pipe == CURRENT_NODE_PIPE) {
// We should try to relay this message to another node
uint8_t route = getChildRoute(msg.destination);
if (route>0 && route<255) {
// This message should be forwarded to a child node. If we send message
// to this nodes pipe then all children will receive it because the are
// all listening to this nodes pipe.
//
// +----B
// -A
// +----C------D
//
// We're node C, Message comes from A and has destination D
//
// lookup route in table and send message there
sendWrite(route, msg);
} else {
// A message comes from a child node and we have no
// route for it.
//
// +----B
// -A
// +----C------D <-- Message comes from D
//
// We're node C
//
// Message should be passed to node A (this nodes relay)
// This message should be routed back towards sensor net gateway
sendWrite(nc.parentNodeId, msg);
// Add this child to our "routing table" if it not already exist
addChildRoute(sender, last);
}
}
return false;
}
MyMessage& MySensor::getLastMessage() {
return msg;
}
void MySensor::saveState(uint8_t pos, uint8_t value) {
if (loadState(pos) != value) {
eeprom_write_byte((uint8_t*)(EEPROM_LOCAL_CONFIG_ADDRESS+pos), value);
}
}
uint8_t MySensor::loadState(uint8_t pos) {
return eeprom_read_byte((uint8_t*)(EEPROM_LOCAL_CONFIG_ADDRESS+pos));
}
void MySensor::addChildRoute(uint8_t childId, uint8_t route) {
if (childNodeTable[childId] != route) {
childNodeTable[childId] = route;
eeprom_write_byte((uint8_t*)EEPROM_ROUTES_ADDRESS+childId, route);
}
}
void MySensor::removeChildRoute(uint8_t childId) {
if (childNodeTable[childId] != 0xff) {
childNodeTable[childId] = 0xff;
eeprom_write_byte((uint8_t*)EEPROM_ROUTES_ADDRESS+childId, 0xff);
}
}
uint8_t MySensor::getChildRoute(uint8_t childId) {
return childNodeTable[childId];
}
int MySensor::getInternalTemp(void)
{
long result;
// Read internal temp sensor against 1.1V reference
ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3);
delay(20); // Wait until Vref has settled
ADCSRA |= _BV(ADSC);
while (bit_is_set(ADCSRA,ADSC));
result = ADCL;
result |= ADCH<<8;
result = (result - 125) * 1075 + 500; // add 500 to round to nearest full degree
return result/10000;
}
int8_t pinIntTrigger = 0;
void wakeUp() //place to send the interrupts
{
pinIntTrigger = 1;
}
void wakeUp2() //place to send the second interrupts
{
pinIntTrigger = 2;
}
void MySensor::internalSleep(unsigned long ms) {
while (!pinIntTrigger && ms >= 8000) { LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF); ms -= 8000; }
if (!pinIntTrigger && ms >= 4000) { LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF); ms -= 4000; }
if (!pinIntTrigger && ms >= 2000) { LowPower.powerDown(SLEEP_2S, ADC_OFF, BOD_OFF); ms -= 2000; }
if (!pinIntTrigger && ms >= 1000) { LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF); ms -= 1000; }
if (!pinIntTrigger && ms >= 500) { LowPower.powerDown(SLEEP_500MS, ADC_OFF, BOD_OFF); ms -= 500; }
if (!pinIntTrigger && ms >= 250) { LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF); ms -= 250; }
if (!pinIntTrigger && ms >= 125) { LowPower.powerDown(SLEEP_120MS, ADC_OFF, BOD_OFF); ms -= 120; }
if (!pinIntTrigger && ms >= 64) { LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF); ms -= 60; }
if (!pinIntTrigger && ms >= 32) { LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_OFF); ms -= 30; }
if (!pinIntTrigger && ms >= 16) { LowPower.powerDown(SLEEP_15Ms, ADC_OFF, BOD_OFF); ms -= 15; }
}
void MySensor::sleep(unsigned long ms) {
// Let serial prints finish (debug, log etc)
Serial.flush();
RF24::powerDown();
pinIntTrigger = 0;
internalSleep(ms);
}
bool MySensor::sleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
// Let serial prints finish (debug, log etc)
bool pinTriggeredWakeup = true;
Serial.flush();
RF24::powerDown();
attachInterrupt(interrupt, wakeUp, mode);
if (ms>0) {
pinIntTrigger = 0;
sleep(ms);
if (0 == pinIntTrigger) {
pinTriggeredWakeup = false;
}
} else {
Serial.flush();
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}
detachInterrupt(interrupt);
return pinTriggeredWakeup;
}
int8_t MySensor::sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) {
int8_t retVal = 1;
Serial.flush(); // Let serial prints finish (debug, log etc)
RF24::powerDown();
attachInterrupt(interrupt1, wakeUp, mode1);
attachInterrupt(interrupt2, wakeUp2, mode2);
if (ms>0) {
pinIntTrigger = 0;
sleep(ms);
if (0 == pinIntTrigger) {
retVal = -1;
}
} else {
Serial.flush();
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}
detachInterrupt(interrupt1);
detachInterrupt(interrupt2);
if (1 == pinIntTrigger) {
retVal = (int8_t)interrupt1;
} else if (2 == pinIntTrigger) {
retVal = (int8_t)interrupt2;
}
return retVal;
}
#ifdef DEBUG
void MySensor::debugPrint(const char *fmt, ... ) {
char fmtBuffer[300];
if (isGateway) {
// prepend debug message to be handled correctly by gw (C_INTERNAL, I_LOG_MESSAGE)
snprintf_P(fmtBuffer, 299, PSTR("0;0;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
Serial.print(fmtBuffer);
}
va_list args;
va_start (args, fmt );
va_end (args);
if (isGateway) {
// Truncate message if this is gateway node
vsnprintf_P(fmtBuffer, 60, fmt, args);
fmtBuffer[59] = '\n';
fmtBuffer[60] = '\0';
} else {
vsnprintf_P(fmtBuffer, 299, fmt, args);
}
va_end (args);
Serial.print(fmtBuffer);
Serial.flush();
//Serial.write(freeRam());
}
#endif
#ifdef DEBUG
int MySensor::freeRam (void) {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
#endif