Files
MySensors/libraries/MySensors/Sensor.cpp
T

542 lines
17 KiB
C++

/*
The Sensor Net Arduino 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 "Sensor.h"
Sensor::Sensor(uint8_t _cepin, uint8_t _cspin) : RF24(_cepin, _cspin) {
isRelay = false;
}
void Sensor::setupRadio() {
failedTransmissions = 0;
// Start up the radio library
RF24::begin();
RF24::enableDynamicPayloads();
RF24::setAutoAck(true);
RF24::setRetries(15, 15);
//RF24::setChannel(70);
RF24::setDataRate(RF24_1MBPS);
RF24::setCRCLength(RF24_CRC_16);
// All (relaying) nodes listen to broadcast pipe (for PING messages)
RF24::openReadingPipe(BROADCAST_PIPE, BASE_RADIO_ID + BROADCAST_ADDRESS);
}
void Sensor::begin(uint8_t _radioId) {
radioId = _radioId;
debug(PSTR("Started %s.\n"), isRelay?"relay":"sensor");
setupRadio();
// Fetch relay from EEPROM
relayId = EEPROM.read(EEPROM_RELAY_ID_ADDRESS);
// Fetch distance from eeprom
distance = EEPROM.read(EEPROM_DISTANCE_ADDRESS);
if (relayId == 0xff) {
// No relay previously fetched and stored in eeprom. Try to find one.
findRelay();
}
debug(PSTR("Relay=%d, distance=%d\n"), relayId, distance);
initializeRadioId();
// Open reading pipe for messages directed to this node
RF24::openReadingPipe(CURRENT_NODE_PIPE, BASE_RADIO_ID+radioId);
// Send presentation for this radio node
sendSensorPresentation(NODE_CHILD_ID, isRelay? S_ARDUINO_RELAY : S_ARDUINO_NODE);
// Send relay information back to sensor net gateway.
sendInternal(I_RELAY_NODE, itoa(relayId, convBuffer, 10));
// Initialize ack message header
ack.header.version = PROTOCOL_VERSION;
ack.header.binary = 0;
ack.header.from = radioId;
ack.header.to = GATEWAY_ADDRESS;
ack.header.messageType = M_SET_VARIABLE;
}
uint8_t Sensor::getRadioId() {
return radioId;
}
void Sensor::initializeRadioId() {
if (radioId == AUTO) {
radioId = EEPROM.read(EEPROM_RADIO_ID_ADDRESS);
if (radioId == 0xFF || radioId == 0) {
radioId = AUTO;
debug(PSTR("No radio id found in EEPROM fetching one from sensor net gateway\n"));
// No radio id has been fetched yet ant EEPROM is unwritten.
// Request new id from sensor net gateway. Use radioId 4095 temporarily
// to be able to receive my correct nodeId.
RF24::openReadingPipe(CURRENT_NODE_PIPE, BASE_RADIO_ID+radioId);
radioId = atoi(getInternal(I_REQUEST_ID));
// Write id to EEPROM
if (radioId == AUTO) { // sensor net gateway will return max id if all sensor id are taken
debug(PSTR("Sensor network is full! You already have the maximum of sensors!\n"));
while (1) {} // Wait here. Nothing else we can do...
} else {
debug(PSTR("Radio id received: %d\n"), radioId);
EEPROM.write(EEPROM_RADIO_ID_ADDRESS, radioId);
}
}
debug(PSTR("Radio id stored in EEPROM was: %d\n"), radioId);
}
}
void Sensor::findRelay() {
// This can be improved by using status = read_register(OBSERVE_TX,&observe_tx,1) on the
// sending side.. the problem is getting this information here without too much fuss.
// Stay in this method until one or more relay nodes answers ping message.
if (radioId == GATEWAY_ADDRESS)
return; // Gateway has no business here!
failedTransmissions = 0;
// Open a reading pipe for current radioId (if it differs from broadcast)
if (radioId != BROADCAST_PIPE) {
RF24::openReadingPipe(CURRENT_NODE_PIPE, BASE_RADIO_ID+radioId);
}
distance = 255;
uint8_t oldRelayId = relayId;
while (distance == 255) {
// Send ping message to BROADCAST_ADDRESS (to which all nodes listens and should reply to)
buildMsg(radioId, BROADCAST_ADDRESS, NODE_CHILD_ID, M_INTERNAL, I_PING, "", 0, false);
sendWrite(BROADCAST_ADDRESS, msg, 0);
// Wait for replies for max 10 seconds (or when buffer for all relay nodes have been filled up)
unsigned long enter = millis();
uint8_t neighborDistanceToGW;
// Wait for ack responses 5 seconds
while (millis() - enter < 5000) {
if (messageAvailable()) {
if (msg.header.messageType == M_INTERNAL &&
msg.header.type == I_PING_ACK &&
msg.header.to == radioId) {
neighborDistanceToGW = atoi(msg.data);
if (neighborDistanceToGW<distance-1) {
// Found a neighbor closer to GW than previously found
distance = neighborDistanceToGW + 1;
relayId = msg.header.from;
debug(PSTR("Using relay %d. Distance is %d\n"), msg.header.from, neighborDistanceToGW);
if (neighborDistanceToGW == 0) // We found gateway. Search no more.
break;
} else {
debug(PSTR("Discarded relay %d. Distance is %d\n"), msg.header.from, neighborDistanceToGW);
}
}
}
}
if (distance == 255) {
debug(PSTR("No relay nodes was found. Trying again in 10 seconds.\n"));
delay(10000);
}
}
// Store new relay address in EEPROM
if (relayId != oldRelayId) {
EEPROM.write(EEPROM_RELAY_ID_ADDRESS, relayId);
EEPROM.write(EEPROM_DISTANCE_ADDRESS, distance);
}
// Always listen to relay pipe (relay broadcasts messages that might be for this
// nodes children)
RF24::openReadingPipe(RELAY_PIPE, BASE_RADIO_ID + relayId);
}
void Sensor::buildMsg(uint8_t from, uint8_t to, uint8_t childId, uint8_t messageType, uint8_t type, const char *data, uint8_t length, boolean binary) {
msg.header.version = PROTOCOL_VERSION;
msg.header.binary = binary;
msg.header.from = from;
msg.header.to = to;
msg.header.childId = childId;
msg.header.messageType = messageType;
msg.header.type = type;
strncpy(msg.data, data, length);
if(length < sizeof(msg.data)-1) {
memset(&msg.data[length], 0, sizeof(msg.data) - 1 - length);
}
}
boolean Sensor::sendData(uint8_t from, uint8_t to, uint8_t childId, uint8_t messageType, uint8_t type, const char *data, uint8_t length, boolean binary) {
bool ok = false;
if (length < sizeof(msg.data)) {
buildMsg(from, to, childId, messageType, type, data, length, binary);
return send(msg,length);
} else {
debug(PSTR("Message too large\n"));
}
return false;
}
boolean Sensor::sendVariableAck() {
ack.header.childId = msg.header.childId;
ack.header.type = msg.header.type;
strncpy(ack.data, msg.data, sizeof(ack.data)-1); // will terminate properly not exceeding message.data
return send(ack,strlen(ack.data));
}
boolean Sensor::send(message_s message, int length) {
debug(PSTR("Relaying message back to gateway.\n"));
// We're a sensor node. Always send messages back to relay node
bool ok = sendWrite(relayId, message, length);
if (!ok && radioId != GATEWAY_ADDRESS) {
// Failure when sending to relay node. The relay node might be down and we
// need to find another route to gateway. Max 20 retries before giving up.
if (failedTransmissions>20) {
findRelay();
}
failedTransmissions++;
} else {
failedTransmissions = 0;
}
return ok;
}
boolean Sensor::sendWrite(uint8_t dest, message_s message, int length) {
// Add current radioId to last-field and calculate crc
message.header.last = radioId;
message.header.crc = crc8Message(message);
debug(PSTR("Sending: from=%d, to=%d, childId=%d, mtype=%d, type=%d, crc=%d, '%s', sent via %d\n"),
message.header.from,message.header.to, message.header.childId, message.header.messageType, message.header.type, message.header.crc, message.data, dest);
RF24::stopListening();
bool ok = false;
RF24::openWritingPipe(BASE_RADIO_ID + dest);
int retry = 5;
do {
ok = RF24::write(&message, min(MAX_MESSAGE_LENGTH, sizeof(message.header) + length));
}
while ( !ok && --retry );
RF24::startListening();
if (ok) {
debug(PSTR("Sent successfully\n"));
} else {
debug(PSTR("Send failed. No ack received.\n"));
}
return ok;
}
void Sensor::sendInternal(uint8_t variableType, const char *value) {
sendData(radioId, GATEWAY_ADDRESS, NODE_CHILD_ID, M_INTERNAL, variableType, value, strlen(value), false);
}
// This will be added in 1.4
/*void Sensor::sendVariable(uint8_t childId, uint8_t variableType,
const char *value, length) {
sendData(radioId, GATEWAY_ADDRESS, childId, M_SET_VARIABLE, variableType, value, length, true);
}*/
void Sensor::sendVariable(uint8_t childId, uint8_t variableType,
const char *value) {
sendData(radioId, GATEWAY_ADDRESS, childId, M_SET_VARIABLE, variableType, value, strlen(value), false);
}
void Sensor::sendVariable(uint8_t childId, uint8_t variableType, double value, int decimals) {
sendVariable(childId, variableType, dtostrf(value,2,decimals,convBuffer));
}
void Sensor::sendVariable(uint8_t childId, uint8_t variableType, int value) {
sendVariable(childId, variableType, itoa(value, convBuffer, 10));
}
void Sensor::sendVariable(uint8_t childId, uint8_t variableType, long value) {
sendVariable(childId, variableType, ltoa(value, convBuffer, 10));
}
void Sensor::sendVariable(uint8_t childId, uint8_t variableType, unsigned long value) {
sendVariable(childId, variableType, ltoa(value, convBuffer, 10));
}
void Sensor::sendVariable(uint8_t childId, uint8_t variableType, unsigned int value) {
sendVariable(childId, variableType, ltoa(value, convBuffer, 10));
}
void Sensor::sendVariable(uint8_t nodeId, uint8_t childId, uint8_t variableType,
const char *value) {
sendData(radioId, nodeId, childId, M_SET_VARIABLE, variableType, value, strlen(value), false);
}
void Sensor::sendVariable(uint8_t nodeId, uint8_t childId, uint8_t variableType, double value, int decimals) {
sendVariable(nodeId, childId, variableType, dtostrf(value,2,decimals,convBuffer));
}
void Sensor::sendVariable(uint8_t nodeId, uint8_t childId, uint8_t variableType, int value) {
sendVariable(nodeId, childId, variableType, itoa(value, convBuffer, 10));
}
void Sensor::sendVariable(uint8_t nodeId, uint8_t childId, uint8_t variableType, long value) {
sendVariable(nodeId, childId, variableType, ltoa(value, convBuffer, 10));
}
void Sensor::sendVariable(uint8_t nodeId, uint8_t childId, uint8_t variableType, unsigned long value) {
sendVariable(nodeId, childId, variableType, ltoa(value, convBuffer, 10));
}
void Sensor::sendVariable(uint8_t nodeId, uint8_t childId, uint8_t variableType, unsigned int value) {
sendVariable(nodeId, childId, variableType, ltoa(value, convBuffer, 10));
}
void Sensor::sendSensorPresentation(uint8_t childId, uint8_t sensorType) {
sendData(radioId, GATEWAY_ADDRESS, childId, M_PRESENTATION, sensorType, LIBRARY_VERSION, strlen(LIBRARY_VERSION), false);
}
void Sensor::requestStatus(uint8_t childId, uint8_t variableType) {
sendData(radioId, GATEWAY_ADDRESS, childId, M_REQ_VARIABLE, variableType, "", 0, false);
}
void Sensor::requestStatus(uint8_t nodeId, int8_t childId, uint8_t variableType) {
sendData(radioId, nodeId, childId, M_REQ_VARIABLE, variableType, "", 0, false);
}
void Sensor::sendBatteryLevel(int value) {
sendInternal(I_BATTERY_LEVEL, ltoa(value, convBuffer, 10));
}
char* Sensor::get(uint8_t nodeId, uint8_t childId, uint8_t sendType, uint8_t receiveType, uint8_t variableType) {
while (1) {
sendData(radioId, nodeId, childId, sendType, variableType, "", 0, false);
uint8_t i = 0;
while (i < 100) { // 5 seconds timeout before re-sending status request
while (messageAvailable()) {
// Check that it is right type of message and not a routing message
if (msg.header.messageType == receiveType &&
msg.header.type == variableType &&
msg.header.childId == childId) {
return msg.data;
}
}
delay(50);
i++;
}
}
return NULL;
}
char* Sensor::getStatus(uint8_t childId, uint8_t variableType) {
return get(GATEWAY_ADDRESS, childId, M_REQ_VARIABLE, M_ACK_VARIABLE, variableType);
}
char * Sensor::getStatus(uint8_t nodeId, int8_t childId, uint8_t variableType) {
return get(nodeId, childId, M_REQ_VARIABLE, M_ACK_VARIABLE, variableType);
}
char* Sensor::getInternal(uint8_t variableType) {
return get(GATEWAY_ADDRESS, NODE_CHILD_ID, M_INTERNAL, M_INTERNAL, variableType);
}
unsigned long Sensor::getTime() {
return atol(getInternal(I_TIME));
}
void Sensor::requestTime() {
sendData(radioId, GATEWAY_ADDRESS, NODE_CHILD_ID, M_INTERNAL, I_TIME, "", 0, false);
}
bool Sensor::isMetricSystem() {
return getInternal(I_UNIT)[0] == 'M';
}
void Sensor::requestIsMetricSystem() {
sendData(radioId, GATEWAY_ADDRESS, NODE_CHILD_ID, M_INTERNAL, I_UNIT, "", 0, false);
}
boolean Sensor::messageAvailable() {
if (RF24::available()) {
readMessage();
boolean ok = validate() == VALIDATE_OK;
debug(PSTR("Mess crc %s.\n"),ok?"ok":"error");
if (ok && msg.header.to == radioId) {
// This message is addressed to this node
debug(PSTR("Message addressed for this node.\n"));
if (msg.header.from == GATEWAY_ADDRESS &&
// If this is variable message from sensor net gateway. Send ack back.
msg.header.messageType == M_SET_VARIABLE) {
// Send back ack message to sensor net gateway
sendVariableAck();
}
// Return message to waiting sketch...
return true;
}
}
return false;
}
message_s Sensor::waitForMessage() {
while (1) {
if (messageAvailable()) {
return msg;
}
}
}
message_s Sensor::getMessage() {
return msg;
}
message_s Sensor::readMessage() {
uint8_t len = RF24::getDynamicPayloadSize();
bool done = RF24::read(&msg, len);
// Make sure string gets terminated ok.
msg.data[len - sizeof(header_s) ] = '\0';
debug(PSTR("Received: from=%d, to=%d, childId=%d, mtype=%d, type=%d, crc=%d, '%s'\n"),
msg.header.from,msg.header.to, msg.header.childId, msg.header.messageType, msg.header.type,msg.header.crc, msg.data);
return msg;
}
/*
* raw CRC 8 bit calculation
*/
uint8_t Sensor::crc8Raw ( uint8_t *data_in, uint8_t number_of_bytes_to_read )
{
uint8_t crc;
uint8_t loop_count;
uint8_t bit_counter;
uint8_t data;
uint8_t feedback_bit;
crc = CRC8INIT;
for (loop_count = 0; loop_count != number_of_bytes_to_read; loop_count++)
{
data = data_in[loop_count];
bit_counter = 8;
do {
feedback_bit = (crc ^ data) & 0x01;
if ( feedback_bit == 0x01 ) {
crc = crc ^ CRC8POLY;
}
crc = (crc >> 1) & 0x7F;
if ( feedback_bit == 0x01 ) {
crc = crc | 0x80;
}
data = data >> 1;
bit_counter--;
} while (bit_counter > 0);
}
return crc;
}
/*
* calculate CRC8 on message_s data taking care of data structure and protocol version
*/
uint8_t Sensor::crc8Message(message_s var_msg) {
// Must set crc to a constant value.
var_msg.header.crc = 0;
// fill unused space by zeroes for string data only
if(!var_msg.header.binary) {
uint8_t len = strlen(var_msg.data);
if(len < sizeof(var_msg.data)-1) {
memset(&var_msg.data[len], 0, sizeof(var_msg.data) - 1 - len);
}
}
return crc8Raw((uint8_t*)&var_msg, (uint8_t)sizeof(var_msg));
}
/*
uint8_t Sensor::crc8Message(message_s var_msg) {
struct {
message_s msg;
uint8_t protocol_version;
} crc_data;
memcpy(&crc_data, &var_msg, sizeof(var_msg));
crc_data.protocol_version = PROTOCOL_VERSION;
// some clean up needed for repeated result
crc_data.msg.header.crc = 0;
// fill unused space by zeroes for string data only
if(!crc_data.msg.header.binary) {
uint8_t len = strlen(crc_data.msg.data);
if(len < sizeof(crc_data.msg.data)-1) {
memset(&crc_data.msg.data[len], 0, sizeof(crc_data.msg.data) - 1 - len);
}
}
return crc8Raw((uint8_t*)&crc_data, (uint8_t)sizeof(crc_data));
} */
/*
* true if message is consistent *
*/
bool Sensor::checkCRC(message_s var_msg) {
uint8_t oldCrc = var_msg.header.crc;
//debug(PSTR("CRC: in %d, calc %d\n"),oldCrc, crc8Message(var_msg));
return (oldCrc == crc8Message(var_msg))?true:false;
}
uint8_t Sensor::validate() {
bool crc_check = checkCRC(msg);
bool version_check = (msg.header.version == PROTOCOL_VERSION);
if(!version_check) return VALIDATE_BAD_VERSION;
if(!crc_check) return VALIDATE_BAD_CRC;
return VALIDATE_OK;
}
#ifdef DEBUG
void Sensor::debugPrint(const char *fmt, ... ) {
char fmtBuffer[300];
if (radioId == GATEWAY_ADDRESS) {
// prepend debug message to be handled correctly by gw
Serial.print("0;0;4;11;");
}
va_list args;
va_start (args, fmt );
vsnprintf_P(fmtBuffer, 300, fmt, args);
va_end (args);
if (radioId == GATEWAY_ADDRESS) {
// Truncte message if this is gateway node
vsnprintf_P(fmtBuffer, 97, fmt, args);
fmtBuffer[96] = '\n';
fmtBuffer[97] = '\0';
} else {
vsnprintf_P(fmtBuffer, 299, fmt, args);
}
va_end (args);
Serial.print(fmtBuffer);
//Serial.println(freeRam());
}
#endif
#ifdef DEBUG
int Sensor::freeRam (void) {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
#endif