Merge branch 'development' into dev-raspberrypi

This commit is contained in:
Marcelo Aquino
2016-08-16 13:42:41 -03:00
1100 changed files with 8306 additions and 162512 deletions
+82
View File
@@ -0,0 +1,82 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Tomas Hozza <thozza@gmail.com>
* Copyright (C) 2015 Tomas Hozza
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyCapabilities_h
#define MyCapabilities_h
#if defined(MY_DISABLE_REMOTE_RESET)
#define MY_CAP_RESET "N"
#else
#define MY_CAP_RESET "R"
#endif
#if defined(MY_OTA_FIRMWARE_FEATURE)
#define MY_CAP_OTA_FW "O"
#else
#define MY_CAP_OTA_FW "N"
#endif
#if defined(MY_RADIO_NRF24)
#define MY_CAP_RADIO "N"
#elif defined(MY_RADIO_RFM69)
#define MY_CAP_RADIO "R"
#elif defined(MY_RS485)
#define MY_CAP_RADIO "S"
#else
#define MY_CAP_RADIO "-"
#endif
#if defined(MY_GATEWAY_FEATURE)
#define MY_CAP_TYPE "G"
#elif defined(MY_REPEATER_FEATURE)
#define MY_CAP_TYPE "R"
#else
#define MY_CAP_TYPE "N"
#endif
#if defined(ARDUINO_ARCH_SAMD)
#define MY_CAP_ARCH "S"
#elif defined(ARDUINO_ARCH_ESP8266)
#define MY_CAP_ARCH "E"
#elif defined(ARDUINO_ARCH_AVR)
#define MY_CAP_ARCH "A"
#else
#define MY_CAP_ARCH "-"
#endif
#if defined(MY_SIGNING_ATSHA204)
#define MY_CAP_SIGN "A"
#elif defined(MY_SIGNING_SOFT)
#define MY_CAP_SIGN "S"
#else
#define MY_CAP_SIGN "-"
#endif
#if defined(MY_RX_MESSAGE_BUFFER_SIZE)
#define MY_CAP_RXBUF "Q"
#else
#define MY_CAP_RXBUF "-"
#endif
#define MY_CAPABILITIES MY_CAP_RESET MY_CAP_RADIO MY_CAP_OTA_FW MY_CAP_TYPE MY_CAP_ARCH MY_CAP_SIGN MY_CAP_RXBUF
#endif /* MyGatewayTransportEthernet_h */
+44
View File
@@ -0,0 +1,44 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyEepromAddresses_h
#define MyEepromAddresses_h
// EEPROM start address for mysensors library data
#define EEPROM_START 0
// EEPROM location of node id
#define EEPROM_NODE_ID_ADDRESS EEPROM_START
// EEPROM location of parent id
#define EEPROM_PARENT_NODE_ID_ADDRESS (EEPROM_START+1)
// EEPROM location of distance to gateway
#define EEPROM_DISTANCE_ADDRESS (EEPROM_PARENT_NODE_ID_ADDRESS+1)
#define EEPROM_ROUTES_ADDRESS (EEPROM_DISTANCE_ADDRESS+1) // Where to start storing routing information in EEPROM. Will allocate 256 bytes.
#define EEPROM_CONTROLLER_CONFIG_ADDRESS (EEPROM_ROUTES_ADDRESS+256) // Location of controller sent configuration (we allow one payload of config data from controller)
#define EEPROM_FIRMWARE_TYPE_ADDRESS (EEPROM_CONTROLLER_CONFIG_ADDRESS+24)
#define EEPROM_FIRMWARE_VERSION_ADDRESS (EEPROM_FIRMWARE_TYPE_ADDRESS+2)
#define EEPROM_FIRMWARE_BLOCKS_ADDRESS (EEPROM_FIRMWARE_VERSION_ADDRESS+2)
#define EEPROM_FIRMWARE_CRC_ADDRESS (EEPROM_FIRMWARE_BLOCKS_ADDRESS+2)
#define EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS (EEPROM_FIRMWARE_CRC_ADDRESS+2)
#define EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS (EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS+32)
#define EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS (EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS+32) // This is set with SecurityPersonalizer.ino
#define EEPROM_SIGNING_SOFT_SERIAL_ADDRESS (EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS+32) // This is set with SecurityPersonalizer.ino
#define EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS (EEPROM_SIGNING_SOFT_SERIAL_ADDRESS+9) // This is set with SecurityPersonalizer.ino
#define EEPROM_NODE_LOCK_COUNTER (EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS+16)
#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_NODE_LOCK_COUNTER+1) // First free address for sketch static configuration
#endif
+65
View File
@@ -0,0 +1,65 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Tomas Hozza <thozza@gmail.com>
* Copyright (C) 2015 Tomas Hozza
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyGatewayTransport.h"
extern bool transportSendRoute(MyMessage &message);
extern MyMessage _msg;
inline void gatewayTransportProcess() {
if (gatewayTransportAvailable()) {
_msg = gatewayTransportReceive();
if (_msg.destination == GATEWAY_ADDRESS) {
// Check if sender requests an ack back.
if (mGetRequestAck(_msg)) {
// Copy message
_msgTmp = _msg;
mSetRequestAck(_msgTmp, false); // Reply without ack flag (otherwise we would end up in an eternal loop)
mSetAck(_msgTmp, true);
_msgTmp.sender = _nc.nodeId;
_msgTmp.destination = _msg.sender;
gatewayTransportSend(_msgTmp);
}
if (mGetCommand(_msg) == C_INTERNAL) {
if (_msg.type == I_VERSION) {
// Request for version. Create the response
gatewayTransportSend(buildGw(_msg, I_VERSION).set(MYSENSORS_LIBRARY_VERSION));
#ifdef MY_INCLUSION_MODE_FEATURE
} else if (_msg.type == I_INCLUSION_MODE) {
// Request to change inclusion mode
inclusionModeSet(atoi(_msg.data) == 1);
#endif
} else {
_processInternalMessages();
}
} else {
// Call incoming message callback if available
if (receive) {
receive(_msg);
}
}
} else {
#if defined(MY_RADIO_FEATURE)
transportSendRoute(_msg);
#endif
}
}
}
+53
View File
@@ -0,0 +1,53 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Tomas Hozza <thozza@gmail.com>
* Copyright (C) 2015 Tomas Hozza
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyGatewayTransport_h
#define MyGatewayTransport_h
#include "MyProtocol.h"
#include "MySensorsCore.h"
// Common gateway functions
void gatewayTransportProcess();
// Gateway "interface" functions
/**
* initialize the driver
*/
bool gatewayTransportInit();
/**
* Send message to controller
*/
bool gatewayTransportSend(MyMessage &message);
/*
* Check if a new message is available from controller
*/
bool gatewayTransportAvailable();
/*
* Pick up last message received from controller
*/
MyMessage& gatewayTransportReceive();
#endif /* MyGatewayTransportEthernet_h */
+390
View File
@@ -0,0 +1,390 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Tomas Hozza <thozza@gmail.com>
* Copyright (C) 2015 Tomas Hozza
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyGatewayTransport.h"
#if defined(MY_CONTROLLER_IP_ADDRESS)
IPAddress _ethernetControllerIP(MY_CONTROLLER_IP_ADDRESS);
#endif
#if defined(MY_IP_ADDRESS)
IPAddress _ethernetGatewayIP(MY_IP_ADDRESS);
#endif
byte _ethernetGatewayMAC[] = { MY_MAC_ADDRESS };
uint16_t _ethernetGatewayPort = MY_PORT;
MyMessage _ethernetMsg;
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
// gatewayTransportSend(buildGw(_msg, I_GATEWAY_READY).set("Gateway startup complete."));
typedef struct
{
char string[MY_GATEWAY_MAX_RECEIVE_LENGTH];
uint8_t idx;
} inputBuffer;
#if defined(MY_GATEWAY_ESP8266)
// Some re-defines to make code more readable below
#define EthernetServer WiFiServer
#define EthernetClient WiFiClient
#define EthernetUDP WiFiUDP
#if defined(MY_IP_ADDRESS)
IPAddress _gatewayIp(MY_IP_GATEWAY_ADDRESS);
IPAddress _subnetIp(MY_IP_SUBNET_ADDRESS);
#endif
static bool clientsConnected[MY_GATEWAY_MAX_CLIENTS];
#endif
#if defined(MY_USE_UDP)
EthernetUDP _ethernetServer;
#else
EthernetServer _ethernetServer(_ethernetGatewayPort);
#endif
#if defined(MY_GATEWAY_ESP8266)
static EthernetClient clients[MY_GATEWAY_MAX_CLIENTS];
static inputBuffer inputString[MY_GATEWAY_MAX_CLIENTS];
#else
static EthernetClient client = EthernetClient();
static inputBuffer inputString;
#endif
#ifndef MY_IP_ADDRESS
void gatewayTransportRenewIP();
#endif
// On W5100 boards with SPI_EN exposed we can use the real SPI bus together with radio
// (if we enable it during usage)
#ifdef MY_W5100_SPI_EN
void _w5100_spi_en(bool enable)
{
if (enable)
{
// Pull up pin
pinMode(MY_W5100_SPI_EN, INPUT);
digitalWrite(MY_W5100_SPI_EN, HIGH);
}
else
{
// Ground pin
pinMode(MY_W5100_SPI_EN, OUTPUT);
digitalWrite(MY_W5100_SPI_EN, LOW);
}
}
#else
#define _w5100_spi_en(x)
#endif
bool gatewayTransportInit() {
_w5100_spi_en(true);
#if defined(MY_GATEWAY_ESP8266)
#if defined(MY_ESP8266_SSID)
// Turn off access point
WiFi.mode (WIFI_STA);
#if defined(MY_ESP8266_HOSTNAME)
WiFi.hostname(MY_ESP8266_HOSTNAME);
#endif
#ifdef MY_IP_ADDRESS
WiFi.config(_ethernetGatewayIP, _gatewayIp, _subnetIp);
#endif
(void)WiFi.begin(MY_ESP8266_SSID, MY_ESP8266_PASSWORD);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
MY_SERIALDEVICE.print(".");
yield();
}
MY_SERIALDEVICE.print(F("IP: "));
MY_SERIALDEVICE.println(WiFi.localIP());
#endif
#else
#ifdef MY_IP_ADDRESS
Ethernet.begin(_ethernetGatewayMAC, _ethernetGatewayIP);
#else
// Get IP address from DHCP
if (!Ethernet.begin(_ethernetGatewayMAC)) {
MY_SERIALDEVICE.print("DHCP FAILURE...");
_w5100_spi_en(false);
return false;
}
#endif
MY_SERIALDEVICE.print(F("IP: "));
MY_SERIALDEVICE.println(Ethernet.localIP());
// give the Ethernet interface a second to initialize
delay(1000);
#endif
#ifdef MY_USE_UDP
_ethernetServer.begin(_ethernetGatewayPort);
#else
// we have to use pointers due to the constructor of EthernetServer
_ethernetServer.begin();
#endif /* USE_UDP */
_w5100_spi_en(false);
return true;
}
bool gatewayTransportSend(MyMessage &message)
{
bool ret = true;
char *_ethernetMsg = protocolFormat(message);
setIndication(INDICATION_GW_TX);
_w5100_spi_en(true);
#if defined(MY_CONTROLLER_IP_ADDRESS)
#if defined(MY_USE_UDP)
_ethernetServer.beginPacket(_ethernetControllerIP, MY_PORT);
_ethernetServer.write(_ethernetMsg, strlen(_ethernetMsg));
// returns 1 if the packet was sent successfully
ret = _ethernetServer.endPacket();
#else
EthernetClient client;
#if defined(MY_CONTROLLER_URL_ADDRESS)
if (client.connected() || client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
#else
if (client.connected() || client.connect(_ethernetControllerIP, MY_PORT)) {
#endif
client.write(_ethernetMsg, strlen(_ethernetMsg));
}
else {
// connecting to the server failed!
ret = false;
}
#endif
#else
// Send message to connected clients
#if defined(MY_GATEWAY_ESP8266)
for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++)
{
if (clients[i] && clients[i].connected())
{
clients[i].write((uint8_t*)_ethernetMsg, strlen(_ethernetMsg));
}
}
#else
_ethernetServer.write(_ethernetMsg);
#endif
#endif
_w5100_spi_en(false);
return ret;
}
#if defined(MY_GATEWAY_ESP8266)
bool _readFromClient(uint8_t i) {
while (clients[i].connected() && clients[i].available()) {
char inChar = clients[i].read();
if (inputString[i].idx < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) {
// if newline then command is complete
if (inChar == '\n' || inChar == '\r') {
// Add string terminator and prepare for the next message
inputString[i].string[inputString[i].idx] = 0;
debug(PSTR("Client %d: %s\n"), i, inputString[i].string);
inputString[i].idx = 0;
if (protocolParse(_ethernetMsg, inputString[i].string)) {
return true;
}
} else {
// add it to the inputString:
inputString[i].string[inputString[i].idx++] = inChar;
}
} else {
// Incoming message too long. Throw away
debug(PSTR("Client %d: Message too long\n"), i);
inputString[i].idx = 0;
// Finished with this client's message. Next loop() we'll see if there's more to read.
break;
}
}
return false;
}
#else
bool _readFromClient() {
while (client.connected() && client.available()) {
char inChar = client.read();
if (inputString.idx < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) {
// if newline then command is complete
if (inChar == '\n' || inChar == '\r') {
// Add string terminator and prepare for the next message
inputString.string[inputString.idx] = 0;
debug(PSTR("Eth: %s\n"), inputString.string);
inputString.idx = 0;
if (protocolParse(_ethernetMsg, inputString.string)) {
return true;
}
} else {
// add it to the inputString:
inputString.string[inputString.idx++] = inChar;
}
} else {
// Incoming message too long. Throw away
debug(PSTR("Eth: Message too long\n"));
inputString.idx = 0;
// Finished with this client's message. Next loop() we'll see if there's more to read.
break;
}
}
return false;
}
#endif
bool gatewayTransportAvailable()
{
_w5100_spi_en(true);
#if !defined(MY_IP_ADDRESS) && defined(MY_GATEWAY_W5100)
// renew IP address using DHCP
gatewayTransportRenewIP();
#endif
#ifdef MY_USE_UDP
int packet_size = _ethernetServer.parsePacket();
if (packet_size) {
//debug(PSTR("UDP packet available. Size:%d\n"), packet_size);
setIndication(INDICATION_GW_RX);
#if defined(MY_GATEWAY_ESP8266)
_ethernetServer.read(inputString[0].string, MY_GATEWAY_MAX_RECEIVE_LENGTH);
inputString[0].string[packet_size] = 0;
debug(PSTR("UDP packet received: %s\n"), inputString[0].string);
return protocolParse(_ethernetMsg, inputString[0].string);
#else
_ethernetServer.read(inputString.string, MY_GATEWAY_MAX_RECEIVE_LENGTH);
inputString.string[packet_size] = 0;
debug(PSTR("UDP packet received: %s\n"), inputString.string);
_w5100_spi_en(false);
return protocolParse(_ethernetMsg, inputString.string);
#endif
}
#else
#if defined(MY_GATEWAY_ESP8266)
// ESP8266: Go over list of clients and stop any that are no longer connected.
// If the server has a new client connection it will be assigned to a free slot.
bool allSlotsOccupied = true;
for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) {
if (!clients[i].connected()) {
if (clientsConnected[i]) {
debug(PSTR("Client %d disconnected\n"), i);
clients[i].stop();
}
//check if there are any new clients
if (_ethernetServer.hasClient()) {
clients[i] = _ethernetServer.available();
inputString[i].idx = 0;
debug(PSTR("Client %d connected\n"), i);
gatewayTransportSend(buildGw(_msg, I_GATEWAY_READY).set("Gateway startup complete."));
// Send presentation of locally attached sensors (and node if applicable)
presentNode();
}
}
bool connected = clients[i].connected();
clientsConnected[i] = connected;
allSlotsOccupied &= connected;
}
if (allSlotsOccupied && _ethernetServer.hasClient()) {
//no free/disconnected spot so reject
debug(PSTR("No free slot available\n"));
EthernetClient c = _ethernetServer.available();
c.stop();
}
// Loop over clients connect and read available data
for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) {
if (_readFromClient(i)) {
setIndication(INDICATION_GW_RX);
_w5100_spi_en(false);
return true;
}
}
#else
// W5100/ENC module does not have hasClient-method. We can only serve one client at the time.
EthernetClient newclient = _ethernetServer.available();
// if a new client connects make sure to dispose any previous existing sockets
if (newclient) {
if (client != newclient) {
client.stop();
client = newclient;
debug(PSTR("Eth: connect\n"));
_w5100_spi_en(false);
gatewayTransportSend(buildGw(_msg, I_GATEWAY_READY).set("Gateway startup complete."));
_w5100_spi_en(true);
presentNode();
}
}
if (client) {
if (!client.connected()) {
debug(PSTR("Eth: disconnect\n"));
client.stop();
} else {
if (_readFromClient()) {
setIndication(INDICATION_GW_RX);
_w5100_spi_en(false);
return true;
}
}
}
#endif
#endif
_w5100_spi_en(false);
return false;
}
MyMessage& gatewayTransportReceive()
{
// Return the last parsed message
return _ethernetMsg;
}
#if !defined(MY_IP_ADDRESS) && !defined(MY_GATEWAY_ESP8266)
void gatewayTransportRenewIP()
{
/* renew/rebind IP address
0 - nothing happened
1 - renew failed
2 - renew success
3 - rebinf failed
4 - rebind success
*/
static unsigned long next_time = hwMillis() + MY_IP_RENEWAL_INTERVAL;
unsigned long now = hwMillis();
// http://playground.arduino.cc/Code/TimingRollover
if ((long)(now - next_time) < 0)
return;
if (Ethernet.maintain() & ~(0x06)) {
debug(PSTR("IP was not renewed correctly\n"));
/* Error occured -> IP was not renewed */
return;
}
_w5100_spi_en(false);
next_time = now + MY_IP_RENEWAL_INTERVAL;
}
#endif /* IP_ADDRESS_DHCP */
+244
View File
@@ -0,0 +1,244 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
// Topic structure: MY_MQTT_PUBLISH_TOPIC_PREFIX/NODE-ID/SENSOR-ID/CMD-TYPE/ACK-FLAG/SUB-TYPE
#include "MyGatewayTransport.h"
#if defined MY_CONTROLLER_IP_ADDRESS
IPAddress _brokerIp(MY_CONTROLLER_IP_ADDRESS);
#endif
#if defined(MY_GATEWAY_ESP8266)
#define EthernetClient WiFiClient
#if defined(MY_IP_ADDRESS)
IPAddress _gatewayIp(MY_IP_GATEWAY_ADDRESS);
IPAddress _subnetIp(MY_IP_SUBNET_ADDRESS);
#endif
#else
byte _MQTT_clientMAC[] = { MY_MAC_ADDRESS };
#endif
#if defined(MY_IP_ADDRESS)
IPAddress _MQTT_clientIp(MY_IP_ADDRESS);
#endif
static EthernetClient _MQTT_ethClient;
static PubSubClient _MQTT_client(_MQTT_ethClient);
static bool _MQTT_connecting = true;
static bool _MQTT_available = false;
static MyMessage _MQTT_msg;
uint8_t protocolH2i(char c) {
uint8_t i = 0;
if (c <= '9')
i += c - '0';
else if (c >= 'a')
i += c - 'a' + 10;
else
i += c - 'A' + 10;
return i;
}
bool gatewayTransportSend(MyMessage &message) {
if (!_MQTT_client.connected())
return false;
setIndication(INDICATION_GW_TX);
char _fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH];
char _convBuffer[MAX_PAYLOAD * 2 + 1];
snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR(MY_MQTT_PUBLISH_TOPIC_PREFIX "/%d/%d/%d/%d/%d"), message.sender, message.sensor, mGetCommand(message), mGetAck(message), message.type);
debug(PSTR("Sending message on topic: %s\n"), _fmtBuffer);
return _MQTT_client.publish(_fmtBuffer, message.getString(_convBuffer));
}
void incomingMQTT(char* topic, byte* payload, unsigned int length) {
debug(PSTR("Message arrived on topic: %s\n"), topic);
char *str, *p;
uint8_t i = 0;
uint8_t bvalue[MAX_PAYLOAD];
uint8_t blen = 0;
uint8_t command = 0;
for (str = strtok_r(topic, "/", &p); str && i <= 5;
str = strtok_r(NULL, "/", &p)) {
switch (i) {
case 0: {
// Topic prefix
if (strcmp(str, MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) != 0) {
// Message not for us or malformed!
return;
}
break;
}
case 1: {
// Node id
_MQTT_msg.destination = atoi(str);
break;
}
case 2: {
// Sensor id
_MQTT_msg.sensor = atoi(str);
break;
}
case 3: {
// Command type
command = atoi(str);
mSetCommand(_MQTT_msg, command);
break;
}
case 4: {
// Ack flag
mSetRequestAck(_MQTT_msg, atoi(str) ? 1 : 0);
break;
}
case 5: {
// Sub type
_MQTT_msg.type = atoi(str);
// Add payload
if (command == C_STREAM) {
blen = 0;
uint8_t val;
while (*payload) {
val = protocolH2i(*payload++) << 4;
val += protocolH2i(*payload++);
bvalue[blen] = val;
blen++;
}
_MQTT_msg.set(bvalue, blen);
}
else {
char* ca;
ca = (char *)payload;
ca += length;
*ca = '\0';
_MQTT_msg.set((const char*)payload);
}
_MQTT_available = true;
}
}
i++;
}
}
bool reconnectMQTT() {
debug(PSTR("Attempting MQTT connection...\n"));
// Attempt to connect
if (_MQTT_client.connect(MY_MQTT_CLIENT_ID
#if defined(MY_MQTT_USER) && defined(MY_MQTT_PASSWORD)
, MY_MQTT_USER, MY_MQTT_PASSWORD
#endif
)) {
debug(PSTR("MQTT connected\n"));
// Send presentation of locally attached sensors (and node if applicable)
presentNode();
// Once connected, publish an announcement...
//_MQTT_client.publish("outTopic","hello world");
// ... and resubscribe
_MQTT_client.subscribe(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX "/+/+/+/+/+");
return true;
}
return false;
}
bool gatewayTransportConnect() {
#if defined(MY_GATEWAY_ESP8266)
while (WiFi.status() != WL_CONNECTED) {
delay(500); // delay calls yield
MY_SERIALDEVICE.print(".");
}
MY_SERIALDEVICE.print("IP: ");
MY_SERIALDEVICE.println(WiFi.localIP());
#else
#ifdef MY_IP_ADDRESS
Ethernet.begin(_MQTT_clientMAC, _MQTT_clientIp);
#else
// Get IP address from DHCP
if (!Ethernet.begin(_MQTT_clientMAC)) {
MY_SERIALDEVICE.print("DHCP FAILURE...");
_MQTT_connecting = false;
return false;
}
#endif
MY_SERIALDEVICE.print("IP: ");
MY_SERIALDEVICE.println(Ethernet.localIP());
// give the Ethernet interface a second to initialize
delay(1000);
#endif
return true;
}
bool gatewayTransportInit() {
_MQTT_connecting = true;
#if defined(MY_CONTROLLER_IP_ADDRESS)
_MQTT_client.setServer(_brokerIp, MY_PORT);
#else
_MQTT_client.setServer(MY_CONTROLLER_URL_ADDRESS, MY_PORT);
#endif
_MQTT_client.setCallback(incomingMQTT);
#if defined(MY_GATEWAY_ESP8266)
// Turn off access point
WiFi.mode(WIFI_STA);
#if defined(MY_ESP8266_HOSTNAME)
WiFi.hostname(MY_ESP8266_HOSTNAME);
#endif
(void)WiFi.begin(MY_ESP8266_SSID, MY_ESP8266_PASSWORD);
#ifdef MY_IP_ADDRESS
WiFi.config(_MQTT_clientIp, _gatewayIp, _subnetIp);
#endif
#endif
gatewayTransportConnect();
_MQTT_connecting = false;
return true;
}
bool gatewayTransportAvailable() {
if (_MQTT_connecting)
return false;
//keep lease on dhcp address
//Ethernet.maintain();
if (!_MQTT_client.connected()) {
//reinitialise client
if (gatewayTransportConnect())
reconnectMQTT();
return false;
}
_MQTT_client.loop();
return _MQTT_available;
}
MyMessage & gatewayTransportReceive() {
// Return the last parsed message
_MQTT_available = false;
return _MQTT_msg;
}
+77
View File
@@ -0,0 +1,77 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyConfig.h"
#include "MyProtocol.h"
#include "MyGatewayTransport.h"
#include "MyMessage.h"
#include "MyProtocol.h"
char _serialInputString[MY_GATEWAY_MAX_RECEIVE_LENGTH]; // A buffer for incoming commands from serial interface
int _serialInputPos;
MyMessage _serialMsg;
bool gatewayTransportSend(MyMessage &message) {
setIndication(INDICATION_GW_TX);
MY_SERIALDEVICE.print(protocolFormat(message));
// Serial print is always successful
return true;
}
bool gatewayTransportInit() {
gatewayTransportSend(buildGw(_msg, I_GATEWAY_READY).set("Gateway startup complete."));
// Send presentation of locally attached sensors (and node if applicable)
presentNode();
return true;
}
bool gatewayTransportAvailable() {
while (MY_SERIALDEVICE.available()) {
// get the new byte:
char inChar = (char) MY_SERIALDEVICE.read();
// if the incoming character is a newline, set a flag
// so the main loop can do something about it:
if (_serialInputPos < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) {
if (inChar == '\n') {
_serialInputString[_serialInputPos] = 0;
bool ok = protocolParse(_serialMsg, _serialInputString);
_serialInputPos = 0;
return ok;
} else {
// add it to the inputString:
_serialInputString[_serialInputPos] = inChar;
_serialInputPos++;
}
} else {
// Incoming message too long. Throw away
_serialInputPos = 0;
}
}
return false;
}
MyMessage & gatewayTransportReceive() {
// Return the last parsed message
return _serialMsg;
}
+114
View File
@@ -0,0 +1,114 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @file MyHw.h
*
* MySensors hardware abstraction layer
*/
#ifndef MyHw_h
#define MyHw_h
#include "MyConfig.h"
#include "MyMessage.h"
#ifdef __cplusplus
#include <Arduino.h>
#endif
// Implement these as functions or macros
/*
#define hwDigitalWrite(__pin, __value)
#define hwInit() MY_SERIALDEVICE.begin(BAUD_RATE)
#define hwWatchdogReset() wdt_reset()
#define hwReboot() wdt_enable(WDTO_15MS); while (1)
#define hwMillis() millis()
void hwReadConfigBlock(void* buf, void* adr, size_t length);
void hwWriteConfigBlock(void* buf, void* adr, size_t length);
void hwWriteConfig(int adr, uint8_t value);
uint8_t hwReadConfig(int adr);
*/
/**
* Sleep for a defined time, using minimum power.
* @param ms Time to sleep, in [ms].
* @return Nonsense, please ignore.
*/
int8_t hwSleep(unsigned long ms);
/**
* Sleep for a defined time, using minimum power, or until woken by interrupt.
* @param interrupt Interrupt number, which can wake the mcu from sleep.
* @param mode Interrupt mode, as passed to attachInterrupt.
* @param ms Time to sleep, in [ms].
* @return -1 when woken by timer, or interrupt number when woken by interrupt.
*/
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms);
/**
* Sleep for a defined time, using minimum power, or until woken by one of the interrupts.
* @param interrupt1 Interrupt1 number, which can wake the mcu from sleep.
* @param mode1 Interrupt1 mode, as passed to attachInterrupt.
* @param interrupt2 Interrupt2 number, which can wake the mcu from sleep.
* @param mode2 Interrupt2 mode, as passed to attachInterrupt.
* @param ms Time to sleep, in [ms].
* @return -1 when woken by timer, or interrupt number when woken by interrupt.
*/
int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms);
#ifdef MY_DEBUG
void hwDebugPrint(const char *fmt, ... );
#endif
/**
* @def MY_CRITICAL_SECTION
* @brief Creates a block of code that is guaranteed to be executed atomically.
* Upon entering the block all interrupts are disabled, and re-enabled upon
* exiting the block from any exit path.
* A typical example that requires atomic access is a 16 (or more) bit variable
* that is shared between the main execution path and an ISR, on an 8-bit
* platform (e.g AVR):
* @code
* volatile uint16_t val = 0;
*
* void interrupHandler()
* {
* val = ~val;
* }
*
* void loop()
* {
* uint16_t copy_val;
* MY_CRITICAL_SECTION
* {
* copy_val = val;
* }
* }
* @endcode
* All code within the MY_CRITICAL_SECTION block will be protected from being
* interrupted during execution.
*/
#ifdef DOXYGEN
#define MY_CRITICAL_SECTION
#endif /* DOXYGEN */
#endif // #ifdef MyHw_h
+226
View File
@@ -0,0 +1,226 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifdef ARDUINO_ARCH_AVR
#include "MyHwATMega328.h"
#define INVALID_INTERRUPT_NUM (0xFFu)
volatile uint8_t _wokeUpByInterrupt = INVALID_INTERRUPT_NUM; // Interrupt number that woke the mcu.
volatile uint8_t _wakeUp1Interrupt = INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp1-callback.
volatile uint8_t _wakeUp2Interrupt = INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp2-callback.
void wakeUp1() //place to send the interrupts
{
detachInterrupt(_wakeUp1Interrupt);
_wokeUpByInterrupt = _wakeUp1Interrupt;
}
void wakeUp2() //place to send the second interrupts
{
detachInterrupt(_wakeUp2Interrupt);
_wokeUpByInterrupt = _wakeUp2Interrupt;
}
bool interruptWakeUp()
{
return _wokeUpByInterrupt != INVALID_INTERRUPT_NUM;
}
// Watchdog Timer interrupt service routine. This routine is required
// to allow automatic WDIF and WDIE bit clearance in hardware.
ISR (WDT_vect)
{
}
void hwPowerDown(period_t period) {
// disable ADC for power saving
ADCSRA &= ~(1 << ADEN);
// save WDT settings
uint8_t WDTsave = WDTCSR;
if (period != SLEEP_FOREVER)
{
wdt_enable(period);
// enable WDT interrupt before system reset
WDTCSR |= (1 << WDCE) | (1 << WDIE);
} else {
// if sleeping forever, disable WDT
wdt_disable();
}
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
cli();
sleep_enable();
#if defined __AVR_ATmega328P__
sleep_bod_disable();
#endif
// Enable interrupts & sleep until WDT or ext. interrupt
sei();
// Directly sleep CPU, to prevent race conditions! (see chapter 7.7 of ATMega328P datasheet)
sleep_cpu();
sleep_disable();
// restore previous WDT settings
cli();
wdt_reset();
// enable WDT changes
WDTCSR |= (1 << WDCE) | (1 << WDE);
// restore saved WDT settings
WDTCSR = WDTsave;
sei();
// enable ADC
ADCSRA |= (1 << ADEN);
}
void hwInternalSleep(unsigned long ms) {
// Let serial prints finish (debug, log etc)
#ifndef MY_DISABLED_SERIAL
MY_SERIALDEVICE.flush();
#endif
while (!interruptWakeUp() && ms >= 8000) { hwPowerDown(SLEEP_8S); ms -= 8000; }
if (!interruptWakeUp() && ms >= 4000) { hwPowerDown(SLEEP_4S); ms -= 4000; }
if (!interruptWakeUp() && ms >= 2000) { hwPowerDown(SLEEP_2S); ms -= 2000; }
if (!interruptWakeUp() && ms >= 1000) { hwPowerDown(SLEEP_1S); ms -= 1000; }
if (!interruptWakeUp() && ms >= 500) { hwPowerDown(SLEEP_500MS); ms -= 500; }
if (!interruptWakeUp() && ms >= 250) { hwPowerDown(SLEEP_250MS); ms -= 250; }
if (!interruptWakeUp() && ms >= 125) { hwPowerDown(SLEEP_120MS); ms -= 120; }
if (!interruptWakeUp() && ms >= 64) { hwPowerDown(SLEEP_60MS); ms -= 60; }
if (!interruptWakeUp() && ms >= 32) { hwPowerDown(SLEEP_30MS); ms -= 30; }
if (!interruptWakeUp() && ms >= 16) { hwPowerDown(SLEEP_15MS); ms -= 15; }
}
int8_t hwSleep(unsigned long ms) {
hwInternalSleep(ms);
return -1;
}
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
return hwSleep(interrupt,mode,INVALID_INTERRUPT_NUM,0u,ms);
}
int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) {
// Disable interrupts until going to sleep, otherwise interrupts occurring between attachInterrupt()
// and sleep might cause the ATMega to not wakeup from sleep as interrupt has already be handled!
cli();
// attach interrupts
_wakeUp1Interrupt = interrupt1;
_wakeUp2Interrupt = interrupt2;
if (interrupt1 != INVALID_INTERRUPT_NUM) attachInterrupt(interrupt1, wakeUp1, mode1);
if (interrupt2 != INVALID_INTERRUPT_NUM) attachInterrupt(interrupt2, wakeUp2, mode2);
if (ms>0) {
// sleep for defined time
hwInternalSleep(ms);
} else {
// sleep until ext interrupt triggered
hwPowerDown(SLEEP_FOREVER);
}
// Return what woke the mcu.
int ret = -1; // default: no interrupt triggered, timer wake up
if (interruptWakeUp()) ret = static_cast<int8_t>(_wokeUpByInterrupt);
// Clear woke-up-by-interrupt flag, so next sleeps won't return immediately.
_wokeUpByInterrupt = INVALID_INTERRUPT_NUM;
return ret;
}
uint16_t hwCPUVoltage() {
// Measure Vcc against 1.1V Vref
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = (_BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1));
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = (_BV(MUX5) | _BV(MUX0));
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = (_BV(MUX3) | _BV(MUX2));
#else
ADMUX = (_BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1));
#endif
// Vref settle
delay(70);
// Do conversion
ADCSRA |= _BV(ADSC);
while (bit_is_set(ADCSRA,ADSC));
// return Vcc in mV
return (1125300UL) / ADC;
}
uint16_t hwCPUFrequency() {
cli();
// setup timer1
TIFR1 = 0xFF;
TCNT1 = 0;
TCCR1A = 0;
TCCR1C = 0;
// save WDT settings
uint8_t WDTsave = WDTCSR;
wdt_enable(WDTO_500MS);
// enable WDT interrupt mode => first timeout WDIF, 2nd timeout reset
WDTCSR |= (1 << WDIE);
wdt_reset();
// start timer1 with 1024 prescaling
TCCR1B = _BV(CS12) | _BV(CS10);
// wait until wdt interrupt
while (bit_is_clear(WDTCSR,WDIF));
// stop timer
TCCR1B = 0;
// restore WDT settings
wdt_reset();
WDTCSR |= (1 << WDCE) | (1 << WDE);
WDTCSR = WDTsave;
sei();
// return frequency in 1/10MHz (accuracy +- 10%)
return TCNT1 * 2048UL / 100000UL;
}
uint16_t hwFreeMem() {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
#ifdef MY_DEBUG
void hwDebugPrint(const char *fmt, ... ) {
char fmtBuffer[300];
#ifdef MY_GATEWAY_FEATURE
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
snprintf_P(fmtBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
MY_SERIALDEVICE.print(fmtBuffer);
#endif
va_list args;
va_start (args, fmt );
va_end (args);
#ifdef MY_GATEWAY_FEATURE
// Truncate message if this is gateway node
vsnprintf_P(fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, fmt, args);
fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH-1] = '\n';
fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH] = '\0';
#else
vsnprintf_P(fmtBuffer, 299, fmt, args);
#endif
va_end (args);
MY_SERIALDEVICE.print(fmtBuffer);
MY_SERIALDEVICE.flush();
//MY_SERIALDEVICE.write(freeRam());
}
#endif
#endif // #ifdef ARDUINO_ARCH_AVR
+99
View File
@@ -0,0 +1,99 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyHwATMega328_h
#define MyHwATMega328_h
#include "MyHw.h"
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/atomic.h>
#ifdef __cplusplus
#include <Arduino.h>
#endif
#define MY_SERIALDEVICE Serial
#if defined __AVR_ATmega328P__
#ifndef sleep_bod_disable
#define sleep_bod_disable() \
do { \
unsigned char tempreg; \
__asm__ __volatile__("in %[tempreg], %[mcucr]" "\n\t" \
"ori %[tempreg], %[bods_bodse]" "\n\t" \
"out %[mcucr], %[tempreg]" "\n\t" \
"andi %[tempreg], %[not_bodse]" "\n\t" \
"out %[mcucr], %[tempreg]" \
: [tempreg] "=&d" (tempreg) \
: [mcucr] "I" _SFR_IO_ADDR(MCUCR), \
[bods_bodse] "i" (_BV(BODS) | _BV(BODSE)), \
[not_bodse] "i" (~_BV(BODSE))); \
} while (0)
#endif
#endif
// Define these as macros to save valuable space
#define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value))
#define hwInit() MY_SERIALDEVICE.begin(MY_BAUD_RATE)
#define hwWatchdogReset() wdt_reset()
#define hwReboot() wdt_enable(WDTO_15MS); while (1)
#define hwMillis() millis()
#define hwReadConfig(__pos) (eeprom_read_byte((uint8_t*)(__pos)))
#ifndef eeprom_update_byte
#define hwWriteConfig(loc, val) if((uint8_t)(val) != eeprom_read_byte((uint8_t*)(loc))) { eeprom_write_byte((uint8_t*)(loc), (val)); }
#else
#define hwWriteConfig(__pos, __value) (eeprom_update_byte((uint8_t*)(__pos), (__value)))
#endif
//
#define hwReadConfigBlock(__buf, __pos, __length) (eeprom_read_block((__buf), (void*)(__pos), (__length)))
#define hwWriteConfigBlock(__pos, __buf, __length) (eeprom_write_block((void*)(__pos), (void*)__buf, (__length)))
enum period_t
{
SLEEP_15MS,
SLEEP_30MS,
SLEEP_60MS,
SLEEP_120MS,
SLEEP_250MS,
SLEEP_500MS,
SLEEP_1S,
SLEEP_2S,
SLEEP_4S,
SLEEP_8S,
SLEEP_FOREVER
};
void hwInternalSleep(unsigned long ms);
#ifndef DOXYGEN
#define MY_CRITICAL_SECTION ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
#endif /* DOXYGEN */
#endif
+158
View File
@@ -0,0 +1,158 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyHwESP8266.h"
#include <EEPROM.h>
/*
int8_t pinIntTrigger = 0;
void wakeUp() //place to send the interrupts
{
pinIntTrigger = 1;
}
void wakeUp2() //place to send the second interrupts
{
pinIntTrigger = 2;
}
// Watchdog Timer interrupt service routine. This routine is required
// to allow automatic WDIF and WDIE bit clearance in hardware.
ISR (WDT_vect)
{
// WDIE & WDIF is cleared in hardware upon entering this ISR
wdt_disable();
}
*/
static void hwInitConfigBlock( size_t length = 1024 /*ATMega328 has 1024 bytes*/ )
{
static bool initDone = false;
if (!initDone)
{
EEPROM.begin(length);
initDone = true;
}
}
void hwReadConfigBlock(void* buf, void* adr, size_t length)
{
hwInitConfigBlock();
uint8_t* dst = static_cast<uint8_t*>(buf);
int offs = reinterpret_cast<int>(adr);
while (length-- > 0)
{
*dst++ = EEPROM.read(offs++);
}
}
void hwWriteConfigBlock(void* buf, void* adr, size_t length)
{
hwInitConfigBlock();
uint8_t* src = static_cast<uint8_t*>(buf);
int offs = reinterpret_cast<int>(adr);
while (length-- > 0)
{
EEPROM.write(offs++, *src++);
}
EEPROM.commit();
}
uint8_t hwReadConfig(int adr)
{
uint8_t value;
hwReadConfigBlock(&value, reinterpret_cast<void*>(adr), 1);
return value;
}
void hwWriteConfig(int adr, uint8_t value)
{
uint8_t curr = hwReadConfig(adr);
if (curr != value)
{
hwWriteConfigBlock(&value, reinterpret_cast<void*>(adr), 1);
}
}
int8_t hwSleep(unsigned long ms) {
// TODO: Not supported!
(void)ms;
return -2;
}
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
// TODO: Not supported!
(void)interrupt;
(void)mode;
(void)ms;
return -2;
}
int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) {
// TODO: Not supported!
(void)interrupt1;
(void)mode1;
(void)interrupt2;
(void)mode2;
(void)ms;
return -2;
}
ADC_MODE(ADC_VCC);
uint16_t hwCPUVoltage() {
// in mV
return ESP.getVcc();
}
uint16_t hwCPUFrequency() {
// in 1/10Mhz
return ESP.getCpuFreqMHz()*10;
}
uint16_t hwFreeMem() {
return ESP.getFreeHeap();
}
#ifdef MY_DEBUG
void hwDebugPrint(const char *fmt, ... ) {
char fmtBuffer[300];
#ifdef MY_GATEWAY_FEATURE
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
snprintf_P(fmtBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
MY_SERIALDEVICE.print(fmtBuffer);
#endif
va_list args;
va_start (args, fmt );
va_end (args);
#ifdef MY_GATEWAY_FEATURE
// Truncate message if this is gateway node
vsnprintf_P(fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, fmt, args);
fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH-1] = '\n';
fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH] = '\0';
#else
vsnprintf_P(fmtBuffer, 299, fmt, args);
#endif
va_end (args);
MY_SERIALDEVICE.print(fmtBuffer);
MY_SERIALDEVICE.flush();
//MY_SERIALDEVICE.write(freeRam());
}
#endif
+59
View File
@@ -0,0 +1,59 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyHwESP8266_h
#define MyHwESP8266_h
#include "MyHw.h"
#ifdef __cplusplus
#include <Arduino.h>
#endif
#define MY_SERIALDEVICE Serial
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
// Define these as macros to save valuable space
#define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value))
#define hwInit() MY_SERIALDEVICE.begin(MY_BAUD_RATE); MY_SERIALDEVICE.setDebugOutput(true)
#define hwWatchdogReset() wdt_reset()
#define hwReboot() ESP.restart();
#define hwMillis() millis()
void hwReadConfigBlock(void* buf, void* adr, size_t length);
void hwWriteConfigBlock(void* buf, void* adr, size_t length);
void hwWriteConfig(int adr, uint8_t value);
uint8_t hwReadConfig(int adr);
/**
* Restore interrupt state.
* Helper function for MY_CRITICAL_SECTION.
*/
static __inline__ void __psRestore(const uint32_t *__s)
{
xt_wsr_ps( *__s );
}
#ifndef DOXYGEN
#define MY_CRITICAL_SECTION for ( uint32_t __psSaved __attribute__((__cleanup__(__psRestore))) = xt_rsil(15), __ToDo = 1; __ToDo ; __ToDo = 0 )
#endif /* DOXYGEN */
#endif // #ifdef ARDUINO_ARCH_ESP8266
+183
View File
@@ -0,0 +1,183 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyHwSAMD.h"
/*
int8_t pinIntTrigger = 0;
void wakeUp() //place to send the interrupts
{
pinIntTrigger = 1;
}
void wakeUp2() //place to send the second interrupts
{
pinIntTrigger = 2;
}
// Watchdog Timer interrupt service routine. This routine is required
// to allow automatic WDIF and WDIE bit clearance in hardware.
ISR (WDT_vect)
{
// WDIE & WDIF is cleared in hardware upon entering this ISR
wdt_disable();
}
*/
void i2c_eeprom_write_byte(unsigned int eeaddress, byte data ) {
int rdata = data;
Wire.beginTransmission(I2C_EEP_ADDRESS);
Wire.write((int)(eeaddress >> 8)); // MSB
Wire.write((int)(eeaddress & 0xFF)); // LSB
Wire.write(rdata);
Wire.endTransmission();
}
byte i2c_eeprom_read_byte(unsigned int eeaddress ) {
byte rdata = 0xFF;
Wire.beginTransmission(I2C_EEP_ADDRESS);
Wire.write((int)(eeaddress >> 8)); // MSB
Wire.write((int)(eeaddress & 0xFF)); // LSB
Wire.endTransmission();
Wire.requestFrom(I2C_EEP_ADDRESS,1);
if (Wire.available()) rdata = Wire.read();
return rdata;
}
void hwReadConfigBlock(void* buf, void* adr, size_t length)
{
uint8_t* dst = static_cast<uint8_t*>(buf);
int offs = reinterpret_cast<int>(adr);
while (length-- > 0)
{
*dst++ = i2c_eeprom_read_byte(offs++);
}
}
void hwWriteConfigBlock(void* buf, void* adr, size_t length)
{
uint8_t* src = static_cast<uint8_t*>(buf);
int offs = reinterpret_cast<int>(adr);
while (length-- > 0)
{
i2c_eeprom_write_byte(offs++, *src++);
}
}
uint8_t hwReadConfig(int adr)
{
uint8_t value;
hwReadConfigBlock(&value, reinterpret_cast<void*>(adr), 1);
return value;
}
void hwWriteConfig(int adr, uint8_t value)
{
uint8_t curr = hwReadConfig(adr);
if (curr != value)
{
hwWriteConfigBlock(&value, reinterpret_cast<void*>(adr), 1);
}
}
void hwInit() {
MY_SERIALDEVICE.begin(MY_BAUD_RATE);
#if defined(MY_GATEWAY_SERIAL)
while (!MY_SERIALDEVICE) {}
#endif
Wire.begin();
}
void hwWatchdogReset() {
// TODO: Not supported!
}
void hwReboot() {
// TODO: Not supported!
}
int8_t hwSleep(unsigned long ms) {
// TODO: Not supported!
(void)ms;
return -2;
}
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
// TODO: Not supported!
(void)interrupt;
(void)mode;
(void)ms;
return -2;
}
int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) {
// TODO: Not supported!
(void)interrupt1;
(void)mode1;
(void)interrupt2;
(void)mode2;
(void)ms;
return -2;
}
uint16_t hwCPUVoltage() {
// TODO: Not supported!
return 0;
}
uint16_t hwCPUFrequency() {
// TODO: Not supported!
return 0;
}
uint16_t hwFreeMem() {
// TODO: Not supported!
return 0;
}
#ifdef MY_DEBUG
void hwDebugPrint(const char *fmt, ... ) {
if (MY_SERIALDEVICE) {
char fmtBuffer[300];
#ifdef MY_GATEWAY_FEATURE
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
snprintf(fmtBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
MY_SERIALDEVICE.print(fmtBuffer);
#endif
va_list args;
va_start (args, fmt );
va_end (args);
#ifdef MY_GATEWAY_FEATURE
// Truncate message if this is gateway node
vsnprintf(fmtBuffer, 60, fmt, args);
fmtBuffer[59] = '\n';
fmtBuffer[60] = '\0';
#else
vsnprintf(fmtBuffer, 299, fmt, args);
#endif
va_end (args);
MY_SERIALDEVICE.print(fmtBuffer);
// MY_SERIALDEVICE.flush();
//MY_SERIALDEVICE.write(freeRam());
}
}
#endif
+85
View File
@@ -0,0 +1,85 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyHwSAMD_h
#define MyHwSAMD_h
#include "MyHw.h"
#include <Wire.h>
#ifdef __cplusplus
#include <Arduino.h>
//#include <SPI.h>
#endif
#include <avr/dtostrf.h>
#define I2C_EEP_ADDRESS 0x50
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
#define snprintf_P(s, f, ...) snprintf((s), (f), __VA_ARGS__)
// Define these as macros to save valuable space
uint8_t configBlock[1024];
#define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value))
void hwInit();
void hwWatchdogReset();
void hwReboot();
#define hwMillis() millis()
void hwReadConfigBlock(void* buf, void* adr, size_t length);
void hwWriteConfigBlock(void* buf, void* adr, size_t length);
void hwWriteConfig(int adr, uint8_t value);
uint8_t hwReadConfig(int adr);
#define MY_SERIALDEVICE SerialUSB
/*
#define hwReadConfigBlock(__buf, __adr, __length) ( __length = __length)
#define hwWriteConfigBlock(__buf, __adr, __length) ( __length = __length)
#define hwWriteConfig(__adr, __value) ( __value = __value)
#define hwReadConfig(__adr) (0)
*/
/**
* Disable all interrupts.
* Helper function for MY_CRITICAL_SECTION.
*/
static __inline__ uint8_t __disableIntsRetVal(void)
{
__disable_irq();
return 1;
}
/**
* Restore priority mask register.
* Helper function for MY_CRITICAL_SECTION.
*/
static __inline__ void __priMaskRestore(const uint32_t *priMask)
{
__set_PRIMASK(*priMask);
}
#ifndef DOXYGEN
#define MY_CRITICAL_SECTION for ( uint32_t __savePriMask __attribute__((__cleanup__(__priMaskRestore))) = __get_PRIMASK(), __ToDo = __disableIntsRetVal(); __ToDo ; __ToDo = 0 )
#endif /* DOXYGEN */
#endif // #ifdef ARDUINO_ARCH_SAMD
+59
View File
@@ -0,0 +1,59 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyInclusionMode.h"
unsigned long _inclusionStartTime;
bool _inclusionMode;
inline void inclusionInit() {
_inclusionMode = false;
#if defined(MY_INCLUSION_BUTTON_FEATURE)
// Setup digital in that triggers inclusion mode
pinMode(MY_INCLUSION_MODE_BUTTON_PIN, INPUT);
digitalWrite(MY_INCLUSION_MODE_BUTTON_PIN, HIGH);
#endif
}
void inclusionModeSet(bool newMode) {
if (newMode != _inclusionMode) {
_inclusionMode = newMode;
// Send back mode change to controller
gatewayTransportSend(buildGw(_msg, I_INCLUSION_MODE).set((uint8_t)(_inclusionMode?1:0)));
if (_inclusionMode) {
_inclusionStartTime = hwMillis();
}
}
}
inline void inclusionProcess() {
#ifdef MY_INCLUSION_BUTTON_FEATURE
if (!_inclusionMode && digitalRead(MY_INCLUSION_MODE_BUTTON_PIN) == MY_INCLUSION_BUTTON_PRESSED) {
// Start inclusion mode
inclusionModeSet(true);
}
#endif
if (_inclusionMode && hwMillis()-_inclusionStartTime>MY_INCLUSION_MODE_DURATION*1000L) {
// inclusionTimeInMinutes minute(s) has passed.. stop inclusion mode
inclusionModeSet(false);
}
}
+33
View File
@@ -0,0 +1,33 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyInclusionMode_h
#define MyInclusionMode_h
#include "MySensorsCore.h"
extern bool gatewayTransportSend(MyMessage &message);
void inclusionInit();
void inclusionModeSet(bool newMode);
void inclusionProcess();
#endif
+42
View File
@@ -0,0 +1,42 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2016 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyIndication.h"
#ifdef MY_LEDS_BLINKING_FEATURE
#include "MyLeds.h"
#endif
void setIndication( const indication_t ind )
{
#ifdef MY_LEDS_BLINKING_FEATURE
if ((INDICATION_TX == ind) || (INDICATION_GW_TX == ind))
{
ledsBlinkTx(1);
} else if ((INDICATION_RX == ind) || (INDICATION_GW_RX == ind))
{
ledsBlinkRx(1);
} else if (ind > INDICATION_ERR_START)
{
// Number of blinks indicates which error occurred.
ledsBlinkErr(ind-INDICATION_ERR_START);
}
#endif
if (indication)
indication(ind);
}
+75
View File
@@ -0,0 +1,75 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2016 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyIndication_h
#define MyIndication_h
/**
* Indication type
*/
typedef enum {
INDICATION_TX = 0, //!< Sent a message.
INDICATION_RX, //!< Received a message.
INDICATION_GW_TX, //!< Gateway transmit message.
INDICATION_GW_RX, //!< Gateway receive message.
INDICATION_FIND_PARENT, //!< Start finding parent node.
INDICATION_GOT_PARENT, //!< Found parent node.
INDICATION_REQ_NODEID, //!< Request node ID.
INDICATION_GOT_NODEID, //!< Got a node ID.
INDICATION_REQ_REGISTRATION, //!< Request node registration.
INDICATION_GOT_REGISTRATION, //!< Got registration reponse.
INDICATION_REBOOT, //!< Rebooting node.
INDICATION_PRESENT, //!< Presenting node to gateway.
INDICATION_CLEAR_ROUTING, //!< Clear rrouting table requested.
INDICATION_SLEEP, //!< Node goes to sleep.
INDICATION_WAKEUP, //!< Node just woke from sleep.
INDICATION_FW_UPDATE_START, //!< Start of OTA firmware update process.
INDICATION_FW_UPDATE_RX, //!< Received a piece of firmware data.
INDICATION_ERR_START = 100,
INDICATION_ERR_TX, //!< Failed to transmit message.
INDICATION_ERR_TRANSPORT_FAILURE, //!< Transport failure.
INDICATION_ERR_INIT_TRANSPORT, //!< MySensors transport hardware (radio) init failure.
INDICATION_ERR_FIND_PARENT, //!< Failed to find parent node.
INDICATION_ERR_GET_NODEID, //!< Failed to receive node ID.
INDICATION_ERR_SIGN, //!< Error signing.
INDICATION_ERR_VERSION, //!< Protocol version mismatch.
INDICATION_ERR_NET_FULL, //!< Network full. All node ID's are taken.
INDICATION_ERR_INIT_GWTRANSPORT, //!< Gateway transport hardware init failure.
INDICATION_ERR_LOCKED, //!< Node is locked.
INDICATION_ERR_FW_FLASH_INIT, //!< Firmware update flash initialisation failure.
INDICATION_ERR_FW_TIMEOUT, //!< Firmware update timeout.
INDICATION_ERR_FW_CHECKSUM, //!< Firmware update checksum mismatch.
INDICATION_ERR_END
} indication_t;
/**
* Function which is called when something changes about the internal state of MySensors.
* @param ind Event indication of what happened.
*/
void setIndication( const indication_t ind );
/**
* Allow user to define their own indication handler.
*/
void indication( const indication_t ) __attribute__((weak));
#endif
+82
View File
@@ -0,0 +1,82 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyLeds.h"
#define LED_ON_OFF_RATIO (4) // Power of 2 please
#define LED_PROCESS_INTERVAL_MS (MY_DEFAULT_LED_BLINK_PERIOD/LED_ON_OFF_RATIO)
// these variables don't need to be volatile, since we are not using interrupts
static uint8_t countRx;
static uint8_t countTx;
static uint8_t countErr;
static unsigned long prevTime = hwMillis() - LED_PROCESS_INTERVAL_MS; // Substract some, to make sure leds gets updated on first run.
inline void ledsInit()
{
// initialize counters
countRx = 0;
countTx = 0;
countErr = 0;
// Setup led pins
pinMode(MY_DEFAULT_RX_LED_PIN, OUTPUT);
pinMode(MY_DEFAULT_TX_LED_PIN, OUTPUT);
pinMode(MY_DEFAULT_ERR_LED_PIN, OUTPUT);
ledsProcess();
}
void ledsProcess() {
// Just return if it is not the time...
if ((hwMillis() - prevTime) < LED_PROCESS_INTERVAL_MS)
return;
prevTime += LED_PROCESS_INTERVAL_MS;
uint8_t state;
// For an On/Off ratio of 4, the pattern repeated will be [on, on, on, off]
// until the counter becomes 0.
state = (countRx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
hwDigitalWrite(MY_DEFAULT_RX_LED_PIN, state);
if (countRx) --countRx;
state = (countTx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
hwDigitalWrite(MY_DEFAULT_TX_LED_PIN, state);
if (countTx) --countTx;
state = (countErr & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
hwDigitalWrite(MY_DEFAULT_ERR_LED_PIN, state);
if (countErr) --countErr;
}
void ledsBlinkRx(uint8_t cnt) {
if (!countRx) { countRx = cnt*LED_ON_OFF_RATIO; }
}
void ledsBlinkTx(uint8_t cnt) {
if(!countTx) { countTx = cnt*LED_ON_OFF_RATIO; }
}
void ledsBlinkErr(uint8_t cnt) {
if(!countErr) { countErr = cnt*LED_ON_OFF_RATIO; }
}
+54
View File
@@ -0,0 +1,54 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyLeds_h
#define MyLeds_h
#ifdef MY_WITH_LEDS_BLINKING_INVERSE
#define LED_ON 0x1
#define LED_OFF 0x0
#else
#define LED_ON 0x0
#define LED_OFF 0x1
#endif
#ifdef MY_LEDS_BLINKING_FEATURE
#define ledBlinkTx(x,...) ledsBlinkTx(x)
#define ledBlinkRx(x,...) ledsBlinkRx(x)
#define ledBlinkErr(x,...) ledsBlinkErr(x)
/**
* Blink with LEDs
* @param cnt how many blink cycles to keep the LED on. Default cycle is 300ms
*/
void ledsInit();
void ledsBlinkRx(uint8_t cnt);
void ledsBlinkTx(uint8_t cnt);
void ledsBlinkErr(uint8_t cnt);
void ledsProcess(); // do the actual blinking
#else
// Remove led functions if feature is disabled
#define ledBlinkTx(x,...)
#define ledBlinkRx(x,...)
#define ledBlinkErr(x,...)
#endif
#endif
+19
View File
@@ -0,0 +1,19 @@
// Initialize library and handle sketch functions like we want to
int main(void) {
init();
#if defined(USBCON)
#if defined(ARDUINO_ARCH_SAMD)
USBDevice.init();
#endif
USBDevice.attach();
#endif
_begin(); // Startup MySensors library
for(;;) {
_process(); // Process incoming data
if (loop) loop(); // Call sketch loop
if (serialEventRun) serialEventRun();
}
return 0;
}
+147
View File
@@ -0,0 +1,147 @@
//This may be used to change user task stack size:
//#define CONT_STACKSIZE 4096
#include "Schedule.h"
extern "C" {
#include "ets_sys.h"
#include "os_type.h"
#include "osapi.h"
#include "mem.h"
#include "user_interface.h"
#include "cont.h"
}
#include <core_version.h>
#define LOOP_TASK_PRIORITY 1
#define LOOP_QUEUE_SIZE 1
#define OPTIMISTIC_YIELD_TIME_US 16000
struct rst_info resetInfo;
extern "C" {
extern const uint32_t __attribute__((section(".ver_number"))) core_version = ARDUINO_ESP8266_GIT_VER;
const char* core_release =
#ifdef ARDUINO_ESP8266_RELEASE
ARDUINO_ESP8266_RELEASE;
#else
NULL;
#endif
} // extern "C"
int atexit(void(*func)()) {
(void)func;
return 0;
}
extern "C" void ets_update_cpu_frequency(int freqmhz);
void initVariant() __attribute__((weak));
void initVariant() {
}
void preloop_update_frequency() __attribute__((weak));
void preloop_update_frequency() {
#if defined(F_CPU) && (F_CPU == 160000000L)
REG_SET_BIT(0x3ff00014, BIT(0));
ets_update_cpu_frequency(160);
#endif
}
extern void(*__init_array_start)(void);
extern void(*__init_array_end)(void);
cont_t g_cont __attribute__((aligned(16)));
static os_event_t g_loop_queue[LOOP_QUEUE_SIZE];
static uint32_t g_micros_at_task_start;
extern "C" void esp_yield() {
if (cont_can_yield(&g_cont)) {
cont_yield(&g_cont);
}
}
extern "C" void esp_schedule() {
ets_post(LOOP_TASK_PRIORITY, 0, 0);
}
extern "C" void __yield() {
if (cont_can_yield(&g_cont)) {
esp_schedule();
esp_yield();
}
else {
panic();
}
}
extern "C" void yield(void) __attribute__((weak, alias("__yield")));
extern "C" void optimistic_yield(uint32_t interval_us) {
if (cont_can_yield(&g_cont) &&
(system_get_time() - g_micros_at_task_start) > interval_us)
{
yield();
}
}
static void loop_wrapper() {
static bool setup_done = false;
preloop_update_frequency();
if (!setup_done) {
_begin(); // Startup MySensors library
setup_done = true;
}
_process(); // Process incoming data
loop();
run_scheduled_functions();
esp_schedule();
}
static void loop_task(os_event_t *events) {
(void)events;
g_micros_at_task_start = system_get_time();
cont_run(&g_cont, &loop_wrapper);
if (cont_check(&g_cont) != 0) {
panic();
}
}
static void do_global_ctors(void) {
void(**p)(void) = &__init_array_end;
while (p != &__init_array_start)
(*--p)();
}
extern "C" void __gdb_init() {}
extern "C" void gdb_init(void) __attribute__((weak, alias("__gdb_init")));
extern "C" void __gdb_do_break() {}
extern "C" void gdb_do_break(void) __attribute__((weak, alias("__gdb_do_break")));
void init_done() {
system_set_os_print(1);
gdb_init();
do_global_ctors();
esp_schedule();
}
extern "C" void user_init(void) {
struct rst_info *rtc_info_ptr = system_get_rst_info();
memcpy((void *)&resetInfo, (void *)rtc_info_ptr, sizeof(resetInfo));
uart_div_modify(0, UART_CLK_FREQ / (115200));
init();
initVariant();
cont_init(&g_cont);
ets_task(loop_task,
LOOP_TASK_PRIORITY, g_loop_queue,
LOOP_QUEUE_SIZE);
system_init_done_cb(&init_done);
}
+276
View File
@@ -0,0 +1,276 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyMessage.h"
#include <stdio.h>
#include <stdlib.h>
MyMessage::MyMessage()
{
clear();
}
MyMessage::MyMessage(uint8_t _sensor, uint8_t _type)
{
clear();
sensor = _sensor;
type = _type;
}
void MyMessage::clear()
{
last = 0u;
sender = 0u;
destination = 0u; // Gateway is default destination
version_length = 0u;
command_ack_payload = 0u;
type = 0u;
sensor = 0u;
(void)memset(data, 0u, sizeof(data));
}
bool MyMessage::isAck() const {
return miGetAck();
}
uint8_t MyMessage::getCommand() const {
return miGetCommand();
}
/* Getters for payload converted to desired form */
void* MyMessage::getCustom() const {
return (void *)data;
}
const char* MyMessage::getString() const {
uint8_t payloadType = miGetPayloadType();
if (payloadType == P_STRING) {
return data;
} else {
return NULL;
}
}
// handles single character hex (0 - 15)
char MyMessage::i2h(uint8_t i) const {
uint8_t k = i & 0x0F;
if (k <= 9)
return '0' + k;
else
return 'A' + k - 10;
}
char* MyMessage::getCustomString(char *buffer) const {
for (uint8_t i = 0; i < miGetLength(); i++)
{
buffer[i * 2] = i2h(data[i] >> 4);
buffer[(i * 2) + 1] = i2h(data[i]);
}
buffer[miGetLength() * 2] = '\0';
return buffer;
}
char* MyMessage::getStream(char *buffer) const {
uint8_t cmd = miGetCommand();
if ((cmd == C_STREAM) && (buffer != NULL)) {
return getCustomString(buffer);
} else {
return NULL;
}
}
char* MyMessage::getString(char *buffer) const {
uint8_t payloadType = miGetPayloadType();
if (buffer != NULL) {
if (payloadType == P_STRING) {
strncpy(buffer, data, miGetLength());
buffer[miGetLength()] = 0;
} else if (payloadType == P_BYTE) {
itoa(bValue, buffer, 10);
} else if (payloadType == P_INT16) {
itoa(iValue, buffer, 10);
} else if (payloadType == P_UINT16) {
utoa(uiValue, buffer, 10);
} else if (payloadType == P_LONG32) {
ltoa(lValue, buffer, 10);
} else if (payloadType == P_ULONG32) {
ultoa(ulValue, buffer, 10);
} else if (payloadType == P_FLOAT32) {
dtostrf(fValue,2,min(fPrecision, 8),buffer);
} else if (payloadType == P_CUSTOM) {
return getCustomString(buffer);
}
return buffer;
} else {
return NULL;
}
}
bool MyMessage::getBool() const {
return getByte();
}
uint8_t MyMessage::getByte() const {
if (miGetPayloadType() == P_BYTE) {
return data[0];
} else if (miGetPayloadType() == P_STRING) {
return atoi(data);
} else {
return 0;
}
}
float MyMessage::getFloat() const {
if (miGetPayloadType() == P_FLOAT32) {
return fValue;
} else if (miGetPayloadType() == P_STRING) {
return atof(data);
} else {
return 0;
}
}
int32_t MyMessage::getLong() const {
if (miGetPayloadType() == P_LONG32) {
return lValue;
} else if (miGetPayloadType() == P_STRING) {
return atol(data);
} else {
return 0;
}
}
uint32_t MyMessage::getULong() const {
if (miGetPayloadType() == P_ULONG32) {
return ulValue;
} else if (miGetPayloadType() == P_STRING) {
return atol(data);
} else {
return 0;
}
}
int16_t MyMessage::getInt() const {
if (miGetPayloadType() == P_INT16) {
return iValue;
} else if (miGetPayloadType() == P_STRING) {
return atoi(data);
} else {
return 0;
}
}
uint16_t MyMessage::getUInt() const {
if (miGetPayloadType() == P_UINT16) {
return uiValue;
} else if (miGetPayloadType() == P_STRING) {
return atoi(data);
} else {
return 0;
}
}
MyMessage& MyMessage::setType(uint8_t _type) {
type = _type;
return *this;
}
MyMessage& MyMessage::setSensor(uint8_t _sensor) {
sensor = _sensor;
return *this;
}
MyMessage& MyMessage::setDestination(uint8_t _destination) {
destination = _destination;
return *this;
}
// Set payload
MyMessage& MyMessage::set(void* value, uint8_t length) {
miSetPayloadType(P_CUSTOM);
miSetLength(length);
memcpy(data, value, min(length, MAX_PAYLOAD));
return *this;
}
MyMessage& MyMessage::set(const char* value) {
uint8_t length = value == NULL ? 0 : min(strlen(value), MAX_PAYLOAD);
miSetLength(length);
miSetPayloadType(P_STRING);
if (length) {
strncpy(data, value, length);
}
// null terminate string
data[length] = 0;
return *this;
}
MyMessage& MyMessage::set(bool value) {
miSetLength(1);
miSetPayloadType(P_BYTE);
data[0] = value;
return *this;
}
MyMessage& MyMessage::set(uint8_t value) {
miSetLength(1);
miSetPayloadType(P_BYTE);
data[0] = value;
return *this;
}
MyMessage& MyMessage::set(float value, uint8_t decimals) {
miSetLength(5); // 32 bit float + persi
miSetPayloadType(P_FLOAT32);
fValue=value;
fPrecision = decimals;
return *this;
}
MyMessage& MyMessage::set(uint32_t value) {
miSetPayloadType(P_ULONG32);
miSetLength(4);
ulValue = value;
return *this;
}
MyMessage& MyMessage::set(int32_t value) {
miSetPayloadType(P_LONG32);
miSetLength(4);
lValue = value;
return *this;
}
MyMessage& MyMessage::set(uint16_t value) {
miSetPayloadType(P_UINT16);
miSetLength(2);
uiValue = value;
return *this;
}
MyMessage& MyMessage::set(int16_t value) {
miSetPayloadType(P_INT16);
miSetLength(2);
iValue = value;
return *this;
}
+379
View File
@@ -0,0 +1,379 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @file MyMessage.h
*
* @brief API and type declarations for MySensors messages
* @defgroup MyMessagegrp MyMessage
* @{
*
* @brief Here you can find all message types used by the MySensors protocol as well as macros for
* parsing and manipulating messages.
*/
#ifndef MyMessage_h
#define MyMessage_h
#ifdef __cplusplus
#include <Arduino.h>
#include <string.h>
#include <stdint.h>
#endif
#define PROTOCOL_VERSION 2 //!< The version of the protocol
#define MAX_MESSAGE_LENGTH 32 //!< The maximum size of a message (including header)
#define HEADER_SIZE 7 //!< The size of the header
#define MAX_PAYLOAD (MAX_MESSAGE_LENGTH - HEADER_SIZE) //!< The maximum size of a payload depends on #MAX_MESSAGE_LENGTH and #HEADER_SIZE
/// @brief The command field (message-type) defines the overall properties of a message
typedef enum {
C_PRESENTATION = 0, //!< Sent by a node when they present attached sensors. This is usually done in presentation() at startup.
C_SET = 1, //!< This message is sent from or to a sensor when a sensor value should be updated.
C_REQ = 2, //!< Requests a variable value (usually from an actuator destined for controller).
C_INTERNAL = 3, //!< Internal MySensors messages (also include common messages provided/generated by the library).
C_STREAM = 4 //!< For firmware and other larger chunks of data that need to be divided into pieces.
} mysensor_command;
/// @brief Type of sensor (used when presenting sensors)
typedef enum {
S_DOOR = 0, //!< Door sensor, V_TRIPPED, V_ARMED
S_MOTION = 1, //!< Motion sensor, V_TRIPPED, V_ARMED
S_SMOKE = 2, //!< Smoke sensor, V_TRIPPED, V_ARMED
S_BINARY = 3, //!< Binary light or relay, V_STATUS, V_WATT
S_LIGHT = 3, //!< \deprecated Same as S_BINARY, **** DEPRECATED, DO NOT USE ****
S_DIMMER = 4, //!< Dimmable light or fan device, V_STATUS (on/off), V_PERCENTAGE (dimmer level 0-100), V_WATT
S_COVER = 5, //!< Blinds or window cover, V_UP, V_DOWN, V_STOP, V_PERCENTAGE (open/close to a percentage)
S_TEMP = 6, //!< Temperature sensor, V_TEMP
S_HUM = 7, //!< Humidity sensor, V_HUM
S_BARO = 8, //!< Barometer sensor, V_PRESSURE, V_FORECAST
S_WIND = 9, //!< Wind sensor, V_WIND, V_GUST
S_RAIN = 10, //!< Rain sensor, V_RAIN, V_RAINRATE
S_UV = 11, //!< Uv sensor, V_UV
S_WEIGHT = 12, //!< Personal scale sensor, V_WEIGHT, V_IMPEDANCE
S_POWER = 13, //!< Power meter, V_WATT, V_KWH, V_VAR, V_VA, V_POWER_FACTOR
S_HEATER = 14, //!< Header device, V_HVAC_SETPOINT_HEAT, V_HVAC_FLOW_STATE, V_TEMP
S_DISTANCE = 15, //!< Distance sensor, V_DISTANCE
S_LIGHT_LEVEL = 16, //!< Light level sensor, V_LIGHT_LEVEL (uncalibrated in percentage), V_LEVEL (light level in lux)
S_ARDUINO_NODE = 17, //!< Used (internally) for presenting a non-repeating Arduino node
S_ARDUINO_REPEATER_NODE = 18, //!< Used (internally) for presenting a repeating Arduino node
S_LOCK = 19, //!< Lock device, V_LOCK_STATUS
S_IR = 20, //!< IR device, V_IR_SEND, V_IR_RECEIVE
S_WATER = 21, //!< Water meter, V_FLOW, V_VOLUME
S_AIR_QUALITY = 22, //!< Air quality sensor, V_LEVEL
S_CUSTOM = 23, //!< Custom sensor
S_DUST = 24, //!< Dust sensor, V_LEVEL
S_SCENE_CONTROLLER = 25, //!< Scene controller device, V_SCENE_ON, V_SCENE_OFF.
S_RGB_LIGHT = 26, //!< RGB light. Send color component data using V_RGB. Also supports V_WATT
S_RGBW_LIGHT = 27, //!< RGB light with an additional White component. Send data using V_RGBW. Also supports V_WATT
S_COLOR_SENSOR = 28, //!< Color sensor, send color information using V_RGB
S_HVAC = 29, //!< Thermostat/HVAC device. V_HVAC_SETPOINT_HEAT, V_HVAC_SETPOINT_COLD, V_HVAC_FLOW_STATE, V_HVAC_FLOW_MODE, V_TEMP
S_MULTIMETER = 30, //!< Multimeter device, V_VOLTAGE, V_CURRENT, V_IMPEDANCE
S_SPRINKLER = 31, //!< Sprinkler, V_STATUS (turn on/off), V_TRIPPED (if fire detecting device)
S_WATER_LEAK = 32, //!< Water leak sensor, V_TRIPPED, V_ARMED
S_SOUND = 33, //!< Sound sensor, V_TRIPPED, V_ARMED, V_LEVEL (sound level in dB)
S_VIBRATION = 34, //!< Vibration sensor, V_TRIPPED, V_ARMED, V_LEVEL (vibration in Hz)
S_MOISTURE = 35, //!< Moisture sensor, V_TRIPPED, V_ARMED, V_LEVEL (water content or moisture in percentage?)
S_INFO = 36, //!< LCD text device / Simple information device on controller, V_TEXT
S_GAS = 37, //!< Gas meter, V_FLOW, V_VOLUME
S_GPS = 38, //!< GPS Sensor, V_POSITION
S_WATER_QUALITY = 39 //!< V_TEMP, V_PH, V_ORP, V_EC, V_STATUS
} mysensor_sensor;
/// @brief Type of sensor data (for set/req/ack messages)
typedef enum {
V_TEMP = 0, //!< S_TEMP. Temperature S_TEMP, S_HEATER, S_HVAC
V_HUM = 1, //!< S_HUM. Humidity
V_STATUS = 2, //!< S_BINARY, S_DIMMER, S_SPRINKLER, S_HVAC, S_HEATER. Used for setting/reporting binary (on/off) status. 1=on, 0=off
V_LIGHT = 2, //!< \deprecated Same as V_STATUS, **** DEPRECATED, DO NOT USE ****
V_PERCENTAGE = 3, //!< S_DIMMER. Used for sending a percentage value 0-100 (%).
V_DIMMER = 3, //!< \deprecated Same as V_PERCENTAGE, **** DEPRECATED, DO NOT USE ****
V_PRESSURE = 4, //!< S_BARO. Atmospheric Pressure
V_FORECAST = 5, //!< S_BARO. Whether forecast. string of "stable", "sunny", "cloudy", "unstable", "thunderstorm" or "unknown"
V_RAIN = 6, //!< S_RAIN. Amount of rain
V_RAINRATE = 7, //!< S_RAIN. Rate of rain
V_WIND = 8, //!< S_WIND. Wind speed
V_GUST = 9, //!< S_WIND. Gust
V_DIRECTION = 10, //!< S_WIND. Wind direction 0-360 (degrees)
V_UV = 11, //!< S_UV. UV light level
V_WEIGHT = 12, //!< S_WEIGHT. Weight(for scales etc)
V_DISTANCE = 13, //!< S_DISTANCE. Distance
V_IMPEDANCE = 14, //!< S_MULTIMETER, S_WEIGHT. Impedance value
V_ARMED = 15, //!< S_DOOR, S_MOTION, S_SMOKE, S_SPRINKLER. Armed status of a security sensor. 1 = Armed, 0 = Bypassed
V_TRIPPED = 16, //!< S_DOOR, S_MOTION, S_SMOKE, S_SPRINKLER, S_WATER_LEAK, S_SOUND, S_VIBRATION, S_MOISTURE. Tripped status of a security sensor. 1 = Tripped, 0
V_WATT = 17, //!< S_POWER, S_BINARY, S_DIMMER, S_RGB_LIGHT, S_RGBW_LIGHT. Watt value for power meters
V_KWH = 18, //!< S_POWER. Accumulated number of KWH for a power meter
V_SCENE_ON = 19, //!< S_SCENE_CONTROLLER. Turn on a scene
V_SCENE_OFF = 20, //!< S_SCENE_CONTROLLER. Turn of a scene
V_HVAC_FLOW_STATE = 21, //!< S_HEATER, S_HVAC. HVAC flow state ("Off", "HeatOn", "CoolOn", or "AutoChangeOver")
V_HEATER = 21, //!< \deprecated Same as V_HVAC_FLOW_STATE, **** DEPRECATED, DO NOT USE ****
V_HVAC_SPEED = 22, //!< S_HVAC, S_HEATER. HVAC/Heater fan speed ("Min", "Normal", "Max", "Auto")
V_LIGHT_LEVEL = 23, //!< S_LIGHT_LEVEL. Uncalibrated light level. 0-100%. Use V_LEVEL for light level in lux
V_VAR1 = 24, //!< VAR1
V_VAR2 = 25, //!< VAR2
V_VAR3 = 26, //!< VAR3
V_VAR4 = 27, //!< VAR4
V_VAR5 = 28, //!< VAR5
V_UP = 29, //!< S_COVER. Window covering. Up
V_DOWN = 30, //!< S_COVER. Window covering. Down
V_STOP = 31, //!< S_COVER. Window covering. Stop
V_IR_SEND = 32, //!< S_IR. Send out an IR-command
V_IR_RECEIVE = 33, //!< S_IR. This message contains a received IR-command
V_FLOW = 34, //!< S_WATER. Flow of water (in meter)
V_VOLUME = 35, //!< S_WATER. Water volume
V_LOCK_STATUS = 36, //!< S_LOCK. Set or get lock status. 1=Locked, 0=Unlocked
V_LEVEL = 37, //!< S_DUST, S_AIR_QUALITY, S_SOUND (dB), S_VIBRATION (hz), S_LIGHT_LEVEL (lux)
V_VOLTAGE = 38, //!< S_MULTIMETER
V_CURRENT = 39, //!< S_MULTIMETER
V_RGB = 40, //!< S_RGB_LIGHT, S_COLOR_SENSOR. Sent as ASCII hex: RRGGBB (RR=red, GG=green, BB=blue component)
V_RGBW = 41, //!< S_RGBW_LIGHT. Sent as ASCII hex: RRGGBBWW (WW=white component)
V_ID = 42, //!< Used for sending in sensors hardware ids (i.e. OneWire DS1820b).
V_UNIT_PREFIX = 43, //!< Allows sensors to send in a string representing the unit prefix to be displayed in GUI, not parsed by controller! E.g. cm, m, km, inch.
V_HVAC_SETPOINT_COOL = 44, //!< S_HVAC. HVAC cool setpoint (Integer between 0-100)
V_HVAC_SETPOINT_HEAT = 45, //!< S_HEATER, S_HVAC. HVAC/Heater setpoint (Integer between 0-100)
V_HVAC_FLOW_MODE = 46, //!< S_HVAC. Flow mode for HVAC ("Auto", "ContinuousOn", "PeriodicOn")
V_TEXT = 47, //!< S_INFO. Text message to display on LCD or controller device
V_CUSTOM = 48, //!< Custom messages used for controller/inter node specific commands, preferably using S_CUSTOM device type.
V_POSITION = 49, //!< GPS position and altitude. Payload: latitude;longitude;altitude(m). E.g. "55.722526;13.017972;18"
V_IR_RECORD = 50, //!< Record IR codes S_IR for playback
V_PH = 51, //!< S_WATER_QUALITY, water PH
V_ORP = 52, //!< S_WATER_QUALITY, water ORP : redox potential in mV
V_EC = 53, //!< S_WATER_QUALITY, water electric conductivity μS/cm (microSiemens/cm)
V_VAR = 54, //!< S_POWER, Reactive power: volt-ampere reactive (var)
V_VA = 55, //!< S_POWER, Apparent power: volt-ampere (VA)
V_POWER_FACTOR = 56, //!< S_POWER, Ratio of real power to apparent power: floating point value in the range [-1,..,1]
} mysensor_data;
/// @brief Type of internal messages (for internal messages)
typedef enum {
I_BATTERY_LEVEL = 0, //!< Battery level
I_TIME = 1, //!< Time
I_VERSION = 2, //!< Version
I_ID_REQUEST = 3, //!< ID request
I_ID_RESPONSE = 4, //!< ID response
I_INCLUSION_MODE = 5, //!< Inclusion mode
I_CONFIG = 6, //!< Config
I_FIND_PARENT = 7, //!< Find parent
I_FIND_PARENT_RESPONSE = 8, //!< Find parent response
I_LOG_MESSAGE = 9, //!< Log message
I_CHILDREN = 10, //!< Children
I_SKETCH_NAME = 11, //!< Sketch name
I_SKETCH_VERSION = 12, //!< Sketch version
I_REBOOT = 13, //!< Reboot request
I_GATEWAY_READY = 14, //!< Gateway ready
I_SIGNING_PRESENTATION = 15, //!< Provides signing related preferences (first byte is preference version)
I_NONCE_REQUEST = 16, //!< Request for a nonce
I_NONCE_RESPONSE = 17, //!< Payload is nonce data
I_HEARTBEAT = 18, //!< Heartbeat request
I_PRESENTATION = 19, //!< Presentation message
I_DISCOVER = 20, //!< Discover request
I_DISCOVER_RESPONSE = 21, //!< Discover response
I_HEARTBEAT_RESPONSE = 22, //!< Heartbeat response
I_LOCKED = 23, //!< Node is locked (reason in string-payload)
I_PING = 24, //!< Ping sent to node, payload incremental hop counter
I_PONG = 25, //!< In return to ping, sent back to sender, payload incremental hop counter
I_REGISTRATION_REQUEST = 26, //!< Register request to GW
I_REGISTRATION_RESPONSE = 27, //!< Register response from GW
I_DEBUG = 28 //!< Debug message
} mysensor_internal;
/// @brief Type of data stream (for streamed message)
typedef enum {
ST_FIRMWARE_CONFIG_REQUEST = 0, //!< Request new FW, payload contains current FW details
ST_FIRMWARE_CONFIG_RESPONSE = 1, //!< New FW details to initiate OTA FW update
ST_FIRMWARE_REQUEST = 2, //!< Request FW block
ST_FIRMWARE_RESPONSE = 3, //!< Response FW block
ST_SOUND = 4, //!< Sound
ST_IMAGE = 5 //!< Image
} mysensor_stream;
/// @brief Type of payload
typedef enum {
P_STRING = 0, //!< Payload type is string
P_BYTE = 1, //!< Payload type is byte
P_INT16 = 2, //!< Payload type is INT16
P_UINT16 = 3, //!< Payload type is UINT16
P_LONG32 = 4, //!< Payload type is INT32
P_ULONG32 = 5, //!< Payload type is UINT32
P_CUSTOM = 6, //!< Payload type is binary
P_FLOAT32 = 7 //!< Payload type is float32
} mysensor_payload;
#ifndef BIT
#define BIT(n) ( 1<<(n) ) //!< Bit indexing macro
#endif
#define BIT_MASK(len) ( BIT(len)-1 ) //!< Create a bitmask of length 'len'
#define BF_MASK(start, len) ( BIT_MASK(len)<<(start) ) //!< Create a bitfield mask of length starting at bit 'start'
#define BF_PREP(x, start, len) ( ((x)&BIT_MASK(len)) << (start) ) //!< Prepare a bitmask for insertion or combining
#define BF_GET(y, start, len) ( ((y)>>(start)) & BIT_MASK(len) ) //!< Extract a bitfield of length 'len' starting at bit 'start' from 'y'
#define BF_SET(y, x, start, len) ( y= ((y) &~ BF_MASK(start, len)) | BF_PREP(x, start, len) ) //!< Insert a new bitfield value 'x' into 'y'
// Getters/setters for special bit fields in header
#define mSetVersion(_msg,_version) BF_SET(_msg.version_length, _version, 0, 2) //!< Set version field
#define mGetVersion(_msg) ((uint8_t)BF_GET(_msg.version_length, 0, 2)) //!< Get version field
#define mSetSigned(_msg,_signed) BF_SET(_msg.version_length, _signed, 2, 1) //!< Set signed field
#define mGetSigned(_msg) ((bool)BF_GET(_msg.version_length, 2, 1)) //!< Get versignedsion field
#define mSetLength(_msg,_length) BF_SET(_msg.version_length, _length, 3, 5) //!< Set length field
#define mGetLength(_msg) ((uint8_t)BF_GET(_msg.version_length, 3, 5)) //!< Get length field
#define mSetCommand(_msg,_command) BF_SET(_msg.command_ack_payload, _command, 0, 3) //!< Set command field
#define mGetCommand(_msg) ((uint8_t)BF_GET(_msg.command_ack_payload, 0, 3)) //!< Get command field
#define mSetRequestAck(_msg,_rack) BF_SET(_msg.command_ack_payload, _rack, 3, 1) //!< Set ack-request field
#define mGetRequestAck(_msg) ((bool)BF_GET(_msg.command_ack_payload, 3, 1)) //!< Get ack-request field
#define mSetAck(_msg,_ackMsg) BF_SET(_msg.command_ack_payload, _ackMsg, 4, 1) //!< Set ack field
#define mGetAck(_msg) ((bool)BF_GET(_msg.command_ack_payload, 4, 1)) //!< Get ack field
#define mSetPayloadType(_msg, _pt) BF_SET(_msg.command_ack_payload, _pt, 5, 3) //!< Set payload type field
#define mGetPayloadType(_msg) ((uint8_t)BF_GET(_msg.command_ack_payload, 5, 3)) //!< Get payload type field
// internal access for special fields
#define miGetCommand() ((uint8_t)BF_GET(command_ack_payload, 0, 3)) //!< Internal getter for command field
#define miSetLength(_length) BF_SET(version_length, _length, 3, 5) //!< Internal setter for length field
#define miGetLength() ((uint8_t)BF_GET(version_length, 3, 5)) //!< Internal getter for length field
#define miSetRequestAck(_rack) BF_SET(command_ack_payload, _rack, 3, 1) //!< Internal setter for ack-request field
#define miGetRequestAck() ((bool)BF_GET(command_ack_payload, 3, 1)) //!< Internal getter for ack-request field
#define miSetAck(_ack) BF_SET(command_ack_payload, _ack, 4, 1) //!< Internal setter for ack field
#define miGetAck() ((bool)BF_GET(command_ack_payload, 4, 1)) //!< Internal getter for ack field
#define miSetPayloadType(_pt) BF_SET(command_ack_payload, _pt, 5, 3) //!< Internal setter for payload type field
#define miGetPayloadType() (uint8_t)BF_GET(command_ack_payload, 5, 3) //!< Internal getter for payload type field
#if !DOXYGEN
#ifdef __cplusplus
class MyMessage
{
private:
char* getCustomString(char *buffer) const;
public:
// Constructors
MyMessage();
MyMessage(uint8_t sensor, uint8_t type);
char i2h(uint8_t i) const;
/**
* Clear message contents.
*/
void clear();
/**
* If payload is something else than P_STRING you can have the payload value converted
* into 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* getStream(char *buffer) const;
char* getString(char *buffer) const;
const char* getString() const;
void* getCustom() const;
bool getBool() const;
uint8_t getByte() const;
float getFloat() const;
int16_t getInt() const;
uint16_t getUInt() const;
int32_t getLong() const;
uint32_t getULong() const;
// Getter for command type
uint8_t getCommand() const;
// Getter for ack-flag. True if this is an ack message.
bool isAck() const;
// Setters for building message "on the fly"
MyMessage& setType(uint8_t type);
MyMessage& setSensor(uint8_t sensor);
MyMessage& setDestination(uint8_t destination);
// Setters for payload
MyMessage& set(void* payload, uint8_t length);
MyMessage& set(const char* value);
MyMessage& set(float value, uint8_t decimals);
MyMessage& set(bool value);
MyMessage& set(uint8_t value);
MyMessage& set(uint32_t value);
MyMessage& set(int32_t value);
MyMessage& set(uint16_t value);
MyMessage& set(int16_t value);
#else
typedef union {
struct
{
#endif
uint8_t last; // 8 bit - Id of last node this message passed
uint8_t sender; // 8 bit - Id of sender node (origin)
uint8_t destination; // 8 bit - Id of destination node
uint8_t version_length; // 2 bit - Protocol version
// 1 bit - Signed flag
// 5 bit - Length of payload
uint8_t command_ack_payload; // 3 bit - Command type
// 1 bit - Request an ack - Indicator that receiver should send an ack back.
// 1 bit - Is ack messsage - Indicator that this is the actual ack message.
// 3 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.
// 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 data types transferred.
union {
uint8_t bValue;
uint16_t uiValue;
int16_t iValue;
uint32_t ulValue;
int32_t lValue;
struct { // Float messages
float fValue;
uint8_t fPrecision; // Number of decimals when serializing
};
struct { // Presentation messages
uint8_t version; // Library version
uint8_t sensorType; // Sensor type hint for controller, see table above
};
char data[MAX_PAYLOAD + 1];
} __attribute__((packed));
#ifdef __cplusplus
} __attribute__((packed));
#else
};
uint8_t array[HEADER_SIZE + MAX_PAYLOAD + 1];
} __attribute__((packed)) MyMessage;
#endif
#endif
#endif
/** @}*/
+149
View File
@@ -0,0 +1,149 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2016 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyOTAFirmwareUpdate.h"
SPIFlash _flash(MY_OTA_FLASH_SS, MY_OTA_FLASH_JDECID);
NodeFirmwareConfig _fc;
bool _fwUpdateOngoing;
unsigned long _fwLastRequestTime;
uint16_t _fwBlock;
uint8_t _fwRetry;
inline void readFirmwareSettings() {
hwReadConfigBlock((void*)&_fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig));
}
inline void firmwareOTAUpdateRequest() {
unsigned long enter = hwMillis();
if (_fwUpdateOngoing && (enter - _fwLastRequestTime > MY_OTA_RETRY_DELAY)) {
if (!_fwRetry) {
setIndication(INDICATION_ERR_FW_TIMEOUT);
debug(PSTR("fw upd fail\n"));
// Give up. We have requested MY_OTA_RETRY times without any packet in return.
_fwUpdateOngoing = false;
return;
}
_fwRetry--;
_fwLastRequestTime = enter;
// Time to (re-)request firmware block from controller
RequestFWBlock firmwareRequest;
firmwareRequest.type = _fc.type;
firmwareRequest.version = _fc.version;
firmwareRequest.block = (_fwBlock - 1);
debug(PSTR("req FW: T=%02X, V=%02X, B=%04X\n"),_fc.type,_fc.version,_fwBlock - 1);
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_REQUEST, false).set(&firmwareRequest,sizeof(RequestFWBlock)));
}
}
inline bool firmwareOTAUpdateProcess() {
if (_msg.type == ST_FIRMWARE_CONFIG_RESPONSE) {
NodeFirmwareConfig *firmwareConfigResponse = (NodeFirmwareConfig *)_msg.data;
// compare with current node configuration, if they differ, start fw fetch process
if (memcmp(&_fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig))) {
setIndication(INDICATION_FW_UPDATE_START);
debug(PSTR("fw update\n"));
// copy new FW config
memcpy(&_fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig));
// Init flash
if (!_flash.initialize()) {
setIndication(INDICATION_ERR_FW_FLASH_INIT);
debug(PSTR("flash init fail\n"));
_fwUpdateOngoing = false;
} else {
// erase lower 32K -> max flash size for ATMEGA328
_flash.blockErase32K(0);
// wait until flash erased
while ( _flash.busy() );
_fwBlock = _fc.blocks;
_fwUpdateOngoing = true;
// reset flags
_fwRetry = MY_OTA_RETRY+1;
_fwLastRequestTime = 0;
}
return true;
}
debug(PSTR("fw update skipped\n"));
} else if (_msg.type == ST_FIRMWARE_RESPONSE) {
if (_fwUpdateOngoing) {
// Save block to flash
setIndication(INDICATION_FW_UPDATE_RX);
debug(PSTR("fw block %d\n"), _fwBlock);
// extract FW block
ReplyFWBlock *firmwareResponse = (ReplyFWBlock *)_msg.data;
// write to flash
_flash.writeBytes( ((_fwBlock - 1) * FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET, firmwareResponse->data, FIRMWARE_BLOCK_SIZE);
// wait until flash written
while ( _flash.busy() );
_fwBlock--;
if (!_fwBlock) {
// We're finished! Do a checksum and reboot.
_fwUpdateOngoing = false;
if (transportIsValidFirmware()) {
debug(PSTR("fw checksum ok\n"));
// All seems ok, write size and signature to flash (DualOptiboot will pick this up and flash it)
uint16_t fwsize = FIRMWARE_BLOCK_SIZE * _fc.blocks;
uint8_t OTAbuffer[10] = {'F','L','X','I','M','G',':',(uint8_t)(fwsize >> 8),(uint8_t)(fwsize & 0xff),':'};
_flash.writeBytes(0, OTAbuffer, 10);
// Write the new firmware config to eeprom
hwWriteConfigBlock((void*)&_fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig));
hwReboot();
} else {
setIndication(INDICATION_ERR_FW_CHECKSUM);
debug(PSTR("fw checksum fail\n"));
}
}
// reset flags
_fwRetry = MY_OTA_RETRY+1;
_fwLastRequestTime = 0;
} else {
debug(PSTR("No fw update ongoing\n"));
}
return true;
}
return false;
}
inline void presentBootloaderInformation(){
RequestFirmwareConfig *reqFWConfig = (RequestFirmwareConfig *)_msgTmp.data;
mSetLength(_msgTmp, sizeof(RequestFirmwareConfig));
mSetCommand(_msgTmp, C_STREAM);
mSetPayloadType(_msgTmp,P_CUSTOM);
// copy node settings to reqFWConfig
memcpy(reqFWConfig,&_fc,sizeof(NodeFirmwareConfig));
// add bootloader information
reqFWConfig->BLVersion = MY_OTA_BOOTLOADER_VERSION;
_fwUpdateOngoing = false;
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_CONFIG_REQUEST, false));
}
// do a crc16 on the whole received firmware
inline bool transportIsValidFirmware() {
// init crc
uint16_t crc = ~0;
for (uint16_t i = 0; i < _fc.blocks * FIRMWARE_BLOCK_SIZE; ++i) {
crc ^= _flash.readByte(i + FIRMWARE_START_OFFSET);
for (int8_t j = 0; j < 8; ++j) {
if (crc & 1)
crc = (crc >> 1) ^ 0xA001;
else
crc = (crc >> 1);
}
}
return crc == _fc.crc;
}
+101
View File
@@ -0,0 +1,101 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2016 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyOTAFirmwareUpdate_h
#define MyOTAFirmwareUpdate_h
#include "MySensorsCore.h"
// Size of each firmware block
#define FIRMWARE_BLOCK_SIZE 16
// Number of times a firmware block should be requested before giving up
#define FIRMWARE_MAX_REQUESTS 5
// Number of times to request a fw block before giving up
#define MY_OTA_RETRY 5
// Number of millisecons before re-request a fw block
#define MY_OTA_RETRY_DELAY 500
// Start offset for firmware in flash (DualOptiboot wants to keeps a signature first)
#define FIRMWARE_START_OFFSET 10
// Bootloader version
#define MY_OTA_BOOTLOADER_MAJOR_VERSION 3
#define MY_OTA_BOOTLOADER_MINOR_VERSION 0
#define MY_OTA_BOOTLOADER_VERSION (MY_OTA_BOOTLOADER_MINOR_VERSION * 256 + MY_OTA_BOOTLOADER_MAJOR_VERSION)
/// @brief FW config structure, stored in eeprom
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t blocks; //!< Number of blocks
uint16_t crc; //!< CRC of block data
} __attribute__((packed)) NodeFirmwareConfig;
/// @brief FW config request structure
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t blocks; //!< Number of blocks
uint16_t crc; //!< CRC of block data
uint16_t BLVersion; //!< Bootloader version
} __attribute__((packed)) RequestFirmwareConfig;
/// @brief FW block request structure
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t block; //!< Block index
} __attribute__((packed)) RequestFWBlock;
/// @brief FW block reply structure
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t block; //!< Block index
uint8_t data[FIRMWARE_BLOCK_SIZE]; //!< Block data
} __attribute__((packed)) ReplyFWBlock;
/**
* @brief Read firmware settings from EEPROM
*
* Current firmware settings (type, version, crc, blocks) are read into _fc
*/
void readFirmwareSettings();
/**
* @brief Handle OTA FW update requests
*/
void firmwareOTAUpdateRequest();
/**
* @brief Handle OTA FW update responses
*
* This function handles incoming OTA FW packets and stores them to external flash (Sensebender)
*/
bool firmwareOTAUpdateProcess();
/**
* @brief Validate uploaded FW CRC
*
* This function verifies if uploaded FW CRC is valid
*/
bool transportIsValidFirmware();
/**
* @brief Present bootloader/FW information upon startup
*/
void presentBootloaderInformation();
#endif
+61
View File
@@ -0,0 +1,61 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/*
* This file contains settings that for various reasons are unsuitable to store in git
* Rename it by removing the .sample extension and place it in the sketch folder and include
* it in the sketch:
* #include "MyPrivateConfig.h"
* Make sure to include it before
* #include <MySensors.h>
* Then adapt the contents to your personal preference.
*/
#ifndef MyPrivateConfig_h
#define MyPrivateConfig_h
/**********************************
* Node identification Settings
***********************************/
//#define MY_NODE_ID 1
/**********************************
* Message Signing Settings
***********************************/
// Pin used for random generation in soft signing (do not connect anything to this when enabled)
#define MY_SIGNING_SOFT_RANDOMSEED_PIN 7 // A7 -
// Enable node whitelisting
//#define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}}}
/**********************************
* NRF24L01 Driver Defaults
***********************************/
// Enables RF24 encryption (all nodes and gateway must have this enabled)
//#define MY_RF24_ENABLE_ENCRYPTION
/**********************************
* RFM69 Driver Defaults
***********************************/
// Default network id. Use the same for all nodes that will talk to each other
#define MY_RFM69_NETWORKID 100
// Enable this for encryption of packets
//#define MY_RFM69_ENABLE_ENCRYPTION
#endif
+34
View File
@@ -0,0 +1,34 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyProtocol_h
#define MyProtocol_h
#include "MySensorsCore.h"
// parse(message, inputString)
// parse a string into a message element
// returns true if successfully parsed the input string
bool protocolParse(MyMessage &message, char *inputString);
// Format MyMessage to the protocol represenataion
char *protocolFormat(MyMessage &message);
#endif
+191
View File
@@ -0,0 +1,191 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyConfig.h"
#include "MyTransport.h"
#include "MyProtocol.h"
uint8_t protocolH2i(char c);
char _fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH];
char _convBuffer[MAX_PAYLOAD*2+1];
bool protocolParse(MyMessage &message, char *inputString) {
char *str, *p, *value=NULL;
uint8_t bvalue[MAX_PAYLOAD];
uint8_t blen = 0;
int i = 0;
uint8_t command = 0;
uint8_t ack = 0;
// Extract command data coming on serial line
for (str = strtok_r(inputString, ";", &p); // split using semicolon
str && i < 6; // loop while str is not null an max 5 times
str = strtok_r(NULL, ";", &p) // get subsequent tokens
) {
switch (i) {
case 0: // Radioid (destination)
message.destination = atoi(str);
break;
case 1: // Childid
message.sensor = atoi(str);
break;
case 2: // Message type
command = atoi(str);
mSetCommand(message, command);
break;
case 3: // Should we request ack from destination?
ack = atoi(str);
break;
case 4: // Data type
message.type = atoi(str);
break;
case 5: // Variable value
if (command == C_STREAM) {
blen = 0;
uint8_t val;
while (*str) {
val = protocolH2i(*str++) << 4;
val += protocolH2i(*str++);
bvalue[blen] = val;
blen++;
}
} else {
value = str;
// Remove trailing carriage return and newline character (if it exists)
uint8_t lastCharacter = strlen(value)-1;
if (value[lastCharacter] == '\r')
value[lastCharacter] = 0;
if (value[lastCharacter] == '\n')
value[lastCharacter] = 0;
}
break;
}
i++;
}
//debug(PSTR("Received %d"), i);
// Check for invalid input
if (i < 5)
return false;
message.sender = GATEWAY_ADDRESS;
message.last = GATEWAY_ADDRESS;
mSetRequestAck(message, ack?1:0);
mSetAck(message, false);
if (command == C_STREAM)
message.set(bvalue, blen);
else
message.set(value);
return true;
}
char * protocolFormat(MyMessage &message) {
snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR("%d;%d;%d;%d;%d;%s\n"), message.sender, message.sensor, (uint8_t)mGetCommand(message), (uint8_t)mGetAck(message), message.type, message.getString(_convBuffer));
return _fmtBuffer;
}
char * protocolFormatMQTTTopic(const char* prefix, MyMessage &message) {
snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR("%s/%d/%d/%d/%d/%d"), prefix, message.sender, message.sensor, mGetCommand(message), mGetAck(message), message.type);
return _fmtBuffer;
}
char * protocolFormatMQTTSubscribe(const char* prefix) {
snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR("%s/+/+/+/+/+"), prefix);
return _fmtBuffer;
}
void protocolMQTTParse(MyMessage &message, char* topic, uint8_t* payload, unsigned int length) {
//TODO: Not sure if we should copy the topic and payload into a buffer so we are not modifying original.
char *str, *p;
uint8_t i = 0;
uint8_t bvalue[MAX_PAYLOAD];
uint8_t blen = 0;
uint8_t command = 0;
for (str = strtok_r(topic, "/", &p); str && i <= 5;
str = strtok_r(NULL, "/", &p)) {
switch (i) {
case 0: {
// Topic prefix
//TODO: Should be checking the topic prefix
//if (strcmp(str, MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) != 0) {
// Message not for us or malformed!
// return;
//}
break;
}
case 1: {
// Node id
message.destination = atoi(str);
break;
}
case 2: {
// Sensor id
message.sensor = atoi(str);
break;
}
case 3: {
// Command type
command = atoi(str);
mSetCommand(message, command);
break;
}
case 4: {
// Ack flag
mSetRequestAck(message, atoi(str)?1:0);
break;
}
case 5: {
// Sub type
message.type = atoi(str);
// Add payload
if (command == C_STREAM) {
blen = 0;
uint8_t val;
while (*payload) {
val = protocolH2i(*payload++) << 4;
val += protocolH2i(*payload++);
bvalue[blen] = val;
blen++;
}
message.set(bvalue, blen);
} else {
char* ca;
ca = (char *) payload;
ca += length;
*ca = '\0';
message.set((const char*) payload);
}
}
}
i++;
}
}
uint8_t protocolH2i(char c) {
uint8_t i = 0;
if (c <= '9')
i += c - '0';
else if (c >= 'a')
i += c - 'a' + 10;
else
i += c - 'A' + 10;
return i;
}
+561
View File
@@ -0,0 +1,561 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MySensorsCore.h"
ControllerConfig _cc; // Configuration coming from controller
NodeConfig _nc; // Essential settings for node to work
MyMessage _msg; // Buffer for incoming messages.
MyMessage _msgTmp; // Buffer for temporary messages (acks and nonces among others).
bool _nodeRegistered = false;
#if defined(MY_DEBUG)
char _convBuf[MAX_PAYLOAD*2+1];
#endif
void (*_timeCallback)(unsigned long); // Callback for requested time messages
void _process() {
hwWatchdogReset();
#if defined (MY_LEDS_BLINKING_FEATURE)
ledsProcess();
#endif
#if defined(MY_INCLUSION_MODE_FEATURE)
inclusionProcess();
#endif
#if defined(MY_GATEWAY_FEATURE)
gatewayTransportProcess();
#endif
#if defined(MY_RADIO_FEATURE)
transportProcess();
#endif
}
void _infiniteLoop() {
while(1) {
#if defined(ARDUINO_ARCH_ESP8266)
yield();
#endif
#if defined (MY_LEDS_BLINKING_FEATURE)
ledsProcess();
#endif
}
}
void _begin() {
if (preHwInit)
preHwInit();
#if !defined(MY_DISABLED_SERIAL)
hwInit();
#endif
debug(PSTR("MCO:BGN:INIT " MY_NODE_TYPE ",CP=" MY_CAPABILITIES ",VER=" MYSENSORS_LIBRARY_VERSION "\n"));
// Call before() in sketch (if it exists)
if (before)
before();
#if defined(MY_LEDS_BLINKING_FEATURE)
ledsInit();
#endif
signerInit();
// Read latest received controller configuration from EEPROM
// Note: _cc.isMetric is bool, hence empty EEPROM (=0xFF) evaluates to true (default)
hwReadConfigBlock((void*)&_cc, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS, sizeof(ControllerConfig));
#if defined(MY_OTA_FIRMWARE_FEATURE)
// Read firmware config from EEPROM, i.e. type, version, CRC, blocks
readFirmwareSettings();
#endif
#if defined(MY_RADIO_FEATURE)
// Save static parent id in eeprom (used by bootloader)
hwWriteConfig(EEPROM_PARENT_NODE_ID_ADDRESS, MY_PARENT_NODE_ID);
transportInitialize();
while (!isTransportReady()) {
hwWatchdogReset();
transportProcess();
#if defined(ARDUINO_ARCH_ESP8266)
yield();
#endif
}
#endif
#ifdef MY_NODE_LOCK_FEATURE
// Check if node has been locked down
if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER) == 0) {
// Node is locked, check if unlock pin is asserted, else hang the node
pinMode(MY_NODE_UNLOCK_PIN, INPUT_PULLUP);
// Make a short delay so we are sure any large external nets are fully pulled
unsigned long enter = hwMillis();
while (hwMillis() - enter < 2);
if (digitalRead(MY_NODE_UNLOCK_PIN) == 0) {
// Pin is grounded, reset lock counter
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, MY_NODE_LOCK_COUNTER_MAX);
// Disable pullup
pinMode(MY_NODE_UNLOCK_PIN, INPUT);
setIndication(INDICATION_ERR_LOCKED);
debug(PSTR("MCO:BGN:NODE UNLOCKED\n"));
} else {
// Disable pullup
pinMode(MY_NODE_UNLOCK_PIN, INPUT);
nodeLock("LDB"); //Locked during boot
}
} else if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER) == 0xFF) {
// Reset walue
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, MY_NODE_LOCK_COUNTER_MAX);
}
#endif
#if defined(MY_GATEWAY_FEATURE)
#if defined(MY_INCLUSION_BUTTON_FEATURE)
inclusionInit();
#endif
// initialize the transport driver
if (!gatewayTransportInit()) {
setIndication(INDICATION_ERR_INIT_GWTRANSPORT);
debug(PSTR("!MCO:BGN:TSP FAIL\n"));
// Nothing more we can do
_infiniteLoop();
}
#endif
#if !defined(MY_GATEWAY_FEATURE)
presentNode();
#endif
// register node
_registerNode();
// Call sketch setup
if (setup) {
setup();
}
debug(PSTR("MCO:BGN:INIT OK,ID=%d,PAR=%d,DIS=%d,REG=%d\n"), _nc.nodeId, _nc.parentNodeId, _nc.distance, _nodeRegistered);
}
void _registerNode() {
#if defined (MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE)
debug(PSTR("MCO:REG:REQ\n")); // registration request
setIndication(INDICATION_REQ_REGISTRATION);
_nodeRegistered = MY_REGISTRATION_DEFAULT;
uint8_t counter = MY_REGISTRATION_RETRIES;
// only proceed if register response received or retries exceeded
do {
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_REGISTRATION_REQUEST, false).set(MY_CORE_VERSION));
} while (!wait(2000, C_INTERNAL, I_REGISTRATION_RESPONSE) && counter--);
#else
_nodeRegistered = true;
debug(PSTR("MCO:REG:NOT NEEDED\n"));
#endif
}
void presentNode() {
setIndication(INDICATION_PRESENT);
// Present node and request config
#if defined(MY_GATEWAY_FEATURE)
// Send presentation for this gateway device
#if defined(MY_REPEATER_FEATURE)
present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE);
#else
present(NODE_SENSOR_ID, S_ARDUINO_NODE);
#endif
#else
#if defined(MY_OTA_FIRMWARE_FEATURE)
presentBootloaderInformation();
#endif
// Send signing preferences for this node to the GW
signerPresentation(_msgTmp, GATEWAY_ADDRESS);
// Send presentation for this radio node
#if defined(MY_REPEATER_FEATURE)
present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE);
#else
present(NODE_SENSOR_ID, S_ARDUINO_NODE);
#endif
// Send a configuration exchange request to controller
// Node sends parent node. Controller answers with latest node configuration
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CONFIG, false).set(_nc.parentNodeId));
// Wait configuration reply.
wait(2000, C_INTERNAL, I_CONFIG);
#endif
if (presentation)
presentation();
}
uint8_t getNodeId() {
return _nc.nodeId;
}
uint8_t getParentNodeId() {
return _nc.parentNodeId;
}
ControllerConfig getConfig() {
return _cc;
}
bool _sendRoute(MyMessage &message) {
#if defined(MY_CORE_ONLY)
(void)message;
#endif
#if defined(MY_GATEWAY_FEATURE)
if (message.destination == _nc.nodeId) {
// This is a message sent from a sensor attached on the gateway node.
// Pass it directly to the gateway transport layer.
return gatewayTransportSend(message);
}
#endif
#if defined(MY_RADIO_FEATURE)
return transportSendRoute(message);
#else
return false;
#endif
}
bool send(MyMessage &message, bool enableAck) {
message.sender = _nc.nodeId;
mSetCommand(message, C_SET);
mSetRequestAck(message, enableAck);
#if defined(MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE)
if (_nodeRegistered) {
return _sendRoute(message);
}
else {
debug(PSTR("!MCO:SND:NODE NOT REG\n")); // node not registered
return false;
}
#else
return _sendRoute(message);
#endif
}
void sendBatteryLevel(uint8_t value, bool enableAck) {
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_BATTERY_LEVEL, enableAck).set(value));
}
void sendHeartbeat(void) {
#if defined(MY_RADIO_NRF24) || defined(MY_RADIO_RFM69) || defined(MY_RS485)
uint32_t heartbeat = transportGetHeartbeat();
#else
uint32_t heartbeat = hwMillis();
#endif
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_HEARTBEAT_RESPONSE, false).set(heartbeat));
}
void present(uint8_t childSensorId, uint8_t sensorType, const char *description, bool enableAck) {
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, childSensorId, C_PRESENTATION, sensorType, enableAck).set(childSensorId==NODE_SENSOR_ID?MYSENSORS_LIBRARY_VERSION:description));
}
void sendSketchInfo(const char *name, const char *version, bool enableAck) {
if (name) _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_NAME, enableAck).set(name));
if (version) _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_VERSION, enableAck).set(version));
}
void request(uint8_t childSensorId, uint8_t variableType, uint8_t destination) {
_sendRoute(build(_msgTmp, _nc.nodeId, destination, childSensorId, C_REQ, variableType, false).set(""));
}
void requestTime() {
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_TIME, false).set(""));
}
// Message delivered through _msg
bool _processInternalMessages() {
uint8_t type = _msg.type;
if (_msg.sender == GATEWAY_ADDRESS) {
if (type == I_REBOOT) {
#if !defined(MY_DISABLE_REMOTE_RESET)
// Requires MySensors or other bootloader with watchdogs enabled
setIndication(INDICATION_REBOOT);
hwReboot();
#endif
}
else if (type == I_REGISTRATION_RESPONSE) {
#if defined (MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE)
_nodeRegistered = _msg.getBool();
setIndication(INDICATION_GOT_REGISTRATION);
debug(PSTR("MCO:PIM:NODE REG=%d\n"), _nodeRegistered); // node registration
#endif
}
else if (type == I_CONFIG) {
// Pick up configuration from controller (currently only metric/imperial) and store it in eeprom if changed
_cc.isMetric = _msg.data[0] == 0x00 || _msg.data[0] == 'M'; // metric if null terminated or M
hwWriteConfig(EEPROM_CONTROLLER_CONFIG_ADDRESS, _cc.isMetric);
}
else if (type == I_PRESENTATION) {
// Re-send node presentation to controller
presentNode();
}
else if (type == I_HEARTBEAT) {
sendHeartbeat();
}
else if (type == I_TIME) {
// Deliver time to callback
if (receiveTime)
receiveTime(_msg.getULong());
}
else if (type == I_CHILDREN) {
#if defined(MY_REPEATER_FEATURE)
if (_msg.data[0] == 'C') {
// Clears child relay data for this node
setIndication(INDICATION_CLEAR_ROUTING);
transportClearRoutingTable();
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CHILDREN, false).set("ok"));
}
#endif
}
else if (type == I_DEBUG) {
#if defined(MY_DEBUG) || defined(MY_SPECIAL_DEBUG)
char debug_msg = _msg.data[0];
if (debug_msg == 'R') { // routing table
#if defined(MY_REPEATER_FEATURE)
for (uint8_t cnt = 0; cnt != 255; cnt++) {
uint8_t route = hwReadConfig(EEPROM_ROUTES_ADDRESS + cnt);
if (route != BROADCAST_ADDRESS) {
debug(PSTR("MCO:PIM:ROUTE N=%d,R=%d\n"), cnt, route);
uint8_t OutBuf[2] = { cnt,route };
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set(OutBuf, 2));
wait(200);
}
}
#endif
}
else if (debug_msg == 'V') { // CPU voltage
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set(hwCPUVoltage()));
}
else if (debug_msg == 'F') { // CPU frequency in 1/10Mhz
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set(hwCPUFrequency()));
}
else if (debug_msg == 'M') { // free memory
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set(hwFreeMem()));
}
else if (debug_msg == 'E') { // clear MySensors eeprom area and reboot
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set("ok"));
for (int i = EEPROM_START; i<EEPROM_LOCAL_CONFIG_ADDRESS; i++) hwWriteConfig(i, 0xFF);
setIndication(INDICATION_REBOOT);
hwReboot();
}
#endif
}
else return false;
}
else {
// sender is a node
if (type == I_REGISTRATION_REQUEST) {
#if defined(MY_GATEWAY_FEATURE)
// register request are exclusively handled by GW/Controller
// !!! eventually define if AUTO ACK or register request forwarded to controller
#if !defined(MY_REGISTRATION_CONTROLLER)
// auto register if version compatible
bool approveRegistration = true;
#if defined(MY_CORE_COMPATIBILITY_CHECK)
approveRegistration = (_msg.getByte() >= MY_CORE_MIN_VERSION);
#endif
_sendRoute(build(_msgTmp, _nc.nodeId, _msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_REGISTRATION_RESPONSE, false).set(approveRegistration));
#else
return false; // processing of this request via controller
#endif
#endif
}
else return false;
}
return true;
}
void saveState(uint8_t pos, uint8_t value) {
hwWriteConfig(EEPROM_LOCAL_CONFIG_ADDRESS+pos, value);
}
uint8_t loadState(uint8_t pos) {
return hwReadConfig(EEPROM_LOCAL_CONFIG_ADDRESS+pos);
}
void wait(unsigned long ms) {
unsigned long enter = hwMillis();
while (hwMillis() - enter < ms) {
_process();
#if defined(ARDUINO_ARCH_ESP8266)
yield();
#endif
}
}
bool wait(unsigned long ms, uint8_t cmd, uint8_t msgtype) {
unsigned long enter = hwMillis();
// invalidate msg type
_msg.type = !msgtype;
bool expectedResponse = false;
while ( (hwMillis() - enter < ms) && !expectedResponse ) {
_process();
#if defined(ARDUINO_ARCH_ESP8266)
yield();
#endif
expectedResponse = (mGetCommand(_msg) == cmd && _msg.type == msgtype);
}
return expectedResponse;
}
int8_t sleep(unsigned long ms) {
#if defined(MY_OTA_FIRMWARE_FEATURE)
if (_fwUpdateOngoing) {
// Do not sleep node while fw update is ongoing
wait(ms);
return -1;
}
#endif
// if repeater, do not sleep
#if defined(MY_REPEATER_FEATURE)
wait(ms);
return -1;
#else
#if defined(MY_RADIO_FEATURE)
transportPowerDown();
#endif
setIndication(INDICATION_SLEEP);
const int8_t res = hwSleep(ms);
setIndication(INDICATION_WAKEUP);
return res;
#endif
}
int8_t smartSleep(unsigned long ms) {
// notify controller about going to sleep
sendHeartbeat();
// listen for incoming messages
wait(MY_SMART_SLEEP_WAIT_DURATION);
return sleep(ms);
}
int8_t sleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
#if defined(MY_OTA_FIRMWARE_FEATURE)
if (_fwUpdateOngoing) {
// not supported
return -2;
}
#endif
#if defined(MY_REPEATER_FEATURE)
// not supported
(void)interrupt;
(void)mode;
(void)ms;
return -2;
#else
#if defined(MY_RADIO_FEATURE)
transportPowerDown();
#endif
setIndication(INDICATION_SLEEP);
const int8_t res = hwSleep(interrupt, mode, ms);
setIndication(INDICATION_WAKEUP);
return res;
#endif
}
int8_t smartSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
// notify controller about going to sleep
sendHeartbeat();
// listen for incoming messages
wait(MY_SMART_SLEEP_WAIT_DURATION);
return sleep(interrupt, mode, ms);
}
int8_t sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) {
#if defined(MY_OTA_FIRMWARE_FEATURE)
if (_fwUpdateOngoing) {
// not supported
return -2;
}
#endif
#if defined(MY_REPEATER_FEATURE)
// not supported
(void)interrupt1;
(void)mode1;
(void)interrupt2;
(void)mode2;
(void)ms;
return -2;
#else
#if defined(MY_RADIO_FEATURE)
transportPowerDown();
#endif
setIndication(INDICATION_SLEEP);
const int8_t res = hwSleep(interrupt1, mode1, interrupt2, mode2, ms);
setIndication(INDICATION_WAKEUP);
return res;
#endif
}
int8_t smartSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) {
// notify controller about going to sleep
sendHeartbeat();
// listen for incoming messages
wait(MY_SMART_SLEEP_WAIT_DURATION);
return sleep(interrupt1, mode1, interrupt2, mode2, ms);
}
#ifdef MY_NODE_LOCK_FEATURE
void nodeLock(const char* str) {
// Make sure EEPROM is updated to locked status
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, 0);
while (1) {
setIndication(INDICATION_ERR_LOCKED);
debug(PSTR("MCO:NLK:NODE LOCKED. TO UNLOCK, GND PIN %d AND RESET\n"), MY_NODE_UNLOCK_PIN);
#if defined(ARDUINO_ARCH_ESP8266)
yield();
#endif
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID,C_INTERNAL, I_LOCKED, false).set(str));
#if defined(MY_RADIO_FEATURE)
transportPowerDown();
#endif
setIndication(INDICATION_SLEEP);
(void)hwSleep((unsigned long)1000*60*30); // Sleep for 30 min before resending LOCKED message
setIndication(INDICATION_WAKEUP);
}
}
#endif
+387
View File
@@ -0,0 +1,387 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @file MySensorsCore.h
*
* @defgroup MySensorsCoregrp MySensorsCore
* @ingroup internals
* @{
*
* MySensorsCore-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE
* - [!] Exclamation mark is prepended in case of error
* - SYSTEM:
* - <b>MCO</b> messages emitted by MySensorsCore
* - SUB SYSTEMS:
* - MCO:<b>BGN</b> from @ref _begin()
* - MCO:<b>REG</b> from @ref _registerNode()
* - MCO:<b>SND</b> from @ref send()
* - MCO:<b>PIM</b> from @ref _processInternalMessages()
* - MCO:<b>NLK</b> from @ref nodeLock()
*
* MySensorsCore debug log messages:
*
* |E| SYS | SUB | Message | Comment
* |-|------|-------|-------------------------------------------|----------------------------------------------------------------------------
* | | MCO | BGN | INIT %%s,CP=%%s,LIB=%%s | Core initialization, capabilities (CP), library version (VER)
* | | MCO | BGN | INIT OK,ID=%%d,PAR=%%d,DIS=%%d,REG=%%d | Core initialized, parent ID (PAR), distance to GW (DIS), registration (REG)
* | | MCO | BGN | NODE UNLOCKED | Node successfully unlocked (see signing chapter)
* |!| MCO | BGN | TSP FAIL | Transport initialization failed
* | | MCO | REG | REQ | Registration request
* | | MCO | REG | NOT NEEDED | No registration needed (i.e. GW)
* |!| MCO | SND | NODE NOT REG | Node is not registered, cannot send message
* | | MCO | PIM | NODE REG=%%d | Registration response received, registration status (REG)
* | | MCO | PIM | ROUTE N=%%d,R=%%d | Routing table, messages to node (N) are routed via node (R)
* | | MCO | NLK | NODE LOCKED. UNLOCK: GND PIN %%d AND RESET| Node locked during booting, see signing chapter for additional information
*
*
* @brief API declaration for MySensorsCore
*/
#ifndef MySensorsCore_h
#define MySensorsCore_h
#include "Version.h" // Auto generated by bot
#include "MyConfig.h"
#include "MyEepromAddresses.h"
#include "MyMessage.h"
#include <stddef.h>
#include <stdarg.h>
#define GATEWAY_ADDRESS ((uint8_t)0) //!< Node ID for GW sketch
#define NODE_SENSOR_ID 0xFF //!< Node child is always created/presented when a node is started
#define MY_CORE_VERSION ((uint8_t)2) //!< core version
#define MY_CORE_MIN_VERSION ((uint8_t)2) //!< min core version required for compatibility
#ifdef MY_DEBUG
#define debug(x,...) hwDebugPrint(x, ##__VA_ARGS__) //!< debug
#else
#define debug(x,...) //!< debug NULL
#endif
/**
* @brief Node configuration
*
* This structure stores node-related configurations
*/
struct NodeConfig {
uint8_t nodeId; //!< Current node id
uint8_t parentNodeId; //!< Where this node sends its messages
uint8_t distance; //!< This nodes distance to sensor net gateway (number of hops)
};
/**
* @brief Controller configuration
*
* This structure stores controllerrelated configurations
*/
struct ControllerConfig {
uint8_t isMetric; //!< Flag indicating if metric or imperial measurements are used
};
extern NodeConfig _nc; //!< Node config
extern MyMessage _msg; //!< Buffer for incoming messages
extern MyMessage _msgTmp; //!< Buffer for temporary messages (acks and nonces among others)
#ifdef MY_DEBUG
extern char _convBuf[MAX_PAYLOAD * 2 + 1];
#endif
// **** public functions ********
/**
* Return this nodes id.
*/
uint8_t getNodeId();
/**
* Return the parent node id.
*/
uint8_t getParentNodeId();
/**
* Sends node information to the gateway.
*/
void presentNode();
/**
* 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 sensorId Select a unique sensor id for this sensor. Choose a number between 0-254.
* @param sensorType The sensor type. See sensor typedef in MyMessage.h.
* @param description A textual description of the sensor.
* @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 description A textual description of the sensor.
*/
void present(uint8_t sensorId, uint8_t sensorType, const char *description="", bool ack=false);
/**
* 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
* @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.
*
*/
void sendSketchInfo(const char *name, const char *version, bool ack=false);
/**
* 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.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool send(MyMessage &msg, bool ack=false);
/**
* Send this nodes battery level to gateway.
* @param level Level between 0-100(%)
* @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.
*
*/
void sendBatteryLevel(uint8_t level, bool ack=false);
/**
* Send a heartbeat message (I'm alive!) to the gateway/controller.
* The payload will be an incremental 16 bit integer value starting at 1 when sensor is powered on.
*/
void sendHeartbeat(void);
/**
* Requests a value from gateway or some other sensor in the radio network.
* Make sure to add callback-method in begin-method to handle request responses.
*
* @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 nodeId of other node in radio network. Default is gateway
*/
void request(uint8_t childSensorId, uint8_t variableType, uint8_t destination=GATEWAY_ADDRESS);
/**
* Requests time from controller. Answer will be delivered to receiveTime function in sketch.
*
*/
void requestTime();
/**
* Returns the most recent node configuration received from controller
*/
ControllerConfig getConfig();
/**
* Save a state (in local EEPROM). Good for actuators to "remember" state between
* power cycles.
*
* You have 256 bytes to play with. Note that there is a limitation on the number
* of writes the EEPROM can handle (~100 000 cycles on ATMega328).
*
* @param pos The position to store value in (0-255)
* @param value to store in position
*/
void saveState(uint8_t pos, uint8_t value);
/**
* Load a state (from local EEPROM).
*
* @param pos The position to fetch value from (0-255)
* @return Value to store in position
*/
uint8_t loadState(uint8_t pos);
/**
* Wait for a specified amount of time to pass. Keeps process()ing.
* This does not power-down the radio nor the Arduino.
* Because this calls process() in a loop, it is a good way to wait
* in your loop() on a repeater node or sensor that listens to messages.
* @param ms Number of milliseconds to sleep.
*/
void wait(unsigned long ms);
/**
* Wait for a specified amount of time to pass or until specified message received. Keeps process()ing.
* This does not power-down the radio nor the Arduino.
* Because this calls process() in a loop, it is a good way to wait
* in your loop() on a repeater node or sensor that listens to messages.
* @param ms Number of milliseconds to sleep.
* @param cmd Command of incoming message.
* @param msgtype Message type.
* @return True if specified message received
*/
bool wait(unsigned long ms, uint8_t cmd, uint8_t msgtype);
/**
* Sleep (PowerDownMode) the MCU and radio. Wake up on timer.
* @param ms Number of milliseconds to sleep.
* @return -1 if timer woke it up, -2 if not possible (e.g. ongoing FW update)
*/
int8_t sleep(unsigned long ms);
/**
* Same as sleep(), send heartbeat upon wakeup and process incoming messages
* @param ms Number of milliseconds to sleep.
* @return -1 if timer woke it up, -2 if not possible (e.g. ongoing FW update)
*/
int8_t smartSleep(unsigned long ms);
/**
* Sleep (PowerDownMode) the MCU and radio. Wake up on timer or pin change.
* See: http://arduino.cc/en/Reference/attachInterrupt for details on modes and which pin
* is assigned to what interrupt. On Nano/Pro Mini: 0=Pin2, 1=Pin3
* @param interrupt Interrupt that should trigger the wakeup
* @param mode RISING, FALLING, CHANGE
* @param ms Number of milliseconds to sleep or 0 to sleep forever
* @return Interrupt number wake up was triggered by pin change, -1 if timer woke it up, -2 if not possible (e.g. ongoing FW update)
*/
int8_t sleep(uint8_t interrupt, uint8_t mode, unsigned long ms=0);
/**
* Same as sleep(), send heartbeat upon wakeup and process incoming messages
* @param interrupt Interrupt that should trigger the wakeup
* @param mode RISING, FALLING, CHANGE
* @param ms Number of milliseconds to sleep.
* @return -1 if timer woke it up, -2 if not possible (e.g. ongoing FW update)
*/
int8_t smartSleep(uint8_t interrupt, uint8_t mode, unsigned long ms=0);
/**
* Sleep (PowerDownMode) the MCU and radio. Wake up on timer or pin change for two separate interrupts.
* See: http://arduino.cc/en/Reference/attachInterrupt for details on modes and which pin
* is assigned to what interrupt. On Nano/Pro Mini: 0=Pin2, 1=Pin3
* @param interrupt1 First interrupt that should trigger the wakeup
* @param mode1 Mode for first interrupt (RISING, FALLING, CHANGE)
* @param interrupt2 Second interrupt that should trigger the wakeup
* @param mode2 Mode for second interrupt (RISING, FALLING, CHANGE)
* @param ms Number of milliseconds to sleep or 0 to sleep forever
* @return Interrupt number wake up was triggered by pin change, -1 if timer woke it up, -2 if not possible (e.g. ongoing FW update)
*/
int8_t sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms=0);
/**
* Same as sleep(), send heartbeat upon wakeup and process incoming messages
* @param interrupt1 First interrupt that should trigger the wakeup
* @param mode1 Mode for first interrupt (RISING, FALLING, CHANGE)
* @param interrupt2 Second interrupt that should trigger the wakeup
* @param mode2 Mode for second interrupt (RISING, FALLING, CHANGE)
* @param ms Number of milliseconds to sleep or 0 to sleep forever
* @return Interrupt number wake up was triggered by pin change, -1 if timer woke it up, -2 if not possible (e.g. ongoing FW update)
*/
int8_t smartSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms=0);
#ifdef MY_NODE_LOCK_FEATURE
/**
* @ingroup MyLockgrp
* @ingroup internals
* @brief Lock a node and transmit provided message with 30m intervals
*
* This function is called if suspicious activity has exceeded the threshold (see
* @ref ATTACK_COUNTER_MAX). Unlocking with a normal Arduino bootloader require erasing the EEPROM
* while unlocking with a custom bootloader require holding @ref UNLOCK_PIN low during power on/reset.
*
* @param str The string to transmit.
*/
void nodeLock(const char* str);
#endif
// **** private functions ********
/**
* @brief Node initialisation
*/
void _begin();
/**
* @brief Main framework process
*/
void _process(void);
/**
* @brief Processes internal messages
* @return True if received message requires further processing
*/
bool _processInternalMessages();
/**
* @brief Puts node to a infinite loop if unrecoverable situation detected
*/
void _infiniteLoop();
/**
* @brief Handles registration request
*/
void _registerNode();
/**
* @brief Sends message according to routing table
* @param message
* @return True if message successfully sent to next node
*/
bool _sendRoute(MyMessage &message);
/**
* @brief Callback for incoming messages
* @param message
*/
void receive(const MyMessage &message) __attribute__((weak));
/**
* @brief Callback for incoming time messages
*/
void receiveTime(unsigned long) __attribute__((weak));
/**
* @brief Node presenation
*/
void presentation() __attribute__((weak));
/**
* @brief Called before node initialises
*/
void before() __attribute__((weak));
/**
* @brief Called before any hwInitialization is done
*/
void preHwInit() __attribute__((weak));
/**
* @brief Called after node initialises but before main loop
*/
void setup() __attribute__((weak));
/**
* @brief Main loop
*/
void loop() __attribute__((weak));
// Inline function and macros
static inline MyMessage& build(MyMessage &msg, uint8_t sender, uint8_t destination, uint8_t sensor, uint8_t command, uint8_t type, bool enableAck) {
msg.sender = sender;
msg.destination = destination;
msg.sensor = sensor;
msg.type = type;
mSetCommand(msg,command);
mSetRequestAck(msg,enableAck);
mSetAck(msg,false);
return msg;
}
static inline MyMessage& buildGw(MyMessage &msg, uint8_t type) {
msg.sender = GATEWAY_ADDRESS;
msg.destination = GATEWAY_ADDRESS;
msg.sensor = NODE_SENSOR_ID;
msg.type = type;
mSetCommand(msg, C_INTERNAL);
mSetRequestAck(msg, false);
mSetAck(msg, false);
return msg;
}
#endif
/** @}*/
+450
View File
@@ -0,0 +1,450 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MySigning.h"
#include "drivers/ATSHA204/sha256.h"
#define SIGNING_PRESENTATION_VERSION_1 1
#define SIGNING_PRESENTATION_REQUIRE_SIGNATURES (1 << 0)
#define SIGNING_PRESENTATION_REQUIRE_WHITELISTING (1 << 1)
#if defined(MY_DEBUG_VERBOSE_SIGNING)
#define SIGN_DEBUG(x,...) debug(x, ##__VA_ARGS__)
#else
#define SIGN_DEBUG(x,...)
#endif
#if defined(MY_SIGNING_REQUEST_SIGNATURES) && (!defined(MY_SIGNING_ATSHA204) && !defined(MY_SIGNING_SOFT))
#error You have to pick either MY_SIGNING_ATSHA204 or MY_SIGNING_SOFT in order to require signatures!
#endif
#ifdef MY_SIGNING_FEATURE
uint8_t _doSign[32]; // Bitfield indicating which sensors require signed communication
uint8_t _doWhitelist[32]; // Bitfield indicating which sensors require serial salted signatures
MyMessage _msgSign; // Buffer for message to sign.
uint8_t _signingNonceStatus;
#ifdef MY_NODE_LOCK_FEATURE
static uint8_t nof_nonce_requests = 0;
static uint8_t nof_failed_verifications = 0;
#endif
// Status when waiting for signing nonce in signerProcessInternal
enum { SIGN_WAITING_FOR_NONCE = 0, SIGN_OK = 1 };
// Macros for manipulating signing requirement table
#define DO_SIGN(node) (~_doSign[node>>3]&(1<<node%8))
#define SET_SIGN(node) (_doSign[node>>3]&=~(1<<node%8))
#define CLEAR_SIGN(node) (_doSign[node>>3]|=(1<<node%8))
/*
* Stores signing identifier and a new nonce in provided message for signing operations.
*
* All space in message payload buffer is used for signing identifier and nonce.
* Returns false if subsystem is busy processing an ongoing signing operation.
* If successful, this marks the start of a signing operation at the receiving side so
* implementation is expected to do any necessary initializations within this call.
* This function is typically called as action when receiving a I_NONCE_REQUEST message.
*/
#if defined(MY_SIGNING_SOFT)
extern void signerAtsha204SoftInit(void);
extern bool signerAtsha204SoftCheckTimer(void);
extern bool signerAtsha204SoftGetNonce(MyMessage &msg);
extern void signerAtsha204SoftPutNonce(MyMessage &msg);
extern bool signerAtsha204SoftVerifyMsg(MyMessage &msg);
extern bool signerAtsha204SoftSignMsg(MyMessage &msg);
#endif
#if defined(MY_SIGNING_ATSHA204)
extern void signerAtsha204Init(void);
extern bool signerAtsha204CheckTimer(void);
extern bool signerAtsha204GetNonce(MyMessage &msg);
extern void signerAtsha204PutNonce(MyMessage &msg);
extern bool signerAtsha204VerifyMsg(MyMessage &msg);
extern bool signerAtsha204SignMsg(MyMessage &msg);
#endif
// Helper function to centralize signing/verification exceptions
static bool skipSign(MyMessage &msg) {
if mGetAck(msg) {
SIGN_DEBUG(PSTR("Skipping security for ACK on command %d type %d\n"), mGetCommand(msg), msg.type);
return true;
} else if (mGetCommand(msg) == C_INTERNAL &&
(msg.type == I_NONCE_REQUEST || msg.type == I_NONCE_RESPONSE || msg.type == I_SIGNING_PRESENTATION ||
msg.type == I_ID_REQUEST || msg.type == I_ID_RESPONSE ||
msg.type == I_FIND_PARENT || msg.type == I_FIND_PARENT_RESPONSE ||
msg.type == I_HEARTBEAT || msg.type == I_HEARTBEAT_RESPONSE ||
msg.type == I_PING || msg.type == I_PONG ||
msg.type == I_REGISTRATION_REQUEST )) {
SIGN_DEBUG(PSTR("Skipping security for command %d type %d\n"), mGetCommand(msg), msg.type);
return true;
} else if (mGetCommand(msg) == C_STREAM &&
(msg.type == ST_FIRMWARE_REQUEST || msg.type == ST_FIRMWARE_RESPONSE ||
msg.type == ST_SOUND || msg.type == ST_IMAGE)) {
SIGN_DEBUG(PSTR("Skipping security for command %d type %d\n"), mGetCommand(msg), msg.type);
return true;
} else {
return false;
}
}
#endif // MY_SIGNING_FEATURE
// Helper to prepare a signing presentation message
static void prepareSigningPresentation(MyMessage &msg, uint8_t destination) {
// Only supports version 1 for now
build(msg, _nc.nodeId, destination, NODE_SENSOR_ID, C_INTERNAL, I_SIGNING_PRESENTATION, false).set("");
mSetLength(msg, 2);
mSetPayloadType(msg, P_CUSTOM); // displayed as hex
msg.data[0] = SIGNING_PRESENTATION_VERSION_1;
msg.data[1] = 0;
}
void signerInit(void) {
#if defined(MY_SIGNING_FEATURE)
// Read out the signing requirements from EEPROM
hwReadConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS,
sizeof(_doSign));
// Read out the whitelist requirements from EEPROM
hwReadConfigBlock((void*)_doWhitelist, (void*)EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS,
sizeof(_doWhitelist));
#if defined(MY_SIGNING_SOFT)
signerAtsha204SoftInit();
#endif
#if defined(MY_SIGNING_ATSHA204)
signerAtsha204Init();
#endif
#endif
}
void signerPresentation(MyMessage &msg, uint8_t destination) {
prepareSigningPresentation(msg, destination);
#if defined(MY_SIGNING_REQUEST_SIGNATURES)
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES;
SIGN_DEBUG(PSTR("Signing required\n"));
#endif
#if defined(MY_SIGNING_NODE_WHITELISTING)
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_WHITELISTING;
SIGN_DEBUG(PSTR("Whitelisting required\n"));
#endif
if (!_sendRoute(msg)) {
SIGN_DEBUG(PSTR("Failed to transmit signing presentation!"));
}
#if defined(MY_SIGNING_FEATURE)
// If we do support signing, wait for the gateway to tell us how it prefer us to transmit our messages
if (destination == GATEWAY_ADDRESS) {
SIGN_DEBUG(PSTR("Waiting for GW to send signing preferences...\n"));
wait(2000, C_INTERNAL, I_SIGNING_PRESENTATION);
}
#endif
}
bool signerProcessInternal(MyMessage &msg) {
uint8_t sender = msg.sender;
(void)sender;
if (mGetCommand(msg) == C_INTERNAL) {
#if !defined(MY_SIGNING_FEATURE) && defined(MY_GATEWAY_FEATURE)
// If we act as gateway and do not have the signing feature and receive a signing request we still
// need to do make sure the requester does not believe the gateway still require signatures
if (msg.type == I_SIGNING_PRESENTATION) {
prepareSigningPresentation(msg, sender);
SIGN_DEBUG(PSTR("Informing node %d that we do not require signatures because we do not support it\n"),
sender);
if (!_sendRoute(msg)) {
SIGN_DEBUG(PSTR("Failed to transmit signing presentation!"));
}
return true; // No need to further process I_SIGNING_PRESENTATION in this case
}
#elif defined(MY_SIGNING_FEATURE)
if (msg.type == I_NONCE_REQUEST) {
#if defined(MY_NODE_LOCK_FEATURE)
nof_nonce_requests++;
SIGN_DEBUG(PSTR("Nonce requests left until lockdown: %d\n"), MY_NODE_LOCK_COUNTER_MAX-nof_nonce_requests);
if (nof_nonce_requests >= MY_NODE_LOCK_COUNTER_MAX) {
nodeLock("TMNR"); //Too many nonces requested
}
#endif
#if defined(MY_SIGNING_SOFT)
if (signerAtsha204SoftGetNonce(msg)) {
#endif
#if defined(MY_SIGNING_ATSHA204)
if (signerAtsha204GetNonce(msg)) {
#endif
if (!_sendRoute(build(msg, _nc.nodeId, msg.sender, NODE_SENSOR_ID,
C_INTERNAL, I_NONCE_RESPONSE, false))) {
SIGN_DEBUG(PSTR("Failed to transmit nonce!\n"));
} else {
SIGN_DEBUG(PSTR("Transmitted nonce\n"));
}
} else {
SIGN_DEBUG(PSTR("Failed to generate nonce!\n"));
}
return true; // No need to further process I_NONCE_REQUEST
} else if (msg.type == I_SIGNING_PRESENTATION) {
if (msg.data[0] != SIGNING_PRESENTATION_VERSION_1) {
SIGN_DEBUG(PSTR("Unsupported signing presentation version (%d)!\n"), msg.data[0]);
return true; // Just drop this presentation message
}
// We only handle version 1 here...
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_SIGNATURES) {
// We received an indicator that the sender require us to sign all messages we send to it
SIGN_DEBUG(PSTR("Mark node %d as one that require signed messages\n"), msg.sender);
SET_SIGN(msg.sender);
} else {
// We received an indicator that the sender does not require us to sign all messages we send to it
SIGN_DEBUG(PSTR("Mark node %d as one that do not require signed messages\n"), msg.sender);
CLEAR_SIGN(msg.sender);
}
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_WHITELISTING) {
// We received an indicator that the sender require us to salt signatures with serial
SIGN_DEBUG(PSTR("Mark node %d as one that require whitelisting\n"), msg.sender);
SET_WHITELIST(msg.sender);
} else {
// We received an indicator that the sender does not require us to sign all messages we send to it
SIGN_DEBUG(PSTR("Mark node %d as one that do not require whitelisting\n"), msg.sender);
CLEAR_WHITELIST(msg.sender);
}
// Save updated tables
hwWriteConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS,
sizeof(_doSign));
hwWriteConfigBlock((void*)_doWhitelist, (void*)EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS,
sizeof(_doWhitelist));
// Inform sender about our preference if we are a gateway, but only require signing if the sender
// required signing
// We do not want a gateway to require signing from all nodes in a network just because it wants one node
// to sign it's messages
#if defined(MY_GATEWAY_FEATURE)
prepareSigningPresentation(msg, sender);
#if defined(MY_SIGNING_REQUEST_SIGNATURES)
if (DO_SIGN(sender)) {
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES;
}
#endif
#if defined(MY_SIGNING_NODE_WHITELISTING)
if (DO_WHITELIST(sender)) {
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_WHITELISTING;
}
#endif
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_SIGNATURES) {
SIGN_DEBUG(PSTR("Informing node %d that we require signatures\n"), sender);
} else {
SIGN_DEBUG(PSTR("Informing node %d that we do not require signatures\n"), sender);
}
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_WHITELISTING) {
SIGN_DEBUG(PSTR("Informing node %d that we require whitelisting\n"), sender);
} else {
SIGN_DEBUG(PSTR("Informing node %d that we do not require whitelisting\n"), sender);
}
if (!_sendRoute(msg)) {
SIGN_DEBUG(PSTR("Failed to transmit signing presentation!"));
}
#endif // MY_GATEWAY_FEATURE
return true; // No need to further process I_SIGNING_PRESENTATION
} else if (msg.type == I_NONCE_RESPONSE) {
// Proceed with signing if nonce has been received
SIGN_DEBUG(PSTR("Nonce received from %d. Proceeding with signing...\n"), sender);
if (sender != _msgSign.destination) {
SIGN_DEBUG(PSTR("Nonce did not come from the destination (%d) of the message to be signed! "
"It came from %d.\n"), _msgSign.destination, sender);
SIGN_DEBUG(PSTR("Silently discarding this nonce\n"));
return true; // No need to further process I_NONCE_RESPONSE
}
#if defined(MY_SIGNING_SOFT)
signerAtsha204SoftPutNonce(msg);
#endif
#if defined(MY_SIGNING_ATSHA204)
signerAtsha204PutNonce(msg);
#endif
#if defined(MY_SIGNING_SOFT)
if (!signerAtsha204SoftSignMsg(_msgSign)) {
#endif
#if defined(MY_SIGNING_ATSHA204)
if (!signerAtsha204SignMsg(_msgSign)) {
#endif
SIGN_DEBUG(PSTR("Failed to sign message!\n"));
} else {
SIGN_DEBUG(PSTR("Message signed\n"));
_signingNonceStatus = SIGN_OK; // _msgSign now contains the signed message pending transmission
}
return true; // No need to further process I_NONCE_RESPONSE
}
#endif // MY_SIGNING_FEATURE
}
return false;
}
bool signerCheckTimer(void) {
#if defined(MY_SIGNING_SOFT)
return signerAtsha204SoftCheckTimer();
#elif defined(MY_SIGNING_ATSHA204)
return signerAtsha204CheckTimer();
#else
return true; // Without a configured backend, we always give "positive" results
#endif
}
bool signerSignMsg(MyMessage &msg) {
#if defined(MY_SIGNING_FEATURE)
// If destination is known to require signed messages and we are the sender,
// sign this message unless it is a handshake message
if (DO_SIGN(msg.destination) && msg.sender == _nc.nodeId) {
if (skipSign(msg)) {
return true;
} else {
// Send nonce-request
_signingNonceStatus=SIGN_WAITING_FOR_NONCE;
if (!_sendRoute(build(_msgSign, _nc.nodeId, msg.destination, msg.sensor,
C_INTERNAL, I_NONCE_REQUEST, false).set(""))) {
SIGN_DEBUG(PSTR("Failed to transmit nonce request!\n"));
return false;
}
SIGN_DEBUG(PSTR("Nonce requested from %d. Waiting...\n"), msg.destination);
// We have to wait for the nonce to arrive before we can sign our original message
// Other messages could come in-between. We trust _process() takes care of them
unsigned long enter = hwMillis();
_msgSign = msg; // Copy the message to sign since message buffer might be touched in _process()
while (hwMillis() - enter < MY_VERIFICATION_TIMEOUT_MS && _signingNonceStatus==SIGN_WAITING_FOR_NONCE) {
_process();
}
if (hwMillis() - enter > MY_VERIFICATION_TIMEOUT_MS) {
SIGN_DEBUG(PSTR("Timeout waiting for nonce!\n"));
return false;
}
if (_signingNonceStatus == SIGN_OK) {
// process() received a nonce and signerProcessInternal successfully signed the message
msg = _msgSign; // Write the signed message back
SIGN_DEBUG(PSTR("Message to send has been signed\n"));
} else {
SIGN_DEBUG(PSTR("Message to send could not be signed!\n"));
return false;
}
// After this point, only the 'last' member of the message structure is allowed to be altered if the
// message has been signed, or signature will become invalid and the message rejected by the receiver
}
} else if (_nc.nodeId == msg.sender) {
mSetSigned(msg, 0); // Message is not supposed to be signed, make sure it is marked unsigned
}
#else
(void)msg;
#endif // MY_SIGNING_FEATURE
return true;
}
bool signerVerifyMsg(MyMessage &msg) {
bool verificationResult = true;
// Before processing message, reject unsigned messages if signing is required and check signature
// (if it is signed and addressed to us)
// Note that we do not care at all about any signature found if we do not require signing
#if defined(MY_SIGNING_FEATURE) && defined(MY_SIGNING_REQUEST_SIGNATURES)
// If we are a node, or we are a gateway and the sender require signatures
// and we are the destination...
if ((!MY_IS_GATEWAY || DO_SIGN(msg.sender)) && msg.destination == _nc.nodeId) {
// Internal messages of certain types are not verified
if (skipSign(msg)) {
verificationResult = true;
}
else if (!mGetSigned(msg)) {
// Got unsigned message that should have been signed
SIGN_DEBUG(PSTR("Message is not signed, but it should have been!\n"));
verificationResult = false;
} else {
#if defined(MY_SIGNING_SOFT)
verificationResult = signerAtsha204SoftVerifyMsg(msg);
#endif
#if defined(MY_SIGNING_ATSHA204)
verificationResult = signerAtsha204VerifyMsg(msg);
#endif
if (!verificationResult) {
SIGN_DEBUG(PSTR("Signature verification failed!\n"));
}
#if defined(MY_NODE_LOCK_FEATURE)
if (verificationResult) {
// On successful verification, clear lock counters
nof_nonce_requests = 0;
nof_failed_verifications = 0;
} else {
nof_failed_verifications++;
SIGN_DEBUG(PSTR("Failed verification attempts left until lockdown: %d\n"), MY_NODE_LOCK_COUNTER_MAX-nof_failed_verifications);
if (nof_failed_verifications >= MY_NODE_LOCK_COUNTER_MAX) {
nodeLock("TMFV"); //Too many failed verifications
}
}
#endif
mSetSigned(msg,0); // Clear the sign-flag now as verification is completed
}
}
#else
(void)msg;
#endif // MY_SIGNING_REQUEST_SIGNATURES
return verificationResult;
}
static uint8_t sha256_hash[32];
Sha256Class _soft_sha256;
void signerSha256Init(void) {
memset(sha256_hash, 0, 32);
_soft_sha256.init();
}
void signerSha256Update(const uint8_t* data, size_t sz) {
for (size_t i = 0; i < sz; i++) {
_soft_sha256.write(data[i]);
}
}
uint8_t* signerSha256Final(void) {
memcpy(sha256_hash, _soft_sha256.result(), 32);
return sha256_hash;
}
int signerMemcmp(const void* a, const void* b, size_t sz) {
int retVal;
size_t i;
int done = 0;
const uint8_t* ptrA = (const uint8_t*)a;
const uint8_t* ptrB = (const uint8_t*)b;
for (i=0; i < sz; i++) {
if (ptrA[i] == ptrB[i]) {
if (done > 0) {
done = 1;
} else {
done = 0;
}
} else {
if (done > 0) {
done = 2;
} else {
done = 3;
}
}
}
if (done > 0) {
retVal = -1;
} else {
retVal = 0;
}
return retVal;
}
+715
View File
@@ -0,0 +1,715 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @defgroup MySigninggrp MySigning
* @ingroup internals
* @{
*
* @brief The signing driver provides a generic API for various signing backends to offer
* signing of MySensors messages.
*
* Signing support created by Patrick "Anticimex" Fallberg <patrick@fallberg.net>
*
* @section MySigninggrpbackground Background and concepts
*
* Suppose two participants, Alice and Bob, wants to exchange a message. Alice sends a message to Bob. In MySensors “language” Alice
* could be a gateway and Bob an actuator (light swich, electronic lock, etc). But to be generic, we will substitute the term
* “gateway” with Alice and a “node” with Bob (although the reverse relationship is also supported).
*
* Alice sends a message to Bob. This message can be heard by anyone who wants to listen (and also by anyone that is within “hearing”
* distance). Normally, this is perhaps not a big issue. Nothing Alice says to Bob may be secret or sensitive in any way.
* However, sometimes (or perhaps always) Bob want to be sure that the message Bob receives actually came from Alice. In cryptography,
* this is known as <i>authenticity</i>. Bob needs some way of determining that the message is authentic from Alice, when Bob receives it.
* This prevents an eavesdropper, Eve, to trick Bob into thinking it was Alice that sent a message Eve in fact transmitted. Bob also needs
* to know how to determine if the message has been repeated. Eve could record a message sent by Alice that Bob accepted and then send
* the same message again. Eve could also in some way prevent Bob from receiving the message and delay it in order to permit the message
* to arrive to Bob at a time Eve chooses, instead of Alice. Such an attack is known as a <b>replay attack</b>.<br>
* <i>Authenticity</i> permits Bob to determine if Alice is the true sender of a message.
* @image html MySigning/alicenfriends.png
* @image latex MySigning/alicenfriends.eps "Alice with Eve and Bob" width=6cm
*
* It can also be interesting for Bob to know that the message Alice sent has not been tampered with in any way. This is the
* <i>integrity</i> of the message. We now introduce Mallory, who could be intercepting the communication between Alice and Bob and
* replace some parts of the message but keeping the parts that authenticate the message. That way, Bob still trusts Alice to be the
* source, but the contents of the message was not the content Alice sent. Bob needs to be able to determine that the contents of the
* message was not altered after Alice sent it.<br>
* Mallory would in this case be a <b>man-in-the-middle</b> attacker.<br>
* <i>Integrity</i> permits Bob to verify that the messages received from Alice has not been tampered with.<br>
* This is achieved by adding a <i>signature</i> to the message, which Bob can inspect to validate that Alice is the author.
* @image html MySigning/alicenfriends2.png
* @image latex MySigning/alicenfriends2.eps "Alice with Mallory and Bob" width=9cm
*
* The signing scheme used, needs to address both these attack scenarios. Neither Eve nor Mallory must be permitted to interfere with the
* message exchange between Alice and Bob.
*
* The key challenge to implementing a secure signing scheme is to ensure that every signature is different, even if the message is not.
* If not, <b>replay attacks</b> would be very hard to prevent.<br>
* One way of doing this is to increment some counter on the sender side and include it in the signature. This is however predictable.<br>
* A better option would be to introduce a random number to the signature. That way, it is impossible to predict what the signature will be.
* The problem is, that also makes it impossible for the receiver (Bob) to verify that the signature is valid.<br>
* A solution to this is to let Bob generate the random number, keep it in memory and send it to Alice. Alice can then use the random number
* in the signature calculation and send the signed message back to Bob who can validate the signature with the random number used.
* This random number is in cryptography known as a <a href="https://en.wikipedia.org/wiki/Cryptographic_nonce">nonce</a> or
* <a href="https://en.wikipedia.org/wiki/Salt_%28cryptography%29">salt</a>.
*
* However, Mallory might be eavesdropping on the communication and snoop up the nonce in order to generate a new valid signature for a
* different message. To counter this, both Alice and Bob keep a secret that only they know. This secret is never transmitted over the air,
* nor is it revealed to anybody. This secret is known as a <a href="https://en.wikipedia.org/wiki/Pre-shared_key"> pre-shared key</a> (PSK).
*
* If Eve or Mallory are really sophisticated, he/she might use a <b>delayed replay attack</b>. This can be done by allowing Bob to transmit
* a nonce to Alice. But when Alice transmits the uniquely signed message, Mallory prevents Bob from receiving it, to a point when Mallory
* decides Bob should receive it. An example of such an attack is described
* <a href="http://spencerwhyte.blogspot.se/2014/03/delay-attack-jam-intercept-and-replay.html">here</a>.<br>
* This needs to be addressed as well, and one way of doing this is to have Bob keep track of time between a transmitted nonce and a signed
* message to verify. If Bob is asked for a nonce, Bob knows that a signed message is going to arrive “soon”. Bob can then decide that if the
* signed message does not arrive within a predefined timeframe, Bob throws away the generated nonce and thus makes it impossible to verify
* the message if it arrives late.
*
* The flow can be described like this:
* @image html MySigning/alicenbob.png
* @image latex MySigning/alicenbob.eps "Alice and Bob" width=8cm
* The benefits for MySensors to support this are obvious. Nobody wants others to be able to control or manipulate any actuators in their home.
*
* @section MySigninggrpencryption Why encryption is not part of this
*
* Well, some could be uncomfortable with somebody being able to snoop temperatures, motion or the state changes of locks in the environment.
* Signing does <b>not</b> address these issues. Encryption is needed to prevent this.<br>
* It is my personal standpoint that encryption should not be part of the MySensors “protocol”. The reason is that a gateway and a node does
* not really care about messages being readable or not by “others”. It makes more sense that such guarantees are provided by the underlying
* transmission layer (RF solution in this case). It is the information transmitted over the air that needs to be secret (if user so desires).
* The “trust” level on the other hand needs to go all the way into the sketches (who might have different requirements of trust depending on
* the message participant), and for this reason, it is more important (and less complicated) to ensure authenticity and <i>integrity</i> at
* protocol-level as message contents is still readable throughout the protocol stack. But as soon as the message leaves the “stack” it can be
* scramble into “garbage” when transmitted over the air and then reassembled by a receiving node before being fed in “the clear” up the stack
* at the receiving end.
*
* There are methods and possibilities to provide encryption also in software, but if this is done, it is my recommendation that this is done
* after integrity- and authentication information has been provided to the message (if this is desired). Integrity and authentication is of
* course not mandatory and some might be happy with only having encryption to cover their need for security. I, however, have only focused on
* <i>integrity</i> and <i>authenticity</i> while at the same time keeping the current message routing mechanisms intact and therefore leave
* the matter of <i>secrecy</i> to be implemented in the “physical” transport layer. With the <i>integrity</i> and <i>authenticity</i> handled
* in the protocol it ought to be enough for a simple encryption (nonce-less AES with a PSK for instance) on the message as it is sent to the
* RF backend. Atmel does provide such circuits as well but I have not investigated the matter further as it given the current size of the
* ethernet gateway sketch is close to the size limit on an Arduino Nano, so it will be difficult to fit this into some existing gateway
* designs.<br>
* Also it is worth to consider that the state of a lock can just as readily be determined by simply looking at the door in question or
* attempting to open it, so obfuscating this information will not necessarily deter an attacker in any way.<br>
* Nevertheless, I do acknowledge that people find the fact that all information is sent “in the clear” even if it require some technical
* effort for an intruder to obtain and inspect this information. So I do encourage the use of encrypting transport layers.<br>
* This is however not covered by this implementation.
*
* @section MySigninggrphow How this is done
*
* There exist many forms of message signature solutions to combat Eve and Mallory.<br>
* Most of these solutions are quite complex in term of computations, so I elected to use an algorithm that an external circuit is able to
* process. This has the added benefit of protecting any keys and intermediate data used for calculating the signature so that even if
* someone were to actually steal a sensor and disassembled it, they would not be able to extract the keys and other information from the
* device.<br>
* A common scheme for message signing (authenticity and integrity) is implemented using
* <a href="http://en.wikipedia.org/wiki/Hash-based_message_authentication_code">HMAC</a> which in combination with a strong
* <a href="http://en.wikipedia.org/wiki/Hash_function"> hash function</a> provides a very strong level of protection.<br>
* The <a href="http://www.atmel.com/devices/ATSHA204A.aspx">Atmel ATSHA204A</a> is a low-cost, low-voltage/current circuit that provides
* HMAC calculation capabilities with SHA256 hashing which is a (currently) virtually unbreakable combination. If SHA256 were to be hacked,
* a certain <a href="http://en.wikipedia.org/wiki/Bitcoin">cryptocurrency</a> would immediately be rendered worthless.<br>
* The ATSHA device also contain a random number generator (RNG) which enables the generation of a good nonce, as in,
* <i>non-predictable</i>.<br>
* As I acknowledge that some might not want to use an additional external circuit, I have also implemented a software version of the ATSHA
* device, capable of generating the same signatures as the ATSHA device does. Because it is pure-software however, it does not provide as
* good nonces (it uses the <a href="http://arduino.cc/en/reference/random">Arduino pseudo-random generator</a>) and the HMAC key is stored
* in SW and is therefore readable if the memory is dumped. It also naturally claims more flash space due to the more complex software. But
* for indoor sensors/actuators this might be good enough for most people.
*
* @section MySigninggrphowuse How to use this
*
* Before we begin with the details, I just want to emphasize that signing is completely optional and not enabled by default. If you do want
* the additional security layer signing provides, you pick the backend of your choise in your sketch. Currently, two compatible backends are
* supported; @ref MY_SIGNING_ATSHA204 (hardware backed) and @ref MY_SIGNING_SOFT (software backed). You can either enable these globally in
* @ref MyConfig.h or in your sketch for sketch specific/local use.
*
* <b>Firstly</b>, you need to make sure to pick a backend to use as described above.
* @code{.cpp}
* //#define MY_SIGNING_SOFT
* #define MY_SIGNING_ATSHA204
* #include <MySensors.h>
* ...
* @endcode
* Make sure to set the define before the inclusion of MySensors.h.
* It is legal to mix hardware- and software-based backends in a network. They work together.
*
* You also need to decide if the node (or gateway) in question require and verify signatures in addition to calculating them.
* This has to be set by at least one of the node in a "pair" or nobody will actually start calculating a signature for a message.
* Just set the flag @ref MY_SIGNING_REQUEST_SIGNATURES and the node will inform the gateway that it expects the gateway to sign all
* messages sent to the node. If this is set in a gateway, it will @b NOT force all nodes to sign messages to it. It will only require
* signatures from nodes that in turn require signatures.<br>
* If you want to have two nodes communicate securely directly with each other, the nodes that require signatures must send a presentation
* message to all nodes it expect signed messages from (only the gateway is informed automatically). See @ref signerPresentation().<br>
* A node can have three "states" with respect to signing:
* 1. Node does not support signing in any way (neither @ref MY_SIGNING_ATSHA204 nor @ref MY_SIGNING_SOFT is set)
* 2. Node does support signing but don't require messages sent to it to be signed (@ref MY_SIGNING_REQUEST_SIGNATURES is not set)
* 3. Node does support signing and require messages sent to it to be signed (@ref MY_SIGNING_REQUEST_SIGNATURES is set)
*
* <b>Secondly</b>, you need to verify the configuration for the backend.<br>
* For hardware backed signing it is the pin the device is connected to. In MyConfig.h there are defaults which you might need to adjust to
* match your personal build. The setting is defined using @ref MY_SIGNING_ATSHA204_PIN and the default is to use pin A3.<br>
* Similar to picking your backend, this can also be set in your sketch:
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #define MY_SIGNING_ATSHA204_PIN 4
* #define MY_SIGNING_REQUEST_SIGNATURES
* #include <MySensors.h>
* ...
* @endcode
* For the software backed signingbackend, an unconnected analog pin is required to set a random seed for the pseudo-random generator.
* It is important that the pin is floating, or the output of the pseudo-random generator will be predictable, and thus compromise the
* signatures. The setting is defined using @ref MY_SIGNING_SOFT_RANDOMSEED_PIN and the default is to use pin A7. The same configuration
* possibilities exist as with the other configuration options.
*
* <b>Thirdly</b>, if you use the software backend, you need to personalize the node (see @ref personalization).
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #include <MySensors.h>
* ...
* @endcode
*
* An example of a node that require signatures is available in @ref SecureActuator.ino.
*
* @anchor personalization If you use the “real” ATSHA204A, before any signing operations can be done, the device needs to be <i>personalized</i>.
* This can be a daunting process as it involves irreversibly writing configurations to the device, which cannot be undone. I have however tried
* to simplify the process as much as possibly by creating a helper-sketch specifically for this purpose in @ref SecurityPersonalizer.ino
* Note that you also need to do personalization for software signing, but then the values are stored in EEPROM (described below).
*
* The process of personalizing the ATSHA204A involves
* - Writing and locking chip configuration
* - (optionally) Generating and (mandatory) writing HMAC key
* - (optionally) Locking data sections
*
* First execute the sketch without modifications to make sure communications with the device can be established. It is highly recommended that
* the first time this is done, a device with serial debug possibilities is used.<br>
* When this has been confirmed, it is time to decide what type of personalization is desired.<br>
* There are a few options here.<br>
* Firstly, enable @ref LOCK_CONFIGURATION to allow the sketch to lock the chip configuration. The sketch will write the default settings to the
* chip because these are fine for our purposes. This also enables the RNG which is required to allow the sketch to automatically generate a PSK
* if this is desired.<br>
* Now it is possible to execute the sketch to lock the configuration and enable the RNG.<br>
* Next step is to decide if a new key should be generated or an existing key should be stored to the device. This is determined using
* @ref USER_KEY, which, if defined, will use the data in the variable user_key_data.<br>
* If @ref USER_KEY is disabled, the RNG will be used to generate a key. This key obviously need to be made available to you so you can use
* it in other devices in the network, and this key is therefore also printed on the serial console when it has been generated.<br>
* The key (generated or provided) will be written to the device unless @ref SKIP_KEY_STORAGE is set. As long as the data zone is kept unlocked
* the key can be replaced at any time. However, Atmel suggests the data region to be locked for maximum security. On the other hand, they also
* claim that the key is not readable from the device even if the data zone remains unlocked so the need for locking the data region is optional
* for MySensors usage.<br>
* For devices that does not have serial debug possibilities, it is possible to set @ref SKIP_UART_CONFIRMATION, but it is required to set
* @ref USER_KEY if this option is enabled since a generated and potentially unknown key could be written to the device and thus rendering
* it useless (if the data zone is also locked).<br>
* For devices with serial debug possibilities it is recommended to not use @ref SKIP_UART_CONFIRMATION as the sketch without that setting will
* ask user to send a ‘space’ character on the serial terminal before any locking operations are executed as an additional confirmation that
* this irreversible operation is done. However, if a number of nodes are to undergo personalization, this option can be enabled to streamline
* the personalization.<br>
* This is a condensed description of settings to fully personalize and lock down a set of sensors (and gateways):
* - Pick a “master” device with serial debug port.
* - Set the following sketch configuration of the personalizer:
* -# Enable @ref LOCK_CONFIGURATION
* -# Disable @ref LOCK_DATA
* -# Enable @ref SKIP_KEY_STORAGE
* -# Disable @ref SKIP_UART_CONFIRMATION
* -# Disable @ref USER_KEY
* - Execute the sketch on the “master” device to obtain a randomized key. Save this key to a secure location and keep it confidential so that\n
you can retrieve it if you need to personalize more devices later on.
* - Now reconfigure the sketch with these settings:
* -# Enable @ref LOCK_CONFIGURATION
* -# Enable @ref LOCK_DATA (if you are sure you do not need to replace/revoke the key, this is the most secure option to protect from key readout\n
* according to Atmel, but they also claim that key is not readable even if data region remains unlocked from the slot we are using)
* -# Disable @ref SKIP_KEY_STORAGE
* -# Enable @ref SKIP_UART_CONFIRMATION
* -# Enable @ref USER_KEY
* - Put the saved key in the user_key_data variable.
* - Now execute the sketch on all devices you want to personalize with this secret key.
*
* For software signing the personalization procedure looks slightly different.<br>
* - If you do not have the ATSHA204A device and need to generate random keys:
* -# Enable @ref USE_SOFT_SIGNING
* - If you do have the ATSHA204A and want to use it to generate random keys for soft signing:
* -# Disable @ref USE_SOFT_SIGNING
* - If you want to review existing EEPROM configuration to determine if anything needs to be updated:
* -# Make sure to disable any ATSHA204A update features if you use it (enable @ref SKIP_KEY_STORAGE, disable @ref LOCK_CONFIGURATION and @ref LOCK_DATA)
* -# Disable @ref STORE_SOFT_KEY
* -# Disable @ref STORE_SOFT_SERIAL
* -# Disable @ref STORE_AES_KEY
* - Execute the sketch and the EEPROM configuration will be printed on the serial console.
* - Decide what you want to update and enable the corresponding flags (@ref STORE_SOFT_KEY, @ref STORE_SOFT_SERIAL, @ref STORE_AES_KEY).
* - If you have existing keys and want to use them instead of having the values randomized:
* - Enable the corresponding flag (@ref USER_SOFT_KEY, @ref USER_SOFT_SERIAL, @ref USER_AES_KEY) and also set the appropriate values
* (@ref user_soft_key_data, @ref user_soft_serial, @ref user_aes_key)
*
* The personalizer also takes care of generation and storage of the AES key used for RF traffic encryption.
* See the software signing personalization flow for how to manage it.
*
* That’s it. Personalization is done and the device is ready to execute signing operations which are valid only on your personal network.
*
* If a node does require signing, any unsigned message sent to the node will be rejected.<br>
* This also applies to the gateway. However, the difference is that the gateway will only require signed messages from nodes it knows in turn
* require signed messages.<br>
* A node can also inform a different node that it expects to receive signed messages from it. This is done by transmitting an internal message
* of type @ref I_SIGNING_PRESENTATION and provide flags as payload that inform the receiver of the signing preferences of the sender.<br>
* All nodes and gateways in a network maintain a table where the signing preferences of all nodes are stored. This is also stored in EEPROM so
* if the gateway reboots, the nodes does not have to retransmit a signing presentation to the gateway for the gateway to realize that the node
* expect signed messages.<br>
* Also, the nodes that do not require signed messages will also inform gateway of this, so if you reprogram a node to stop require signing,
* the gateway will adhere to this as soon as the new node has presented itself to the gateway.
*
* The following sequence diagram illustrate how messages are passed in a MySensors network with respect to signing:
* @image html MySigning/signingsequence.png
* @image latex MySigning/signingsequence.eps "Signing sequence diagram" width=9cm
*
* None of this activity is “visible” to you (as the sensor sketch implementor). All you need to do is to set your preferences in MyConfig.h or
* in your sketch. Depending on chosen backend, do personalization or key configurations and set @ref MY_SIGNING_REQUEST_SIGNATURES on the node that
* you want protected. That is enough to enable protection from both Eve and Mallory in your network (although if you do not also enable encryption,
* Eve can eavesdrop, but not do anything about, your messages).
*
* @section MySigningwhitelisting Whitelisting and node revocation
*
* Consider the situation when you have set up your secure topology. We use the remotely operated garage door as an example:
* - You have a node inside your garage (therefore secure and out of reach from prying eyes) that controls your garage door motor.\n
* This node requires signing since you do not want an unauthorized person sending it orders to open the door.
* - You have a keyfob node with a signing backend that uses the same PSK as your door opener node.
*
* In this setup, your keyfob can securely transmit messages to your door node since the keyfob will sign the messages it sends and the door node
* will verify that these were sent from a trusted node (since it used the correct PSK). If the keyfob does not sign the messages, the door node
* will not accept them.
*
* One day your keyfob gets stolen or you lost it or it simply broke down.
*
* You now end up with a problem; you need some way of telling your door node that the keyfob in question cannot be trusted any more. Furthermore,
* you maybe locked the data region in your door nodes ATSHA device and is not able to revoke/change your PSK, or you have some other reason for
* not wanting to replace the PSK. How do you make sure that the "rogue" keyfob can be removed from the "trusted chain"?
*
* The answer to this is whitelisting. You let your door node keep a whitelist of all nodes it trusts. If you stop trusting a particular node, you
* remove it from the nodes whitelist, and it will no longer be able to communicate signed messages to the door node.
*
* In case you want to be able to "whitelist" trusted nodes (in order to be able to revoke them in case they are lost) you also need to take note
* of the serial number of the ATSHA device or the software value stored in EEPROM. This is unique for each device. The serial number is printed
* in a copy+paste friendly format by the personalizer for this purpose.<br>
* The whitelist is stored on the node that require signatures. When a received message is verified, the serial of the sender is looked up in a
* list stored on the receiving node, and the corresponding serial stored in the list for that sender is then included in the signature verification
* process. The list is stored as the value of the flag that enables whitelisting, @ref MY_SIGNING_NODE_WHITELISTING.<br>
*
* Whitelisting is achieved by 'salting' the signature with some node-unique information known to the receiver. In the case of ATSHA204A this is the
* unique serial number programmed into the circuit. This unique number is never transmitted over the air in clear text, so Eve will not be able to
* figure out a "trusted" serial by snooping on the traffic.<br>
* Instead the value is hashed together with the senders NodeId into the HMAC signature to produce the final signature. The receiver will then take
* the originating NodeId of the signed message and do the corresponding calculation with the serial it has stored in it's whitelist if it finds a
* matching entry in it's whitelist.
*
* Whitelisting is an optional alternative because it adds some code and configuration options which might not be desirable for every user. So if
* you want the ability to use whitelists, you need to enable @ref MY_SIGNING_NODE_WHITELISTING.<br>
* The whitelist is provided as value of the flag that enable it as follows (example is a node that require signing as well):
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #define MY_SIGNING_REQUEST_SIGNATURES
* #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}},{.nodeId = 2,.serial = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09}}}
* #include <MySensors.h>
* ...
* @endcode
* In this example, there are two nodes in the whitelist; the gateway, and a separate node that communicates directly with this node (with signed
* messages). You do not need to do anything special for the sending nodes, apart from making sure they support signing.
*
* The "soft" backend of course also support whitelisting. Example:
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}},{.nodeId = 2,.serial = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09}}}
* #include <MySensors.h>
* ...
* @endcode
*
* For a node that should transmit whitelisted messages but not receive whitelisted messages, you do not need any special configurations:
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* @endcode
* Remember that you always need to select a signing backend for all nodes that communicate to a node that require whitelisting.
* Also, note that a node that use whitelisting will not accept messages from nodes that are not present in it's whitelist.
* And you have to personalize all nodes that use signing with a common HMAC key but different serial numbers (ATSHA204A always has unique serials).
*
* @section MySigningtechnical The technical stuff
*
* How are the messages actually affected by the signing?<br>
* The following illustration shows what part of the message is signed, and where the signature is stored:
* @image html MySigning/signingillustrated1.png
* @image latex MySigning/signingillustrated1.eps "Message structure" width=11cm
*
* The first byte of the header is not covered by the signature, because in the network, this byte is used to track hops in the network and therefore
* might change if the message is passing a relay node. So it cannot be part of the signature, or the signature would be invalid when it arrives to
* its destination. The signature also carries a byte with a signing identifier to prevent false results from accidental mixing of incompatible
* signing backends in the network. Thus, the maximum size for a payload is 29-7 bytes. Larger payloads are not possible to sign. Another thing to
* consider is that the strength of the signature is inversely proportional to the payload size.
*
* As for the software backend, it turns out that the ATSHA does not do “vanilla” HMAC processing. Fortunately, Atmel has documented exactly how
* the circuit processes the data and hashes thus making it possible to generate signatures that are identical to signatures generated by the circuit.
*
* The signatures are calculates in the following way:
* @image html MySigning/signingillustrated2.png
* @image latex MySigning/signingillustrated2.eps "HMAC processing" width=10cm
*
* Exactly how this is done can be reviewd in the source for the ATSHA204SOFT backend and the ATSHA204A
* <a href="http://www.atmel.com/Images/Atmel-8885-CryptoAuth-ATSHA204A-Datasheet.pdf">datasheet</a>.
* In the MySensors protocol, the following internal messagetypes handles signature requirements and nonce requests:<br>
* @ref I_SIGNING_PRESENTATION <br>
* @ref I_NONCE_REQUEST <br>
* @ref I_NONCE_RESPONSE <br>
*
* Also, the version field in the header has been reduced from 3 to 2 bits in order to fit a single bit to indicate that a message is signed.
*
* @section MySigninglimitations Known limitations
*
* Due to the limiting factor of our cheapest Arduino nodes, the use of diversified keys is not implemented. That mean that all nodes in your
* network share the same PSK (at least the ones that are supposed to exchange signed data). It is important to understand the implications of
* this, and that is covered in the "Typical use cases" chapter below.<br>
* Also be reminded that the strength of the signature is inversely proportional to the size of the message. The larger the message, the weaker
* the signature.
*
* @section MySigningusecases Typical use cases
*
* "Securely located" in this context mean a node which is not physically publicly accessible. Typically at least your gateway.<br>
* "Public" in this context mean a node that is located outside your "trusted environment". This includes sensors located outdoors, keyfobs etc.
*
* @subsection MySigninglock Securely located lock
*
* You have a securely located gateway and a lock somewhere inside your "trusted environment" (e.g. inside your house door, the door to your
* dungeon or similar).<br>
* You should then keep the data section of your gateway and your lock node unlocked. Locking the data (and therefore the PSK) will require you
* to replace at least the signing circuit in case you need to revoke the PSK because some other node in your network gets compromised.<br>
* You need to make your node require signed messages but you do not necessarily need to make your gateway require signed messsages (unless you
* are concerned that someone might spoof the lock status of your lock).<br>
* Configuration example for the secure lock node:<br>
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #define MY_SIGNING_REQUEST_SIGNATURES
* #include <MySensors.h>
* ...
* @endcode
* If you do also want your gateway to require signatures from your lock you just enable the same (or similar if using software signing) settings
* in the gateway.
*
* @subsection MySigningpatio Patio motion sensor
*
* Your gateway is securely located inside your house, but your motion sensor is located outside your house. You have for some reason elected
* that this node should sign the messages it send to your gateway. You should lock the data (PSK) in this node then, because if someone were
* to steal your patio motion sensor, they could rewrite the firmware and spoof your gateway to use it to transmit a correctly signed message
* to your secure lock inside your house. But if you revoke your gateway (and lock) PSK the outside sensor cannot be used for this anymore.
* Nor can it be changed in order to do it in the future. You can also use whitelisting to revoke your lost node.<br>
* This is an unlikely use case because there really is no reason to sign sensor values. If you for some reason want to obfuscate sensor data,
* encryption is a better alternative.<br>
* Configuration example for a motion sensor:<br>
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #include <MySensors.h>
* ...
* @endcode
*
* The gateway needs to configured with a whitelist (and it have to have an entry for all nodes that send and/or require signed messages):<br>
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = MOTION_SENSOR_ID,.serial = {0x12,0x34,0x56,0x78,0x90,0x12,0x34,0x56,0x78}}}
* #include <MySensors.h>
* ...
* @endcode
* @subsection MySigningkeyfob Keyfob for garage door opener
*
* Perhaps the most typical usecase for signed messages. Your keyfob should be totally locked down. If the garage door opener is secured
* (and it should be) it can be unlocked. That way, if you loose your keyfob, you can revoke the PSK in both the opener and your gateway,
* thus rendering the keyfob useless without having to replace your nodes. You can also use whitelisting to revoke your lost keyfob.<br>
* Configuration example for the keyfob (keyfob will only transmit to another node and not receive anything):<br>
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #include <MySensors.h>
* ...
* @endcode
*
* Configuration example for the door controller node (should require signing from anyone who wants to control it):<br>
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88}},{.nodeId = KEYFOB_ID,.serial = {<FROM ATSHA ON KEYFOB>}}}
* #include <MySensors.h>
* ...
* @endcode
*
* @subsection MySigningsketches Relevant sketches
*
* - @ref SecureActuator.ino
* - @ref SecurityPersonalizer.ino
*/
/**
* @file MySigning.h
*
* @brief API declaration for MySigning signing backend
*/
#ifndef MySigning_h
#define MySigning_h
#include "MySensorsCore.h"
#include "drivers/ATSHA204/ATSHA204.h"
#ifdef MY_SIGNING_NODE_WHITELISTING
typedef struct {
uint8_t nodeId; /**< @brief The ID of the node */
uint8_t serial[SHA204_SERIAL_SZ]; /**< @brief Node specific serial number */
} whitelist_entry_t;
#endif
/** @brief Helper macro to determine the number of elements in a array */
#define NUM_OF(x) (sizeof(x)/sizeof(x[0]))
/** @brief Helper macro to determine if node require serial salted signatures */
#define DO_WHITELIST(node) (~_doWhitelist[node>>3]&(1<<node%8))
/** @brief Helper macro to set that node require serial salted signatures */
#define SET_WHITELIST(node) (_doWhitelist[node>>3]&=~(1<<node%8))
/** @brief Helper macro to set that node does not require serial salted signatures */
#define CLEAR_WHITELIST(node) (_doWhitelist[node>>3]|=(1<<node%8))
/**
* @brief Initializes signing infrastructure and associated backend.
*
* This function makes sure that the internal states of the signing infrastructure
* is set to a known initial state.
* \n@b Usage: This fuction should be called before any signing related operations take place.
*/
void signerInit(void);
/**
* @brief Does signing specific presentation for a node.
*
* This function makes sure any signing related presentation info is shared with the other part.
* The presentation of the gateways signing preferences is done in @ref signerProcessInternal().
* \n@b Usage: This function should be called by the presentation routine of the mysensors library.
* You only need to call this directly from a sketch to set up a node to node signed message exchange.
* If you do call this directly from a sketch, and you at some point change your sketch to go from
* requireing signing to not requireing signatures, you need to present this change to the node at least
* once, so it can update its requirements tables accordingly. Or it will keep believing that this node
* require signatures and attempt to send signed messages to it.
*
* @param msg Message buffer to use.
* @param destination Node ID of the destination.
*/
void signerPresentation(MyMessage &msg, uint8_t destination);
/**
* @brief Manages internal signing message handshaking.
*
* This function takes care of signing related message handshaknig such as nonce exchange.
* \n@b Usage: This function should be called by the incoming message handler before any further message
* processing is performed on internal messages. This function should only be called for @ref C_INTERNAL class
* messages.
*
* @param msg Message buffer to process.
* @returns @c true if caller should stop further message processing.
*/
bool signerProcessInternal(MyMessage &msg);
/**
* @brief Check timeout of verification session.
*
* Nonce will be purged if it takes too long for a signed message to be sent to the receiver.
* \n@b Usage: This function should be called on regular intervals, typically within some process loop.
*
* @returns @c true if session is still valid.
*/
bool signerCheckTimer(void);
/**
* @brief Get nonce from provided message and store for signing operations.
*
* Returns @c false if subsystem is busy processing an ongoing signing operation.<br>
* Returns @c false if signing identifier found in message is not supported by the used backend.<br>
* If successful, this marks the start of a signing operation at the sending side so
* implementation is expected to do any necessary initializations within this call.
* \n@b Usage: This function is typically called as action when receiving a @ref I_NONCE_RESPONSE
* message.
*
* @param msg The message to get the nonce from.
* @returns @c true if successful, else @c false.
*/
bool signerPutNonce(MyMessage &msg);
/**
* @brief Signs provided message. All remaining space in message payload buffer is used for
* signing identifier and signature.
*
* Nonce used for signature calculation is the one generated previously within @ref signerProcessInternal().<br>
* Nonce will be cleared when this function is called to prevent re-use of nonce.<br>
* Returns @c false if subsystem is busy processing an ongoing signing operation.<br>
* Returns @c false if not two bytes or more of free payload space is left in provided message.<br>
* This ends a signing operation at the sending side so implementation is expected to do any
* deinitializations and enter a power saving state within this call.
* \n@b Usage: This function is typically called as action when receiving a @ref I_NONCE_RESPONSE
* message and after @ref signerPutNonce() has successfully been executed.
*
* @param msg The message to sign.
* @returns @c true if successful, else @c false.
*/
bool signerSignMsg(MyMessage &msg);
/**
* @brief Verifies signature in provided message.
*
* Nonce used for verification is the one previously set using @ref signerPutNonce().<br>
* Nonce will be cleared when this function is called to prevent re-use of nonce.<br>
* Returns @c false if subsystem is busy processing an ongoing signing operation.<br>
* Returns @c false if signing identifier found in message is not supported by the used backend.<br>
* This ends a signing operation at the receiving side so implementation is expected to do any
* deinitializations and enter a power saving state within this call.
* \n@b Usage: This function is typically called when receiving a message that is flagged as signed
* and @ref MY_SIGNING_REQUEST_SIGNATURES is activated.
*
* @param msg The message to verify.
* @returns @c true if successful, else @c false.
*/
bool signerVerifyMsg(MyMessage &msg);
/**
* @brief Initialize a hash calculation session.
*
* Any ongoing calculation session will be terminated/reset.
*/
void signerSha256Init(void);
/**
* @brief Add data to an ongoing hash calculation session.
*
* More data can be added by repeated calls to this function.
*
* @param data Buffer with data to add.
* @param sz Size of data buffer.
*/
void signerSha256Update(const uint8_t* data, size_t sz);
/**
* @brief Finalize an ongong hash calculation session and return the hash.
*
* The returned hash size is always 32 bytes. Buffer can be assumed to be valid
* until @ref signerSha256Init() is called again.
*
* @returns Buffer with 32-byte hash.
*/
uint8_t* signerSha256Final(void);
/**
* @brief Do a timing neutral memory comparison.
*
* The function behaves similar to memcmp with the difference that it will
* always use the same number of instructions for a given number of bytes,
* no matter how the two buffers differ and the response is either 0 or -1.
*
* @param a First buffer for comparison.
* @param b Second buffer for comparison.
* @param sz The number of bytes to compare.
* @returns 0 if buffers match, -1 if they do not.
*/
int signerMemcmp(const void* a, const void* b, size_t sz);
#endif
/** @}*/
/**
* @defgroup MySigningTroubleshootinggrp Troubleshooting
* @ingroup MySigninggrp
* @{
*
* @section MySigningTroubleshootingSymptoms Symptoms and solutions
*
* The first thing to do if you suspect signing is causing problems, is to enable the verbose debug
* flag for the signing backend. @see MY_DEBUG_VERBOSE_SIGNING
*
* If you are having trouble getting signing to work, please see the following troubleshooting tips.
*
* @subsection MySigningTroubleshootingSymptomStFail Signing fails and logs show st=fail on transmissions
*
* This is actually not a signing problem, although ofthen st=fail becomes st=ok when signing is disabled.
* This is by far the most commonly reported problem with signing, but the problems is not with signing,
* it is with radio performence.<br>
* This is a typical log which might look like a signing related issue but isn't:
* @code{.unparsed}
* 0;255;3;0;9;Skipping security for command 3 type 16
* 0;255;3;0;9;read: 3-3-0 s=255,c=3,t=16,pt=0,l=0,sg=0:
* 0;255;3;0;9;Signing backend: ATSHA204Soft
* 0;255;3;0;9;SHA256: 86DEAE1DAF50D577A4E2262B33ABF9DEE05DD8FAF84F94F50900000000000000
* 0;255;3;0;9;Skipping security for command 3 type 17
* 0;255;3;0;9;send: 0-0-3-3 s=255,c=3,t=17,pt=6,l=25,sg=0,st=fail:86DEAE1DAF50D577A4E2262B33ABF9DEE05DD8FAF84F94F509
* 0;255;3;0;9;Failed to transmit nonce!
* 0;255;3;0;9;Message is not signed, but it should have been!
* 0;255;3;0;9;verify fail
* @endcode
*
* The reason for this is that when signing is used, the messages transmitted become relatively large.<br>
* Because of this, the message is more sensitive to noise, and the chance for a message to get scrambled
* increase with the message size. Please refer to the troubleshooting section at the MySensors forum for
* information on how to improve radio performence.<br>
* This is a good place to start: https://forum.mysensors.org/topic/666/debug-faq-and-how-ask-for-help
*
* @subsection MySigningTroubleshootingSymptomNonce Failed to generate nonce
*
* The signing backend failed to generate the nonce needed to sign a message. This indicate a hardware
* problem. Please post the debug info on the forum together with a description of your hardware setup.
*
* @subsection MySigningTroubleshootingSymptomSign Failed to sign message
*
* The signing backend failed to sign the message. Typically this happens if your message is so large,
* that there is no room left in the buffer to store a signature.
*
* @subsection MySigningTroubleshootingSymptomWrongSource Nonce did not come from the destination (XX) of the message to be signed! It came from YY
*
* This should not really happen. The reason for this message is that the signing backend is only capable
* of handling one signed message session at any time. If for some reason multiple nodes send a nonce message to
* the same node, only the nonce from a node that is the destination of the current message signing session will be
* accepted. Any other nonces will be dropped. This should not happen as no node should send a nonce unless asked to,
* and a node will only ask for a nonce to one destination for every signing session.<br>
* If you see this message, please post the debugging details on the MySensors forum so it can be investigated further
* together with a description of your setup.
*
* @subsection MySigningTroubleshootingSymptomNotSigned Message is not signed, but it should have been
*
* A node has failed to comply with the signing preferences of this node. Check that the node has received a
* signing presentation message from this node. This is automatically transmitted to gateways. For other nodes,
* you need to transmit the presentation from the sketch. @see signerPresentation
*
* @subsection MySigningTroubleshootingSymptomNotSignedGeneral "Messages do not appear to be signed but I think they should be..."
*
* Make sure you have enabled the flag to require signatures to require signatures and have enabled one of the signing
* backends. @see MY_SIGNING_REQUEST_SIGNATURES @see MY_SIGNING_ATSHA204 @see MY_SIGNING_SOFT
*
* @subsection MySigningTroubleshootingSymptomNotWorkingWhitelisting Signature verification failed!
*
* Make sure both source and destination of the signed message has undergone @ref personalization with the same HMAC key.<br>
* Also, if whitelisting is used, make sure the proper serial is paired with the proper node ID at the destination.
* Whitelisting preferences are communicated with the signing presentation (done automatically from nodes to gateway but
* has to be explicitly done by sketch for node to node communication). @see signerPresentation
*/
/** @}*/
+291
View File
@@ -0,0 +1,291 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
*******************************
*
* DESCRIPTION
* Signing support created by Patrick "Anticimex" Fallberg <patrick@fallberg.net>
* ATSHA204 signing backend. The Atmel ATSHA204 offers true random number generation and
* HMAC-SHA256 authentication with a readout-protected key.
*
*/
#include "MySigning.h"
#define SIGNING_IDENTIFIER (1)
// Define MY_DEBUG_VERBOSE_SIGNING in your sketch to enable signing backend debugprints
unsigned long _signing_timestamp;
bool _signing_verification_ongoing = false;
uint8_t _signing_verifying_nonce[NONCE_NUMIN_SIZE_PASSTHROUGH+SHA204_SERIAL_SZ+1];
uint8_t _signing_signing_nonce[NONCE_NUMIN_SIZE_PASSTHROUGH+SHA204_SERIAL_SZ+1];
uint8_t _signing_temp_message[SHA_MSG_SIZE];
uint8_t _signing_rx_buffer[SHA204_RSP_SIZE_MAX];
uint8_t _signing_tx_buffer[SHA204_CMD_SIZE_MAX];
extern uint8_t _doWhitelist[32];
#ifdef MY_SIGNING_NODE_WHITELISTING
const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING;
#endif
static void signerCalculateSignature(MyMessage &msg, bool signing);
static uint8_t* signerSha256(const uint8_t* data, size_t sz);
#ifdef MY_DEBUG_VERBOSE_SIGNING
static char i2h(uint8_t i)
{
uint8_t k = i & 0x0F;
if (k <= 9)
return '0' + k;
else
return 'A' + k - 10;
}
static void DEBUG_SIGNING_PRINTBUF(const __FlashStringHelper* str, uint8_t* buf, uint8_t sz) {
static char printBuffer[300];
#ifdef MY_GATEWAY_FEATURE
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
snprintf_P(printBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
MY_SERIALDEVICE.print(printBuffer);
#endif
for (int i=0; i<sz; i++)
{
printBuffer[i * 2] = i2h(buf[i] >> 4);
printBuffer[(i * 2) + 1] = i2h(buf[i]);
}
printBuffer[sz * 2] = '\0';
#ifdef MY_GATEWAY_FEATURE
// Truncate message if this is gateway node
printBuffer[MY_GATEWAY_MAX_SEND_LENGTH-1-strlen_P((const char*)str)] = '\0';
#endif
MY_SERIALDEVICE.print(str);
if (sz > 0)
{
MY_SERIALDEVICE.print(printBuffer);
}
MY_SERIALDEVICE.println("");
}
#else
#define DEBUG_SIGNING_PRINTBUF(str, buf, sz)
#endif
void signerAtsha204Init(void) {
atsha204_init(MY_SIGNING_ATSHA204_PIN);
}
bool signerAtsha204CheckTimer(void) {
if (_signing_verification_ongoing) {
if (hwMillis() < _signing_timestamp || hwMillis() > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) {
DEBUG_SIGNING_PRINTBUF(F("Verification timeout"), NULL, 0);
// Purge nonce
memset(_signing_signing_nonce, 0x00, NONCE_NUMIN_SIZE_PASSTHROUGH);
memset(_signing_verifying_nonce, 0x00, NONCE_NUMIN_SIZE_PASSTHROUGH);
_signing_verification_ongoing = false;
return false;
}
}
return true;
}
bool signerAtsha204GetNonce(MyMessage &msg) {
DEBUG_SIGNING_PRINTBUF(F("Signing backend: ATSHA204"), NULL, 0);
// Generate random number for use as nonce
// We used a basic whitening technique that XORs each byte in a 32byte random value with current hwMillis() counter
// This 32-byte random value is then hashed (SHA256) to produce the resulting nonce
(void)atsha204_wakeup(_signing_temp_message);
if (atsha204_execute(SHA204_RANDOM, RANDOM_SEED_UPDATE, 0, 0, NULL,
RANDOM_COUNT, _signing_tx_buffer, RANDOM_RSP_SIZE, _signing_rx_buffer) != SHA204_SUCCESS) {
DEBUG_SIGNING_PRINTBUF(F("Failed to generate nonce"), NULL, 0);
return false;
}
for (int i = 0; i < 32; i++) {
_signing_verifying_nonce[i] = _signing_rx_buffer[SHA204_BUFFER_POS_DATA+i] ^ (hwMillis()&0xFF);
}
memcpy(_signing_verifying_nonce, signerSha256(_signing_verifying_nonce, 32), MAX_PAYLOAD);
atsha204_idle(); // We just idle the chip now since we expect to use it soon when the signed message arrives
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
memset(&_signing_verifying_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_verifying_nonce)-MAX_PAYLOAD);
// Transfer the first part of the nonce to the message
msg.set(_signing_verifying_nonce, MAX_PAYLOAD);
_signing_verification_ongoing = true;
_signing_timestamp = hwMillis(); // Set timestamp to determine when to purge nonce
// Be a little fancy to handle turnover (prolong the time allowed to timeout after turnover)
// Note that if message is "too" quick, and arrives before turnover, it will be rejected
// but this is consider such a rare case that it is accepted and rejects are 'safe'
if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < hwMillis()) _signing_timestamp = 0;
return true;
}
void signerAtsha204PutNonce(MyMessage &msg) {
DEBUG_SIGNING_PRINTBUF(F("Signing backend: ATSHA204"), NULL, 0);
memcpy(_signing_signing_nonce, (uint8_t*)msg.getCustom(), MAX_PAYLOAD);
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
memset(&_signing_signing_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_signing_nonce)-MAX_PAYLOAD);
}
bool signerAtsha204SignMsg(MyMessage &msg) {
// If we cannot fit any signature in the message, refuse to sign it
if (mGetLength(msg) > MAX_PAYLOAD-2) {
DEBUG_SIGNING_PRINTBUF(F("Message too large"), NULL, 0);
return false;
}
// Calculate signature of message
mSetSigned(msg, 1); // make sure signing flag is set before signature is calculated
signerCalculateSignature(msg, true);
if (DO_WHITELIST(msg.destination)) {
// Salt the signature with the senders nodeId and the unique serial of the ATSHA device
memcpy(_signing_signing_nonce, &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], 32); // We can reuse the nonce buffer now since it is no longer needed
_signing_signing_nonce[32] = msg.sender;
atsha204_getSerialNumber(&_signing_signing_nonce[33]);
(void)signerSha256(_signing_signing_nonce, 32+1+SHA204_SERIAL_SZ); // we can 'void' sha256 because the hash is already put in the correct place
DEBUG_SIGNING_PRINTBUF(F("Signature salted with serial"), NULL, 0);
}
// Put device back to sleep
atsha204_sleep();
// Overwrite the first byte in the signature with the signing identifier
_signing_rx_buffer[SHA204_BUFFER_POS_DATA] = SIGNING_IDENTIFIER;
// Transfer as much signature data as the remaining space in the message permits
memcpy(&msg.data[mGetLength(msg)], &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], MAX_PAYLOAD-mGetLength(msg));
DEBUG_SIGNING_PRINTBUF(F("Signature in message: "), (uint8_t*)&msg.data[mGetLength(msg)], MAX_PAYLOAD-mGetLength(msg));
return true;
}
bool signerAtsha204VerifyMsg(MyMessage &msg) {
if (!_signing_verification_ongoing) {
DEBUG_SIGNING_PRINTBUF(F("No active verification session"), NULL, 0);
return false;
} else {
// Make sure we have not expired
if (!signerCheckTimer()) {
return false;
}
_signing_verification_ongoing = false;
if (msg.data[mGetLength(msg)] != SIGNING_IDENTIFIER) {
DEBUG_SIGNING_PRINTBUF(F("Incorrect signing identifier"), NULL, 0);
return false;
}
DEBUG_SIGNING_PRINTBUF(F("Signature in message: "), (uint8_t*)&msg.data[mGetLength(msg)], MAX_PAYLOAD-mGetLength(msg));
signerCalculateSignature(msg, false); // Get signature of message
#ifdef MY_SIGNING_NODE_WHITELISTING
// Look up the senders nodeId in our whitelist and salt the signature with that data
size_t j;
for (j=0; j < NUM_OF(_signing_whitelist); j++) {
if (_signing_whitelist[j].nodeId == msg.sender) {
DEBUG_SIGNING_PRINTBUF(F("Sender found in whitelist"), NULL, 0);
memcpy(_signing_verifying_nonce, &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], 32); // We can reuse the nonce buffer now since it is no longer needed
_signing_verifying_nonce[32] = msg.sender;
memcpy(&_signing_verifying_nonce[33], _signing_whitelist[j].serial, SHA204_SERIAL_SZ);
(void)signerSha256(_signing_verifying_nonce, 32+1+SHA204_SERIAL_SZ); // we can 'void' sha256 because the hash is already put in the correct place
break;
}
}
if (j == NUM_OF(_signing_whitelist)) {
DEBUG_SIGNING_PRINTBUF(F("Sender not found in whitelist, message rejected!"), NULL, 0);
// Put device back to sleep
atsha204_sleep();
return false;
}
#endif
// Put device back to sleep
atsha204_sleep();
// Overwrite the first byte in the signature with the signing identifier
_signing_rx_buffer[SHA204_BUFFER_POS_DATA] = SIGNING_IDENTIFIER;
// Compare the caluclated signature with the provided signature
if (signerMemcmp(&msg.data[mGetLength(msg)], &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], MAX_PAYLOAD-mGetLength(msg))) {
DEBUG_SIGNING_PRINTBUF(F("Signature bad: "), &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], MAX_PAYLOAD-mGetLength(msg));
#ifdef MY_SIGNING_NODE_WHITELISTING
DEBUG_SIGNING_PRINTBUF(F("Is the sender whitelisted and serial correct?"), NULL, 0);
#endif
return false;
} else {
DEBUG_SIGNING_PRINTBUF(F("Signature OK"), NULL, 0);
return true;
}
}
}
// Helper to calculate signature of msg (returned in _signing_rx_buffer[SHA204_BUFFER_POS_DATA])
static void signerCalculateSignature(MyMessage &msg, bool signing) {
(void)atsha204_wakeup(_signing_temp_message);
memset(_signing_temp_message, 0, 32);
memcpy(_signing_temp_message, (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg)));
// Program the data to sign into the ATSHA204
DEBUG_SIGNING_PRINTBUF(F("Message to process: "), (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg)));
DEBUG_SIGNING_PRINTBUF(F("Current nonce: "), signing ? _signing_signing_nonce : _signing_verifying_nonce, 32);
(void)atsha204_execute(SHA204_WRITE, SHA204_ZONE_DATA | SHA204_ZONE_COUNT_FLAG, 8 << 3, 32, _signing_temp_message,
WRITE_COUNT_LONG, _signing_tx_buffer, WRITE_RSP_SIZE, _signing_rx_buffer);
// Program the nonce to use for the signature (has to be done just before GENDIG due to chip limitations)
(void)atsha204_execute(SHA204_NONCE, NONCE_MODE_PASSTHROUGH, 0, NONCE_NUMIN_SIZE_PASSTHROUGH,
signing ? _signing_signing_nonce : _signing_verifying_nonce,
NONCE_COUNT_LONG, _signing_tx_buffer, NONCE_RSP_SIZE_SHORT, _signing_rx_buffer);
// Purge nonce when used
memset(signing ? _signing_signing_nonce : _signing_verifying_nonce, 0x00, NONCE_NUMIN_SIZE_PASSTHROUGH);
// Generate digest of data and nonce
(void)atsha204_execute(SHA204_GENDIG, GENDIG_ZONE_DATA, 8, 0, NULL,
GENDIG_COUNT_DATA, _signing_tx_buffer, GENDIG_RSP_SIZE, _signing_rx_buffer);
// Calculate HMAC of message+nonce digest and secret key
(void)atsha204_execute(SHA204_HMAC, HMAC_MODE_SOURCE_FLAG_MATCH, 0, 0, NULL,
HMAC_COUNT, _signing_tx_buffer, HMAC_RSP_SIZE, _signing_rx_buffer);
DEBUG_SIGNING_PRINTBUF(F("HMAC: "), &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], 32);
}
// Helper to calculate a generic SHA256 digest of provided buffer (only supports one block)
// The pointer to the hash is returned, but the hash is also stored in _signing_rx_buffer[SHA204_BUFFER_POS_DATA])
static uint8_t* signerSha256(const uint8_t* data, size_t sz) {
// Initiate SHA256 calculator
(void)atsha204_execute(SHA204_SHA, SHA_INIT, 0, 0, NULL,
SHA_COUNT_SHORT, _signing_tx_buffer, SHA_RSP_SIZE_SHORT, _signing_rx_buffer);
// Calculate a hash
memset(_signing_temp_message, 0x00, SHA_MSG_SIZE);
memcpy(_signing_temp_message, data, sz);
_signing_temp_message[sz] = 0x80;
// Write length data to the last bytes
_signing_temp_message[SHA_MSG_SIZE-2] = (sz >> 5);
_signing_temp_message[SHA_MSG_SIZE-1] = (sz << 3);
(void)atsha204_execute(SHA204_SHA, SHA_CALC, 0, SHA_MSG_SIZE, _signing_temp_message,
SHA_COUNT_LONG, _signing_tx_buffer, SHA_RSP_SIZE_LONG, _signing_rx_buffer);
DEBUG_SIGNING_PRINTBUF(F("SHA256: "), &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], 32);
return &_signing_rx_buffer[SHA204_BUFFER_POS_DATA];
}
+303
View File
@@ -0,0 +1,303 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
*******************************
*
* DESCRIPTION
* Signing support created by Patrick "Anticimex" Fallberg <patrick@fallberg.net>
* ATSHA204 emulated signing backend. The emulated ATSHA204 implementation offers pseudo random
* number generation and HMAC-SHA256 authentication compatible with a "physical" ATSHA204.
* NOTE: Key is stored in clear text in the Arduino firmware. Therefore, the use of this back-end
* could compromise the key used in the signed message infrastructure if device is lost and its memory
* dumped.
*
*/
#include "MySigning.h"
#include "drivers/ATSHA204/sha256.h"
#define SIGNING_IDENTIFIER (1)
// Define MY_DEBUG_VERBOSE_SIGNING in your sketch to enable signing backend debugprints
Sha256Class _signing_sha256;
unsigned long _signing_timestamp;
bool _signing_verification_ongoing = false;
uint8_t _signing_verifying_nonce[32];
uint8_t _signing_signing_nonce[32];
uint8_t _signing_temp_message[32];
static uint8_t _signing_hmac_key[32];
uint8_t _signing_hmac[32];
extern uint8_t _doWhitelist[32];
static uint8_t _signing_node_serial_info[9];
#ifdef MY_SIGNING_NODE_WHITELISTING
const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING;
#endif
static void signerCalculateSignature(MyMessage &msg, bool signing);
#ifdef MY_DEBUG_VERBOSE_SIGNING
static char i2h(uint8_t i)
{
uint8_t k = i & 0x0F;
if (k <= 9)
return '0' + k;
else
return 'A' + k - 10;
}
static void DEBUG_SIGNING_PRINTBUF(const __FlashStringHelper* str, uint8_t* buf, uint8_t sz) {
static char printBuffer[300];
#ifdef MY_GATEWAY_FEATURE
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
snprintf_P(printBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
MY_SERIALDEVICE.print(printBuffer);
#endif
for (int i=0; i<sz; i++)
{
printBuffer[i * 2] = i2h(buf[i] >> 4);
printBuffer[(i * 2) + 1] = i2h(buf[i]);
}
printBuffer[sz * 2] = '\0';
#ifdef MY_GATEWAY_FEATURE
// Truncate message if this is gateway node
printBuffer[MY_GATEWAY_MAX_SEND_LENGTH-1-strlen_P((const char*)str)] = '\0';
#endif
MY_SERIALDEVICE.print(str);
if (sz > 0)
{
MY_SERIALDEVICE.print(printBuffer);
}
MY_SERIALDEVICE.println("");
}
#else
#define DEBUG_SIGNING_PRINTBUF(str, buf, sz)
#endif
void signerAtsha204SoftInit(void) {
// initialize pseudo-RNG
randomSeed(analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN));
// Set secrets
hwReadConfigBlock((void*)_signing_hmac_key, (void*)EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS, 32);
hwReadConfigBlock((void*)_signing_node_serial_info, (void*)EEPROM_SIGNING_SOFT_SERIAL_ADDRESS, 9);
}
bool signerAtsha204SoftCheckTimer(void) {
if (_signing_verification_ongoing) {
if (hwMillis() < _signing_timestamp || hwMillis() > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) {
DEBUG_SIGNING_PRINTBUF(F("Verification timeout"), NULL, 0);
// Purge nonce
memset(_signing_signing_nonce, 0xAA, 32);
memset(_signing_verifying_nonce, 0xAA, 32);
_signing_verification_ongoing = false;
return false;
}
}
return true;
}
bool signerAtsha204SoftGetNonce(MyMessage &msg) {
DEBUG_SIGNING_PRINTBUF(F("Signing backend: ATSHA204Soft"), NULL, 0);
// We used a basic whitening technique that XORs a random byte with the current hwMillis() counter and then the byte is
// hashed (SHA256) to produce the resulting nonce
_signing_sha256.init();
for (int i = 0; i < 32; i++) {
_signing_sha256.write(random(256) ^ (hwMillis()&0xFF));
}
memcpy(_signing_verifying_nonce, _signing_sha256.result(), MAX_PAYLOAD);
DEBUG_SIGNING_PRINTBUF(F("SHA256: "), _signing_verifying_nonce, 32);
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
memset(&_signing_verifying_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_verifying_nonce)-MAX_PAYLOAD);
// Transfer the first part of the nonce to the message
msg.set(_signing_verifying_nonce, MAX_PAYLOAD);
_signing_verification_ongoing = true;
_signing_timestamp = hwMillis(); // Set timestamp to determine when to purge nonce
// Be a little fancy to handle turnover (prolong the time allowed to timeout after turnover)
// Note that if message is "too" quick, and arrives before turnover, it will be rejected
// but this is consider such a rare case that it is accepted and rejects are 'safe'
if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < hwMillis()) _signing_timestamp = 0;
return true;
}
void signerAtsha204SoftPutNonce(MyMessage &msg) {
DEBUG_SIGNING_PRINTBUF(F("Signing backend: ATSHA204Soft"), NULL, 0);
memcpy(_signing_signing_nonce, (uint8_t*)msg.getCustom(), MAX_PAYLOAD);
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
memset(&_signing_signing_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_signing_nonce)-MAX_PAYLOAD);
}
bool signerAtsha204SoftSignMsg(MyMessage &msg) {
// If we cannot fit any signature in the message, refuse to sign it
if (mGetLength(msg) > MAX_PAYLOAD-2) {
DEBUG_SIGNING_PRINTBUF(F("Message too large"), NULL, 0);
return false;
}
// Calculate signature of message
mSetSigned(msg, 1); // make sure signing flag is set before signature is calculated
signerCalculateSignature(msg, true);
if (DO_WHITELIST(msg.destination)) {
// Salt the signature with the senders nodeId and the (hopefully) unique serial The Creator has provided
_signing_sha256.init();
for (int i=0; i<32; i++) _signing_sha256.write(_signing_hmac[i]);
_signing_sha256.write(msg.sender);
for (int i=0; i<SHA204_SERIAL_SZ; i++) _signing_sha256.write(_signing_node_serial_info[i]);
memcpy(_signing_hmac, _signing_sha256.result(), 32);
DEBUG_SIGNING_PRINTBUF(F("SHA256: "), _signing_hmac, 32);
DEBUG_SIGNING_PRINTBUF(F("Signature salted with serial"), NULL, 0);
}
// Overwrite the first byte in the signature with the signing identifier
_signing_hmac[0] = SIGNING_IDENTIFIER;
// Transfer as much signature data as the remaining space in the message permits
memcpy(&msg.data[mGetLength(msg)], _signing_hmac, MAX_PAYLOAD-mGetLength(msg));
DEBUG_SIGNING_PRINTBUF(F("Signature in message: "), (uint8_t*)&msg.data[mGetLength(msg)], MAX_PAYLOAD-mGetLength(msg));
return true;
}
bool signerAtsha204SoftVerifyMsg(MyMessage &msg) {
if (!_signing_verification_ongoing) {
DEBUG_SIGNING_PRINTBUF(F("No active verification session"), NULL, 0);
return false;
} else {
// Make sure we have not expired
if (!signerCheckTimer()) {
return false;
}
_signing_verification_ongoing = false;
if (msg.data[mGetLength(msg)] != SIGNING_IDENTIFIER) {
DEBUG_SIGNING_PRINTBUF(F("Incorrect signing identifier"), NULL, 0);
return false;
}
// Get signature of message
DEBUG_SIGNING_PRINTBUF(F("Signature in message: "), (uint8_t*)&msg.data[mGetLength(msg)], MAX_PAYLOAD-mGetLength(msg));
signerCalculateSignature(msg, false);
#ifdef MY_SIGNING_NODE_WHITELISTING
// Look up the senders nodeId in our whitelist and salt the signature with that data
size_t j;
for (j=0; j < NUM_OF(_signing_whitelist); j++) {
if (_signing_whitelist[j].nodeId == msg.sender) {
DEBUG_SIGNING_PRINTBUF(F("Sender found in whitelist"), NULL, 0);
_signing_sha256.init();
for (int i=0; i<32; i++) _signing_sha256.write(_signing_hmac[i]);
_signing_sha256.write(msg.sender);
for (int i=0; i<SHA204_SERIAL_SZ; i++) _signing_sha256.write(_signing_whitelist[j].serial[i]);
memcpy(_signing_hmac, _signing_sha256.result(), 32);
DEBUG_SIGNING_PRINTBUF(F("SHA256: "), _signing_hmac, 32);
break;
}
}
if (j == NUM_OF(_signing_whitelist)) {
DEBUG_SIGNING_PRINTBUF(F("Sender not found in whitelist, message rejected!"), NULL, 0);
return false;
}
#endif
// Overwrite the first byte in the signature with the signing identifier
_signing_hmac[0] = SIGNING_IDENTIFIER;
// Compare the caluclated signature with the provided signature
if (signerMemcmp(&msg.data[mGetLength(msg)], _signing_hmac, MAX_PAYLOAD-mGetLength(msg))) {
DEBUG_SIGNING_PRINTBUF(F("Signature bad: "), _signing_hmac, MAX_PAYLOAD-mGetLength(msg));
#ifdef MY_SIGNING_NODE_WHITELISTING
DEBUG_SIGNING_PRINTBUF(F("Is the sender whitelisted and serial correct?"), NULL, 0);
#endif
return false;
} else {
DEBUG_SIGNING_PRINTBUF(F("Signature OK"), NULL, 0);
return true;
}
}
}
// Helper to calculate signature of msg (returned in hmac)
static void signerCalculateSignature(MyMessage &msg, bool signing) {
memset(_signing_temp_message, 0, 32);
memcpy(_signing_temp_message, (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg)));
DEBUG_SIGNING_PRINTBUF(F("Message to process: "), (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg)));
DEBUG_SIGNING_PRINTBUF(F("Current nonce: "), signing ? _signing_signing_nonce : _signing_verifying_nonce, 32);
// ATSHA204 calculates the HMAC with a PSK and a SHA256 digest of the following data:
// 32 bytes zeroes
// 32 bytes digest,
// 1 byte OPCODE (0x11)
// 1 byte Mode (0x04)
// 2 bytes SlotID (0x0000)
// 11 bytes zeroes
// SN[8] (0xEE)
// 4 bytes zeroes
// SN[0:1] (0x0123)
// 2 bytes zeroes
// The digest is calculated as a SHA256 digest of the following:
// 32 bytes message
// 1 byte OPCODE (0x15)
// 1 byte param1 (0x02)
// 2 bytes param2 (0x0800)
// SN[8] (0xEE)
// SN[0:1] (0x0123)
// 25 bytes zeroes
// 32 bytes nonce
// Calculate message digest first
_signing_sha256.init();
for (int i=0; i<32; i++) _signing_sha256.write(_signing_temp_message[i]);
_signing_sha256.write(0x15); // OPCODE
_signing_sha256.write(0x02); // param1
_signing_sha256.write(0x08); // param2(1)
_signing_sha256.write(0x00); // param2(2)
_signing_sha256.write(0xEE); // SN[8]
_signing_sha256.write(0x01); // SN[0]
_signing_sha256.write(0x23); // SN[1]
for (int i=0; i<25; i++) _signing_sha256.write(0x00);
for (int i=0; i<32; i++) _signing_sha256.write(signing ? _signing_signing_nonce[i] : _signing_verifying_nonce[i]);
// Purge nonce when used
memset(signing ? _signing_signing_nonce : _signing_verifying_nonce, 0xAA, 32);
memcpy(_signing_temp_message, _signing_sha256.result(), 32);
// Feed "message" to HMAC calculator
_signing_sha256.initHmac(_signing_hmac_key,32); // Set the key to use
for (int i=0; i<32; i++) _signing_sha256.write(0x00); // 32 bytes zeroes
for (int i=0; i<32; i++) _signing_sha256.write(_signing_temp_message[i]); // 32 bytes digest
_signing_sha256.write(0x11); // OPCODE
_signing_sha256.write(0x04); // Mode
_signing_sha256.write(0x00); // SlotID(1)
_signing_sha256.write(0x00); // SlotID(2)
for (int i=0; i<11; i++) _signing_sha256.write(0x00); // 11 bytes zeroes
_signing_sha256.write(0xEE); // SN[8]
for (int i=0; i<4; i++) _signing_sha256.write(0x00); // 4 bytes zeroes
_signing_sha256.write(0x01); // SN[0]
_signing_sha256.write(0x23); // SN[1]
for (int i=0; i<2; i++) _signing_sha256.write(0x00); // 2 bytes zeroes
memcpy(_signing_hmac, _signing_sha256.resultHmac(), 32);
DEBUG_SIGNING_PRINTBUF(F("HMAC: "), _signing_hmac, 32);
}
+720
View File
@@ -0,0 +1,720 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyTransport.h"
// SM: transitions and update states
static transportState stInit = { stInitTransition, NULL };
static transportState stParent = { stParentTransition, stParentUpdate };
static transportState stID = { stIDTransition, stIDUpdate };
static transportState stUplink = { stUplinkTransition, NULL };
static transportState stReady = { stReadyTransition, stReadyUpdate };
static transportState stFailure = { stFailureTransition, stFailureUpdate };
// transport SM variables
static transportSM _transportSM;
// stInit: initialize transport HW
void stInitTransition() {
TRANSPORT_DEBUG(PSTR("TSM:INIT\n"));
// initialize status variables
_transportSM.pingActive = false;
_transportSM.transportActive = false;
#if defined(MY_TRANSPORT_SANITY_CHECK) || defined(MY_REPEATER_FEATURE)
_transportSM.lastSanityCheck = hwMillis();
#endif
_transportSM.lastUplinkCheck = 0;
// Read node settings (ID, parentId, GW distance) from EEPROM
hwReadConfigBlock((void*)&_nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(NodeConfig));
// initialize radio
if (!transportInit()) {
TRANSPORT_DEBUG(PSTR("!TSM:INIT:TSP FAIL\n"));
setIndication(INDICATION_ERR_INIT_TRANSPORT);
transportSwitchSM(stFailure);
}
else {
TRANSPORT_DEBUG(PSTR("TSM:INIT:TSP OK\n"));
_transportSM.transportActive = true;
#if defined(MY_GATEWAY_FEATURE)
// Set configuration for gateway
TRANSPORT_DEBUG(PSTR("TSM:INIT:GW MODE\n"));
_nc.parentNodeId = GATEWAY_ADDRESS;
_nc.distance = 0;
_nc.nodeId = GATEWAY_ADDRESS;
transportSetAddress(GATEWAY_ADDRESS);
// GW mode: skip FPAR,ID,UPL states
transportSwitchSM(stReady);
#else
if ((uint8_t)MY_NODE_ID != AUTO) {
TRANSPORT_DEBUG(PSTR("TSM:INIT:STATID,ID=%d\n"),MY_NODE_ID);
// Set static id
_nc.nodeId = MY_NODE_ID;
// Save static id in eeprom
hwWriteConfig(EEPROM_NODE_ID_ADDRESS, MY_NODE_ID);
}
// set ID if static or set in EEPROM
if (_nc.nodeId == AUTO || transportAssignNodeID(_nc.nodeId)) {
// if node ID > 0, proceed to next state
transportSwitchSM(stParent);
}
else {
// ID invalid (=0), nothing we can do
transportSwitchSM(stFailure);
}
#endif
}
}
// stParent: find parent
void stParentTransition() {
TRANSPORT_DEBUG(PSTR("TSM:FPAR\n")); // find parent
setIndication(INDICATION_FIND_PARENT);
_transportSM.uplinkOk = false;
_transportSM.preferredParentFound = false;
#if defined(MY_PARENT_NODE_IS_STATIC)
TRANSPORT_DEBUG(PSTR("TSM:FPAR:STATP=%d\n"),MY_PARENT_NODE_ID); // static parent
_transportSM.findingParentNode = false;
_nc.distance = 1; // assumption, CHKUPL:GWDC will update this variable
_nc.parentNodeId = MY_PARENT_NODE_ID;
// skipping find parent
setIndication(INDICATION_GOT_PARENT);
transportSwitchSM(stID);
#else
_transportSM.findingParentNode = true;
_nc.distance = DISTANCE_INVALID; // Set distance to max and invalidate parent node ID
_nc.parentNodeId = AUTO;
// Broadcast find parent request
transportRouteMessage(build(_msgTmp, _nc.nodeId, BROADCAST_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT, false).set(""));
#endif
}
// stParentUpdate
void stParentUpdate() {
#if !defined(MY_PARENT_NODE_IS_STATIC)
if (transportTimeInState() > STATE_TIMEOUT || _transportSM.preferredParentFound) {
// timeout or preferred parent found
if (_nc.parentNodeId != AUTO) {
// parent assigned
TRANSPORT_DEBUG(PSTR("TSM:FPAR:OK\n")); // find parent ok
_transportSM.findingParentNode = false;
setIndication(INDICATION_GOT_PARENT);
// go to next state
transportSwitchSM(stID);
}
else if (transportTimeInState() > STATE_TIMEOUT) {
// timeout w/o reply or valid parent
if (_transportSM.retries < STATE_RETRIES) {
// retries left
TRANSPORT_DEBUG(PSTR("!TSM:FPAR:NO REPLY\n")); // find parent, no reply
// reenter state
transportSwitchSM(stParent);
}
else {
// no retries left, finding parent failed
TRANSPORT_DEBUG(PSTR("!TSM:FPAR:FAIL\n")); // find parent fail
setIndication(INDICATION_ERR_FIND_PARENT);
transportSwitchSM(stFailure);
}
}
}
#endif
}
// stID: verify and request ID if necessary
void stIDTransition() {
TRANSPORT_DEBUG(PSTR("TSM:ID\n")); // verify/request node ID
if (_nc.nodeId == AUTO) {
// send ID request
setIndication(INDICATION_REQ_NODEID);
TRANSPORT_DEBUG(PSTR("TSM:ID:REQ\n")); // request node ID
transportRouteMessage(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_ID_REQUEST, false).set(""));
}
}
void stIDUpdate() {
if (_nc.nodeId != AUTO) {
// current node ID is valid
TRANSPORT_DEBUG(PSTR("TSM:ID:OK,ID=%d\n"), _nc.nodeId);
setIndication(INDICATION_GOT_NODEID);
// proceed to next state
transportSwitchSM(stUplink);
}
else if (transportTimeInState() > STATE_TIMEOUT) {
// timeout
if (_transportSM.retries < STATE_RETRIES) {
// reenter
transportSwitchSM(stID);
}
else {
// no retries left
TRANSPORT_DEBUG(PSTR("!TSM:ID:FAIL,ID=%d\n"), _nc.nodeId);
setIndication(INDICATION_ERR_GET_NODEID);
transportSwitchSM(stFailure);
}
}
}
void stUplinkTransition() {
TRANSPORT_DEBUG(PSTR("TSM:UPL\n"));
// check uplink
if(transportCheckUplink(true)) {
// uplink ok, i.e. GW replied
TRANSPORT_DEBUG(PSTR("TSM:UPL:OK\n")); // uplink ok
// proceed to next state
transportSwitchSM(stReady);
}
else {
// uplink failed, at this point, no retries or timeout
TRANSPORT_DEBUG(PSTR("!TSM:UPL:FAIL\n")); // uplink failed
// go back to stParent
transportSwitchSM(stParent);
}
}
void stReadyTransition() {
// transport is ready and fully operational
TRANSPORT_DEBUG(PSTR("TSM:READY\n")); // transport is ready
_transportSM.uplinkOk = true;
_transportSM.failedUplinkTransmissions = 0; // reset counter
}
// stReadyUpdate: monitors uplink failures
void stReadyUpdate() {
#if !defined(MY_GATEWAY_FEATURE)
if (_transportSM.failedUplinkTransmissions > TRANSMISSION_FAILURES) {
// too many uplink transmissions failed, find new parent (if non-static)
#if !defined(MY_PARENT_NODE_IS_STATIC)
TRANSPORT_DEBUG(PSTR("!TSM:READY:UPL FAIL,SNP\n")); // uplink failed, search new parent
transportSwitchSM(stParent);
#else
TRANSPORT_DEBUG(PSTR("!TSM:READY:UPL FAIL,STATP\n")); // uplink failed, static parent
// reset counter
_transportSM.failedUplinkTransmissions = 0;
#endif
}
#endif
}
// stFailure: entered upon HW init failure or max retries exceeded
void stFailureTransition() {
TRANSPORT_DEBUG(PSTR("TSM:FAILURE\n"));
_transportSM.uplinkOk = false; // uplink nok
_transportSM.transportActive = false; // transport inactive
setIndication(INDICATION_ERR_INIT_TRANSPORT);
// power down transport, no need until re-init
TRANSPORT_DEBUG(PSTR("TSM:FAILURE:PDT\n")); // power down transport
transportPowerDown();
}
void stFailureUpdate() {
if (transportTimeInState()> TIMEOUT_FAILURE_STATE) {
TRANSPORT_DEBUG(PSTR("TSM:FAILURE:RE-INIT\n")); // attempt to re-initialize transport
transportSwitchSM(stInit);
}
}
void transportSwitchSM(transportState& newState) {
if (_transportSM.currentState != &newState) {
// state change, reset retry counter
_transportSM.retries = 0;
// change state
_transportSM.currentState = &newState;
}
else {
_transportSM.retries++; // increment retries
}
// Transition event
if (_transportSM.currentState->Transition) _transportSM.currentState->Transition();
// save time
_transportSM.stateEnter = hwMillis();
}
uint32_t transportTimeInState() {
return hwMillis() - _transportSM.stateEnter;
}
void transportUpdateSM(){
if (_transportSM.currentState->Update) _transportSM.currentState->Update();
}
bool isTransportReady() {
return _transportSM.uplinkOk;
}
bool isTransportSearchingParent() {
return _transportSM.findingParentNode;
}
void transportInitialize() {
// intial state
_transportSM.currentState = &stFailure;
transportSwitchSM(stInit);
}
// update TSM and process incoming messages
void transportProcess() {
// update state machine
transportUpdateSM();
// process transport FIFO
transportProcessFIFO();
}
bool transportCheckUplink(bool force) {
if (!force && (hwMillis() - _transportSM.lastUplinkCheck) < CHKUPL_INTERVAL) {
TRANSPORT_DEBUG(PSTR("TSF:CHKUPL:OK,FCTRL\n")); // flood control
return true;
}
// ping GW
uint8_t hopsCount = transportPingNode(GATEWAY_ADDRESS);
// verify hops
if (hopsCount != INVALID_HOPS) {
// update
_transportSM.lastUplinkCheck = hwMillis();
TRANSPORT_DEBUG(PSTR("TSF:CHKUPL:OK\n"));
// did distance to GW change upstream, eg. re-routing of uplink nodes
if (hopsCount != _nc.distance) {
TRANSPORT_DEBUG(PSTR("TSF:CHKUPL:DGWC,O=%d,N=%d\n"), _nc.distance, hopsCount); // distance to GW changed
_nc.distance = hopsCount;
}
return true;
}
else {
TRANSPORT_DEBUG(PSTR("TSF:CHKUPL:FAIL\n"));
return false;
}
}
bool transportAssignNodeID(uint8_t newNodeId) {
// verify if ID valid
if (newNodeId != GATEWAY_ADDRESS && newNodeId != AUTO) {
_nc.nodeId = newNodeId;
transportSetAddress(newNodeId);
// Write ID to EEPROM
hwWriteConfig(EEPROM_NODE_ID_ADDRESS, newNodeId);
TRANSPORT_DEBUG(PSTR("TSF:ASID:OK,ID=%d\n"),newNodeId);
return true;
}
else {
TRANSPORT_DEBUG(PSTR("!TSF:ASID:FAIL,ID=%d\n"),newNodeId);
setIndication(INDICATION_ERR_NET_FULL);
return false;
}
}
bool transportRouteMessage(MyMessage &message) {
uint8_t destination = message.destination;
uint8_t route;
if (_transportSM.findingParentNode && destination != BROADCAST_ADDRESS) {
TRANSPORT_DEBUG(PSTR("!TSF:ROUTE:FPAR ACTIVE\n")); // find parent active, message not sent
// request to send a non-BC message while finding parent active, abort
return false;
}
if (destination == GATEWAY_ADDRESS) {
route = _nc.parentNodeId; // message to GW always routes via parent
}
else if (destination == BROADCAST_ADDRESS) {
route = BROADCAST_ADDRESS; // message to BC does not require routing
}
else {
#if defined(MY_REPEATER_FEATURE)
// destination not GW & not BC, get route
route = hwReadConfig(EEPROM_ROUTES_ADDRESS + destination);
if (route == AUTO) {
// route unknown
if (message.last != _nc.parentNodeId) {
// message not from parent, i.e. child node - route it to parent
TRANSPORT_DEBUG(PSTR("!TSF:ROUTE:%d UNKNOWN\n"), destination);
route = _nc.parentNodeId;
}
else {
// route unknown and msg received from parent, send it to destination assuming in rx radius
route = destination;
}
}
#else
route = _nc.parentNodeId; // not a repeater, all traffic routed via parent
#endif
}
// send message
bool ok = transportSendWrite(route, message);
#if !defined(MY_GATEWAY_FEATURE)
// update counter
if (route == _nc.parentNodeId) {
if (!ok) {
setIndication(INDICATION_ERR_TX);
_transportSM.failedUplinkTransmissions++;
}
else _transportSM.failedUplinkTransmissions = 0;
}
#else
if(!ok) setIndication(INDICATION_ERR_TX);
#endif
return ok;
}
bool transportSendRoute(MyMessage &message) {
if (isTransportReady()) {
return transportRouteMessage(message);
}
else {
// TNR: transport not ready
TRANSPORT_DEBUG(PSTR("!TSF:SEND:TNR\n"));
return false;
}
}
// only be used inside transport
bool transportWait(uint32_t ms, uint8_t cmd, uint8_t msgtype){
uint32_t enter = hwMillis();
// invalidate msg type
_msg.type = !msgtype;
bool expectedResponse = false;
while ((hwMillis() - enter < ms) && !expectedResponse) {
// process incoming messages
transportProcessFIFO();
#if defined(ARDUINO_ARCH_ESP8266)
yield();
#endif
expectedResponse = (mGetCommand(_msg) == cmd && _msg.type == msgtype);
}
return expectedResponse;
}
uint8_t transportPingNode(uint8_t targetId) {
if(!_transportSM.pingActive){
if(targetId == _nc.nodeId) {
// ping to ourself, pingActive remains false
return 0;
}
_transportSM.pingActive = true;
_transportSM.pingResponse = INVALID_HOPS;
TRANSPORT_DEBUG(PSTR("TSF:PING:SEND,TO=%d\n"), targetId);
transportRouteMessage(build(_msgTmp, _nc.nodeId, targetId, NODE_SENSOR_ID, C_INTERNAL, I_PING, false).set((uint8_t)0x01));
// Wait for ping reply or timeout
transportWait(2000, C_INTERNAL, I_PONG);
// make sure missing I_PONG msg does not block pinging function by leaving pignActive=true
_transportSM.pingActive = false;
return _transportSM.pingResponse;
}
else {
return INVALID_HOPS;
}
}
void transportClearRoutingTable() {
for (uint8_t i = 0; i != 255; i++) {
hwWriteConfig(EEPROM_ROUTES_ADDRESS + i, BROADCAST_ADDRESS);
}
TRANSPORT_DEBUG(PSTR("TSF:CRT:OK\n")); // clear routing table
}
uint32_t transportGetHeartbeat() {
return transportTimeInState();
}
void transportProcessMessage() {
(void)signerCheckTimer(); // Manage signing timeout
uint8_t payloadLength = transportReceive((uint8_t *)&_msg);
(void)payloadLength; // currently not used
setIndication(INDICATION_RX);
uint8_t command = mGetCommand(_msg);
uint8_t type = _msg.type;
uint8_t sender = _msg.sender;
uint8_t last = _msg.last;
uint8_t destination = _msg.destination;
TRANSPORT_DEBUG(PSTR("TSF:MSG:READ,%d-%d-%d,s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d:%s\n"),
sender, last, destination, _msg.sensor, mGetCommand(_msg), type, mGetPayloadType(_msg), mGetLength(_msg), mGetSigned(_msg), _msg.getString(_convBuf));
// verify protocol version
if(mGetVersion(_msg) != PROTOCOL_VERSION) {
setIndication(INDICATION_ERR_VERSION);
TRANSPORT_DEBUG(PSTR("!TSF:MSG:PVER,%d!=%d\n"), mGetVersion(_msg),PROTOCOL_VERSION); // protocol version mismatch
return;
}
// Reject messages that do not pass verification
if (!signerVerifyMsg(_msg)) {
setIndication(INDICATION_ERR_SIGN);
TRANSPORT_DEBUG(PSTR("!TSF:MSG:SIGN VERIFY FAIL\n"));
return;
}
// Is message addressed to this node?
if (destination == _nc.nodeId) {
// prevent buffer overflow by limiting max. possible message length (5 bits=31 bytes max) to MAX_PAYLOAD (25 bytes)
mSetLength(_msg, min(mGetLength(_msg),MAX_PAYLOAD));
// null terminate data
_msg.data[mGetLength(_msg)] = 0x00;
// update routing table if msg not from parent
#if defined(MY_REPEATER_FEATURE)
if (last != _nc.parentNodeId) {
// Message is from one of the child nodes. Add it to routing table.
hwWriteConfig(EEPROM_ROUTES_ADDRESS+sender, last);
}
#endif
// Check if sender requests an ack back.
if (mGetRequestAck(_msg)) {
_msgTmp = _msg; // Copy message
mSetRequestAck(_msgTmp, false); // Reply without ack flag (otherwise we would end up in an eternal loop)
mSetAck(_msgTmp, true); // set ACK flag
_msgTmp.sender = _nc.nodeId;
_msgTmp.destination = sender;
// send ACK
TRANSPORT_DEBUG(PSTR("TSF:MSG:ACK REQ\n")); // ACK requested
// use transportSendRoute since ACK reply is not internal, i.e. if !transportOK do not reply
transportSendRoute(_msgTmp);
}
if(!mGetAck(_msg)) {
// only process if not ACK
if (command == C_INTERNAL) {
// Process signing related internal messages
if (signerProcessInternal(_msg)) {
return; // Signer processing indicated no further action needed
}
#if !defined(MY_GATEWAY_FEATURE)
if (type == I_ID_RESPONSE) {
#if (MY_NODE_ID == AUTO)
// only active if node ID dynamic
transportAssignNodeID(_msg.getByte());
#endif
return; // no further processing required
}
if (type == I_FIND_PARENT_RESPONSE) {
#if !defined(MY_GATEWAY_FEATURE) && !defined(MY_PARENT_NODE_IS_STATIC)
if (_transportSM.findingParentNode) { // only process if find parent active
// Reply to a I_FIND_PARENT message. Check if the distance is shorter than we already have.
uint8_t distance = _msg.getByte();
TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR RES,ID=%d,D=%d\n"), sender, distance); // find parent response
if (isValidDistance(distance)) {
// Distance to gateway is one more for us w.r.t. parent
distance++;
// update settings if distance shorter or preferred parent found
if (((isValidDistance(distance) && distance < _nc.distance) || (!_autoFindParent && sender == MY_PARENT_NODE_ID)) && !_transportSM.preferredParentFound) {
// Found a neighbor closer to GW than previously found
if (!_autoFindParent && sender == MY_PARENT_NODE_ID) {
_transportSM.preferredParentFound = true;
TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR PREF FOUND\n")); // find parent, preferred parent found
}
_nc.distance = distance;
_nc.parentNodeId = sender;
TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR OK,ID=%d,D=%d\n"), _nc.parentNodeId, _nc.distance);
}
}
}
else {
TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR INACTIVE\n")); // find parent response received, but inactive
}
return;
#endif
}
#endif
// general
if (type == I_PING) {
TRANSPORT_DEBUG(PSTR("TSF:MSG:PINGED,ID=%d,HP=%d\n"), sender, _msg.getByte()); // node pinged
transportRouteMessage(build(_msgTmp, _nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_PONG, false).set((uint8_t)0x01));
return; // no further processing required
}
if (type == I_PONG) {
if (_transportSM.pingActive) {
_transportSM.pingActive = false;
_transportSM.pingResponse = _msg.getByte();
TRANSPORT_DEBUG(PSTR("TSF:MSG:PONG RECV,HP=%d\n"), _transportSM.pingResponse); // pong received
}
return; // no further processing required
}
if (_processInternalMessages()) {
return; // no further processing required
}
} else if (command == C_STREAM) {
#if defined(MY_OTA_FIRMWARE_FEATURE)
if(firmwareOTAUpdateProcess()){
return; // OTA FW update processing indicated no further action needed
}
#endif
}
}
else {
TRANSPORT_DEBUG(PSTR("TSF:MSG:ACK\n")); // received message is ACK, no internal processing, handover to msg callback
}
#if defined(MY_GATEWAY_FEATURE)
// Hand over message to controller
gatewayTransportSend(_msg);
#endif
// Call incoming message callback if available
if (receive) {
receive(_msg);
}
return;
}
else if (destination == BROADCAST_ADDRESS) {
TRANSPORT_DEBUG(PSTR("TSF:MSG:BC\n")); // broadcast msg
if (command == C_INTERNAL) {
if (isTransportReady()) {
// only reply if node is fully operational
if (type == I_FIND_PARENT) {
#if defined(MY_REPEATER_FEATURE)
if (sender != _nc.parentNodeId) { // no circular reference
TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR REQ,ID=%d\n"), sender); // FPR: find parent request
// node is in our range, update routing table - important if node has new repeater as parent
hwWriteConfig(EEPROM_ROUTES_ADDRESS + sender, sender);
// check if uplink functional - node can only be parent node if link to GW functional
// this also prevents circular references in case GW ooo
if(transportCheckUplink(false)){
_transportSM.lastUplinkCheck = hwMillis();
TRANSPORT_DEBUG(PSTR("TSF:MSG:GWL OK\n")); // GW uplink ok
// delay minimizes collisions
delay(hwMillis() & 0x3ff);
transportRouteMessage(build(_msgTmp, _nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT_RESPONSE, false).set(_nc.distance));
}
else {
TRANSPORT_DEBUG(PSTR("!TSF:MSG:GWL FAIL\n")); // GW uplink fail, do not respond to parent request
}
}
#endif
return; // no further processing required
}
}
if (type == I_DISCOVER) {
if (last == _nc.parentNodeId) {
// random wait to minimize collisions
delay(hwMillis() & 0x3ff);
transportRouteMessage(build(_msgTmp, _nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_DISCOVER_RESPONSE, false).set(_nc.parentNodeId));
// no return here (for fwd if repeater)
}
}
}
// controlled BC relay
#if defined(MY_REPEATER_FEATURE)
// controlled BC repeating: forward only if message received from parent and sender not self to prevent circular fwds
if(last == _nc.parentNodeId && sender != _nc.nodeId && isTransportReady()){
TRANSPORT_DEBUG(PSTR("TSF:MSG:FWD BC MSG\n")); // controlled broadcast msg forwarding
transportRouteMessage(_msg);
}
#endif
// Call incoming message callback if available, but only if message received from parent
if (command != C_INTERNAL && last == _nc.parentNodeId && receive) {
receive(_msg);
}
}
else {
// msg not to us and not BC, relay msg
#if defined(MY_REPEATER_FEATURE)
if (isTransportReady()) {
TRANSPORT_DEBUG(PSTR("TSF:MSG:REL MSG\n")); // relay msg
// update routing table if message not received from parent
if (last != _nc.parentNodeId) {
hwWriteConfig(EEPROM_ROUTES_ADDRESS + sender, last);
}
if (command == C_INTERNAL) {
if (type == I_PING || type == I_PONG) {
uint8_t hopsCnt = _msg.getByte();
if (hopsCnt != MAX_HOPS) {
TRANSPORT_DEBUG(PSTR("TSF:MSG:REL PxNG,HP=%d\n"), hopsCnt);
_msg.set((uint8_t)(hopsCnt + 1));
}
}
}
// Relay this message to another node
transportRouteMessage(_msg);
}
#else
TRANSPORT_DEBUG(PSTR("!TSF:MSG:REL MSG,NORP\n")); // message relaying request, but not a repeater
#endif
}
}
void transportInvokeSanityCheck() {
if (!transportSanityCheck()) {
TRANSPORT_DEBUG(PSTR("!TSF:SANCHK:FAIL\n")); // sanity check fail
transportSwitchSM(stFailure);
return;
}
else {
TRANSPORT_DEBUG(PSTR("TSF:SANCHK:OK\n")); // sanity check ok
}
}
inline void transportProcessFIFO() {
if (_transportSM.transportActive) {
#if defined(MY_TRANSPORT_SANITY_CHECK) || defined(MY_REPEATER_FEATURE)
if (hwMillis() - _transportSM.lastSanityCheck > MY_TRANSPORT_SANITY_CHECK_INTERVAL) {
_transportSM.lastSanityCheck = hwMillis();
transportInvokeSanityCheck();
}
#endif
}
else {
// transport not active, nothing to be done
return;
}
uint8_t _processedMessages = MAX_SUBSEQ_MSGS;
// process all msgs in FIFO or counter exit
while (transportAvailable() && _processedMessages--) {
transportProcessMessage();
}
#if defined(MY_OTA_FIRMWARE_FEATURE)
if (isTransportReady()) {
// only process if transport ok
firmwareOTAUpdateRequest();
}
#endif
}
bool transportSendWrite(uint8_t to, MyMessage &message) {
// set protocol version and update last
mSetVersion(message, PROTOCOL_VERSION);
message.last = _nc.nodeId;
// sign message if required
if (!signerSignMsg(message)) {
TRANSPORT_DEBUG(PSTR("!TSF:MSG:SIGN FAIL\n"));
setIndication(INDICATION_ERR_SIGN);
return false;
}
// msg length changes if signed
uint8_t length = mGetSigned(message) ? MAX_MESSAGE_LENGTH : mGetLength(message);
// send
setIndication(INDICATION_TX);
bool ok = transportSend(to, &message, min(MAX_MESSAGE_LENGTH, HEADER_SIZE + length));
TRANSPORT_DEBUG(PSTR("%sTSF:MSG:SEND,%d-%d-%d-%d,s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d,ft=%d,st=%s:%s\n"),
(ok || to == BROADCAST_ADDRESS ? "" : "!"),message.sender,message.last, to, message.destination, message.sensor, mGetCommand(message), message.type,
mGetPayloadType(message), mGetLength(message), mGetSigned(message), _transportSM.failedUplinkTransmissions, to==BROADCAST_ADDRESS ? "bc" : (ok ? "OK":"NACK"), message.getString(_convBuf));
return (ok || to==BROADCAST_ADDRESS);
}
// EOF MyTransport.cpp
+391
View File
@@ -0,0 +1,391 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @file MyTransport.h
*
* @defgroup MyTransportgrp MyTransport
* @ingroup internals
* @{
*
* Transport-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE
* - [!] Exclamation mark is prepended in case of error
* - SYSTEM:
* - <b>TSM</b>: messages emitted by the transport state machine
* - <b>TSF</b>: messages emitted by transport support functions
* - SUB SYSTEMS:
* - Transport state machine (<b>TSM</b>)
* - TSM:INIT from <b>stInit</b> Initialize transport and radio
* - TSM:FPAR from <b>stParent</b> Find parent
* - TSM:ID from <b>stID</b> Check/request node ID, if dynamic node ID set
* - TSM:UPL from <b>stUplink</b> Verify uplink connection by pinging GW
* - TSM:READY from <b>stReady</b> Transport is ready and fully operational
* - TSM:FAILURE from <b>stFailure</b> Failure in transport link or transport HW
* - Transport support function (<b>TSF</b>)
* - TSF:CHKUPL from @ref transportCheckUplink(), checks connection to GW
* - TSF:ASID from @ref transportAssignNodeID(), assigns node ID
* - TSF:PING from @ref transportPingNode(), pings a node
* - TSF:CRT from @ref transportClearRoutingTable(), clears routing table stored in EEPROM
* - TSF:MSG from @ref transportProcessMessage(), processes incoming message
* - TSF:SANCHK from @ref transportInvokeSanityCheck(), calls transport-specific sanity check
* - TSF:ROUTE from @ref transportRouteMessage(), sends message
* - TSF:SEND from @ref transportSendRoute(), sends message if transport is ready (exposed)
*
* Transport debug log messages:
*
* |E| SYS | SUB | Message | Comment
* |-|------|-----------|-----------------------|---------------------------------------------------------------------
* | | TSM | INIT | | <b>Transition to stInit state</b>
* | | TSM | INIT | STATID,ID=%%d | Node ID is static
* | | TSM | INIT | TSP OK | Transport device configured and fully operational
* | | TSM | INIT | GW MODE | Node is set up as GW, thus omitting ID and findParent states
* |!| TSM | INIT | TSP FAIL | Transport device initialization failed
* | | TSM | FPAR | | <b>Transition to stParent state</b>
* | | TSM | FPAR | STATP=%%d | Static parent set, skip finding parent
* | | TSM | FPAR | OK | Parent node identified
* |!| TSM | FPAR | NO REPLY | No potential parents replied to find parent request
* |!| TSM | FPAR | FAIL | Finding parent failed
* | | TSM | ID | | <b>Transition to stID state</b>
* | | TSM | ID | OK,ID=%%d | Node ID is valid
* | | TSM | ID | REQ | Request node ID from controller
* |!| TSM | ID | FAIL,ID=%%d | ID verification failed, ID invalid
* | | TSM | UPL | | <b>Transition to stUplink state</b>
* | | TSM | UPL | OK | Uplink OK, GW returned ping
* |!| TSM | UPL | FAIL | Uplink check failed, i.e. GW could not be pinged
* | | TSM | READY | | <b>Transition to stReady</b>, i.e. transport is ready and fully operational
* |!| TSM | READY | UPL FAIL,SNP | Too many failed uplink transmissions, search new parent
* |!| TSM | READY | FAIL,STATP | Too many failed uplink transmissions, static parent enforced
* | | TSM | FAILURE | | <b>Transition to stFailure state</b>
* | | TSM | FAILURE | PDT | Power-down transport
* | | TSM | FAILURE | RE-INIT | Attempt to re-initialize transport
* | | TSF | CHKUPL | OK | Uplink OK
* | | TSF | CHKUPL | OK,FCTRL | Uplink OK, flood control prevents pinging GW in too short intervals
* | | TSF | CHKUPL | DGWC,O=%%d,N=%%d | Uplink check revealed changed network topology, old distance (O), new distance (N)
* | | TSF | CHKUPL | FAIL | No reply received when checking uplink
* | | TSF | ASID | OK,ID=%%d | Node ID assigned
* |!| TSF | ASID | FAIL,ID=%%d | Assigned ID is invalid
* | | TSF | PING | SEND,TO=%%d | Send ping to destination (TO)
* | | TSF | MSG | ACK REQ | ACK message requested
* | | TSF | MSG | ACK | ACK message, do not proceed but forward to callback
* | | TSF | MSG | FPAR RES,ID=%%d,D=%%d | Response to find parent received from node (ID) with distance (D) to GW
* | | TSF | MSG | FPAR PREF FOUND | Preferred parent found, i.e. parent defined via MY_PARENT_NODE_ID
* | | TSF | MSG | FPAR OK,ID=%%d,D=%%d | Find parent response from node (ID) is valid, distance (D) to GW
* | | TSF | MSG | FPAR INACTIVE | Find parent response received, but no find parent request active, skip response
* | | TSF | MSG | FPAR REQ,ID=%%d | Find parent request from node (ID)
* | | TSF | MSG | PINGED,ID=%%d,HP=%%d | Node pinged by node (ID) with (HP) hops
* | | TSF | MSG | PONG RECV,HP=%%d | Pinged node replied with (HP) hops
* | | TSF | MSG | BC | Broadcast message received
* | | TSF | MSG | GWL OK | Link to GW ok
* | | TSF | MSG | FWD BC MSG | Controlled broadcast message forwarding
* | | TSF | MSG | REL MSG | Relay message
* | | TSF | MSG | REL PxNG,HP=%%d | Relay PING/PONG message, increment hop counter (HP)
* |!| TSF | MSG | PVER,%%d!=%%d | Message protocol version mismatch
* |!| TSF | MSG | SIGN VERIFY FAIL | Signing verification failed
* |!| TSF | MSG | REL MSG,NORP | Node received a message for relaying, but node is not a repeater, message skipped
* |!| TSF | MSG | SIGN FAIL | Signing message failed
* |!| TSF | MSG | GWL FAIL | GW uplink failed
* | | TSF | SANCHK | OK | Sanity check passed
* |!| TSF | SANCHK | FAIL | Sanity check failed, attempt to re-initialize radio
* | | TSF | CRT | OK | Clearing routing table successful
* |!| TSF | ROUTE | FPAR ACTIVE | Finding parent active, message not sent
* |!| TSF | ROUTE | DST %%d UNKNOWN | Routing for destination (DST) unknown, send message to parent
* |!| TSF | SEND | TNR | Transport not ready, message cannot be sent
*
* Incoming / outgoing messages:
*
* See <a href="https://www.mysensors.org/download/serial_api_20">here</a> for more detail on the format and definitons.
*
* Receiving a message
* - TSF:MSG:READ,sender-last-destination,s=%%d,c=%%d,t=%%d,pt=%%d,l=%%d,sg=%%d:%%s
*
* Sending a message
* - [!]TSF:MSG:SEND,sender-last-next-destination,s=%%d,c=%%d,t=%%d,pt=%%d,l=%%d,sg=%%d,ft=%%d,st=%%s:%%s
*
* Message fields:
* - <b>s</b>=sensor ID
* - <b>c</b>=command
* - <b>t</b>=msg type
* - <b>pt</b>=payload type
* - <b>l</b>=length
* - <b>ft</b>=failed uplink transmission counter
* - <b>sg</b>=signing flag
*
* @brief API declaration for MyTransport
*
*/
#ifndef MyTransport_h
#define MyTransport_h
#include "MySensorsCore.h"
// debug
#if defined(MY_DEBUG)
#define TRANSPORT_DEBUG(x,...) debug(x, ##__VA_ARGS__) //!< debug
#else
#define TRANSPORT_DEBUG(x,...) //!< debug NULL
#endif
#if defined(MY_REPEATER_FEATURE)
#define TRANSMISSION_FAILURES 10 //!< search for a new parent node after this many transmission failures, higher threshold for repeating nodes
#else
#define TRANSMISSION_FAILURES 5 //!< search for a new parent node after this many transmission failures, lower threshold for non-repeating nodes
#endif
#define TIMEOUT_FAILURE_STATE 10000 //!< duration failure state
#define STATE_TIMEOUT 2000 //!< general state timeout
#define STATE_RETRIES 3 //!< retries before switching to FAILURE
#define AUTO 255 //!< ID 255 is reserved for auto initialization of nodeId.
#define BROADCAST_ADDRESS ((uint8_t)255) //!< broadcasts are addressed to ID 255
#define DISTANCE_INVALID ((uint8_t)255) //!< invalid distance when searching for parent
#define MAX_HOPS ((uint8_t)254) //!< maximal mumber of hops for ping/pong
#define INVALID_HOPS ((uint8_t)255) //!< invalid hops
#define MAX_SUBSEQ_MSGS 5 //!< Maximum number of subsequentially processed messages in FIFO (to prevent transport deadlock if HW issue)
#define CHKUPL_INTERVAL ((uint32_t)10000) //!< Minimum time interval to re-check uplink
#define _autoFindParent (bool)(MY_PARENT_NODE_ID == AUTO) //!< returns true if static parent id is undefined
#define isValidDistance(distance) (bool)(distance!=DISTANCE_INVALID) //!< returns true if distance is valid
#define isValidParent(parent) (bool)(parent != AUTO) //!< returns true if parent is valid
/**
* @brief SM state
*
* This structure stores SM state definitions
*/
struct transportState {
void(*Transition)(); //!< state transition function
void(*Update)(); //!< state update function
};
/**
* @brief Status variables and SM state
*
* This structure stores transport status and SM variables
*/
typedef struct {
transportState* currentState; //!< pointer to current fsm state
uint32_t stateEnter; //!< state enter timepoint
uint32_t lastUplinkCheck; //!< last uplink check, required to prevent GW flooding
uint32_t lastSanityCheck; //!< last sanity check
// 8 bits
bool findingParentNode : 1; //!< flag finding parent node is active
bool preferredParentFound : 1; //!< flag preferred parent found
bool uplinkOk : 1; //!< flag uplink ok
bool pingActive : 1; //!< flag ping active
bool transportActive : 1; //!< flag transport active
uint8_t reserved : 3; //!< reserved
// 8 bits
uint8_t retries : 4; //!< retries / state re-enter
uint8_t failedUplinkTransmissions : 4; //!< counter failed uplink transmissions
// 8 bits
uint8_t pingResponse; //!< stores hops received in I_PONG
} __attribute__((packed)) transportSM;
// PRIVATE functions
/**
* @brief Initialize SM variables and transport HW
*/
void stInitTransition();
/**
* @brief Find parent
*/
void stParentTransition();
/**
* @brief Verify find parent responses
*/
void stParentUpdate();
/**
* @brief Send ID request
*/
void stIDTransition();
/**
* @brief Verify ID response and GW link
*/
void stIDUpdate();
/**
* @brief Send uplink ping request
*/
void stUplinkTransition();
/**
* @brief Set transport OK
*/
void stReadyTransition();
/**
* @brief Monitor transport link
*/
void stReadyUpdate();
/**
* @brief Transport failure and power down radio
*/
void stFailureTransition();
/**
* @brief Re-initialize transport after timeout
*/
void stFailureUpdate();
/**
* @brief Switch SM state
* @param newState New state to switch SM to
*/
void transportSwitchSM(transportState& newState);
/**
* @brief Update SM state
*/
void transportUpdateSM();
/**
* @brief Request time in current SM state
* @return ms in current state
*/
uint32_t transportTimeInState();
/**
* @brief Call transport driver sanity check
*/
void transportInvokeSanityCheck();
/**
* @brief Process all pending messages in RX FIFO
*/
void transportProcessFIFO();
/**
* @brief Receive message from RX FIFO and process
*/
void transportProcessMessage();
/**
* @brief Assign node ID
* @param newNodeId New node ID
* @return true if node ID valid and successfully assigned
*/
bool transportAssignNodeID(uint8_t newNodeId);
/**
* @brief Wait and process messages for a defined amount of time until specified message received
* @param ms Time to wait and process incoming messages in ms
* @param cmd Specific command
* @param msgtype Specific message type
* @return true if specified command received within waiting time
*/
bool transportWait(uint32_t ms, uint8_t cmd, uint8_t msgtype);
/**
* @brief Ping node
* @param targetId Node to be pinged
* @return hops from pinged node or 255 if no answer received within 2000ms
*/
uint8_t transportPingNode(uint8_t targetId);
/**
* @brief Send and route message according to destination
*
* This function is used in MyTransport and omits the transport state check, i.e. message can be sent even if transport is not ready
*
* @param message
* @return true if message sent successfully
*/
bool transportRouteMessage(MyMessage &message);
/**
* @brief Send and route message according to destination with transport state check
* @param message
* @return true if message sent successfully and false if sending error or transport !OK
*/
bool transportSendRoute(MyMessage &message);
/**
* @brief Send message to recipient
* @param to Recipient of message
* @param message
* @return true if message sent successfully
*/
bool transportSendWrite(uint8_t to, MyMessage &message);
/**
* @brief Check uplink to GW, includes flooding control
* @param force to override flood control timer
* @return true if uplink ok
*/
bool transportCheckUplink(bool force);
// PUBLIC functions
/**
* @brief Initialize transport and SM
*/
void transportInitialize();
/**
* @brief Process FIFO msg and update SM
*/
void transportProcess();
/**
* @brief Flag transport ready
* @return true if transport is initialize and ready
*/
bool isTransportReady();
/**
* @brief Flag searching parent ongoing
* @return true if transport is searching for parent
*/
bool isTransportSearchingParent();
/**
* @brief Clear routing table
*/
void transportClearRoutingTable();
/**
* @brief Return heart beat, i.e. ms in current state
*/
uint32_t transportGetHeartbeat();
// interface functions for radio driver
/**
* @brief Initialize transport HW
* @return true if initialization successful
*/
bool transportInit();
/**
* @brief Set node address
*/
void transportSetAddress(uint8_t address);
/**
* @brief Retrieve node address
*/
uint8_t transportGetAddress();
/**
* @brief Send message
* @param to recipient
* @param data message to be sent
* @param len length of message (header + payload)
* @return true if message sent successfully
*/
bool transportSend(uint8_t to, const void* data, uint8_t len);
/**
* @brief Verify if RX FIFO has pending messages
* @return true if message available in RX FIFO
*/
bool transportAvailable();
/**
* @brief Sanity check for transport: is transport still responsive?
* @return true transport ok
*/
bool transportSanityCheck();
/**
* @brief Receive message from FIFO
* @return length of recevied message (header + payload)
*/
uint8_t transportReceive(void* data);
/**
* @brief Power down transport HW
*/
void transportPowerDown();
#endif // MyTransport_h
/** @}*/
+148
View File
@@ -0,0 +1,148 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyConfig.h"
#include "MyTransport.h"
#include "drivers/RF24/RF24.h"
#include "drivers/CircularBuffer/CircularBuffer.h"
#if defined(MY_RF24_ENABLE_ENCRYPTION)
#include "drivers/AES/AES.h"
#endif
#ifdef MY_RF24_IRQ_PIN
typedef struct _transportQueuedMessage
{
uint8_t m_len; // Length of the data
uint8_t m_data[MAX_MESSAGE_LENGTH]; // The raw data
} transportQueuedMessage;
/** Buffer to store queued messages in. */
static transportQueuedMessage transportRxQueueStorage[MY_RX_MESSAGE_BUFFER_SIZE];
/** Circular buffer, which uses the transportRxQueueStorage and administers stored messages. */
static CircularBuffer<transportQueuedMessage> transportRxQueue(transportRxQueueStorage, MY_RX_MESSAGE_BUFFER_SIZE);
static volatile uint8_t transportLostMessageCount = 0;
static void transportRxCallback(void)
{
// Called for each message received by radio, from interrupt context.
// This function _must_ call RF24_readMessage() to de-assert interrupt line!
if (!transportRxQueue.full())
{
transportQueuedMessage* msg = transportRxQueue.getFront();
msg->m_len = RF24_readMessage(msg->m_data); // Read payload & clear RX_DR
(void)transportRxQueue.pushFront(msg);
} else {
// Queue is full. Discard message.
(void)RF24_readMessage(NULL); // Read payload & clear RX_DR
// Keep track of messages lost. Max 255, prevent wrapping.
if (transportLostMessageCount < 255)
{
++transportLostMessageCount;
}
}
}
#endif
#if defined(MY_RF24_ENABLE_ENCRYPTION)
AES _aes;
uint8_t _dataenc[32] = {0};
uint8_t _psk[16];
#endif
bool transportInit() {
#if defined(MY_RF24_ENABLE_ENCRYPTION)
hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
//set up AES-key
_aes.set_key(_psk, 16);
// Make sure it is purged from memory when set
memset(_psk, 0, 16);
#endif
#ifdef MY_RF24_IRQ_PIN
RF24_registerReceiveCallback( transportRxCallback );
#endif
return RF24_initialize();
}
void transportSetAddress(uint8_t address) {
RF24_setNodeAddress(address);
RF24_startListening();
}
uint8_t transportGetAddress() {
return RF24_getNodeID();
}
bool transportSend(uint8_t recipient, const void* data, uint8_t len) {
#if defined(MY_RF24_ENABLE_ENCRYPTION)
// copy input data because it is read-only
memcpy(_dataenc,data,len);
// has to be adjusted, WIP!
_aes.set_IV(0);
len = len > 16 ? 32 : 16;
//encrypt data
_aes.cbc_encrypt(_dataenc, _dataenc, len/16);
bool status = RF24_sendMessage( recipient, _dataenc, len );
#else
bool status = RF24_sendMessage( recipient, data, len );
#endif
return status;
}
bool transportAvailable() {
#ifdef MY_RF24_IRQ_PIN
(void)RF24_isDataAvailable; // Prevent 'defined but not used' warning
return !transportRxQueue.empty();
#else
return RF24_isDataAvailable();
#endif
}
bool transportSanityCheck() {
return RF24_sanityCheck();
}
uint8_t transportReceive(void* data) {
uint8_t len = 0;
#ifdef MY_RF24_IRQ_PIN
transportQueuedMessage* msg = transportRxQueue.getBack();
if (msg)
{
len = msg->m_len;
(void)memcpy(data, msg->m_data, len);
(void)transportRxQueue.popBack();
}
#else
len = RF24_readMessage(data);
#endif
#if defined(MY_RF24_ENABLE_ENCRYPTION)
// has to be adjusted, WIP!
_aes.set_IV(0);
// decrypt data
_aes.cbc_decrypt((byte*)(data), (byte*)(data), len>16?2:1);
#endif
return len;
}
void transportPowerDown() {
RF24_powerDown();
}
+76
View File
@@ -0,0 +1,76 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyConfig.h"
#include "MyTransport.h"
#include <stdint.h>
#include "drivers/RFM69/RFM69.h"
RFM69 _radio(MY_RF69_SPI_CS, MY_RF69_IRQ_PIN, MY_RFM69HW, MY_RF69_IRQ_NUM);
uint8_t _address;
bool transportInit() {
// Start up the radio library (_address will be set later by the MySensors library)
if (_radio.initialize(MY_RFM69_FREQUENCY, _address, MY_RFM69_NETWORKID)) {
#ifdef MY_RFM69_ENABLE_ENCRYPTION
uint8_t _psk[16];
hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
_radio.encrypt((const char*)_psk);
memset(_psk, 0, 16); // Make sure it is purged from memory when set
#endif
return true;
}
return false;
}
void transportSetAddress(uint8_t address) {
_address = address;
_radio.setAddress(address);
}
uint8_t transportGetAddress() {
return _address;
}
bool transportSend(uint8_t to, const void* data, uint8_t len) {
return _radio.sendWithRetry(to,data,len);
}
bool transportAvailable() {
return _radio.receiveDone();
}
bool transportSanityCheck() {
// not implemented yet
return true;
}
uint8_t transportReceive(void* data) {
memcpy(data,(const void *)_radio.DATA, _radio.DATALEN);
// Send ack back if this message wasn't a broadcast
if (_radio.TARGETID != RF69_BROADCAST_ADDR)
_radio.ACKRequested();
_radio.sendACK();
return _radio.DATALEN;
}
void transportPowerDown() {
_radio.sleep();
}
+360
View File
@@ -0,0 +1,360 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* -------------------------------------------------------------------------------
*
* Copyright (c) 2013, Majenko Technologies and S.J.Hoeksma
* Copyright (c) 2015, LeoDesigner
* https://github.com/leodesigner/mysensors-serial-transport
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those
* of the authors and should not be interpreted as representing official policies,
* either expressed or implied, of Majenko Technologies.
********************************************************************************/
// transport(Serial,0,-1); // serial port, node, dePin (-1 disabled)
// Serial Transport
#include "MyConfig.h"
#include "MyTransport.h"
#include <stdint.h>
#if defined(ARDUINO) && ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
#include "MyTransport.h"
#if defined(MY_RS485_DE_PIN)
#define assertDE() digitalWrite(MY_RS485_DE_PIN, HIGH); delayMicroseconds(5)
#define deassertDE() digitalWrite(MY_RS485_DE_PIN, LOW)
#else
#define assertDE()
#define deassertDE()
#endif
// We only use SYS_PACK in this application
#define ICSC_SYS_PACK 0x58
// Receiving header information
char _header[6];
// Reception state machine control and storage variables
unsigned char _recPhase;
unsigned char _recPos;
unsigned char _recCommand;
unsigned char _recLen;
unsigned char _recStation;
unsigned char _recSender;
unsigned char _recCS;
unsigned char _recCalcCS;
AltSoftSerial _dev;
unsigned char _nodeId;
char _data[MY_RS485_MAX_MESSAGE_LENGTH];
uint8_t _packet_len;
unsigned char _packet_from;
bool _packet_received;
// Packet wrapping characters, defined in standard ASCII table
#define SOH 1
#define STX 2
#define ETX 3
#define EOT 4
//Reset the state machine and release the data pointer
void _serialReset(){
_recPhase = 0;
_recPos = 0;
_recLen = 0;
_recCommand = 0;
_recCS = 0;
_recCalcCS = 0;
}
// This is the main reception state machine. Progress through the states
// is keyed on either special control characters, or counted number of bytes
// received. If all the data is in the right format, and the calculated
// checksum matches the received checksum, AND the destination station is
// our station ID, then look for a registered command that matches the
// command code. If all the above is true, execute the command's
// function.
bool _serialProcess()
{
char inch;
unsigned char i;
if (!_dev.available()) return false;
while(_dev.available()) {
inch = _dev.read();
switch(_recPhase) {
// Case 0 looks for the header. Bytes arrive in the serial interface and get
// shifted through a header buffer. When the start and end characters in
// the buffer match the SOH/STX pair, and the destination station ID matches
// our ID, save the header information and progress to the next state.
case 0:
memcpy(&_header[0],&_header[1],5);
_header[5] = inch;
if ((_header[0] == SOH) && (_header[5] == STX) && (_header[1] != _header[2])) {
_recCalcCS = 0;
_recStation = _header[1];
_recSender = _header[2];
_recCommand = _header[3];
_recLen = _header[4];
for (i=1; i<=4; i++) {
_recCalcCS += _header[i];
}
_recPhase = 1;
_recPos = 0;
//Check if we should process this message
//We reject the message if we are the sender
//We reject if we are not the receiver and message is not a broadcast
if ((_recSender == _nodeId) ||
(_recStation != _nodeId &&
_recStation != BROADCAST_ADDRESS)) {
_dev.print(" wrongid: ");
_dev.print(_recStation);
_dev.print(" - ");
_dev.println(_nodeId);
_serialReset();
break;
}
if (_recLen == 0) {
_recPhase = 2;
}
}
break;
// Case 1 receives the data portion of the packet. Read in "_recLen" number
// of bytes and store them in the _data array.
case 1:
_data[_recPos++] = inch;
_recCalcCS += inch;
if (_recPos == _recLen) {
_recPhase = 2;
}
break;
// After the data comes a single ETX character. Do we have it? If not,
// reset the state machine to default and start looking for a new header.
case 2:
// Packet properly terminated?
if (inch == ETX) {
_recPhase = 3;
} else {
_serialReset();
}
break;
// Next comes the checksum. We have already calculated it from the incoming
// data, so just store the incoming checksum byte for later.
case 3:
_recCS = inch;
_recPhase = 4;
break;
// The final state - check the last character is EOT and that the checksum matches.
// If that test passes, then look for a valid command callback to execute.
// Execute it if found.
case 4:
if (inch == EOT) {
if (_recCS == _recCalcCS) {
// First, check for system level commands. It is possible
// to register your own callback as well for system level
// commands which will be called after the system default
// hook.
switch (_recCommand) {
case ICSC_SYS_PACK:
_packet_from = _recSender;
_packet_len = _recLen;
_packet_received = true;
break;
}
}
}
//Clear the data
_serialReset();
//Return true, we have processed one command
return true;
break;
}
}
return true;
}
bool transportSend(uint8_t to, const void* data, uint8_t len)
{
const char *datap = static_cast<char const *>(data);
unsigned char i;
unsigned char cs = 0;
unsigned char del;
// This is how many times to try and transmit before failing.
unsigned char timeout = 10;
// Let's start out by looking for a collision. If there has been anything seen in
// the last millisecond, then wait for a random time and check again.
while (_serialProcess()) {
del = rand() % 20;
for (i = 0; i < del; i++) {
delay(1);
_serialProcess();
}
timeout--;
if (timeout == 0) {
// Failed to transmit!!!
return false;
}
}
#if defined(MY_RS485_DE_PIN)
digitalWrite(MY_RS485_DE_PIN, HIGH);
delayMicroseconds(5);
#endif
// Start of header by writing multiple SOH
for(byte w=0;w<1;w++) _dev.write(SOH);
_dev.write(to); // Destination address
cs += to;
_dev.write(_nodeId); // Source address
cs += _nodeId;
_dev.write(ICSC_SYS_PACK); // Command code
cs += ICSC_SYS_PACK;
_dev.write(len); // Length of text
cs += len;
_dev.write(STX); // Start of text
for(i=0; i<len; i++) {
_dev.write(datap[i]); // Text bytes
cs += datap[i];
}
_dev.write(ETX); // End of text
_dev.write(cs);
_dev.write(EOT);
#if defined(MY_RS485_DE_PIN)
#ifdef __PIC32MX__
// MPIDE has nothing yet for this. It uses the hardware buffer, which
// could be up to 8 levels deep. For now, let's just delay for 8
// characters worth.
delayMicroseconds((F_CPU/9600)+1);
#else
#if defined(ARDUINO) && ARDUINO >= 100
#if ARDUINO >= 104
// Arduino 1.0.4 and upwards does it right
_dev.flush();
#else
// Between 1.0.0 and 1.0.3 it almost does it - need to compensate
// for the hardware buffer. Delay for 2 bytes worth of transmission.
_dev.flush();
delayMicroseconds((20000000UL/9600)+1);
#endif
#endif
#endif
digitalWrite(MY_RS485_DE_PIN, LOW);
#endif
return true;
}
bool transportInit() {
// Reset the state machine
_dev.begin(MY_RS485_BAUD_RATE);
_serialReset();
#if defined(MY_RS485_DE_PIN)
pinMode(MY_RS485_DE_PIN, OUTPUT);
digitalWrite(MY_RS485_DE_PIN, LOW);
#endif
return true;
}
void transportSetAddress(uint8_t address) {
_nodeId = address;
}
uint8_t transportGetAddress() {
return _nodeId;
}
bool transportAvailable() {
_serialProcess();
return _packet_received;
}
bool transportSanityCheck() {
// not implemented yet
return true;
}
uint8_t transportReceive(void* data) {
if (_packet_received) {
memcpy(data,_data,_packet_len);
_packet_received = false;
return _packet_len;
}
else {
return (0);
}
}
void transportPowerDown() {
// Nothing to shut down here
}
+11
View File
@@ -0,0 +1,11 @@
/***
* This file defines the Sensor library version number
* Normally, contributors should not modify this directly
* as it is managed by the MySensors Bot.
*/
#ifndef Version_h
#define Version_h
#define MYSENSORS_LIBRARY_VERSION "2.0.1-beta"
#endif