Compiling. No tests what so ever.

This commit is contained in:
Henrik Ekblad
2014-05-12 23:50:44 +02:00
parent 960efcd6f7
commit 7856dfb209
14 changed files with 1396 additions and 1257 deletions
+3 -3
View File
@@ -52,9 +52,9 @@
#include <RF24.h>
#include <MsTimer2.h>
#include <PinChangeInt.h>
#include <Gateway.h>
#include <MyGateway.h>
#include <stdarg.h>
#include <avr/progmem.h>
//#include <avr/progmem.h>
#include <UIPEthernet.h> // Use this if you have attached a Ethernet ENC28J60 shields
//#include <Ethernet.h> // Use this fo WizNET module and Arduino Ethernet Shield
@@ -81,7 +81,7 @@ byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // DEAD BEEF FEED
EthernetServer server = EthernetServer(IP_PORT);
// No blink or button functionality. Use the vanilla constructor.
Gateway gw(RADIO_CE_PIN, RADIO_SPI_SS_PIN, INCLUSION_MODE_TIME);
MyGateway gw(RADIO_CE_PIN, RADIO_SPI_SS_PIN, INCLUSION_MODE_TIME);
// Uncomment this constructor if you have leds and include button attached to your gateway
//Gateway gw(RADIO_CE_PIN, RADIO_SPI_SS_PIN, INCLUSION_MODE_TIME, INCLUSION_MODE_PIN, RADIO_RX_LED_PIN, RADIO_TX_LED_PIN, RADIO_ERROR_LED_PIN);
@@ -1,5 +1,5 @@
#ifndef Config_h
#define Config_h
#ifndef MyConfig_h
#define MyConfig_h
/***
* Configure Sensor Network
@@ -12,6 +12,6 @@
/***
* Enable/Disable debug logging
*/
#define DEBUG
//#define DEBUG
#endif
@@ -9,17 +9,15 @@
version 2 as published by the Free Software Foundation.
*/
#include "Gateway.h"
#include "MyGateway.h"
Gateway::Gateway(uint8_t _cepin, uint8_t _cspin, uint8_t _inclusion_time) : Relay(_cepin, _cspin) {
MyGateway::MyGateway(uint8_t _cepin, uint8_t _cspin, uint8_t _inclusion_time) : MySensor(_cepin, _cspin) {
ledMode = false;
isRelay = true;
inclusionTime = _inclusion_time;
}
Gateway::Gateway(uint8_t _cepin, uint8_t _cspin, uint8_t _inclusion_time, uint8_t _inclusion_pin, uint8_t _rx, uint8_t _tx, uint8_t _er) : Relay(_cepin, _cspin) {
MyGateway::MyGateway(uint8_t _cepin, uint8_t _cspin, uint8_t _inclusion_time, uint8_t _inclusion_pin, uint8_t _rx, uint8_t _tx, uint8_t _er) : MySensor(_cepin, _cspin) {
ledMode = true;
isRelay = true;
pinInclusion = _inclusion_pin;
inclusionTime = _inclusion_time;
pinRx = _rx;
@@ -27,8 +25,10 @@ Gateway::Gateway(uint8_t _cepin, uint8_t _cspin, uint8_t _inclusion_time, uint8_
pinEr = _er;
}
void Gateway::begin(rf24_pa_dbm_e paLevel, uint8_t channel, rf24_datarate_e dataRate, void (*inDataCallback)(char *)) {
void MyGateway::begin(rf24_pa_dbm_e paLevel, uint8_t channel, rf24_datarate_e dataRate, void (*inDataCallback)(char *)) {
Serial.begin(BAUD_RATE);
relayMode = true;
setupRelayMode();
if (inDataCallback != NULL) {
useWriteCallback = true;
@@ -72,25 +72,25 @@ void Gateway::begin(rf24_pa_dbm_e paLevel, uint8_t channel, rf24_datarate_e data
RF24::startListening();
// Send startup log message on serial
serial(PSTR("0;0;%d;%d;Arduino startup complete.\n"), M_INTERNAL, I_LOG_MESSAGE);
serial(PSTR("0;0;%d;%d;Arduino startup complete.\n"), C_INTERNAL, I_LOG_MESSAGE);
}
boolean Gateway::isLedMode() {
boolean MyGateway::isLedMode() {
return ledMode;
}
void Gateway::startInclusionInterrupt() {
void MyGateway::startInclusionInterrupt() {
buttonTriggeredInclusion = true;
}
void Gateway::checkButtonTriggeredInclusion() {
void MyGateway::checkButtonTriggeredInclusion() {
if (buttonTriggeredInclusion) {
// Ok, someone pressed the inclusion button on the gateway
// start inclusion mode for 1 munute.
serial(PSTR("0;0;%d;%d;Inclusion started by button.\n"), M_INTERNAL, I_LOG_MESSAGE);
serial(PSTR("0;0;%d;%d;Inclusion started by button.\n"), C_INTERNAL, I_LOG_MESSAGE);
buttonTriggeredInclusion = false;
setInclusionMode(true);
@@ -101,7 +101,7 @@ void Gateway::checkButtonTriggeredInclusion() {
}
}
void Gateway::checkInclusionFinished() {
void MyGateway::checkInclusionFinished() {
if (inclusionMode && millis()-inclusionStartTime>60000UL*inclusionTime) {
// inclusionTimeInMinutes minute(s) has passed.. stop inclusion mode
setInclusionMode(false);
@@ -109,13 +109,13 @@ void Gateway::checkInclusionFinished() {
}
void Gateway::parseAndSend(char *commandBuffer) {
void MyGateway::parseAndSend(char *commandBuffer) {
boolean ok = false;
char *str, *p, *value=NULL;
int i = 0;
uint16_t sensorRadioId = 0;
uint8_t childId = 0;
uint8_t messageType = 0;
uint16_t destination = 0;
uint8_t sensor = 0;
uint8_t command = 0;
uint8_t type = 0;
// Extract command data coming on serial line
@@ -125,13 +125,13 @@ void Gateway::parseAndSend(char *commandBuffer) {
) {
switch (i) {
case 0: // Radioid (destination)
sensorRadioId = atoi(str);
destination = atoi(str);
break;
case 1: // Childid
childId = atoi(str);
sensor = atoi(str);
break;
case 2: // Message type
messageType = atoi(str);
command = atoi(str);
break;
case 3: // Data type
type = atoi(str);
@@ -143,19 +143,18 @@ void Gateway::parseAndSend(char *commandBuffer) {
i++;
}
if (sensorRadioId==GATEWAY_ADDRESS && messageType==M_INTERNAL) {
if (destination==GATEWAY_ADDRESS && command==C_INTERNAL) {
// Handle messages directed to gateway
if (type == I_VERSION) {
// Request for version
serial(PSTR("0;0;%d;%d;%s\n"),M_INTERNAL, I_VERSION, LIBRARY_VERSION);
serial(PSTR("0;0;%d;%d;%s\n"),C_INTERNAL, I_VERSION, LIBRARY_VERSION);
} else if (type == I_INCLUSION_MODE) {
// Request to change inclusion mode
setInclusionMode(atoi(value) == 1);
}
} else {
txBlink(1);
ok = sendData(GATEWAY_ADDRESS, sensorRadioId, childId, messageType, type, value, strlen(value), false);
ok = sendRoute(msg.build(GATEWAY_ADDRESS, destination, sensor, command, type).set(value));
if (!ok) {
errBlink(1);
}
@@ -163,11 +162,11 @@ void Gateway::parseAndSend(char *commandBuffer) {
}
void Gateway::setInclusionMode(boolean newMode) {
void MyGateway::setInclusionMode(boolean newMode) {
if (newMode != inclusionMode)
inclusionMode = newMode;
// Send back mode change on serial line to ack command
serial(PSTR("0;0;%d;%d;%d\n"), M_INTERNAL, I_INCLUSION_MODE, inclusionMode?1:0);
serial(PSTR("0;0;%d;%d;%d\n"), C_INTERNAL, I_INCLUSION_MODE, inclusionMode?1:0);
if (inclusionMode) {
inclusionStartTime = millis();
@@ -176,32 +175,32 @@ void Gateway::setInclusionMode(boolean newMode) {
// Override normal validate to add error blink if crc check fails
uint8_t Gateway::validate(uint8_t length) {
uint8_t res = Sensor::validate(length);
uint8_t MyGateway::validate(MyMessage message) {
uint8_t res = MySensor::validate(message);
if (res == VALIDATE_BAD_CRC) {
errBlink(1);
}
return res;
}
void Gateway::processRadioMessage() {
if (messageAvailable()) {
void MyGateway::processRadioMessage() {
if (process()) {
// A new message was received from one of the sensors
message_s msg = getMessage();
if (msg.header.messageType == M_PRESENTATION && inclusionMode) {
MyMessage message = getLastMessage();
if (message.getCommand() == C_PRESENTATION && inclusionMode) {
rxBlink(3);
} else {
rxBlink(1);
}
// Pass along the message from sensors to serial line
serial(msg);
serial(message);
}
checkButtonTriggeredInclusion();
checkInclusionFinished();
}
void Gateway::serial(const char *fmt, ... ) {
void MyGateway::serial(const char *fmt, ... ) {
va_list args;
va_start (args, fmt );
vsnprintf_P(serialBuffer, MAX_SEND_LENGTH, fmt, args);
@@ -213,12 +212,12 @@ void Gateway::serial(const char *fmt, ... ) {
}
}
void Gateway::serial(message_s msg) {
serial(PSTR("%d;%d;%d;%d;%s\n"),msg.header.from, msg.header.childId, msg.header.messageType, msg.header.type, msg.data);
void MyGateway::serial(MyMessage msg) {
serial(PSTR("%d;%d;%d;%d;%s\n"),msg.getSender(), msg.getSensor(), msg.getCommand(), msg.getType(), msg.getString());
}
void Gateway::ledTimersInterrupt() {
void MyGateway::ledTimersInterrupt() {
if(countRx && countRx != 255) {
// switch led on
digitalWrite(pinRx, LOW);
@@ -249,13 +248,13 @@ void Gateway::ledTimersInterrupt() {
}
void Gateway::rxBlink(uint8_t cnt) {
void MyGateway::rxBlink(uint8_t cnt) {
if(countRx == 255) { countRx = cnt; }
}
void Gateway::txBlink(uint8_t cnt) {
void MyGateway::txBlink(uint8_t cnt) {
if(countTx == 255 && !inclusionMode) { countTx = cnt; }
}
void Gateway::errBlink(uint8_t cnt) {
void MyGateway::errBlink(uint8_t cnt) {
if(countErr == 255) { countErr = cnt; }
}
+259
View File
@@ -0,0 +1,259 @@
#include "MyMessage.h"
#include <stdio.h>
#include <stdlib.h>
MyMessage::MyMessage() {
setVersion(PROTOCOL_VERSION);
}
MyMessage::MyMessage(uint8_t _sensor, uint8_t _type, uint8_t _destination) {
sensor = _sensor;
type = _type;
destination = _destination;
setVersion(PROTOCOL_VERSION);
setCommand(C_SET_VARIABLE);
}
MyMessage MyMessage::build(uint8_t _sender, uint8_t _destination, uint8_t _sensor, uint8_t _command, uint8_t _type) {
sender = _sender;
destination = _destination;
sensor = _sensor;
type = _type;
setCommand(_command);
setLength(0);
setPayloadType(P_STRING);
return *this;
}
/*MyMessage MyMessage::failed() {
return getCommand() == C_FAILED
}*/
// Header getters
uint8_t MyMessage::getCRC() {
return crc;
}
uint8_t MyMessage::getVersion() {
return BF_GET(version_length, 0, 3);
}
uint8_t MyMessage::getLength() {
return BF_GET(version_length, 3, 5);
}
uint8_t MyMessage::getCommand() {
return BF_GET(command_payload, 0, 4);
}
uint8_t MyMessage::getPayloadType() {
return BF_GET(command_payload, 4, 4);
}
uint8_t MyMessage::getType() {
return type;
}
uint8_t MyMessage::getSensor() {
return sensor;
}
uint8_t MyMessage::getSender() {
return sender;
}
uint8_t MyMessage::getLast() {
return last;
}
uint8_t MyMessage::getDestination() {
return destination;
}
/* Getters for payload converted to desired form */
void* MyMessage::getCustom() {
return (void *)data;
}
/* NOTE: Calling this method might affect the payload by converting it to its string representation */
char* MyMessage::getString(char *buffer) {
uint8_t payloadType = getPayloadType();
if (payloadType == P_STRING) {
data[getLength()] = '\0';
return data;
} else if (buffer != NULL) {
switch(payloadType) {
case P_BYTE:
itoa(bValue, buffer, MAX_PAYLOAD);
break;
case P_INT16:
itoa(iValue, data, MAX_PAYLOAD);
break;
case P_UINT16:
ltoa(uiValue, data, MAX_PAYLOAD);
break;
case P_LONG32:
ltoa(lValue, data, MAX_PAYLOAD);
break;
case P_ULONG32:
ltoa(ulValue, data, MAX_PAYLOAD);
break;
case P_CUSTOM:
// TODO: Ok, what do we do here? We should probably convert this to hex
// Mostly gateway interested in this so we do the special handling of this
// over there.
break;
}
return buffer;
} else {
return NULL;
}
}
uint8_t MyMessage::getByte() {
return data[0];
}
bool MyMessage::getBool() {
return !(data[0]-'0'==0);
}
double MyMessage::getDouble() {
return strtod(data, NULL);
}
long MyMessage::getLong() {
return lValue;
}
unsigned long MyMessage::getULong() {
return ulValue;
}
int MyMessage::getInt() {
return iValue;
}
unsigned int MyMessage::getUInt() {
return uiValue;
}
// Header setters
MyMessage MyMessage::setCRC(uint8_t _crc) {
crc = _crc;
return *this;
}
MyMessage MyMessage::setVersion(uint8_t version) {
BF_SET(version_length, version, 0, 3);
return *this;
}
MyMessage MyMessage::setLength(uint8_t length) {
BF_SET(version_length, length, 3, 5);
return *this;
}
MyMessage MyMessage::setCommand(uint8_t command) {
BF_SET(command_payload, command, 0, 4);
return *this;
}
MyMessage MyMessage::setPayloadType(uint8_t pt) {
BF_SET(command_payload, pt, 4, 4);
return *this;
}
MyMessage MyMessage::setType(uint8_t _type) {
type = _type;
return *this;
}
MyMessage MyMessage::setSensor(uint8_t _sensor) {
sensor = _sensor;
return *this;
}
MyMessage MyMessage::setSender(uint8_t _sender) {
sender = _sender;
return *this;
}
MyMessage MyMessage::setLast(uint8_t _last) {
last=_last;
return *this;
}
MyMessage MyMessage::setDestination(uint8_t _destination) {
destination = _destination;
return *this;
}
// Set payload
MyMessage MyMessage::set(void* value, uint8_t length) {
setPayloadType(P_CUSTOM);
setLength(length);
memcpy(data, value, min(length, MAX_PAYLOAD));
return *this;
}
MyMessage MyMessage::set(const char* value) {
uint8_t length = strlen(value);
setLength(length);
setPayloadType(P_STRING);
strncpy(data, value, min(length, MAX_PAYLOAD));
return *this;
}
MyMessage MyMessage::set(uint8_t value) {
setLength(1);
setPayloadType(P_BYTE);
data[0] = value;
return *this;
}
MyMessage MyMessage::set(bool value) {
setLength(1);
setPayloadType(P_STRING);
data[0] = value?'1':'0';
return *this;
}
MyMessage MyMessage::set(double value, uint8_t decimals) {
dtostrf(value,2,decimals,data);
setLength(strlen(data));
setPayloadType(P_STRING);
return *this;
}
MyMessage MyMessage::set(unsigned long value) {
setPayloadType(P_ULONG32);
setLength(4);
ulValue = value;
return *this;
}
MyMessage MyMessage::set(long value) {
setPayloadType(P_LONG32);
setLength(4);
lValue = value;
return *this;
}
MyMessage MyMessage::set(unsigned int value) {
setPayloadType(P_INT16);
setLength(2);
uiValue = value;
return *this;
}
MyMessage MyMessage::set(int value) {
setPayloadType(P_UINT16);
setLength(2);
iValue = value;
return *this;
}
+184
View File
@@ -0,0 +1,184 @@
/*
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.
*/
#ifndef MyMessage_h
#define MyMessage_h
#include <SPI.h>
#define PROTOCOL_VERSION 2
#define MAX_MESSAGE_LENGTH 32
#define HEADER_SIZE 8
#define MAX_PAYLOAD (MAX_MESSAGE_LENGTH - HEADER_SIZE)
#define GATEWAY_ADDRESS ((uint8_t)0)
// Message types
typedef enum {
C_PRESENTATION = 0,
C_SET_VARIABLE = 1,
C_REQ_VARIABLE = 2,
C_SET_WITH_ACK = 3,
C_INTERNAL = 4,
C_STREAM = 5, // For Firmaware and other larger chunks of data that need to be divided into pieces.
C_FAILED = 6
} command;
// Type of sensor data (for set/req/ack messages)
typedef enum {
V_TEMP,V_HUM, V_LIGHT, V_DIMMER, V_PRESSURE, V_FORECAST, V_RAIN,
V_RAINRATE, V_WIND, V_GUST, V_DIRECTION, V_UV, V_WEIGHT, V_DISTANCE,
V_IMPEDANCE, V_ARMED, V_TRIPPED, V_WATT, V_KWH, V_SCENE_ON, V_SCENE_OFF,
V_HEATER, V_HEATER_SW, V_LIGHT_LEVEL, V_VAR1, V_VAR2, V_VAR3, V_VAR4, V_VAR5,
V_UP, V_DOWN, V_STOP, V_IR_SEND, V_IR_RECEIVE, V_FLOW, V_VOLUME, V_LOCK_STATUS
} data;
// Type of internal messages (for internal messages)
typedef enum {
I_BATTERY_LEVEL, I_TIME, I_VERSION, I_REQUEST_ID,
I_INCLUSION_MODE, I_RELAY_NODE, I_PING, I_PING_ACK,
I_LOG_MESSAGE, I_CHILDREN, I_UNIT, I_SKETCH_NAME, I_SKETCH_VERSION
} internal;
// Type of sensor (for presentation message)
typedef enum {
S_DOOR, S_MOTION, S_SMOKE, S_LIGHT, S_DIMMER, S_COVER, S_TEMP, S_HUM, S_BARO, S_WIND,
S_RAIN, S_UV, S_WEIGHT, S_POWER, S_HEATER, S_DISTANCE, S_LIGHT_LEVEL, S_ARDUINO_NODE,
S_ARDUINO_RELAY, S_LOCK, S_IR, S_WATER
} sensor;
// Type of data stream (for streamed message)
typedef enum {
ST_FIRMWARE, ST_SOUND, ST_IMAGE
} stream;
typedef enum {
P_STRING, P_BYTE, P_INT16, P_UINT16, P_LONG32, P_ULONG32, P_CUSTOM
} payload;
// Possible return values by validate() when doing crc check of received message.
typedef enum {
VALIDATE_OK, VALIDATE_BAD_CRC, VALIDATE_BAD_VERSION
} validationResult;
#define BIT(n) ( 1<<(n) )
// Create a bitmask of length len.
#define BIT_MASK(len) ( BIT(len)-1 )
// Create a bitfield mask of length starting at bit 'start'.
#define BF_MASK(start, len) ( BIT_MASK(len)<<(start) )
// Prepare a bitmask for insertion or combining.
#define BF_PREP(x, start, len) ( ((x)&BIT_MASK(len)) << (start) )
// Extract a bitfield of length len starting at bit 'start' from y.
#define BF_GET(y, start, len) ( ((y)>>(start)) & BIT_MASK(len) )
// Insert a new bitfield value x into y.
#define BF_SET(y, x, start, len) ( y= ((y) &~ BF_MASK(start, len)) | BF_PREP(x, start, len) )
class MyMessage
{
public:
// Constructors
MyMessage(uint8_t sensor, uint8_t type, uint8_t destination=GATEWAY_ADDRESS);
MyMessage();
/* Check this after callback to see if request failed */
// bool failed();
// Getters for header
uint8_t getCRC();
uint8_t getVersion();
uint8_t getLength();
uint8_t getCommand();
uint8_t getPayloadType();
uint8_t getType();
uint8_t getSensor();
uint8_t getSender();
uint8_t getLast();
uint8_t getDestination();
// Getters for payload
/**
* If payload is something else than P_STRING you can have payload the value converted
* to string representation by supplying a buffer with the minimum size of
* 2*MAX_PAYLOAD+1. This is to be able to fit hex-conversion of a full binary payload.
*/
char* getString(char *buffer = NULL);
/**
* Returns payload untouched
*/
void* getCustom();
uint8_t getByte();
bool getBool();
double getDouble();
long getLong();
unsigned long getULong();
int getInt();
unsigned int getUInt();
// Builder helper
MyMessage build(uint8_t sender, uint8_t destination, uint8_t sensor, uint8_t command, uint8_t type);
// Setters for header
MyMessage setCRC(uint8_t crc);
MyMessage setVersion(uint8_t version);
MyMessage setLength(uint8_t length);
MyMessage setCommand(uint8_t command);
MyMessage setPayloadType(uint8_t pt);
MyMessage setType(uint8_t type);
MyMessage setSensor(uint8_t sensor);
MyMessage setSender(uint8_t sender);
MyMessage setLast(uint8_t last);
MyMessage setDestination(uint8_t destination);
// Setters for payload
MyMessage set(void* payload, uint8_t length);
MyMessage set(const char* value);
MyMessage set(uint8_t value);
MyMessage set(bool value);
MyMessage set(double value, uint8_t decimals);
MyMessage set(unsigned long value);
MyMessage set(long value);
MyMessage set(unsigned int value);
MyMessage set(int value);
uint8_t version_length; // 3 bit - Protocol version + 5 bit - Length of payload
uint8_t command_payload; // 4 bit - Command type + 4 bit - Payload data type
uint8_t type; // 8 bit - Type varies depending on command
uint8_t sensor; // 8 bit - Id of sensor that this message concerns.
uint8_t sender; // 8 bit - Id of sender node
uint8_t last; // 8 bit - Id of last node this message passed
uint8_t destination; // 8 bit - Id of destination node
uint8_t crc; // 8 bit - Message checksum
// Each message can transfer a payload. We add one extra byte for string
// terminator \0 to be "printable" this is not transferred OTA
// This union is used to simplify the construction of the binary transferred int/long values.
union {
uint8_t bValue;
unsigned long ulValue;
long lValue;
unsigned int uiValue;
int iValue;
char data[MAX_PAYLOAD + 1];
};
};
#endif
+650
View File
@@ -0,0 +1,650 @@
/*
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"
MySensor::MySensor(uint8_t _cepin, uint8_t _cspin) : RF24(_cepin, _cspin) {
}
void MySensor::setupRadio(rf24_pa_dbm_e paLevel, uint8_t channel, rf24_datarate_e dataRate) {
failedTransmissions = 0;
// Start up the radio library
RF24::begin();
RF24::setAutoAck(1);
RF24::enableAckPayload();
if (!RF24::isPVariant()) {
debug(PSTR("Radio wiring NOT ok or not using P version of NRF24L01.\n"));
while(1);
}
RF24::setChannel(channel);
RF24::setPALevel(paLevel);
RF24::setDataRate(dataRate);
RF24::setRetries(5,15);
RF24::setCRCLength(RF24_CRC_16);
RF24::enableDynamicPayloads();
// All repeater nodes and gateway listen to broadcast pipe (for PING messages)
if (relayMode) {
RF24::openReadingPipe(BROADCAST_PIPE, TO_ADDR(BROADCAST_ADDRESS));
}
}
void MySensor::setupRelayMode(){
childNodeTable = (uint8_t *) malloc(256);
// Read routing table from EEPROM
for (unsigned int i=0;i<sizeof(childNodeTable);i++) {
childNodeTable[i] = EEPROM.read(EEPROM_ROUTES_ADDRESS+i);
}
}
void MySensor::begin(void (*_msgCallback)(MyMessage), boolean _relayMode, uint8_t _radioId, rf24_pa_dbm_e paLevel, uint8_t channel, rf24_datarate_e dataRate) {
Serial.begin(BAUD_RATE);
relayMode = _relayMode;
msgCallback = _msgCallback;
radioId = _radioId;
debug(PSTR("Started %s.\n"), relayMode?"relay":"sensor");
if (relayMode) {
setupRelayMode();
}
setupRadio(paLevel, channel, dataRate);
// 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 (set write pipe to same)
RF24::openReadingPipe(WRITE_PIPE, TO_ADDR(radioId));
RF24::openReadingPipe(CURRENT_NODE_PIPE, TO_ADDR(radioId));
// Send presentation for this radio node
present(NODE_CHILD_ID, relayMode? S_ARDUINO_RELAY : S_ARDUINO_NODE);
// Send relay information back to sensor net gateway.
sendInternal(I_RELAY_NODE, itoa(relayId, convBuffer, 10));
}
uint8_t MySensor::getRadioId() {
return radioId;
}
void MySensor::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, TO_ADDR(radioId));
radioId = getInternal(I_REQUEST_ID).getByte();
// 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 MySensor::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.
// Try to find relay 3 times before giving up
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, TO_ADDR(radioId));
debug(PSTR("Open ping reading pipe: %d\n"), radioId);
}
distance = 255;
uint8_t oldRelayId = relayId;
uint8_t retries=0;
while (distance == 255 && retries<FIND_RELAY_RETRIES) {
// Send ping message to BROADCAST_ADDRESS (to which all nodes listens and should reply to)
msg.build(radioId, BROADCAST_ADDRESS, NODE_CHILD_ID, C_INTERNAL, I_PING);
sendWrite(BROADCAST_ADDRESS, msg);
// 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 (process()) {
if (msg.getCommand() == C_INTERNAL &&
msg.getType() == I_PING_ACK &&
msg.getDestination() == radioId) {
neighborDistanceToGW = msg.getByte();
if (neighborDistanceToGW<distance-1) {
// Found a neighbor closer to GW than previously found
distance = neighborDistanceToGW + 1;
relayId = msg.getSender();
debug(PSTR("Using relay %d. Distance is %d\n"), msg.getSender(), neighborDistanceToGW);
if (neighborDistanceToGW == 0) // We found gateway. Search no more.
break;
} else {
debug(PSTR("Discarded relay %d. Distance is %d\n"), msg.getSender(), neighborDistanceToGW);
}
}
}
}
if (distance == 255) {
debug(PSTR("No relay nodes was found. Trying again in 10 seconds.\n"));
delay(10000);
}
retries++;
}
// Store new relay address in EEPROM
if (relayId != oldRelayId) {
EEPROM.write(EEPROM_RELAY_ID_ADDRESS, relayId);
EEPROM.write(EEPROM_DISTANCE_ADDRESS, distance);
}
}
boolean MySensor::sendAck(MyMessage message) {
// Build ack message
ack = message;
// memcpy((void *)ack, (void *)message, sizeof(MyMessage));
ack.setSender(radioId);
ack.setDestination(message.getSender());
return sendRoute(ack);
}
boolean MySensor::sendRoute(MyMessage message) {
if (relayMode) {
uint8_t dest = message.getDestination();
uint8_t route = getChildRoute(dest);
if (route>0 && route<255 && dest != GATEWAY_ADDRESS) {
debug(PSTR("Routing message to %d.\n"), route);
// Message destination is not gateway and is in routing table for this node.
// Send it downstream
return sendWrite(route, message);
}
}
if (radioId != GATEWAY_ADDRESS) {
debug(PSTR("Relaying message back to gateway.\n"));
// Should be routed back to gateway.
bool ok = sendWrite(relayId, message);
if (!ok) {
// 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 > FIND_RELAY_AFTER_FAILED_TRANSMISSIONS) {
findRelay();
}
failedTransmissions++;
} else {
failedTransmissions = 0;
}
return ok;
}
return false;
}
boolean MySensor::sendWrite(uint8_t next, MyMessage message) {
uint8_t length = message.getLength();
message.setLast(radioId);
message.setCRC(crc8Message(message));
// Only print payload if string type
debug(PSTR("Tx: fr=%d,to=%d,la=%d,ne=%d,ci=%d,mt=%d,ty=%d,cr=%d:%s\n"),
message.getSender(),message.getDestination(), message.getLast(), next, message.getSensor(), message.getCommand(), message.getType(), message.getCRC(), message.getString());
bool broadcast = message.getCommand() == C_INTERNAL && message.getType() == I_PING;
// Make sure radio has powered up
RF24::powerUp();
RF24::stopListening();
RF24::openWritingPipe(TO_ADDR(next));
bool ok = RF24::write(&message, min(MAX_MESSAGE_LENGTH, HEADER_SIZE + length), broadcast);
RF24::startListening();
if (ok)
debug(PSTR("Sent successfully\n"));
else
debug(PSTR("Send failed\n"));
return ok;
}
void MySensor::sendInternal(uint8_t variableType, const char *value) {
sendRoute(msg.build(radioId, GATEWAY_ADDRESS, NODE_CHILD_ID, C_INTERNAL, variableType).set(value));
}
bool MySensor::send(MyMessage message, bool enableAck, uint8_t retry) {
message.setSender(radioId);
if (enableAck) {
message.setCommand(C_SET_WITH_ACK);
return wait(message, C_SET_VARIABLE, retry).getCommand() != C_FAILED;
} else {
return sendRoute(message);;
}
}
void MySensor::present(uint8_t childSensorId, uint8_t sensorType) {
sendRoute(msg.build(radioId, GATEWAY_ADDRESS, childSensorId, C_PRESENTATION, sensorType).set(LIBRARY_VERSION));
}
void MySensor::sketchInfo(const char *name, const char *version) {
if (name != NULL)
sendInternal(I_SKETCH_NAME, name);
if (version != NULL)
sendInternal(I_SKETCH_VERSION, version);
}
MyMessage MySensor::get( uint8_t childSensorId, uint8_t variableType, uint8_t destination, uint8_t retry) {
msg.build(radioId, destination, childSensorId, C_REQ_VARIABLE, variableType);
return wait(msg, C_SET_VARIABLE, retry);
}
void MySensor::request(uint8_t childSensorId, uint8_t variableType, uint8_t destination) {
process(); // Make sure to process any queued messages before sending new request
sendRoute(msg.build(radioId, destination, childSensorId, C_REQ_VARIABLE, variableType));
}
void MySensor::sendBatteryLevel(int value) {
sendInternal(I_BATTERY_LEVEL, ltoa(value, convBuffer, 10));
}
MyMessage MySensor::wait(MyMessage message, uint8_t expectedResponseCommand, uint8_t retry) {
while (retry>0) {
uint8_t sensor = message.getSensor();
uint8_t type = message.getType();
sendRoute(message);
uint8_t i = 0;
while (i < 100) { // 1 seconds timeout before re-sending status request
while (process()) {
// Check that it is right type of message and not a routing message
if (msg.getCommand() == expectedResponseCommand &&
msg.getType() == type &&
msg.getSensor() == sensor) {
return msg;
}
}
delay(10);
i++;
}
retry--;
}
msg.setCommand(C_FAILED);
return msg;
}
MyMessage MySensor::getInternal(uint8_t variableType, uint8_t retry) {
msg.build(radioId, GATEWAY_ADDRESS, NODE_CHILD_ID, C_INTERNAL, variableType);
return wait(msg, C_INTERNAL, retry);
}
/*unsigned long MySensor::getTime(uint8_t retry) {
MyMessage message = getInternal(I_TIME, retry);
return message == NULL ? 0UL : message.getULong();
}*/
void MySensor::requestTime() {
sendRoute(msg.build(radioId, GATEWAY_ADDRESS, NODE_CHILD_ID, C_INTERNAL, I_TIME));
}
/*bool MySensor::isMetricSystem(uint8_t retry) {
MyMessage message = getInternal(I_UNIT, retry);
return (message == NULL)
return true;
else
return message.getByte() == 'M';
}*/
void MySensor::requestIsMetricSystem() {
sendRoute(msg.build(radioId, GATEWAY_ADDRESS, NODE_CHILD_ID, C_INTERNAL, I_UNIT));
}
boolean MySensor::process() {
uint8_t pipe;
boolean available = RF24::available(&pipe);
if (available) {
debug(PSTR("Message available on pipe %d\n"),pipe);
}
if (available && pipe<7) {
uint8_t len = RF24::getDynamicPayloadSize();
RF24::read(&msg, len);
RF24::writeAckPayload(pipe,&pipe, 1 );
uint8_t valid = validate(msg);
boolean ok = valid == VALIDATE_OK;
// Make sure string gets terminated ok for full string messages.
msg.data[len - HEADER_SIZE ] = '\0';
debug(PSTR("Rx: fr=%d,to=%d,la=%d,ci=%d,mt=%d,t=%d,cr=%d(%s): %s\n"),
msg.getSender(),msg.getDestination(), msg.getLast(), msg.getSensor(), msg.getCommand(), msg.getType(), msg.getCRC(), valid==0?"ok":valid==1?"ec":"ev", msg.getString());
if (ok) {
uint8_t command = msg.getCommand();
uint8_t type = msg.getType();
uint8_t sender = msg.getSender();
uint8_t last = msg.getLast();
uint8_t destination = msg.getDestination();
if (relayMode && command == C_INTERNAL && type == I_PING) {
// Relays should always answer ping messages
// Wait a random delay of 0-2 seconds to minimize collision
// between ping ack messages from other relaying nodes
randomSeed(millis());
delay(random(2000));
uint8_t replyTo = msg.getSender();
msg.build(radioId, replyTo, NODE_CHILD_ID, C_INTERNAL, I_PING_ACK).set(distance);
sendWrite(replyTo, msg);
} else if (sender == radioId) {
// This message is addressed to this node
if (relayMode && command == C_INTERNAL) {
// Handle all internal messages to this node
if (type == I_RELAY_NODE && destination != GATEWAY_ADDRESS) {
// Someone at sensor net gateway side wants this node to refresh its relay node
findRelay();
// Send relay information back to sensor net gateway node device.
sendInternal(I_RELAY_NODE, ltoa(relayId, convBuffer, 10));
return false;
} else if (type == I_CHILDREN && destination != GATEWAY_ADDRESS) {
debug(PSTR("Route command received"));
if (msg.getByte() == 'F') {
// Send in as many children we can fit in a message (binary)
sendChildren();
} else if (msg.getByte() == 'C') {
// Clears child relay data for this node
clearChildRoutes();
}
return false;
}
// Return rest of internal messages back to sketch...
if (last != GATEWAY_ADDRESS)
addChildRoute(sender, last);
return true;
} else {
// If this is variable message from sensor net gateway. Send ack back.
debug(PSTR("Message addressed for this node.\n"));
// Send set-message back to sender if sender wants this
if (command == C_SET_WITH_ACK) {
sendAck(msg);
// The library user should not need to care about this ack request. Just treat it as a normal SET.
msg.setCommand(C_SET_VARIABLE);
}
// Call incoming message callback if available
if (msgCallback != NULL) {
msgCallback(msg);
}
if (last != GATEWAY_ADDRESS)
addChildRoute(sender, last);
// Return true if message was addressed for this node...
return true;
}
} else if (relayMode) {
// We should try to relay this message to another node
uint8_t route = getChildRoute(msg.getDestination());
if (route>0 && route<255) {
debug(PSTR("Routing message to child node.\n"));
// 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 if (pipe == CURRENT_NODE_PIPE) {
// 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)
debug(PSTR("Routing message to relay.\n"));
// This message should be routed back towards sensor net gateway
sendWrite(relayId, msg);
// Add this child to our "routing table" if it not already exist
addChildRoute(sender, last);
} else {
// We're snooped a message directed to gateway from another branch
// Make sure to remove the sender node from our routing table.
//
// +-----B <-- Message comes from here
// -A
// +-----C <-- We're here
//
// So the sender of message should never be in our
// routing table.
//
// We could also end up in this else-statement if message
// destination has no route.
// This can happen if a node never has sent any message to
// sensor net gateway (which will build up routing information in all nodes
// that gets passed).
if (radioId == GATEWAY_ADDRESS) {
// We should perhaps inform gateway about the no-route situation?!
debug(PSTR("Unknown route from GW\n"));
} else {
debug(PSTR("Remove child node from routing table.\n"));
removeChildRoute(sender);
}
}
}
}
}
return false;
}
MyMessage MySensor::getLastMessage() {
return msg;
}
/*
* calculate CRC8 on MyMessage data taking care of data structure and protocol version
*/
uint8_t MySensor::crc8Message(MyMessage message) {
uint8_t len = message.getLength();
uint8_t crc = 0x00;
uint8_t loop_count;
uint8_t bit_counter;
uint8_t data;
uint8_t feedback_bit;
uint8_t number_of_bytes_to_read = (uint8_t)sizeof(message);
// Must set crc to a constant value.
message.setCRC(0);
// fill unused space by zeroes for string data only
if(len>=0 && len < sizeof(message.data)-1) {
memset(&message.data[len], 0, sizeof(message.data) - 1 - len);
}
for (loop_count = 0; loop_count != number_of_bytes_to_read; loop_count++)
{
data = ((uint8_t*)&message)[loop_count];
bit_counter = 8;
do {
feedback_bit = (crc ^ data) & 0x01;
if ( feedback_bit == 0x01 ) {
crc = crc ^ 0x18; //0X18 = X^8+X^5+X^4+X^0
}
crc = (crc >> 1) & 0x7F;
if ( feedback_bit == 0x01 ) {
crc = crc | 0x80;
}
data = data >> 1;
bit_counter--;
} while (bit_counter > 0);
}
return crc;
}
uint8_t MySensor::validate(MyMessage message) {
uint8_t oldCrc = message.getCRC();
uint8_t newCrc = crc8Message(message);
if(!(message.getVersion() == PROTOCOL_VERSION)) return VALIDATE_BAD_VERSION;
if(!(oldCrc == newCrc)) return VALIDATE_BAD_CRC;
return VALIDATE_OK;
}
void MySensor::addChildRoute(uint8_t childId, uint8_t route) {
if (childNodeTable[childId] != route) {
childNodeTable[childId] = route;
EEPROM.write(EEPROM_ROUTES_ADDRESS+childId, route);
}
}
void MySensor::removeChildRoute(uint8_t childId) {
if (childNodeTable[childId] != 0xff) {
childNodeTable[childId] = 0xff;
EEPROM.write(EEPROM_ROUTES_ADDRESS+childId, 0xff);
}
}
uint8_t MySensor::getChildRoute(uint8_t childId) {
return childNodeTable[childId];
}
void MySensor::clearChildRoutes() {
debug(PSTR("Clear child routing data\n"));
for (unsigned int i=0;i< sizeof(childNodeTable); i++) {
removeChildRoute(i);
}
sendInternal(I_CHILDREN, "");
}
void MySensor::sendChildren() {
// Send in which children that is using this node as an relay.
// TODO: Fix this
debug(PSTR("Dump beginning of routing table\n"));
for (int i=0;i< 50; i++) {
// Serial.println(childNodeTable[i]);
debug(PSTR("rt:%d, %d\n"), i, getChildRoute(i) );
}
sendInternal(I_CHILDREN, "not implemented");
}
#ifdef DEBUG
void MySensor::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) {
// 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.print("0;0;4;11;Mem free:");
//Serial.println(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
+237
View File
@@ -0,0 +1,237 @@
/*
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.
*/
#ifndef MySensor_h
#define MySensor_h
#include "Version.h" // Auto generated by bot
#include "MyConfig.h"
#include "MyMessage.h"
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>
#include <Arduino.h>
#include <stddef.h>
#include <SPI.h>
#include <EEPROM.h>
#include <avr/pgmspace.h>
#include <stdarg.h>
#ifdef DEBUG
#define debug(x,...) debugPrint(x, ##__VA_ARGS__)
#else
#define debug(x,...)
#endif
#define BAUD_RATE 115200
#define AUTO 0xFF // 0-254. Id 255 is reserved for auto initialization of radioId.
#define NODE_CHILD_ID 0xFF // Node child id is always created for when a new sensor is detected
#define EEPROM_RADIO_ID_ADDRESS 0 // Where to store radio id in EEPROM
#define EEPROM_RELAY_ID_ADDRESS 1 // Where to store relay id in EEPROM
#define EEPROM_DISTANCE_ADDRESS 2 // Where to store distance to gateway in EEPROM
#define EEPROM_ROUTES_ADDRESS ((uint8_t)3) // Where to start storing routing information in EEPROM. Will allocate 256 bytes.
// This is the radioId for sensor net gateway receiver sketch (where all sensors should send their data).
// This is also act as base value for sensor radioId
#define BASE_RADIO_ID ((uint64_t)0xA8A8E1FC00LL)
#define GATEWAY_ADDRESS ((uint8_t)0)
#define BROADCAST_ADDRESS ((uint8_t)0xFF)
#define TO_ADDR(x) (BASE_RADIO_ID + x)
#define WRITE_PIPE ((uint8_t)0)
#define CURRENT_NODE_PIPE ((uint8_t)1)
#define BROADCAST_PIPE ((uint8_t)2)
#define SEND_RETRIES 3
#define FIND_RELAY_RETRIES 2
#define FIND_RELAY_AFTER_FAILED_TRANSMISSIONS 20
class MySensor : public RF24
{
public:
/**
* Constructor
*
* Creates a new instance of Sensor class.
*
* @param _cepin The pin attached to RF24 Chip Enable on the RF module (defualt 9)
* @param _cspin The pin attached to RF24 Chip Select (default 10)
*/
MySensor(uint8_t _cepin=9, uint8_t _cspin=10);
/**
* Begin operation of the MySensors library
*
* Call this in setup(), before calling any other sensor net library methods.
* @param incomingMessageCallback Callback function for incoming messages. Default is NULL
* @param relayMode Activate relay mode. This node will forward messages to other nodes in the radio network. Make sure to call process() regularly. Default in false
* @param radioId The unique id (1-254) for this sensor. Default is AUTO(255) which means sensor tries to fetch an id from controller.
* @param paLevel Radio PA Level for this sensor. Default RF24_PA_MAX
* @param channel Radio channel. Default is channel 76
* @param dataRate Radio transmission speed. Default RF24_1MBPS
*/
void begin(void (* msgCallback)(MyMessage)=NULL, boolean relayMode=false, uint8_t radioId=AUTO, rf24_pa_dbm_e paLevel=RF24_PA_LEVEL, uint8_t channel=RF24_CHANNEL, rf24_datarate_e dataRate=RF24_DATARATE);
/**
* Return the nodes radioId.
*/
uint8_t getRadioId();
/**
* Each node must present all attached sensors before any values can be handled correctly by the controller.
* It is usually good to present all attached sensors after power-up in setup().
*
* @param sensorChildId Sensor child id for the presented. Choose anything between 0-254.
* @param sensorType The sensor type. See sensor typedef in Message.h.
*/
void present(uint8_t sensorChildId, uint8_t sensorType);
/**
* Sends sketch meta information to the gateway. Not mandatory but a nice thing to do.
* @param name String containing a short Sketch name or NULL if not applicable
* @param version String containing a short Sketch version or NULL if not applicable
*/
void sketchInfo(const char *name, const char *version);
/**
* Sends a message to gateway or one of the other nodes in the radio network
*
* @param msg Message to send
* @param ack Set this to true if you want destination node to send ack back to this node. Default is not to request any ack.
* @param retry Number of attempts before giving up. Default is SEND_RETRIES
* @return true Retuns false if ack is enabled and no ack came back
*/
bool send(MyMessage msg, bool ack=false, uint8_t retry=SEND_RETRIES);
/**
* Send this nodes battery level to gateway.
* @param level Level between 0-100(%)
*/
void sendBatteryLevel(int level);
/**
* Fetches a value from gateway or some other sensor in the radio network. Resends request and waits for response.
* Note that if you have registered a callback in begin() it will also be called before message is returned
* by get()
*
* @param childSensorId The unique child id for the different sensors connected to this arduino. 0-254.
* @param variableType The variableType to fetch
* @param destination The radioId of other node in radio network. Default is gateway
* @param number of attempts to fetch
* @return Message response
*/
MyMessage get(uint8_t childSensorId, uint8_t variableType, uint8_t destination=GATEWAY_ADDRESS, uint8_t retry=SEND_RETRIES);
/**
* Requests a value from gateway or some other sensor in the radio network.
* Make sure to add callback in begin() to handle request response.
*
* @param childSensorId The unique child id for the different sensors connected to this arduino. 0-254.
* @param variableType The variableType to fetch
* @param callback for result. This will be called when a message reply is received or request fails (see status of second argument).
* @param destination The radioId of other node in radio network. Default is gateway
*/
/*void (* msgCallback)(MyMessage, bool)=NULL, */
void request(uint8_t childSensorId, uint8_t variableType, uint8_t destination=GATEWAY_ADDRESS);
/**
* Fetches time from sensor net gateway.
*
* @return Seconds since 1970, 0 if failed.
*/
// unsigned long getTime(uint8_t retry=SEND_RETRIES);
void requestTime();
/**
* Fetches unit setting from sensor net gateway. Returns true if metric system has been selected which means
* that sensor should report it's information in: celsius, meter, cm, gram, km/h, m/s etc..
* If false is returned the sensor should report data in imperial system which means
* fahrenheit, feet, gallon, mph etc...
*/
void requestIsMetricSystem();
// bool isMetricSystem(uint8_t retry=SEND_RETRIES);
/**
* Processes incoming messages to this node. If this is a relaying node it will
* Returns true if there is a message addressed for this node just was received.
* Use callback to handle incoming messages.
*/
boolean process(void);
/**
* Returns the last received message
*/
MyMessage getLastMessage(void);
#ifdef DEBUG
void debugPrint(const char *fmt, ... );
int freeRam();
#endif
protected:
MyMessage msg; // Buffer for incoming messages.
uint8_t failedTransmissions;
boolean relayMode;
uint8_t radioId;
uint8_t distance; // This nodes distance to sensor net gateway (number of hops)
uint8_t relayId;
char convBuffer[20];
void setupRelayMode();
void setupRadio(rf24_pa_dbm_e paLevel, uint8_t channel, rf24_datarate_e dataRate);
void findRelay();
boolean sendRoute(MyMessage message);
boolean sendWrite(uint8_t dest, MyMessage message);
void sendInternal(uint8_t variableType, const char *value);
boolean sendAck(MyMessage message);
boolean sendData(uint8_t from, uint8_t to, uint8_t childId, uint8_t messageType, uint8_t type, const char *data, uint8_t length, boolean binaryMessage);
uint8_t validate(MyMessage message);
void sendChildren();
private:
// Callbacks for reqeusts
/* struct callback {
bool active; // Deactivates callback after each reply
uint8_t command; // Command type
uint8_t sender;
uint8_t sensor;
uint8_t type;
void (*msgCallback)(MyMessage, bool);
};*/
MyMessage ack; // Buffer for ack messages.
uint8_t *childNodeTable; // In memory buffer for routing to other nodes. Also stored in EEPROM
void (*msgCallback)(MyMessage); // Callback for undefined incoming messages
void initializeRadioId();
uint8_t crc8Message(MyMessage);
MyMessage wait(MyMessage msg, uint8_t expectedReceiveType, uint8_t retry);
MyMessage getInternal(uint8_t variableType, uint8_t retry=SEND_RETRIES);
uint8_t getChildRoute(uint8_t childId);
void addChildRoute(uint8_t childId, uint8_t route);
void removeChildRoute(uint8_t childId);
void clearChildRoutes();
};
#endif
-271
View File
@@ -1,271 +0,0 @@
/*
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 "Relay.h"
Relay::Relay(uint8_t _cepin, uint8_t _cspin) : Sensor(_cepin, _cspin) {
isRelay = true;
}
void Relay::begin(uint8_t radioId, rf24_pa_dbm_e paLevel, uint8_t channel, rf24_datarate_e dataRate) {
Sensor::begin(radioId, paLevel, channel, dataRate);
// Read routing table from EEPROM
for (unsigned int i=0;i<sizeof(childNodeTable);i++) {
childNodeTable[i] = EEPROM.read(EEPROM_ROUTES_ADDRESS+i);
}
}
boolean Relay::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)) {
uint8_t route = getChildRoute(to);
if (route>0) {
debug(PSTR("Found child in routing table. Sending to %d\n"),route);
buildMsg(from, to, childId, messageType, type, data, length, binary);
// Found node in route table
ok = sendWrite(route, msg, length);
} else if (radioId == GATEWAY_ADDRESS) {
// If we're GW (no parent...). As a last resort try sending message directly to node.
debug(PSTR("No route... try sending direct.\n"));
buildMsg(from, to, childId, messageType, type, data, length, binary);
// Found node in route table
ok = sendWrite(to, msg, length);
} else {
// We are probably a repeater node which should send message back to relay
ok = Sensor::sendData(from, to, childId, messageType, type, data, length, binary);
}
} else {
debug(PSTR("Message too large\n"));
}
return ok;
}
boolean Relay::send(message_s message, int length) {
bool ok = true;
uint8_t route = getChildRoute(msg.header.to);
if (route>0 && route<255 && message.header.to != GATEWAY_ADDRESS) {
debug(PSTR("Routing message to %d.\n"), route);
// Message destination is not gateway and is in routing table for this node.
// Send it downstream
ok = sendWrite(route, message, length);
} else if (radioId != GATEWAY_ADDRESS) {
debug(PSTR("Sending message back towards gw.\n"));
// Should be routed back to gateway. Sensor code knows how to do this.
ok = Sensor::send(message, length);
}
if (!ok) {
debug(PSTR("No ack received.\n"));
}
return ok;
}
boolean Relay::messageAvailable() {
uint8_t pipe;
boolean available = RF24::available(&pipe);
if (available) {
debug(PSTR("Message available on pipe %d\n"),pipe);
}
if (available && pipe<7) {
uint8_t len = RF24::getDynamicPayloadSize();
RF24::read(&msg, len);
RF24::writeAckPayload(pipe,&pipe, 1 );
uint8_t valid = validate(len-sizeof(header_s));
boolean ok = valid == VALIDATE_OK;
// Make sure string gets terminated ok for full sized messages.
msg.data[len - sizeof(header_s) ] = '\0';
debug(PSTR("Rx: fr=%d,to=%d,la=%d,ci=%d,mt=%d,t=%d,cr=%d(%s): %s\n"),
msg.header.from,msg.header.to, msg.header.last, msg.header.childId, msg.header.messageType, msg.header.type, msg.header.crc, valid==0?"ok":valid==1?"ec":"ev", msg.data);
//boolean ok = readMessage();
if (ok) {
if (msg.header.messageType == M_INTERNAL &&
msg.header.type == I_PING) {
// We always answer ping messages
// Wait a random delay of 0-2 seconds to minimize collision
// between ping ack messages from other relaying nodes
randomSeed(millis());
delay(random(2000));
ltoa(distance, convBuffer, 10);
uint8_t to = msg.header.from;
debug(PSTR("Answer ping message. %d\n"), strlen(convBuffer));
buildMsg(radioId, to, NODE_CHILD_ID, M_INTERNAL, I_PING_ACK, convBuffer,strlen(convBuffer), false);
sendWrite(to, msg, strlen(convBuffer));
} else if (msg.header.to == radioId) {
// This message is addressed to this node
if (msg.header.messageType == M_INTERNAL) {
// Handle all internal messages to this node
if (msg.header.type == I_RELAY_NODE && msg.header.to != GATEWAY_ADDRESS) {
// Someone at sensor net gateway side wants this node to refresh its relay node
findRelay();
// Send relay information back to sensor net gateway node device.
sendInternal(I_RELAY_NODE, ltoa(relayId, convBuffer, 10));
return false;
} else if (msg.header.type == I_CHILDREN && msg.header.to != GATEWAY_ADDRESS) {
debug(PSTR("Route command received"));
if (strncmp(msg.data,"F", 1) == 0) {
// Send in as many children we can fit in a message (binary)
sendChildren();
} else if (strncmp(msg.data,"C", 1) == 0) {
// Clears child relay data for this node
clearChildRoutes();
}
return false;
}
// Return rest of internal messages back to sketch...
if (msg.header.last != GATEWAY_ADDRESS)
addChildRoute(msg.header.from, msg.header.last);
return true;
} else {
// If this is variable message from sensor net gateway. Send ack back.
debug(PSTR("Message addressed for this node.\n"));
// Send set-message back to sender if sender wants this
if (msg.header.messageType == M_SET_WITH_ACK) {
sendVariableAck();
// The library user should not need to care about this ack request. Just treat it as a normal SET.
msg.header.messageType = M_SET_VARIABLE;
}
// Return message to waiting sketch...
if (msg.header.last != GATEWAY_ADDRESS)
addChildRoute(msg.header.from, msg.header.last);
return true;
}
} else {
// We should probably try to relay this message
relayMessage(len-sizeof(msg.header), pipe);
}
}
}
return false;
}
void Relay::relayMessage(uint8_t length, uint8_t pipe) {
uint8_t route = getChildRoute(msg.header.to);
if (route>0 && route<255) {
debug(PSTR("Routing message to child node.\n"));
// 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, length);
} else if (pipe == CURRENT_NODE_PIPE) {
// 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)
debug(PSTR("Routing message to relay.\n"));
// This message should be routed back towards sensor net gateway
sendWrite(relayId, msg, length);
// Add this child to our "routing table" if it not already exist
addChildRoute(msg.header.from, msg.header.last);
} else {
// We're snooped a message directed to gateway from another branch
// Make sure to remove the sender node from our routing table.
//
// +-----B <-- Message comes from here
// -A
// +-----C <-- We're here
//
// So the sender of message should never be in our
// routing table.
//
// We could also end up in this else-statement if message
// destination has no route.
// This can happen if a node never has sent any message to
// sensor net gateway (which will build up routing information in all nodes
// that gets passed).
if (radioId == GATEWAY_ADDRESS) {
// We should perhaps inform gateway about the no-route situation?!
debug(PSTR("Unknown route from GW\n"));
} else {
debug(PSTR("Remove child node from routing table.\n"));
removeChildRoute(msg.header.from);
}
}
}
void Relay::addChildRoute(uint8_t childId, uint8_t route) {
if (childNodeTable[childId] != route) {
childNodeTable[childId] = route;
EEPROM.write(EEPROM_ROUTES_ADDRESS+childId, route);
}
}
void Relay::removeChildRoute(uint8_t childId) {
if (childNodeTable[childId] != 0xff) {
childNodeTable[childId] = 0xff;
EEPROM.write(EEPROM_ROUTES_ADDRESS+childId, 0xff);
}
}
uint8_t Relay::getChildRoute(uint8_t childId) {
return childNodeTable[childId];
}
void Relay::clearChildRoutes() {
debug(PSTR("Clear child routing data\n"));
for (unsigned int i=0;i< sizeof(childNodeTable); i++) {
removeChildRoute(i);
}
sendInternal(I_CHILDREN, "");
}
void Relay::sendChildren() {
// Send in which children that is using this node as an relay.
// TODO: Fix this
debug(PSTR("Dump beginning of routing table\n"));
for (int i=0;i< 50; i++) {
// Serial.println(childNodeTable[i]);
debug(PSTR("rt:%d, %d\n"), i, getChildRoute(i) );
}
sendInternal(I_CHILDREN, "not implemented");
}
-62
View File
@@ -1,62 +0,0 @@
/*
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.
*/
#ifndef Relay_h
#define Relay_h
#include "Sensor.h"
#ifdef DEBUG
#define debug(x,...) debugPrint(x, ##__VA_ARGS__)
#else
#define debug(x,...)
#endif
#define EEPROM_ROUTES_ADDRESS ((uint8_t)3) // Where to start storing routing information in EEPROM. Will allocate 256 bytes.
class Relay : public Sensor
{
public:
/**
* Constructor
*
* Creates a new instance of Relay class.
*
* @param _cepin The pin attached to RF24 Chip Enable on the RF module (default 9)
* @param _cspin The pin attached to RF24 Chip Select (default 10)
*/
Relay(uint8_t _cepin = 9, uint8_t _cspin = 10);
void begin(uint8_t radioId=AUTO, rf24_pa_dbm_e paLevel=RF24_PA_LEVEL, uint8_t channel=RF24_CHANNEL, rf24_datarate_e dataRate=RF24_DATARATE);
boolean messageAvailable();
boolean send(message_s message, int length);
boolean sendData(uint8_t from, uint8_t to, uint8_t childId, uint8_t messageType, uint8_t type, const char *data, uint8_t length, boolean binary);
protected:
void sendChildren();
private:
uint8_t childNodeTable[256]; // Buffer to store child node information. Store this in EEPROM
uint8_t getChildRoute(uint8_t childId);
void addChildRoute(uint8_t childId, uint8_t route);
void removeChildRoute(uint8_t childId);
void clearChildRoutes();
void relayMessage(uint8_t length, uint8_t pipe);
};
#endif
-555
View File
@@ -1,555 +0,0 @@
/*
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 "Sensor.h"
Sensor::Sensor(uint8_t _cepin, uint8_t _cspin) : RF24(_cepin, _cspin) {
isRelay = false;
}
void Sensor::setupRadio(rf24_pa_dbm_e paLevel, uint8_t channel, rf24_datarate_e dataRate) {
failedTransmissions = 0;
// Start up the radio library
RF24::begin();
RF24::setAutoAck(1);
RF24::enableAckPayload();
if (!RF24::isPVariant()) {
debug(PSTR("Sorry, you'll need to use the P version of NRF24L01.\n"));
while(1);
}
RF24::setChannel(channel);
RF24::setPALevel(paLevel);
RF24::setDataRate(dataRate);
RF24::setRetries(5,15);
RF24::setCRCLength(RF24_CRC_16);
RF24::enableDynamicPayloads();
// All repeater nodes and gateway listen to broadcast pipe (for PING messages)
if (isRelay) {
RF24::openReadingPipe(BROADCAST_PIPE, TO_ADDR(BROADCAST_ADDRESS));
}
}
void Sensor::begin(uint8_t _radioId, rf24_pa_dbm_e paLevel, uint8_t channel, rf24_datarate_e dataRate) {
Serial.begin(BAUD_RATE);
radioId = _radioId;
debug(PSTR("Started %s.\n"), isRelay?"relay":"sensor");
setupRadio(paLevel, channel, dataRate);
// 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 (set write pipe to same)
RF24::openReadingPipe(WRITE_PIPE, TO_ADDR(radioId));
RF24::openReadingPipe(CURRENT_NODE_PIPE, TO_ADDR(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, TO_ADDR(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.
// Try to find relay 3 times before giving up
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, TO_ADDR(radioId));
debug(PSTR("Open ping reading pipe: %d\n"), radioId);
}
distance = 255;
uint8_t oldRelayId = relayId;
uint8_t retries=0;
while (distance == 255 && retries<FIND_RELAY_RETRIES) {
// 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);
}
retries++;
}
// Store new relay address in EEPROM
if (relayId != oldRelayId) {
EEPROM.write(EEPROM_RELAY_ID_ADDRESS, relayId);
EEPROM.write(EEPROM_DISTANCE_ADDRESS, distance);
}
}
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) {
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 > FIND_RELAY_AFTER_FAILED_TRANSMISSIONS) {
findRelay();
}
failedTransmissions++;
} else {
failedTransmissions = 0;
}
return ok;
}
boolean Sensor::sendWrite(uint8_t dest, message_s message, int length) {
message.header.last = radioId;
message.header.crc = crc8Message(message, length);
debug(PSTR("Tx: fr=%d,to=%d,la=%d,ne=%d,ci=%d,mt=%d,ty=%d,cr=%d: %s\n"),
message.header.from,message.header.to, message.header.last, dest, message.header.childId, message.header.messageType, message.header.type, message.header.crc, message.data);
bool broadcast = message.header.messageType == M_INTERNAL && message.header.type == I_PING;
RF24::stopListening();
RF24::openWritingPipe(TO_ADDR(dest));
bool ok = RF24::write(&message, min(MAX_MESSAGE_LENGTH, sizeof(message.header) + length), broadcast);
RF24::startListening();
//RF24::closeReadingPipe(WRITE_PIPE); // Stop listening to write-pipe after transmit
//delay(50);
if (ok)
debug(PSTR("Sent successfully\n"));
else
debug(PSTR("Send failed\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::sendSketchInfo(const char *name, const char *version) {
sendSketchName(name);
sendSketchVersion(version);
}
void Sensor::sendSketchName(const char *name) {
if (name != NULL)
sendInternal(I_SKETCH_NAME, name);
}
void Sensor::sendSketchVersion(const char *version) {
if (version != NULL)
sendInternal(I_SKETCH_VERSION, version);
}
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_SET_VARIABLE, variableType);
}
char * Sensor::getStatus(uint8_t nodeId, int8_t childId, uint8_t variableType) {
return get(nodeId, childId, M_REQ_VARIABLE, M_SET_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() {
uint8_t pipe;
boolean available = RF24::available(&pipe);
if (available && pipe<7) {
uint8_t len = RF24::getDynamicPayloadSize();
RF24::read(&msg, len);
RF24::writeAckPayload(pipe,&pipe, 1 );
if (available) {
debug(PSTR("Message available on pipe %d\n"),pipe);
}
uint8_t valid = validate(len-sizeof(header_s));
boolean ok = valid == VALIDATE_OK;
// Make sure string gets terminated ok for full sized messages.
msg.data[len - sizeof(header_s) ] = '\0';
debug(PSTR("Rx: fr=%d,to=%d,la=%d,ci=%d,mt=%d,t=%d,cr=%d(%s): %s\n"),
msg.header.from,msg.header.to, msg.header.last, msg.header.childId, msg.header.messageType, msg.header.type, msg.header.crc, valid==0?"ok":valid==1?"ec":"ev", msg.data);
// boolean ok = readMessage();
if (ok && msg.header.to == radioId) {
// This message is addressed to this node
debug(PSTR("Message addressed for this node.\n"));
// Send set-message back to sender if sender wants this
if (msg.header.messageType == M_SET_WITH_ACK) {
sendVariableAck();
// The library user should not need to care about this ack request. Just treat it as a normal SET.
msg.header.messageType = M_SET_VARIABLE;
}
// 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;
}
/*boolean Sensor::readMessage() {
uint8_t len = RF24::getDynamicPayloadSize();
RF24::read(&msg, len);
uint8_t valid = validate(len-sizeof(header_s));
boolean ok = valid == VALIDATE_OK;
// Make sure string gets terminated ok for full sized messages.
msg.data[len - sizeof(header_s) ] = '\0';
debug(PSTR("Rx: fr=%d,to=%d,la=%d,ci=%d,mt=%d,t=%d,cr=%d(%s): %s\n"),
msg.header.from,msg.header.to, msg.header.last, msg.header.childId, msg.header.messageType, msg.header.type, msg.header.crc, valid==0?"ok":valid==1?"ec":"ev", msg.data);
return ok;
}*/
/*
* calculate CRC8 on message_s data taking care of data structure and protocol version
*/
uint8_t Sensor::crc8Message(message_s var_msg, uint8_t len) {
uint8_t crc = 0x00;
uint8_t loop_count;
uint8_t bit_counter;
uint8_t data;
uint8_t feedback_bit;
uint8_t number_of_bytes_to_read = (uint8_t)sizeof(var_msg);
// Must set crc to a constant value.
var_msg.header.crc = 0;
// fill unused space by zeroes for string data only
if(len>=0 && len < sizeof(var_msg.data)-1) {
memset(&var_msg.data[len], 0, sizeof(var_msg.data) - 1 - len);
}
for (loop_count = 0; loop_count != number_of_bytes_to_read; loop_count++)
{
data = ((uint8_t*)&var_msg)[loop_count];
bit_counter = 8;
do {
feedback_bit = (crc ^ data) & 0x01;
if ( feedback_bit == 0x01 ) {
crc = crc ^ 0x18; //0X18 = X^8+X^5+X^4+X^0
}
crc = (crc >> 1) & 0x7F;
if ( feedback_bit == 0x01 ) {
crc = crc | 0x80;
}
data = data >> 1;
bit_counter--;
} while (bit_counter > 0);
}
return crc;
}
uint8_t Sensor::validate(uint8_t length) {
uint8_t oldCrc = msg.header.crc;
uint8_t newCrc = crc8Message(msg, length);
if(!(msg.header.version == PROTOCOL_VERSION)) return VALIDATE_BAD_VERSION;
if(!(oldCrc == newCrc)) 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) {
// 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.print("0;0;4;11;Mem free:");
//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
-298
View File
@@ -1,298 +0,0 @@
/*
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.
*/
#ifndef Sensor_h
#define Sensor_h
#include "Config.h"
#include "Version.h"
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>
#include <Arduino.h>
#include <stddef.h>
#include <SPI.h>
#include <EEPROM.h>
#include <avr/pgmspace.h>
#include <stdarg.h>
#ifdef DEBUG
#define debug(x,...) debugPrint(x, ##__VA_ARGS__)
#else
#define debug(x,...)
#endif
#define PROTOCOL_VERSION 1
#define BAUD_RATE 115200
#define AUTO 0xFF // 0-254. Id 255 is reserved for auto initialization of radioId.
#define NODE_CHILD_ID 0xFF // Node child id is always created for when a new sensor is detected
#define EEPROM_RADIO_ID_ADDRESS 0 // Where to store radio id in EEPROM
#define EEPROM_RELAY_ID_ADDRESS 1 // Where to store relay id in EEPROM
#define EEPROM_DISTANCE_ADDRESS 2 // Where to store distance to gateway in EEPROM
// This is the radioId for sensor net gateway receiver sketch (where all sensors should send their data).
// This is also act as base value for sensor radioId
#define BASE_RADIO_ID ((uint64_t)0xA8A8E1FC00LL)
#define GATEWAY_ADDRESS ((uint8_t)0)
#define BROADCAST_ADDRESS ((uint8_t)0xFF)
#define TO_ADDR(x) (BASE_RADIO_ID + x)
#define WRITE_PIPE ((uint8_t)0)
#define CURRENT_NODE_PIPE ((uint8_t)1)
#define BROADCAST_PIPE ((uint8_t)2)
#define FIND_RELAY_RETRIES 2
#define FIND_RELAY_AFTER_FAILED_TRANSMISSIONS 20
#define MAX_MESSAGE_LENGTH 32
// Message types
typedef enum {
M_PRESENTATION = 0,
M_SET_VARIABLE = 1,
M_REQ_VARIABLE = 2,
M_SET_WITH_ACK = 3,
M_INTERNAL = 4
} messageType;
// Sensor variables that can be used in sketches
typedef enum {
V_TEMP,V_HUM, V_LIGHT, V_DIMMER, V_PRESSURE, V_FORECAST, V_RAIN,
V_RAINRATE, V_WIND, V_GUST, V_DIRECTION, V_UV, V_WEIGHT, V_DISTANCE,
V_IMPEDANCE, V_ARMED, V_TRIPPED, V_WATT, V_KWH, V_SCENE_ON, V_SCENE_OFF,
V_HEATER, V_HEATER_SW, V_LIGHT_LEVEL, V_VAR1, V_VAR2, V_VAR3, V_VAR4, V_VAR5,
V_UP, V_DOWN, V_STOP, V_IR_SEND, V_IR_RECEIVE, V_FLOW, V_VOLUME, V_LOCK_STATUS
} variableType;
// Internal messages
typedef enum {
I_BATTERY_LEVEL, I_BATTERY_DATE, I_LAST_TRIP, I_TIME, I_VERSION, I_REQUEST_ID,
I_INCLUSION_MODE, I_RELAY_NODE, I_LAST_UPDATE, I_PING, I_PING_ACK,
I_LOG_MESSAGE, I_CHILDREN, I_UNIT, I_SKETCH_NAME, I_SKETCH_VERSION
} internalMessageType;
// Sensor types
typedef enum {
S_DOOR, S_MOTION, S_SMOKE, S_LIGHT, S_DIMMER, S_COVER, S_TEMP, S_HUM, S_BARO, S_WIND,
S_RAIN, S_UV, S_WEIGHT, S_POWER, S_HEATER, S_DISTANCE, S_LIGHT_LEVEL, S_ARDUINO_NODE,
S_ARDUINO_RELAY, S_LOCK, S_IR, S_WATER
} sensor;
// Possible return values by validate() when doing crc check of received message.
enum {
VALIDATE_OK, VALIDATE_BAD_CRC, VALIDATE_BAD_VERSION
};
// The message structure
typedef struct {
uint8_t crc : 8; // 8 bits crc
uint8_t version : 3; // 3 bits protocol version
uint8_t binary : 1; // 1 bit. Data is binary and should be encoded when sent to sensor net gateway
uint8_t from : 8; // 8 bits. RadioId of sender node
uint8_t to : 8; // 8 bits. RadioId of destination node
uint8_t last : 8; // 8 bits. RadioId of last node this message passed
uint8_t childId : 8; // 1 byte. Up to MAX_CHILD_DEVICES child sensors per radioId
uint8_t messageType : 4; // 4 bits. Type of message. See messageType
uint8_t type : 8; // 8 bits. variableType or deviceType depending on messageType
} header_s;
typedef struct {
header_s header;
char data[MAX_MESSAGE_LENGTH - sizeof(header_s) + 1]; // Each message can transfer a payload. Add one extra byte for \0
} message_s;
class Sensor : public RF24
{
public:
/**
* Constructor
*
* Creates a new instance of Sensor class.
*
* @param _cepin The pin attached to RF24 Chip Enable on the RF module (defualt 9)
* @param _cspin The pin attached to RF24 Chip Select (default 10)
*/
Sensor(uint8_t _cepin=9, uint8_t _cspin=10);
/**
* Begin operation of the MySensors library
*
* Call this in setup(), before calling any other sensor net library methods.
* @param radioId The unique id (1-254) for this sensor. Default 255 (auto mode).
* @param paLevel Radio PA Level for this sensor. Default RF24_PA_MAX
* @param channel Radio channel. Default is channel 76
* @param dataRate Radio transmission speed. Default RF24_2MBPS
*/
void begin(uint8_t radioId=AUTO, rf24_pa_dbm_e paLevel=RF24_PA_LEVEL, uint8_t channel=RF24_CHANNEL, rf24_datarate_e dataRate=RF24_DATARATE);
uint8_t getRadioId();
/**
* The arduino node must send a presentation of all the sensors connected before any
* variable changes will be registered on sensor net gateway side.
* Usually it's good to present all sensors when arduino starts up (setup).
* Note that gateway must be in include mode to register new devices.
*
* @param childId The unique child id for the different sensors connected to this arduino. 0-254.
* @param sensorType Sensor types to create. They will be numbered from 0 up to 127.
*/
void sendSensorPresentation(uint8_t childId, uint8_t sensorType);
/**
* Sends a variable change to sensor net gateway (or another sensor if radioId is specified)
*
* @param radioId The radioId of other node in radio network (used for communicating between sensors)
* @param childId The child id for which to update variable. Value can be 0-254.
* @param variableType The variableType to update
* @param value New value of the variable
*/
void sendVariable(uint8_t childId, uint8_t variableType, const char *value);
void sendVariable(uint8_t childId, uint8_t variableType, double value, int decimals);
void sendVariable(uint8_t childId, uint8_t variableType, unsigned long value);
void sendVariable(uint8_t childId, uint8_t variableType, long value);
void sendVariable(uint8_t childId, uint8_t variableType, unsigned int value);
void sendVariable(uint8_t childId, uint8_t variableType, int value);
void sendVariable(uint8_t radioId, uint8_t childId, uint8_t variableType, const char *value);
void sendVariable(uint8_t radioId, uint8_t childId, uint8_t variableType, double value, int decimals);
void sendVariable(uint8_t radioId, uint8_t childId, uint8_t variableType, unsigned long value);
void sendVariable(uint8_t radioId, uint8_t childId, uint8_t variableType, long value);
void sendVariable(uint8_t radioId, uint8_t childId, uint8_t variableType, unsigned int value);
void sendVariable(uint8_t radioId, uint8_t childId, uint8_t variableType, int value);
/**
* Sends sketch info to sensor net gateway for this radio node.
* @param name String containing a short Sketch name
* @param version String containing a short Sketch version
*/
void sendSketchInfo(const char *name, const char *version);
/**
* Sends sketch name to sensor net gateway for this radio node.
* @param info String containing a short Sketch name and version
*/
void sendSketchName(const char *name);
/**
* Sends sketch version to sensor net gateway for this radio node.
* @param info String containing a short Sketch name and version
*/
void sendSketchVersion(const char *version);
/**
* Sends battery status to sensor net gateway for this radio node.
* @param value Set a value between 0-100(%)
*/
void sendBatteryLevel(int value);
/**
* Requests a variable value from sensor net gateway (or another sensor). This method will not wait for an answer.
* You should use mesageAvailable/getMessage to pick up the response.
*
* @param radioId The radioId of other node in radio network (used when fetching from other node)
* @param childId The unique child id for the different sensors connected to this arduino. 0-254.
* @param variableType The variableType to fetch
*/
void requestStatus(uint8_t childId, uint8_t variableType);
void requestStatus(uint8_t radioId, int8_t childId, uint8_t variableType);
/**
* Requests status for a sensor variable from sensor net gateway. Waits until message arrives.
*
* @param radioId The radioId of other node in radio network (used when fetching from other node)
* @param childId The unique child id for the different sensors connected to this arduino. 0-254.
* @param variableType The variableType to fetch
* @return The variable value
*/
char *getStatus(uint8_t childId, uint8_t variableType);
char *getStatus(uint8_t radioId, int8_t childId, uint8_t variableType);
/**
* Fetches time from sensor net gateway
*/
void requestTime();
unsigned long getTime();
/**
* Fetches unit setting from sensor net gateway. Returns true if metric system has been selected which means
* that sensor should report it's information in: celsius, meter, cm, gram, km/h, m/s etc..
* If false is returned the sensor should report data in imperial system which means
* fahrenheit, feet, gallon, mph etc...
*/
void requestIsMetricSystem();
bool isMetricSystem();
/**
* Busy waits until there is a message for this node available to be read
*/
message_s waitForMessage(void);
/**
* Returns true if there is a message addressed for this node is available to be read
*/
boolean messageAvailable(void);
/**
* Returns the last received message
*/
message_s getMessage(void);
/**
* Validates consistency of the message including CRC and protocol version
*/
uint8_t validate(uint8_t length);
#ifdef DEBUG
void debugPrint(const char *fmt, ... );
int freeRam();
#endif
protected:
uint8_t failedTransmissions;
boolean isRelay;
uint8_t radioId;
uint8_t distance; // This nodes distance to sensor net gateway (number of hops)
uint8_t relayId;
message_s msg; // Buffer for incoming messages.
char convBuffer[20];
void setupRadio(rf24_pa_dbm_e paLevel, uint8_t channel, rf24_datarate_e dataRate);
void findRelay();
boolean send(message_s message, int length);
boolean sendWrite(uint8_t dest, message_s message, int length);
//boolean readMessage();
void buildMsg(uint8_t from, uint8_t to, uint8_t childId, uint8_t messageType, uint8_t type, const char *data, uint8_t length, boolean binary);
void sendInternal(uint8_t variableType, const char *value);
boolean sendVariableAck();
boolean sendData(uint8_t from, uint8_t to, uint8_t childId, uint8_t messageType, uint8_t type, const char *data, uint8_t length, boolean binaryMessage);
private:
message_s ack; // Buffer for ack messages.
void initializeRadioId();
uint8_t crc8Message(message_s, uint8_t length);
char* get(uint8_t nodeId, uint8_t childId, uint8_t sendType, uint8_t receiveType, uint8_t variableType);
char *getInternal(uint8_t variableType);
};
#endif
@@ -1,27 +1,31 @@
// Example sketch showing how to control physical relays.
#include <Relay.h>
#include <MyConfig.h>
#include <MyMessage.h>
#include <MySensor.h>
#include <SPI.h>
#include <EEPROM.h>
#include <RF24.h>
#define RELAY_1 3 // Arduino Digital I/O pin number for first relay (second on pin+1 etc)
#define NUMBER_OF_RELAYS 1 // Total number of attached relays
#define RELAY_ON 1 // GPIO value to write to turn on attached relay
#define RELAY_OFF 0 // GPIO value to write to turn off attached relay
Relay gw;
MySensor gw;
void setup()
{
gw.begin();
{
// Initialize library and add callback for incoming messages
gw.begin(incomingMessage);
// Send the sketch version information to the gateway and Controller
gw.sendSketchInfo("Relay", "1.0");
gw.sketchInfo("Relay", "1.0");
// Register all sensors to gw (they will be created as child devices)
for (int i=0; i<NUMBER_OF_RELAYS;i++) {
gw.sendSensorPresentation(RELAY_1+i, S_LIGHT);
gw.present(RELAY_1+i, S_LIGHT);
}
// Fetch relay status
for (int i=0; i<NUMBER_OF_RELAYS;i++) {
@@ -30,35 +34,30 @@ void setup()
// Then set relay pins in output mode
pinMode(RELAY_1+i, OUTPUT);
// Request/wait for relay status
gw.getStatus(RELAY_1+i, V_LIGHT);
setRelayStatus(gw.getMessage()); // Wait here until status message arrive from gw
// Request and wait for relay status from controller
gw.request(RELAY_1+i, V_LIGHT);
}
}
/*
* Example on how to asynchronously check for new messages from gw
*/
void loop()
{
if (gw.messageAvailable()) {
message_s message = gw.getMessage();
setRelayStatus(message);
}
// Process incoming messages
gw.process();
}
void setRelayStatus(message_s message) {
if (message.header.messageType==M_SET_VARIABLE &&
message.header.type==V_LIGHT) {
int incomingRelayStatus = atoi(message.data);
void incomingMessage(MyMessage message) {
// We only expect one type of message from controller. But we better check anyway.
if (message.getType()==V_LIGHT) {
bool status = message.getBool();
// Change relay state
digitalWrite(message.header.childId, incomingRelayStatus==1?RELAY_ON:RELAY_OFF);
digitalWrite(message.getSensor(), status?RELAY_ON:RELAY_OFF);
// Write some debug info
Serial.print("Incoming change for relay on pin:");
Serial.print(message.header.childId);
Serial.print(message.getSensor());
Serial.print(", New status: ");
Serial.println(incomingRelayStatus);
Serial.println(status);
}
}
-3
View File
@@ -677,9 +677,6 @@ void RF24::startFastWrite( const void* buf, uint8_t len, const bool multicast){
//Allows the library to pass all tests
void RF24::startWrite( const void* buf, uint8_t len, const bool multicast ){
// Powerup radio if not already alive.
powerUp();
// Send the payload
//write_payload( buf, len );