Merge branch 'ver4dev' of https://github.com/biveraxe/IoTManager into ver4dev

This commit is contained in:
2022-12-22 08:57:21 +03:00
333 changed files with 68380 additions and 629 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -4,12 +4,12 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>IoT Manager 4.4.3</title>
<title>IoT Manager 4.4.4</title>
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="stylesheet" href="/build/bundle.css?443" />
<link rel="stylesheet" href="/build/bundle.css?444" />
<script defer src="/build/bundle.js?443"></script>
<script defer src="/build/bundle.js?444"></script>
</head>
<body></body>

View File

@@ -61,7 +61,10 @@
"int": 1,
"logid": "t",
"points": 365,
"column": 0
"telegram": 0,
"test": 0,
"btn-defvalue": 0,
"btn-reset": "nil"
},
{
"global": 0,
@@ -435,7 +438,23 @@
},
{
"global": 0,
"name": "30. Sht20 Температура",
"name": "30. PZEM настройка",
"type": "Reading",
"subtype": "Pzem004cmd",
"id": "set",
"widget": "nil",
"page": "",
"descr": "",
"int": 15,
"addr": "0xF8",
"changeaddr": 0,
"setaddr": "0x01",
"reset": 0,
"num": 30
},
{
"global": 0,
"name": "31. Sht20 Температура",
"type": "Reading",
"subtype": "Sht20t",
"id": "tmp2",
@@ -444,11 +463,11 @@
"descr": "Температура",
"int": 15,
"round": 1,
"num": 30
"num": 31
},
{
"global": 0,
"name": "31. Sht20 Влажность",
"name": "32. Sht20 Влажность",
"type": "Reading",
"subtype": "Sht20h",
"id": "Hum2",
@@ -457,11 +476,11 @@
"descr": "Влажность",
"int": 15,
"round": 1,
"num": 31
"num": 32
},
{
"global": 0,
"name": "32. Sht30 Температура",
"name": "33. Sht30 Температура",
"type": "Reading",
"subtype": "Sht30t",
"id": "tmp30",
@@ -470,11 +489,11 @@
"descr": "SHT30 Температура",
"int": 15,
"round": 1,
"num": 32
"num": 33
},
{
"global": 0,
"name": "33. Sht30 Влажность",
"name": "34. Sht30 Влажность",
"type": "Reading",
"subtype": "Sht30h",
"id": "Hum30",
@@ -483,12 +502,12 @@
"descr": "SHT30 Влажность",
"int": 15,
"round": 1,
"num": 33
"num": 34
},
{
"global": 0,
"name": "34. HC-SR04 Ультразвуковой дальномер",
"num": 34,
"name": "35. HC-SR04 Ультразвуковой дальномер",
"num": 35,
"type": "Reading",
"subtype": "Sonar",
"id": "sonar",
@@ -500,7 +519,7 @@
"int": 5
},
{
"name": "35. UART",
"name": "36. UART",
"type": "Reading",
"subtype": "UART",
"page": "",
@@ -512,14 +531,14 @@
"line": 2,
"speed": 9600,
"eventFormat": 0,
"num": 35
"num": 36
},
{
"header": "Исполнительные устройства"
},
{
"global": 0,
"name": "36. Кнопка подключенная к пину",
"name": "37. Кнопка подключенная к пину",
"type": "Writing",
"subtype": "ButtonIn",
"id": "btn",
@@ -533,11 +552,11 @@
"pinMode": "INPUT",
"debounceDelay": 50,
"fixState": 0,
"num": 36
"num": 37
},
{
"global": 0,
"name": "37. Управление пином",
"name": "38. Управление пином",
"type": "Writing",
"subtype": "ButtonOut",
"needSave": 0,
@@ -548,11 +567,11 @@
"int": 0,
"inv": 0,
"pin": 2,
"num": 37
"num": 38
},
{
"global": 0,
"name": "38. Сервопривод",
"name": "39. Сервопривод",
"type": "Writing",
"subtype": "IoTServo",
"id": "servo",
@@ -563,11 +582,11 @@
"pin": 12,
"apin": -1,
"amap": "0, 4096, 0, 180",
"num": 38
"num": 39
},
{
"global": 0,
"name": "39. Расширитель портов Mcp23017",
"name": "40. Расширитель портов Mcp23017",
"type": "Reading",
"subtype": "Mcp23017",
"id": "Mcp",
@@ -577,11 +596,11 @@
"int": "0",
"addr": "0x20",
"index": 1,
"num": 39
"num": 40
},
{
"global": 0,
"name": "40. MP3 плеер",
"name": "41. MP3 плеер",
"type": "Reading",
"subtype": "Mp3",
"id": "mp3",
@@ -591,11 +610,11 @@
"int": 1,
"pins": "14,12",
"volume": 20,
"num": 40
"num": 41
},
{
"global": 0,
"name": "41. Сенсорная кнопка",
"name": "42. Сенсорная кнопка",
"type": "Writing",
"subtype": "Multitouch",
"id": "impulse",
@@ -609,11 +628,11 @@
"pinMode": "INPUT",
"debounceDelay": 50,
"PWMDelay": 500,
"num": 41
"num": 42
},
{
"global": 0,
"name": "42. Расширитель портов Pcf8574",
"name": "43. Расширитель портов Pcf8574",
"type": "Reading",
"subtype": "Pcf8574",
"id": "Pcf",
@@ -623,11 +642,11 @@
"int": "0",
"addr": "0x20",
"index": 1,
"num": 42
"num": 43
},
{
"global": 0,
"name": "43. PWM ESP8266",
"name": "44. PWM ESP8266",
"type": "Writing",
"subtype": "Pwm8266",
"id": "pwm",
@@ -639,11 +658,11 @@
"freq": 5000,
"val": 0,
"apin": -1,
"num": 43
"num": 44
},
{
"global": 0,
"name": "44. Телеграм-Лайт",
"name": "45. Телеграм-Лайт",
"type": "Writing",
"subtype": "TelegramLT",
"id": "tg",
@@ -652,14 +671,14 @@
"descr": "",
"token": "",
"chatID": "",
"num": 44
"num": 45
},
{
"header": "Экраны"
},
{
"global": 0,
"name": "45. LCD экран 2004",
"name": "46. LCD экран 2004",
"type": "Reading",
"subtype": "Lcd2004",
"id": "Lcd",
@@ -671,10 +690,10 @@
"size": "20,4",
"coord": "0,0",
"id2show": "id датчика",
"num": 45
"num": 46
},
{
"name": "46. LCD экран 1602",
"name": "47. LCD экран 1602",
"type": "Reading",
"subtype": "Lcd2004",
"id": "Lcd",
@@ -686,6 +705,6 @@
"size": "16,2",
"coord": "0,0",
"id2show": "id датчика",
"num": 46
"num": 47
}
]

View File

@@ -1,41 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>IoT Manager 4.4.1</title>
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="stylesheet" href="https://iotmanager.org/esp01ota/build/bundle.css?440" />
<title>IoT Manager 4.4.3</title>
<link rel="icon" type="image/png" href="/favicon.ico" />
<link
rel="stylesheet"
href="https://iotmanager.org/esp01ota/build/bundle.css?443"
/>
</head>
<body>
<script>
let script = document.createElement('script');
script.src = "https://iotmanager.org/esp01ota/build/bundle.js?440";
script.onload = execute;
document.head.append(script);
script.onerror = error;
function error() {
console.log("ошибка при загрузке ресурса");
}
function execute() {
console.log("скрипт загружен");
document.getElementById("my-form").style.display="none";
}
</script>
<div style="display: flex; align-items: center; justify-content: center;" >
<form method="GET" action="http://192.168.4.1/set" id="my-form">
<br><br><h1>Настройка WiFi</h1><br>
<div>
<input name="routerssid" type="text" placeholder="WIFI">
</div><br>
<div>
<input name="routerpass" placeholder="Password">
</div><br><br>
<input type="submit" value="сохранить">
</form>
</div>
<body>
<script>
let script = document.createElement("script");
script.src = "https://iotmanager.org/esp01ota/build/bundle.js?443";
script.onload = execute;
document.head.append(script);
script.onerror = error;
function error() {
console.log("ошибка при загрузке ресурса");
}
function execute() {
console.log("скрипт загружен");
document.getElementById("my-form").style.display = "none";
}
</script>
<div style="display: flex; align-items: center; justify-content: center">
<form method="GET" action="http://192.168.4.1/set" id="my-form">
<br /><br />
<h1>Настройка WiFi</h1>
<br />
<div>
<input name="routerssid" type="text" placeholder="WIFI" />
</div>
<br />
<div>
<input name="routerpass" placeholder="Password" />
</div>
<br /><br />
<input type="submit" value="сохранить" />
</form>
</div>
</body>
</html>

View File

@@ -1,7 +1,7 @@
#pragma once
//Версия прошивки
#define FIRMWARE_VERSION 433
#define FIRMWARE_VERSION 434
#ifdef esp8266_1mb_ota
#define FIRMWARE_NAME "esp8266_1mb_ota"
@@ -19,6 +19,13 @@
#define FIRMWARE_NAME "esp32_4mb"
#endif
#define MYSENSORS
//#ifdef esp32_4mb_ms
//#define FIRMWARE_NAME "esp32_4mb_ms"
//#define MYSENSORS
//#endif
//Размер буфера json
#define JSON_BUFFER_SIZE 2048 //держим 2 кб не меняем
#define WEB_SOCKETS_FRAME_SIZE 2048

View File

@@ -7,7 +7,6 @@
#include <ArduinoJson.h>
#include <TickerScheduler.h>
#include <PubSubClient.h>
#include <StringCommand.h>
#include <list>
#ifdef ESP32
@@ -60,7 +59,7 @@ extern IoTGpio IoTgpio;
extern TickerScheduler ts;
extern WiFiClient espClient;
extern PubSubClient mqtt;
extern StringCommand sCmd;
#ifdef ASYNC_WEB_SERVER
extern AsyncWebServer server;
#endif
@@ -90,6 +89,7 @@ extern bool needSaveValues;
// buf
extern String orderBuf;
extern String eventBuf;
extern String mysensorBuf;
// wifi
extern String ssidListHeapJson;
@@ -133,13 +133,15 @@ struct Time_t {
extern unsigned long unixTime;
extern unsigned long unixTimeShort;
extern String prevDate;
extern bool firstTimeInit;
extern bool isTimeSynch;
extern Time_t _time_local;
extern Time_t _time_utc;
extern bool _time_isTrust;
//extern unsigned long loopPeriod;
// extern unsigned long loopPeriod;
// extern DynamicJsonDocument settingsFlashJsonDoc;
// extern DynamicJsonDocument paramsFlashJsonDoc;

View File

@@ -18,16 +18,13 @@ void mqttSubscribe();
boolean publish(const String& topic, const String& data);
boolean publishData(const String& topic, const String& data);
boolean publishChartMqtt(const String& topic, const String& data);
boolean publishControl(String id, String topic, String state);
boolean publishChart_test(const String& topic, const String& data);
boolean publishJsonMqtt(const String& topic, const String& json);
boolean publishStatusMqtt(const String& topic, const String& data);
boolean publishEvent(const String& topic, const String& data);
boolean publishInfo(const String& topic, const String& data);
boolean publishAnyJsonKey(const String& topic, const String& key, const String& data);
bool publishChartFileToMqtt(String path, String id, int maxCount);
void publishWidgets();
void publishState();
void mqttCallback(char* topic, uint8_t* payload, size_t length);
void handleMqttStatus(bool send);

View File

@@ -8,6 +8,7 @@ extern void breakEpochToTime(unsigned long epoch, Time_t& tm);
extern void ntpInit();
extern time_t getSystemTime();
extern void synchTime();
extern bool onDayChange();
extern const String getTimeLocal_hhmm();
extern const String getTimeLocal_hhmmss();
extern const String getDateTimeDotFormated();

View File

@@ -14,7 +14,7 @@ extern void hexdump(const void* mem, uint32_t len, uint8_t cols);
#endif
void publishStatusWs(const String& topic, const String& data);
void publishChartWs(int num, String& path);
void publishJsonWs(const String& topic, String& json);
void periodicWsSend();
void sendFileToWsByFrames(const String& filename, const String& header, const String& json, int client_id, size_t frameSize);

View File

@@ -10,7 +10,7 @@ struct IoTValue {
class IoTItem {
public:
IoTItem(const String &parameters);
IoTItem(const String& parameters);
virtual ~IoTItem() {}
virtual void loop();
virtual void doByInterval();
@@ -29,6 +29,8 @@ class IoTItem {
long getInterval();
bool isGlobal();
void sendSubWidgetsValues(String& id, String& json);
void setInterval(long interval);
void setIntFromNet(int interval);
@@ -38,7 +40,7 @@ class IoTItem {
IoTValue value; // хранение основного значения, которое обновляется из сценария, execute(), loop() или doByInterval()
//bool iAmDead = false; // признак необходимости удалить объект из базы
// bool iAmDead = false; // признак необходимости удалить объект из базы
bool iAmLocal = true; // признак того, что айтем был создан локально
bool enableDoByInt = true;
@@ -49,11 +51,16 @@ class IoTItem {
String getRoundValue();
void getNetEvent(String& event);
// хуки для системных событий
// хуки для системных событий (должны начинаться с "on")
virtual void onRegEvent(IoTItem* item);
virtual void onMqttRecive(String& topic, String& msg);
virtual void onMqttWsAppConnectEvent();
virtual void onModuleOrder(String& key, String& value);
//методы для графиков
// делаем доступным модулям отправку сообщений в телеграм
virtual void sendTelegramMsg(bool often, String msg);
// методы для графиков (будет упрощено)
virtual void publishValue();
virtual void clearValue();
virtual void setPublishDestination(int type, int wsNum = -1);
@@ -63,11 +70,11 @@ class IoTItem {
protected:
bool _needSave = false; // признак необходимости сохранять и загружать значение элемента на flash
String _subtype = "";
String _id = "errorId"; // если будет попытка создания Item без указания id, то элемент оставит это значение
String _id = "errorId"; // если будет попытка создания Item без указания id, то элемент оставит это значение
long _interval = 0;
int _intFromNet = -2; // количество секунд доверия, пришедших из сети вместе с данными для текущего ИД
// -2 - данные не приходили, скорее всего, элемент локальный, доверие есть
// -1 - данные приходили и обратный отсчет дошел до нуля, значит доверия нет
int _intFromNet = -2; // количество секунд доверия, пришедших из сети вместе с данными для текущего ИД
// -2 - данные не приходили, скорее всего, элемент локальный, доверие есть
// -1 - данные приходили и обратный отсчет дошел до нуля, значит доверия нет
float _multiply; // умножаем на значение
float _plus; // увеличиваем на значение
@@ -80,9 +87,9 @@ class IoTItem {
bool _global = false; // характеристика айтема, что ему нужно слать и принимать события из внешнего мира
};
IoTItem* findIoTItem(const String& name); // поиск экземпляра элемента модуля по имени
String getItemValue(const String& name); // поиск плюс получение значения
bool isItemExist(const String& name); // существует ли айтем
IoTItem* findIoTItem(const String& name); // поиск экземпляра элемента модуля по имени
String getItemValue(const String& name); // поиск плюс получение значения
bool isItemExist(const String& name); // существует ли айтем
StaticJsonDocument<JSON_BUFFER_SIZE>* getLocalItemsAsJSON(); // сбор всех локальных значений Items
IoTItem* createItemFromNet(const String& itemId, const String& value, int interval);

View File

@@ -3,3 +3,4 @@
extern const String prettySeconds(unsigned long time_s);
extern const String prettyMillis(unsigned long time_ms);
extern const String prettyMinutsTimeout(unsigned long time_m);

View File

@@ -1,137 +0,0 @@
/**
* SerialCommand - A Wiring/Arduino library to tokenize and parse commands
* received over a serial port.
*
* Copyright (C) 2012 Stefan Rado
* Copyright (C) 2011 Steven Cogswell <steven.cogswell@gmail.com>
* http://husks.wordpress.com
*
* Version 20120522
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "StringCommand.h"
/**
* Constructor makes sure some things are set.
*/
StringCommand::StringCommand()
: commandList(NULL),
commandCount(0),
defaultHandler(NULL),
term('\n'), // default terminator for commands, newline character
last(NULL),
main(NULL)
{
strcpy(delim, " "); // strtok_r needs a null-terminated string
clearBuffer();
}
/**
* Adds a "command" and a handler function to the list of available commands.
* This is used for matching a found token in the buffer, and gives the pointer
* to the handler function to deal with it.
*/
void StringCommand::addCommand(const char *command, void (*function)()) {
#ifdef SERIALCOMMAND_DEBUG
Serial.print("Adding command (");
Serial.print(commandCount);
Serial.print("): ");
Serial.println(command);
#endif
commandList = (StringCommandCallback *) realloc(commandList, (commandCount + 1) * sizeof(StringCommandCallback));
strncpy(commandList[commandCount].command, command, SERIALCOMMAND_MAXCOMMANDLENGTH);
commandList[commandCount].function = function;
commandCount++;
}
/**
* This sets up a handler to be called in the event that the receveived command string
* isn't in the list of commands.
*/
void StringCommand::setDefaultHandler(void (*function)(const char *)) {
defaultHandler = function;
}
/**
* This checks the Serial stream for characters, and assembles them into a buffer.
* When the terminator character (default '\n') is seen, it starts parsing the
* buffer for a prefix command, and calls handlers setup by addCommand() member
*/
void StringCommand::readStr(String sBuffer ) {
sBuffer.toCharArray(buffer, SERIALCOMMAND_BUFFER);
#ifdef SERIALCOMMAND_DEBUG
Serial.print("Received: ");
Serial.println(buffer);
#endif
char *command = strtok_r(buffer, delim, &last); // Search for command at start of buffer
if (command != NULL) {
boolean matched = false;
for (int i = 0; i < commandCount; i++) {
#ifdef SERIALCOMMAND_DEBUG
Serial.print("Comparing [");
Serial.print(command);
Serial.print("] to [");
Serial.print(commandList[i].command);
Serial.println("]");
#endif
// Compare the found command against the list of known commands for a match
if (strncmp(command, commandList[i].command, SERIALCOMMAND_MAXCOMMANDLENGTH) == 0) {
#ifdef SERIALCOMMAND_DEBUG
Serial.print("Matched Command: ");
Serial.println(command);
#endif
// Execute the stored handler function for the command
(*commandList[i].function)();
matched = true;
break;
}
}
if (!matched && (defaultHandler != NULL)) {
(*defaultHandler)(command);
}
clearBuffer();
}
}
/*
* Clear the input buffer.
*/
void StringCommand::clearBuffer() {
buffer[0] = '\0';
bufPos = 0;
}
/**
* Retrieve the next token ("word" or "argument") from the command buffer.
* Returns NULL if no more tokens exist.
*/
char *StringCommand::next() {
return strtok_r(NULL, delim, &last);
}
char *StringCommand::order() {
return strtok_r(buffer, delim, &main);
}

View File

@@ -1,77 +0,0 @@
/**
* SerialCommand - A Wiring/Arduino library to tokenize and parse commands
* received over a serial port.
*
* Copyright (C) 2012 Stefan Rado
* Copyright (C) 2011 Steven Cogswell <steven.cogswell@gmail.com>
* http://husks.wordpress.com
*
* Version 20120522
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef StringCommand_h
#define StringCommand_h
#if defined(WIRING) && WIRING >= 100
#include <Wiring.h>
#elif defined(ARDUINO) && ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
#include <string.h>
// Size of the input buffer in bytes (maximum length of one command plus arguments)
#define SERIALCOMMAND_BUFFER 128 //256
// Maximum length of a command excluding the terminating null
#define SERIALCOMMAND_MAXCOMMANDLENGTH 16
// Uncomment the next line to run the library in debug mode (verbose messages)
//#define SERIALCOMMAND_DEBUG
class StringCommand {
public:
StringCommand(); // Constructor
void addCommand(const char *command, void(*function)()); // Add a command to the processing dictionary.
void setDefaultHandler(void (*function)(const char *)); // A handler to call when no valid command received.
void readStr(String sBuffer ); // Main entry point.
void clearBuffer(); // Clears the input buffer.
char *next(); // Returns pointer to next token found in command buffer (for getting arguments to commands).
char *order();
private:
// Command/handler dictionary
struct StringCommandCallback {
char command[SERIALCOMMAND_MAXCOMMANDLENGTH + 1];
void (*function)();
}; // Data structure to hold Command/Handler function key-value pairs
StringCommandCallback *commandList; // Actual definition for command/handler array
byte commandCount;
// Pointer to the default handler function
void (*defaultHandler)(const char *);
char delim[2]; // null-terminated list of character to be used as delimeters for tokenizing (default " ")
char term; // Character that signals end of command (default '\n')
char buffer[SERIALCOMMAND_BUFFER + 1]; // Buffer of stored characters while waiting for terminator character
byte bufPos; // Current position in the buffer
char *last; // State variable used by strtok_r during processing
char *main;
};
#endif //StringCommand_h

View File

@@ -1,23 +0,0 @@
#######################################
# Datatypes (KEYWORD1)
#######################################
StringCommand KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
addCommand KEYWORD2
setDefaultHandler KEYWORD2
readString KEYWORD2
clearBuffer KEYWORD2
next KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
#######################################
# Constants (LITERAL1)
#######################################

View File

@@ -1,5 +0,0 @@
StringCommand
=============
Библиотека для ESP8266 позволяющая связать запуск пользовательских функций с строковой переменной.

View File

@@ -0,0 +1,3 @@
-Idrivers\Linux
-Idrivers\ATSHA204
-Icore

View File

@@ -0,0 +1,19 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with UTF-8 encoding for every file
[*]
end_of_line = lf
charset = utf-8
# For all source code
[*.{c,cpp,h,ino,sh}]
insert_final_newline = true
indent_style = tab
indent_size = 2
# Tab indentation also for makefiles
[Makefile]
indent_style = tab

22
lib/MySensors/.gitattributes vendored Normal file
View File

@@ -0,0 +1,22 @@
# Set default behaviour, in case users don't have core.autocrlf set.
* text eol=lf
# Explicitly declare text files we want to always be normalized and converted
# to native line endings on checkout.
*.c text
*.cpp text
*.h text
*.ino text
*.xml text
*.lua text
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
*.gif binary
*.ttf binary
*.eot binary
*.svg binary
*.woff binary
*.epg binary
*.jar binary

1
lib/MySensors/.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
custom: https://www.mysensors.org/hall-of-fame

15
lib/MySensors/.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
stino.settings
*~
*bak
MyPrivateConfig.h
Documentation/html
doxygen_sqlite3.db
Makefile.inc
build
bin
*.sublime-workspace
.idea
doxygen.log
TAGS
tags
.DS_Store

1
lib/MySensors/.piopm Normal file
View File

@@ -0,0 +1 @@
{"type": "library", "name": "MySensors", "version": "2.3.2", "spec": {"owner": "mysensors", "id": 548, "name": "MySensors", "requirements": null, "uri": null}}

11
lib/MySensors/.project Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>MySensors-Arduino</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
</natures>
</projectDescription>

View File

@@ -0,0 +1 @@
<a href="https://www.mysensors.org/download/contributing">MySensors Code Contribution Guidelines</a>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,38 @@
<!DOCTYPE html><html><head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Log Parser | MySensors - Create your own Connected Home Experience</title>
<link rel="stylesheet" href="logparser.css" />
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
<div id="parser">
<h4>Paste log from gateway or node here:</h4>
<textarea class="form-control" @change="parse" rows="10" v-model="source"></textarea>
<button @click="parse" class="btn btn-primary">Parse</button>
<button @click="copy" class="btn btn-primary">Copy this log URL to clipboard</button>
<button @click="source=''" class="btn btn-default">Clear</button>
<h4>Human readable output:</h4>
<div class="table-responsive">
<table class="table table-condensed">
<tr>
<th>Node Id</th>
<th>Child Sensor</th>
<th>Command Type</th>
<th>Echo Req/Resp</th>
<th>Type</th>
<th>Payload</th>
<th>Description</th>
</tr>
<tr v-for="r in parsed">
<td v-for="c in r" v-html="c"></td>
</tr>
</table>
</div>
</div>
<script src="logparser.js"></script>
</body>
</html>

View File

@@ -0,0 +1,579 @@
function copyTextToClipboard(text) {
var textArea = document.createElement("textarea");
//
// *** This styling is an extra step which is likely not required. ***
//
// Why is it here? To ensure:
// 1. the element is able to have focus and selection.
// 2. if element was to flash render it has minimal visual impact.
// 3. less flakyness with selection and copying which **might** occur if
// the textarea element is not visible.
//
// The likelihood is the element won't even render, not even a flash,
// so some of these are just precautions. However in IE the element
// is visible whilst the popup box asking the user for permission for
// the web page to copy to the clipboard.
//
// Place in top-left corner of screen regardless of scroll position.
textArea.style.position = 'fixed';
textArea.style.top = 0;
textArea.style.left = 0;
// Ensure it has a small width and height. Setting to 1px / 1em
// doesn't work as this gives a negative w/h on some browsers.
textArea.style.width = '2em';
textArea.style.height = '2em';
// We don't need padding, reducing the size if it does flash render.
textArea.style.padding = 0;
// Clean up any borders.
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
// Avoid flash of white box if rendered for any reason.
textArea.style.background = 'transparent';
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
var successful = document.execCommand('copy');
var msg = successful ? 'successful' : 'unsuccessful';
console.log('Copying text command was ' + msg);
} catch (err) {
console.log('Oops, unable to copy');
}
document.body.removeChild(textArea);
}
var types = {
"presentation":[
"S_DOOR",
"S_MOTION",
"S_SMOKE",
"S_BINARY",
"S_DIMMER",
"S_COVER",
"S_TEMP",
"S_HUM",
"S_BARO",
"S_WIND",
"S_RAIN",
"S_UV",
"S_WEIGHT",
"S_POWER",
"S_HEATER",
"S_DISTANCE",
"S_LIGHT_LEVEL",
"S_ARDUINO_NODE",
"S_ARDUINO_REPEATER_NODE",
"S_LOCK",
"S_IR",
"S_WATER",
"S_AIR_QUALITY",
"S_CUSTOM",
"S_DUST",
"S_SCENE_CONTROLLER",
"S_RGB_LIGHT",
"S_RGBW_LIGHT",
"S_COLOR_SENSOR",
"S_HVAC",
"S_MULTIMETER",
"S_SPRINKLER",
"S_WATER_LEAK",
"S_SOUND",
"S_VIBRATION",
"S_MOISTURE",
"S_INFO",
"S_GAS",
"S_GPS",
"S_WATER_QUALITY"
],
"internal": [
"I_BATTERY_LEVEL",
"I_TIME",
"I_VERSION",
"I_ID_REQUEST",
"I_ID_RESPONSE",
"I_INCLUSION_MODE",
"I_CONFIG",
"I_FIND_PARENT_REQUEST",
"I_FIND_PARENT_RESPONSE",
"I_LOG_MESSAGE",
"I_CHILDREN",
"I_SKETCH_NAME",
"I_SKETCH_VERSION",
"I_REBOOT",
"I_GATEWAY_READY",
"I_SIGNING_PRESENTATION",
"I_NONCE_REQUEST",
"I_NONCE_RESPONSE",
"I_HEARTBEAT_REQUEST",
"I_PRESENTATION",
"I_DISCOVER_REQUEST",
"I_DISCOVER_RESPONSE",
"I_HEARTBEAT_RESPONSE",
"I_LOCKED",
"I_PING",
"I_PONG",
"I_REGISTRATION_REQUEST",
"I_REGISTRATION_RESPONSE",
"I_DEBUG",
"I_SIGNAL_REPORT_REQUEST",
"I_SIGNAL_REPORT_REVERSE",
"I_SIGNAL_REPORT_RESPONSE",
"I_PRE_SLEEP_NOTIFICATION",
"I_POST_SLEEP_NOTIFICATION"
],
"subtype":[
"V_TEMP",
"V_HUM",
"V_STATUS",
"V_PERCENTAGE",
"V_PRESSURE",
"V_FORECAST",
"V_RAIN",
"V_RAINRATE",
"V_WIND",
"V_GUST",
"V_DIRECTION",
"V_UV",
"V_WEIGHT",
"V_DISTANCE",
"V_IMPEDANCE",
"V_ARMED",
"V_TRIPPED",
"V_WATT",
"V_KWH",
"V_SCENE_ON",
"V_SCENE_OFF",
"V_HVAC_FLOW_STATE",
"V_HVAC_SPEED",
"V_LIGHT_LEVEL",
"V_VAR1",
"V_VAR2",
"V_VAR3",
"V_VAR4",
"V_VAR5",
"V_UP",
"V_DOWN",
"V_STOP",
"V_IR_SEND",
"V_IR_RECEIVE",
"V_FLOW",
"V_VOLUME",
"V_LOCK_STATUS",
"V_LEVEL",
"V_VOLTAGE",
"V_CURRENT",
"V_RGB",
"V_RGBW",
"V_ID",
"V_UNIT_PREFIX",
"V_HVAC_SETPOINT_COOL",
"V_HVAC_SETPOINT_HEAT",
"V_HVAC_FLOW_MODE",
"V_TEXT",
"V_CUSTOM",
"V_POSITION",
"V_IR_RECORD",
"V_PH",
"V_ORP",
"V_EC",
"V_VAR",
"V_VA",
"V_POWER_FACTOR"
],
"stream":[
"ST_FIRMWARE_CONFIG_REQUEST",
"ST_FIRMWARE_CONFIG_RESPONSE",
"ST_FIRMWARE_REQUEST",
"ST_FIRMWARE_RESPONSE",
"ST_SOUND",
"ST_IMAGE"],
command: [
"PRESENTATION",
"SET",
"REQ",
"INTERNAL",
"STREAM"
],
"payloadtype":[
"P_STRING",
"P_BYTE",
"P_INT16",
"P_UINT16",
"P_LONG32",
"P_ULONG32",
"P_CUSTOM",
"P_FLOAT32"
]};
//mysgw: Client 0: 0;0;3;0;18;PING
var rprefix = "(?:\\d+ )?(?:mysgw: )?(?:Client 0: )?";
var match = [
{ re: "MCO:BGN:INIT CP=([^,]+)", d: "Core initialization with capabilities <b>$1</b>" },
{ re: "MCO:BGN:INIT (\\w+),CP=([^,]+),VER=(.*)", d: "Core initialization of <b>$1</b>, with capabilities <b>$2</b>, library version <b>$3</b>" },
{ re: "MCO:BGN:INIT (\\w+),CP=([^,]+),REL=(.*),VER=(.*)", d: "Core initialization of <b>$1</b>, with capabilities <b>$2</b>, library version <b>$4</b>, release <b>$3</b>" },
{ re: "MCO:BGN:INIT (\\w+),CP=([^,]+),FQ=(\\d+),REL=(.*),VER=(.*)", d: "Core initialization of <b>$1</b>, with capabilities <b>$2</b>, CPU frequency <b>$4</b> MHz, library version <b>$5</b>, release <b>$4</b>" },
{ re: "MCO:BGN:BFR", d: "Callback before()" },
{ re: "MCO:BGN:STP", d: "Callback setup()" },
{ re: "MCO:BGN:INIT OK,TSP=(.*)", d: "Core initialized, transport status <b>$1</b>, (1=initialized, 0=not initialized, NA=not available)" },
{ re: "MCO:BGN:NODE UNLOCKED", d: "Node successfully unlocked (see signing chapter)" },
{ re: "!MCO:BGN:TSP FAIL", d: "Transport initialization failed" },
{ re: "MCO:REG:REQ", d: "Registration request" },
{ re: "MCO:REG:NOT NEEDED", d: "No registration needed (i.e. GW)" },
{ re: "!MCO:SND:NODE NOT REG", d: "Node is not registered, cannot send message" },
{ re: "MCO:PIM:NODE REG=(\\d+)", d: "Registration response received, registration status <b>$1</b>" },
{ re: "MCO:PIM:ROUTE N=(\\d+),R=(\\d+)", d: "Routing table, messages to node <b>$1</b> are routed via node <b>$2</b>" },
{ re: "MCO:SLP:MS=(\\d+),SMS=(\\d+),I1=(\\d+),M1=(\\d+),I2=(\\d+),M2=(\\d+)", d: "Sleep node, duration <b>$1</b> ms, SmartSleep=<b>$2</b>, Int1=<b>$3</b>, Mode1=<b>$4</b>, Int2=<b>$5</b>, Mode2=<b>$6</b>" },
{ re: "MCO:SLP:MS=(\\d+)", d: "Sleep node, duration <b>$1</b> ms" },
{ re: "MCO:SLP:TPD", d: "Sleep node, powerdown transport" },
{ re: "MCO:SLP:WUP=(-?\\d+)", d: "Node woke-up, reason/IRQ=<b>$1</b> (-2=not possible, -1=timer, >=0 IRQ)" },
{ re: "!MCO:SLP:FWUPD", d: "Sleeping not possible, FW update ongoing" },
{ re: "!MCO:SLP:REP", d: "Sleeping not possible, repeater feature enabled" },
{ re: "!MCO:SLP:TNR", d: " Transport not ready, attempt to reconnect until timeout" },
{ re: "MCO:NLK:NODE LOCKED. UNLOCK: GND PIN (\\d+) AND RESET", d: "Node locked during booting, see signing documentation for additional information" },
{ re: "MCO:NLK:TPD", d: "Powerdown transport" },
{ re: "TSM:INIT", d: "Transition to <b>Init</b> state" },
{ re: "TSM:INIT:STATID=(\\d+)", d: "Init static node id <b>$1</b>" },
{ re: "TSM:INIT:TSP OK", d: "Transport device configured and fully operational" },
{ re: "TSM:INIT:GW MODE", d: "Node is set up as GW, thus omitting ID and findParent states" },
{ re: "!TSM:INIT:TSP FAIL", d: "Transport device initialization failed" },
{ re: "TSM:FPAR", d: "Transition to <b>Find Parent</b> state" },
{ re: "TSM:FPAR:STATP=(\\d+)", d: "Static parent <b>$1</b> has been set, skip finding parent" },
{ re: "TSM:FPAR:OK", d: "Parent node identified" },
{ re: "!TSM:FPAR:NO REPLY", d: "No potential parents replied to find parent request" },
{ re: "!TSM:FPAR:FAIL", d: "Finding parent failed" },
{ re: "TSM:ID", d: "Transition to <b>Request Id</b> state" },
{ re: "TSM:ID:OK,ID=(\\d+)", d: "Node id <b>$1</b> is valid" },
{ re: "TSM:ID:REQ", d: "Request node id from controller" },
{ re: "!TSM:ID:FAIL", d: "Did not receive a node id from controller. Is your controller connected and correctly configured?" },
{ re: "!TSM:ID:FAIL,ID=(\\d+)", d: "Id verification failed, <b>$1</b> is invalid" },
{ re: "TSM:UPL", d: "Transition to <b>Check Uplink</b> state" },
{ re: "TSM:UPL:OK", d: "Uplink OK, GW returned ping" },
{ re: "!TSM:UPL:FAIL", d: "Uplink check failed, i.e. GW could not be pinged" },
{ re: "TSM:READY:NWD REQ", d: "Send transport network discovery request" },
{ re: "TSM:READY:SRT", d: "Save routing table" },
{ re: "TSM:READY:ID=(\\d+),PAR=(\\d+),DIS=(\\d+)", d: "Transport ready, node id <b>$1</b>, parent node id <b>$2</b>, distance to GW is <b>$3</b>" },
{ re: "!TSM:READY:UPL FAIL,SNP", d: "Too many failed uplink transmissions, search new parent" },
{ re: "!TSM:READY:FAIL,STATP", d: "Too many failed uplink transmissions, static parent enforced" },
{ re: "TSM:READY", d: "Transition to <b>Ready</b> state" },
{ re: "TSM:FAIL:DIS", d: "Disable transport" },
{ re: "TSM:FAIL:CNT=(\\d+)", d: "Transition to <b>Failure</b> state, consecutive failure counter is <b>$1</b>" },
{ re: "TSM:FAIL:PDT", d: "Power-down transport" },
{ re: "TSM:FAIL:RE-INIT", d: "Attempt to re-initialize transport" },
{ re: "TSF:CKU:OK,FCTRL", d: "Uplink OK, flood control prevents pinging GW in too short intervals" },
{ re: "TSF:CKU:OK", d: "Uplink OK" },
{ re: "TSF:CKU:DGWC,O=(\\d+),N=(\\d+)", d: "Uplink check revealed changed network topology, old distance <b>$1</b>, new distance <b>$2</b>" },
{ re: "TSF:CKU:FAIL", d: "No reply received when checking uplink" },
{ re: "TSF:SID:OK,ID=(\\d+)", d: "Node id <b>$1</b> assigned" },
{ re: "!TSF:SID:FAIL,ID=(\\d+)", d: "Assigned id <b>$1</b> is invalid" },
{ re: "TSF:PNG:SEND,TO=(\\d+)", d: "Send ping to destination <b>$1</b>" },
{ re: "!TSF:PNG:ACTIVE", d: "Ping active, cannot start new ping" },
{ re: "TSF:WUR:MS=(\\d+)", d: "Wait until transport ready, timeout <b>$1</b>" },
{ re: "TSF:MSG:ECHO REQ", d: "ECHO message requested" },
{ re: "TSF:MSG:ECHO", d: "ECHO message, do not proceed but forward to callback" },
{ re: "TSF:MSG:FPAR RES,ID=(\\d+),D=(\\d+)", d: "Response to find parent request received from node <b>$1</b> with distance <b>$2</b> to GW" },
{ re: "TSF:MSG:FPAR PREF FOUND", d: "Preferred parent found, i.e. parent defined via MY_PARENT_NODE_ID" },
{ re: "TSF:MSG:FPAR OK,ID=(\\d+),D=(\\d+)", d: "Find parent response from node <b>$1</b> is valid, distance <b>$2</b> to GW" },
{ re: "!TSF:MSG:FPAR INACTIVE", d: "Find parent response received, but no find parent request active, skip response" },
{ re: "TSF:MSG:FPAR REQ,ID=(\\d+)", d: "Find parent request from node <b>$1</b>" },
{ re: "TSF:MSG:PINGED,ID=(\\d+),HP=(\\d+)", d: "Node pinged by node <b>$1</b> with <b>$2</b> hops" },
{ re: "TSF:MSG:PONG RECV,HP=(\\d+)", d: "Pinged node replied with <b>$1</b> hops" },
{ re: "!TSF:MSG:PONG RECV,INACTIVE", d: "Pong received, but !pingActive" },
{ re: "TSF:MSG:BC", d: "Broadcast message received" },
{ re: "TSF:MSG:GWL OK", d: "Link to GW ok" },
{ re: "TSF:MSG:FWD BC MSG", d: "Controlled broadcast message forwarding" },
{ re: "TSF:MSG:REL MSG", d: "Relay message" },
{ re: "TSF:MSG:REL PxNG,HP=(\\d+)", d: "Relay PING/PONG message, increment hop counter to <b>$1</b>" },
{ re: "!TSF:MSG:LEN,(\\d+)!=(\\d+)", d: "Invalid message length, <b>$1</b> (actual) != <b>$2</b> (expected)" },
{ re: "!TSF:MSG:PVER,(\\d+)!=(\\d+)", d: "Message protocol version mismatch, <b>$1</b> (actual) != <b>$2</b> (expected)" },
{ re: "!TSF:MSG:SIGN VERIFY FAIL", d: "Signing verification failed" },
{ re: "!TSF:MSG:REL MSG,NORP", d: "Node received a message for relaying, but node is not a repeater, message skipped" },
{ re: "!TSF:MSG:SIGN FAIL", d: "Signing message failed" },
{ re: "!TSF:MSG:GWL FAIL", d: "GW uplink failed" },
{ re: "!TSF:MSG:ID TK INVALID", d: "Token for ID request invalid" },
{ re: "TSF:SAN:OK", d: "Sanity check passed" },
{ re: "!TSF:SAN:FAIL", d: "Sanity check failed, attempt to re-initialize radio" },
{ re: "TSF:CRT:OK", d: "Clearing routing table successful" },
{ re: "TSF:LRT:OK", d: "Loading routing table successful" },
{ re: "TSF:SRT:OK", d: "Saving routing table successful" },
{ re: "!TSF:RTE:FPAR ACTIVE", d: "Finding parent active, message not sent" },
{ re: "!TSF:RTE:DST (\\d+) UNKNOWN", d: "Routing for destination <b>$1</b> unknown, sending message to parent" },
{ re: "!TSF:RTE:N2N FAIL", d: "Direct node-to-node communication failed - handing over to parent" },
{ re: "TSF:RRT:ROUTE N=(\\d+),R=(\\d+)", d: "Routing table, messages to node (<b>$1</b>) are routed via node (<b>$2</b>)"},
{ re: "!TSF:SND:TNR", d: "Transport not ready, message cannot be sent" },
{ re: "TSF:TDI:TSL", d: "Set transport to sleep" },
{ re: "TSF:TDI:TPD", d: "Power down transport" },
{ re: "TSF:TRI:TRI", d: "Reinitialise transport" },
{ re: "TSF:TRI:TSB", d: "Set transport to standby" },
{ re: "TSF:TRI:TPU", d: "Power up transport" },
{ re: "TSF:SIR:CMD=(\\d+),VAL=(\\d+)", d: "Get signal report <b>$1</b>, value: <b>$2</b>" },
{ re: "TSF:MSG:READ,(\\d+)-(\\d+)-(\\d+),s=(\\d+),c=(\\d+),t=(\\d+),pt=(\\d+),l=(\\d+),sg=(\\d+):(.*)", d: "<u><b>Received Message</b></u><br><b>Sender</b>: $1<br><b>Last Node</b>: $2<br><b>Destination</b>: $3<br><b>Sensor Id</b>: $4<br><b>Command</b>: {command:$5}<br><b>Message Type</b>: {type:$5:$6}<br><b>Payload Type</b>: {pt:$7}<br><b>Payload Length</b>: $8<br><b>Signing</b>: $9<br><b>Payload</b>: $10" },
{ re: "TSF:MSG:SEND,(\\d+)-(\\d+)-(\\d+)-(\\d+),s=(\\d+),c=(\\d+),t=(\\d+),pt=(\\d+),l=(\\d+),sg=(\\d+),ft=(\\d+),st=(\\w+):(.*)", d: "<u><b>Sent Message</b></u><br><b>Sender</b>: $1<br><b>Last Node</b>: $2<br><b>Next Node</b>: $3<br><b>Destination</b>: $4<br><b>Sensor Id</b>: $5<br><b>Command</b>: {command:$6}<br><b>Message Type</b>:{type:$6:$7}<br><b>Payload Type</b>: {pt:$8}<br><b>Payload Length</b>: $9<br><b>Signing</b>: $10<br><b>Failed uplink counter</b>: $11<br><b>Status</b>: $12 (OK=success, NACK=no radio ACK received)<br><b>Payload</b>: $13" },
{ re: "!TSF:MSG:SEND,(\\d+)-(\\d+)-(\\d+)-(\\d+),s=(\\d+),c=(\\d+),t=(\\d+),pt=(\\d+),l=(\\d+),sg=(\\d+),ft=(\\d+),st=(\\w+):(.*)", d: "<u><b style='color:red'>Sent Message</b></u><br><b>Sender</b>: $1<br><b>Last Node</b>: $2<br><b>Next Node</b>: $3<br><b>Destination</b>: $4<br><b>Sensor Id</b>: $5<br><b>Command</b>: {command:$6}<br><b>Message Type</b>:{type:$6:$7}<br><b>Payload Type</b>: {pt:$8}<br><b>Payload Length</b>: $9<br><b>Signing</b>: $10<br><b>Failed uplink counter</b>: $11<br><b>Status</b>: $12 (OK=success, NACK=no radio ACK received)<br><b>Payload</b>: $13" },
{ re: "\\?TSF:MSG:SEND,(\\d+)-(\\d+)-(\\d+)-(\\d+),s=(\\d+),c=(\\d+),t=(\\d+),pt=(\\d+),l=(\\d+),sg=(\\d+),ft=(\\d+),st=(\\w+):(.*)", d: "<u><b style='color:orange'>Sent Message without radio ACK</b></u><br><b>Sender</b>: $1<br><b>Last Node</b>: $2<br><b>Next Node</b>: $3<br><b>Destination</b>: $4<br><b>Sensor Id</b>: $5<br><b>Command</b>: {command:$6}<br><b>Message Type</b>:{type:$6:$7}<br><b>Payload Type</b>: {pt:$8}<br><b>Payload Length</b>: $9<br><b>Signing</b>: $10<br><b>Failed uplink counter</b>: $11<br><b>Status</b>: $12 (OK=success, NACK=no radio ACK received)<br><b>Payload</b>: $13" },
// transport HAL
{ re: "THA:INIT", d: "Initialise transport HAL" },
{ re: "THA:INIT:PSK=(.*)", d: "Initialise transport HAL, PSK=<b>$1</b>" },
{ re: "THA:SAD:ADDR=(\\d+)", d: "Set transport address: <b>$1</b>" },
{ re: "THA:GAD:ADDR=(\\d+)", d: "Get transport address: <b>$1</b>" },
{ re: "THA:DATA:AVAIL", d: "Transport HAL received data" },
{ re: "THA:SAN:RES=(\\d+)", d: "Transport sanity check, result=<b>$1</b> (0=NOK, 1=OK)" },
{ re: "THA:RCV:MSG=(.*)", d: "Message received: <b>$1</b>" },
{ re: "THA:RCV:DECRYPT", d: "Decrypt message" },
{ re: "THA:RCV:PLAIN=(.*)", d: "Message plaint text: <b>$1</b>" },
{ re: "!THA:RCV:PVER=(\\d+)", d: "Message protocol version mismatch: <b>$1</b>" },
{ re: "!THA:RCV:LEN=(\\d+),EXP=(\\d+)", d: "Invalid message length, actual <b>$1</b>, expected <b>$2</b>" },
{ re: "THA:RCV:MSG LEN=(\\d+)", d: "Length of received message: <b>$1</b>" },
{ re: "THA:SND:MSG=(.*)", d: "Send message: <b>$1</b>" },
{ re: "THA:SND:ENCRYPT", d: "Encrypt message" },
{ re: "THA:SND:CIP=(.*)", d: "Ciphertext of encrypted message: <b>$1</b>" },
{ re: "THA:SND:MSG LEN=(\\d+),RES=(\\d+)", d: "Sending message with length=<b>$1</b>, result=<b>$2</b> (0=NOK, 1=OK)" },
// Signing backend
{ re: "SGN:INI:BND OK", d: "Backend has initialized ok" },
{ re: "!SGN:INI:BND FAIL", d: "Backend has not initialized ok" },
{ re: "SGN:PER:OK", d: "Personalization data is ok" },
{ re: "!SGN:PER:TAMPERED", d: "Personalization data has been tampered" },
{ re: "SGN:PRE:SGN REQ", d: "Signing required" },
{ re: "SGN:PRE:SGN REQ,TO=(\\d+)", d: "Tell node <b>$1</b> that we require signing" },
{ re: "SGN:PRE:SGN REQ,FROM=(\\d+)", d: " Node <b>$1</b> require signing" },
{ re: "SGN:PRE:SGN NREQ", d: "Signing not required" },
{ re: "SGN:PRE:SGN REQ,TO=(\\d+)", d: "Tell node <b>$1</b> that we do not require signing" },
{ re: "SGN:PRE:SGN NREQ,FROM=(\\d+)", d: "Node <b>$1</b> does not require signing" },
{ re: "!SGN:PRE:SGN NREQ,FROM=(\\d+) REJ", d: "Node <b>$1</b> does not require signing but used to (requirement remain unchanged)" },
{ re: "SGN:PRE:WHI REQ", d: "Whitelisting required" },
{ re: "SGN:PRE:WHI REQ;TO=(\\d+)", d: "Tell <b>$1</b> that we require whitelisting" },
{ re: "SGN:PRE:WHI REQ,FROM=(\\d+)", d: "Node <b>$1</b> require whitelisting" },
{ re: "SGN:PRE:WHI NREQ", d: " Whitelisting not required" },
{ re: "SGN:PRE:WHI NREQ,TO=(\\d+)", d: "Tell node <b>$1</b> that we do not require whitelisting" },
{ re: "SGN:PRE:WHI NREQ,FROM=(\\d+)", d: "Node <b>$1</b> does not require whitelisting" },
{ re: "!SGN:PRE:WHI NREQ,FROM=(\\d+) REJ", d: "Node <b>$1</b> does not require whitelisting but used to (requirement remain unchanged)" },
{ re: "SGN:PRE:XMT,TO=(\\d+)", d: "Presentation data transmitted to node <b>$1</b>" },
{ re: "!SGN:PRE:XMT,TO=(\\d+) FAIL", d: "Presentation data not properly transmitted to node <b>$1</b>" },
{ re: "SGN:PRE:WAIT GW", d: "Waiting for gateway presentation data" },
{ re: "!SGN:PRE:VER=(\\d+)", d: "Presentation version <b>$1</b> is not supported" },
{ re: "SGN:PRE:NSUP", d: "Received signing presentation but signing is not supported" },
{ re: "SGN:PRE:NSUP,TO=(\\d+)", d: "Informing node <b>$1</b> that we do not support signing" },
{ re: "SGN:SGN:NCE REQ,TO=(\\d+)", d: "Nonce request transmitted to node <b>$1</b>" },
{ re: "!SGN:SGN:NCE REQ,TO=(\\d+) FAIL", d: "Nonce request not properly transmitted to node <b>$1</b>" },
{ re: "!SGN:SGN:NCE TMO", d: "Timeout waiting for nonce" },
{ re: "SGN:SGN:SGN", d: "Message signed" },
{ re: "!SGN:SGN:SGN FAIL", d: "Message failed to be signed" },
{ re: "SGN:SGN:NREQ=(\\d+)", d: "Node <b>$1</b> does not require signed messages" },
{ re: "SGN:SGN:(\\d+)!=(\\d+) NUS", d: "Will not sign because <b>$1</b> is not <b>$2</b> (repeater)" },
{ re: "!SGN:SGN:STATE", d: "Security system in a invalid state (personalization data tampered)" },
{ re: "!SGN:VER:NSG", d: "Message was not signed, but it should have been" },
{ re: "!SGN:VER:FAIL", d: "Verification failed" },
{ re: "SGN:VER:OK", d: "Verification succeeded" },
{ re: "SGN:VER:LEFT=(\\d+)", d: "<b>$1</b> number of failed verifications left in a row before node is locked" },
{ re: "!SGN:VER:STATE", d: "Security system in a invalid state (personalization data tampered)" },
{ re: "SGN:SKP:MSG CMD=(\\d+),TYPE=(\\d+)", d: "Message with command <b>$1</b> and type <b>$2</b> does not need to be signed" },
{ re: "SGN:SKP:ECHO CMD=(\\d+),TYPE=(\\d+)", d: "ECHO messages do not need to be signed" },
{ re: "SGN:NCE:LEFT=(\\d+)", d: "<b>$1</b> number of nonce requests between successful verifications left before node is locked" },
{ re: "SGN:NCE:XMT,TO=(\\d+)", d: "Nonce data transmitted to node <b>$1</b>" },
{ re: "!SGN:NCE:XMT,TO=(\\d+) FAIL", d: "Nonce data not properly transmitted to node <b>$1</b>" },
{ re: "!SGN:NCE:GEN", d: "Failed to generate nonce" },
{ re: "SGN:NCE:NSUP (DROPPED)", d: "Ignored nonce/request for nonce (signing not supported)" },
{ re: "SGN:NCE:FROM=(\\d+)", d: "Received nonce from node <b>$1</b>" },
{ re: "SGN:NCE:(\\d+)!=(\\d+) (DROPPED)", d: "Ignoring nonce as it did not come from the desgination of the message to sign" },
{ re: "!SGN:BND:INIT FAIL", d: "Failed to initialize signing backend" },
{ re: "!SGN:BND:PWD<8", d: "Signing password too short" },
{ re: "!SGN:BND:PER", d: "Backend not personalized" },
{ re: "!SGN:BND:SER", d: "Could not get device unique serial from backend" },
{ re: "!SGN:BND:TMR", d: "Backend timed out" },
{ re: "!SGN:BND:SIG,SIZE,(\\d+)>(\\d+)", d: "Refusing to sign message with length <b>$1</b> because it is bigger than allowed size <b>$2</b> " },
{ re: "SGN:BND:SIG WHI,ID=(\\d+)", d: "Salting message with our id <b>$1</b>" },
{ re: "SGN:BND:SIG WHI,SERIAL=(.*)", d: "Salting message with our serial <b>$1</b>" },
{ re: "!SGN:BND:VER ONGOING", d: "Verification failed, no ongoing session" },
{ re: "!SGN:BND:VER,IDENT=(\\d+)", d: "Verification failed, identifier <b>$1</b> is unknown" },
{ re: "SGN:BND:VER WHI,ID=(\\d+)", d: "Id <b>$1</b> found in whitelist" },
{ re: "SGN:BND:VER WHI,SERIAL=(.*)", d: "Expecting serial <b>$1</b> for this sender" },
{ re: "!SGN:BND:VER WHI,ID=(\\d+) MISSING", d: "Id <b>$1</b> not found in whitelist" },
{ re: "SGN:BND:NONCE=(.*)", d: "Calculating signature using nonce <b>$1</b>" },
{ re: "SGN:BND:HMAC=(.*)", d: "Calculated signature is <b>$1</b>" },
// NodeManager
{ re: "NM:INIT:VER=(.*)", d: "NodeManager version <b>$1</b>" },
{ re: "NM:INIT:INO=(.*)", d: "Sketch <b>$1</b>" },
{ re: "NM:INIT:LIB VER=(.+) CP=(.+)", d: "MySensors Library version <b>$1</b>, capabilities <b>$2</b>" },
{ re: "NM:INIT:RBT p=(\\d+)", d: "Configured reboot pin <b>$1</b>" },
{ re: "NM:BFR:INIT", d: "Connecting to the gateway..." },
{ re: "NM:BFR:OK", d: "Connection to the gateway successful" },
{ re: "NM:BFR:INT p=(\\d+) m=(\\d+)", d: "Setting up interrupt on pin <b>$1</b> with mode <b>$2</b>" },
{ re: "NM:PRES:(\\w+)\\((\\d+)\\) p=(\\d+) t=(\\d+)", d: "Presented to the gateway <b>child $2</b> for sensor <b>$1</b> as <b>{type:0:$3}</b> with type <b>{type:1:$4}</b>" },
{ re: "NM:STP:ID=(\\d+) M=(\\d+)", d: "This node has id <b>$1</b> and metric is set to <b>$2</b>" },
{ re: "NM:STP:SD T=(\\d+)", d: "Connected SD card reader, type <b>$1</b>" },
{ re: "NM:STP:HW V=(\\d+) F=(\\d+) M=(\\d+)", d: "CPU Vcc is <b>$1 mV</b>, CPU frequency <b>$2 Mhz</b>, free memory <b>$3 bytes</b>" },
{ re: "NM:LOOP:(\\w+)\\((\\d+)\\):SET t=(\\d+) v=(.+)", d: "New value for <b>child $2</b> of sensor <b>$1</b> with <b>{type:1:$3}</b> = <b>$4</b>" },
{ re: "NM:LOOP:INT p=(\\d+) v=(\\d+)", d: "Interrupt received on pin <b>$1</b>, value <b>$2</b>" },
{ re: "NM:LOOP:INPUT\\.\\.\\.", d: "Waiting for input from the serial port" },
{ re: "NM:LOOP:INPUT v=(.*)", d: "Received an input from the serial port: <b>$1</b>" },
{ re: "NM:TIME:REQ", d: "Requesting the time to the controller" },
{ re: "NM:TIME:OK ts=(\\d+)", d: "Received the time from the controller: <b>$1</b>" },
{ re: "NM:SLP:WKP", d: "Wakeup requested" },
{ re: "NM:SLP:SLEEP s=(\\d+)", d: "Going to sleep for <b>$1</b> seconds" },
{ re: "NM:SLP:AWAKE", d: "Waking up from sleep" },
{ re: "NM:SLP:LOAD s=(\\d+)", d: "Loaded configured sleep time: <b>$1</b> seconds" },
{ re: "NM:MSG:SEND\\((\\d+)\\) t=(\\d+) p=(.+)", d: "<b>Child $1</b> sent <b>{type:1:$2}</b> = <b>$3</b>" },
{ re: "NM:MSG:RECV\\((\\d+)\\) c=(\\d+) t=(\\d+) p=(.+)", d: "Received a <b>{command:$2}</b> message for <b>child $1</b> with <b>{type:$2:$3}</b> = <b>$4</b>" },
{ re: "NM:PWR:RBT", d: "Rebooting the node as requested" },
{ re: "NM:PWR:ON p=(\\d+)", d: "Powering <b>on</b> the sensor(s) through pin <b>$1</b>" },
{ re: "NM:PWR:OFF p=(\\d+)", d: "Powering <b>off</b> the sensor(s) through pin <b>$1</b>" },
{ re: "NM:OTA:REQ f=(\\d+) v=(\\d+)", d: "Over-the-air configuration change requested, function <b>$1</b> value <b>$2</b>" },
{ re: "NM:EEPR:CLR", d: "Clearing the EEPROM as requested" },
{ re: "NM:EEPR:LOAD i=(\\d+) v=(\\d+)", d: "Read from EEPROM at position <b>$1</b> the value <b>$2</b>" },
{ re: "NM:EEPR:SAVE i=(\\d+) v=(\\d+)", d: "Wrote to EEPROM at position <b>$1</b> the value <b>$2</b>" },
{ re: "NM:EEPR:(\\w+)\\((\\d+)\\):LOAD", d: "Restoring from EEPROM the value of <b>child $2</b> of sensor <b>$1</b>" },
{ re: "NM:EEPR:(\\w+)\\((\\d+)\\):SAVE", d: "Saving to EEPROM the value of <b>child $2</b> of sensor <b>$1</b" },
{ re: "NM:SENS:([^:]+):(.+)", d: "Sensor <b>$1</b>: $2" },
{ re: "!NM:SENS:([^:]+):(.+)", d: "Error in sensor <b>$1</b>: <b style='color:red'>$2</b>" },
];
// Init regexes
for (var i=0, len=match.length;i<len; i++) {
match[i].re = new RegExp("^" + rprefix + match[i].re);
}
var stripPrefix = new RegExp("^" + rprefix + "(.*)");
function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}
var splitWithTail = function(value, separator, limit) {
var pattern, startIndex, m, parts = [];
if(!limit) {
return value.split(separator);
}
if(separator instanceof RegExp) {
pattern = new RegExp(separator.source, 'g' + (separator.ignoreCase ? 'i' : '') + (separator.multiline ? 'm' : ''));
} else {
pattern = new RegExp(separator.replace(/([.*+?^${}()|\[\]\/\\])/g, '\\$1'), 'g');
}
do {
startIndex = pattern.lastIndex;
if(m = pattern.exec(value)) {
parts.push(value.substr(startIndex, m.index - startIndex));
}
} while(m && parts.length < limit - 1);
parts.push(value.substr(pattern.lastIndex));
return parts;
}
new Vue({
el: "#parser",
data: function() {
return {
source: decodeURIComponent(getQueryVariable("log") || ""),
parsed: []
};
},
mounted: function() {
this.parse();
},
watch: {
source: function() {
this.parse();
}
},
methods: {
selector: function(cmd) {
switch (cmd) {
case "0":
return "presentation";
break;
case "1":
case "2":
return "subtype";
break;
case "3":
return "internal";
break;
case "4":
return "stream";
break;
}
},
type: function(cmd, type) {
var t = types[this.selector(cmd)]
return t !== undefined ? t[type] || "Undefined" : "Undefined";
},
match: function(msg) {
var self = this;
var found = false;
for (var i=0, len=match.length;!found && i<len; i++) {
var r = match[i];
if (r.re.test(msg)) {
msg = msg.replace(r.re, r.d);
msg = msg.replace(/{command:(\d+)}/g, function(match, m1) { return types.command[m1] });
msg = msg.replace(/{pt:(\d+)}/g, function(match, m1) { return types.payloadtype[m1] });
return msg.replace(/{type:(\d+):(\d+)}/g, function(match, cmd, type) {
return self.type(cmd, type);
});
}
}
},
parse: function() {
console.log(this.source);
var self = this;
var rows = this.source.split("\n");
this.parsed = _.map(rows, function(r) {
//var p = r.split(";");
var p = splitWithTail(r, ";", 6);
if (p.length !== 6) {
var desc = self.match(r);
return ["","","","",desc?"":"Unknown", r, desc];
}
var sel = self.selector(p[2]);
var desc = "";
if (p[2] == "3" && p[4] == "9") {
desc = self.match(p[5]);
}
var node = stripPrefix.exec(p[0]);
return [
node[1],
p[1],
types.command[p[2]],
p[3]=="1"?"true":"false",
types[sel][p[4]],
p[5],
desc
];
});
},
copy: function() {
copyTextToClipboard('https://www.mysensors.org/build/parser?log='+encodeURIComponent(this.source));
}
}
});

154
lib/MySensors/Makefile Normal file
View File

@@ -0,0 +1,154 @@
#############################################################################
#
# Makefile for MySensors
#
#
# The arduino library build part were inspired by
# Arduino-Makefile project, Copyright (C) 2012 Sudar <http://sudarmuthu.com>
#
# Description:
# ------------
# use make all and make install to install the gateway
#
CONFIG_FILE=Makefile.inc
include $(CONFIG_FILE)
CPPFLAGS+=-Ofast -g -Wall -Wextra
DEPFLAGS=-MT $@ -MMD -MP
GATEWAY_BIN=mysgw
GATEWAY=$(BINDIR)/$(GATEWAY_BIN)
GATEWAY_C_SOURCES=$(wildcard hal/architecture/Linux/drivers/core/*.c)
GATEWAY_CPP_SOURCES=$(wildcard hal/architecture/Linux/drivers/core/*.cpp) examples_linux/mysgw.cpp
GATEWAY_OBJECTS=$(patsubst %.c,$(BUILDDIR)/%.o,$(GATEWAY_C_SOURCES)) $(patsubst %.cpp,$(BUILDDIR)/%.o,$(GATEWAY_CPP_SOURCES))
INCLUDES=-I. -I./core -I./hal/architecture/Linux/drivers/core
ifeq ($(SOC),$(filter $(SOC),BCM2835 BCM2836 BCM2837 BCM2711))
BCM_C_SOURCES=$(wildcard hal/architecture/Linux/drivers/BCM/*.c)
BCM_CPP_SOURCES=$(wildcard hal/architecture/Linux/drivers/BCM/*.cpp)
GATEWAY_OBJECTS+=$(patsubst %.c,$(BUILDDIR)/%.o,$(BCM_C_SOURCES)) $(patsubst %.cpp,$(BUILDDIR)/%.o,$(BCM_CPP_SOURCES))
INCLUDES+=-I./hal/architecture/Linux/drivers/BCM
endif
# Gets include flags for library
get_library_includes = $(if $(and $(wildcard $(1)/src), $(wildcard $(1)/library.properties)), \
-I$(1)/src, \
$(addprefix -I,$(1) $(wildcard $(1)/utility)))
# Gets all sources with given extension (param2) for library (path = param1)
# for old (1.0.x) layout looks in . and "utility" directories
# for new (1.5.x) layout looks in src and recursively its subdirectories
get_library_files = $(if $(and $(wildcard $(1)/src), $(wildcard $(1)/library.properties)), \
$(call rwildcard,$(1)/src/,*.$(2)), \
$(wildcard $(1)/*.$(2) $(1)/utility/*.$(2)))
ifdef ARDUINO_LIB_DIR
ARDUINO=arduino
ARDUINO_LIBS:=$(shell find $(ARDUINO_LIB_DIR) -mindepth 1 -maxdepth 1 -type d)
ARDUINO_INCLUDES:=$(foreach lib, $(ARDUINO_LIBS), $(call get_library_includes,$(lib)))
ARDUINO_LIB_CPP_SRCS:=$(foreach lib, $(ARDUINO_LIBS), $(call get_library_files,$(lib),cpp))
ARDUINO_LIB_C_SRCS:=$(foreach lib, $(ARDUINO_LIBS), $(call get_library_files,$(lib),c))
ARDUINO_LIB_AS_SRCS:=$(foreach lib, $(ARDUINO_LIBS), $(call get_library_files,$(lib),S))
ARDUINO_LIB_OBJS=$(patsubst $(ARDUINO_LIB_DIR)/%.cpp,$(BUILDDIR)/arduinolibs/%.cpp.o,$(ARDUINO_LIB_CPP_SRCS)) \
$(patsubst $(ARDUINO_LIB_DIR)/%.c,$(BUILDDIR)/arduinolibs/%.c.o,$(ARDUINO_LIB_C_SRCS)) \
$(patsubst $(ARDUINO_LIB_DIR)/%.S,$(BUILDDIR)/arduinolibs/%.S.o,$(ARDUINO_LIB_AS_SRCS))
INCLUDES+=$(ARDUINO_INCLUDES)
DEPS+=$(ARDUINO_LIB_OBJS:.o=.d)
endif
DEPS+=$(GATEWAY_OBJECTS:.o=.d)
.PHONY: all createdir cleanconfig clean install uninstall
all: createdir $(ARDUINO) $(GATEWAY)
createdir:
@mkdir -p $(BUILDDIR) $(BINDIR)
# Arduino libraries Build
$(ARDUINO): CPPFLAGS+=-DARDUINO=100
$(ARDUINO): $(ARDUINO_LIB_OBJS)
@printf "[Done building Arduino Libraries]\n"
# Gateway Build
$(GATEWAY): $(GATEWAY_OBJECTS) $(ARDUINO_LIB_OBJS)
$(CXX) $(LDFLAGS) -o $@ $(GATEWAY_OBJECTS) $(ARDUINO_LIB_OBJS)
# Include all .d files
-include $(DEPS)
$(BUILDDIR)/arduinolibs/%.cpp.o: $(ARDUINO_LIB_DIR)/%.cpp
@mkdir -p $(dir $@)
$(CXX) $(DEPFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(INCLUDES) -c $< -o $@
$(BUILDDIR)/arduinolibs/%.c.o: $(ARDUINO_LIB_DIR)/%.c
@mkdir -p $(dir $@)
$(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) $(INCLUDES) -c $< -o $@
$(BUILDDIR)/arduinolibs/%.S.o: $(ARDUINO_LIB_DIR)/%.S
@mkdir -p $(dir $@)
$(CC) $(DEPFLAGS) $(CPPFLAGS) $(ASFLAGS) $(INCLUDES) -c $< -o $@
$(BUILDDIR)/%.o: %.cpp
@mkdir -p $(dir $@)
$(CXX) $(DEPFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(INCLUDES) -c $< -o $@
$(BUILDDIR)/%.o: %.c
@mkdir -p $(dir $@)
$(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) $(INCLUDES) -c $< -o $@
# clear configuration files
cleanconfig:
@echo "[Cleaning configuration]"
rm -rf $(CONFIG_FILE)
# clear build files
clean:
@echo "[Cleaning]"
rm -rf $(BUILDDIR) $(BINDIR)
$(CONFIG_FILE):
@echo "[Running configure]"
@./configure --no-clean
install: all install-gateway install-initscripts
install-gateway:
@echo "Installing $(GATEWAY) to ${DESTDIR}$(GATEWAY_DIR)"
@install -m 0755 $(GATEWAY) ${DESTDIR}$(GATEWAY_DIR)
install-initscripts:
ifeq ($(INIT_SYSTEM), systemd)
install -m0644 initscripts/mysgw.systemd ${DESTDIR}/etc/systemd/system/mysgw.service
@sed -i -e "s|%gateway_dir%|${GATEWAY_DIR}|g" ${DESTDIR}/etc/systemd/system/mysgw.service
systemctl daemon-reload
@echo "MySensors gateway has been installed, to add to the boot run:"
@echo " sudo systemctl enable mysgw.service"
@echo "To start the gateway run:"
@echo " sudo systemctl start mysgw.service"
else ifeq ($(INIT_SYSTEM), sysvinit)
install -m0755 initscripts/mysgw.sysvinit ${DESTDIR}/etc/init.d/mysgw
@sed -i -e "s|%gateway_dir%|${GATEWAY_DIR}|g" ${DESTDIR}/etc/init.d/mysgw
@echo "MySensors gateway has been installed, to add to the boot run:"
@echo " sudo update-rc.d mysgw defaults"
@echo "To start the gateway run:"
@echo " sudo service mysgw start"
endif
uninstall:
ifeq ($(INIT_SYSTEM), systemd)
@echo "Stopping daemon mysgw (ignore errors)"
-@systemctl stop mysgw.service
@echo "removing files"
rm /etc/systemd/system/mysgw.service $(GATEWAY_DIR)/$(GATEWAY_BIN)
else ifeq ($(INIT_SYSTEM), sysvinit)
@echo "Stopping daemon mysgw (ignore errors)"
-@service mysgw stop
@echo "removing files"
rm /etc/init.d/mysgw $(GATEWAY_DIR)/$(GATEWAY_BIN)
endif

39
lib/MySensors/MyASM.S Normal file
View File

@@ -0,0 +1,39 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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.
*
*/
#if defined(ARDUINO_ARCH_SAMD)
/* workaround to prevent compiler error */
.thumb_func
doNothing:
nop
.size doNothing, .-doNothing
#elif defined(ARDUINO_ARCH_NRF5)
/* workaround to prevent compiler error */
.thumb_func
doNothing:
nop
.size doNothing, .-doNothing
#elif defined(ARDUINO_ARCH_AVR)
#include "hal/crypto/AVR/drivers/SHA256/SHA256.S"
#endif

2369
lib/MySensors/MyConfig.h Normal file

File diff suppressed because it is too large Load Diff

456
lib/MySensors/MySensors.h Normal file
View File

@@ -0,0 +1,456 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 MySensorsgrp MySensors
* @ingroup publics
* @{
* @brief The primary public API declaration for the MySensors library
*/
/**
* @file MySensors.h
*
* @brief API declaration for MySensors
*
* Include this header into your sketch to include the MySensors library and harness the power of
* all those sensors!
*/
#ifndef MySensors_h
#define MySensors_h
#ifdef __cplusplus
#include <Arduino.h>
#endif
#include <stdint.h>
#include "MyConfig.h"
#include "core/MyHelperFunctions.cpp"
#include "core/MySplashScreen.h"
#include "core/MySensorsCore.h"
// OTA Debug, has to be defined before HAL
#if defined(MY_OTA_LOG_SENDER_FEATURE) || defined(MY_OTA_LOG_RECEIVER_FEATURE)
#include "core/MyOTALogging.h"
#endif
// HARDWARE
#include "hal/architecture/MyHwHAL.h"
#include "hal/crypto/MyCryptoHAL.h"
#if defined(ARDUINO_ARCH_ESP8266)
#include "hal/architecture/ESP8266/MyHwESP8266.cpp"
#include "hal/crypto/generic/MyCryptoGeneric.cpp"
#elif defined(ARDUINO_ARCH_ESP32)
#include "hal/architecture/ESP32/MyHwESP32.cpp"
#include "hal/crypto/ESP32/MyCryptoESP32.cpp"
#elif defined(ARDUINO_ARCH_AVR)
#include "hal/architecture/AVR/MyHwAVR.cpp"
#include "hal/crypto/AVR/MyCryptoAVR.cpp"
#elif defined(ARDUINO_ARCH_SAMD)
#include "drivers/extEEPROM/extEEPROM.cpp"
#include "hal/architecture/SAMD/MyHwSAMD.cpp"
#include "hal/crypto/generic/MyCryptoGeneric.cpp"
#elif defined(ARDUINO_ARCH_STM32F1)
#include "hal/architecture/STM32F1/MyHwSTM32F1.cpp"
#include "hal/crypto/generic/MyCryptoGeneric.cpp"
#elif defined(ARDUINO_ARCH_NRF5) || defined(ARDUINO_ARCH_NRF52)
#include "hal/architecture/NRF5/MyHwNRF5.cpp"
#include "hal/crypto/generic/MyCryptoGeneric.cpp"
#elif defined(__arm__) && defined(TEENSYDUINO)
#include "hal/architecture/Teensy3/MyHwTeensy3.cpp"
#include "hal/crypto/generic/MyCryptoGeneric.cpp"
#elif defined(__linux__)
#include "hal/architecture/Linux/MyHwLinuxGeneric.cpp"
#include "hal/crypto/generic/MyCryptoGeneric.cpp"
#else
#error Hardware abstraction not defined (unsupported platform)
#endif
#include "hal/architecture/MyHwHAL.cpp"
// commonly used macros, sometimes missing in arch definitions
#if !defined(_BV)
#define _BV(x) (1<<(x)) //!< _BV
#endif
#if !defined(min) && !defined(__linux__)
#define min(a,b) ((a)<(b)?(a):(b)) //!< min
#endif
#if !defined(max) && !defined(__linux__)
#define max(a,b) ((a)>(b)?(a):(b)) //!< max
#endif
#if !defined(MIN)
#define MIN min //!< MIN
#endif
#if !defined(MAX)
#define MAX max //!< MAX
#endif
// OTA Debug second part, depends on HAL
#if defined(MY_OTA_LOG_SENDER_FEATURE) || defined(MY_OTA_LOG_RECEIVER_FEATURE)
#include "core/MyOTALogging.cpp"
#endif
#if defined(MY_LEDS_BLINKING_FEATURE)
#error MY_LEDS_BLINKING_FEATURE is now removed from MySensors core,\
define MY_DEFAULT_ERR_LED_PIN, MY_DEFAULT_TX_LED_PIN or\
MY_DEFAULT_RX_LED_PIN in your sketch instead to enable LEDs
#endif
#if defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
#include "core/MyLeds.cpp"
#else
#include "core/MyLeds.h"
#endif
#include "core/MyIndication.cpp"
// INCLUSION MODE
#if defined(MY_INCLUSION_MODE_FEATURE)
#include "core/MyInclusionMode.cpp"
#endif
// SIGNING
#include "core/MySigning.cpp"
#if defined(MY_SIGNING_FEATURE)
// SIGNING COMMON FUNCTIONS
#if defined(MY_SIGNING_ATSHA204) && defined(MY_SIGNING_SOFT)
#error Only one signing engine can be activated
#endif
#if defined(MY_SIGNING_ATSHA204) && defined(__linux__)
#error No support for ATSHA204 on this platform
#endif
#if defined(MY_SIGNING_ATSHA204)
#include "core/MySigningAtsha204.cpp"
#include "drivers/ATSHA204/ATSHA204.cpp"
#elif defined(MY_SIGNING_SOFT)
#include "core/MySigningAtsha204Soft.cpp"
#endif
#endif
// FLASH
#if defined(MY_OTA_FIRMWARE_FEATURE)
#ifndef MCUBOOT_PRESENT
#if defined(MY_OTA_USE_I2C_EEPROM)
#include "drivers/I2CEeprom/I2CEeprom.cpp"
#else
#include "drivers/SPIFlash/SPIFlash.cpp"
#endif
#endif
#include "core/MyOTAFirmwareUpdate.cpp"
#endif
// GATEWAY - TRANSPORT
#if defined(MY_CONTROLLER_IP_ADDRESS) || defined(MY_CONTROLLER_URL_ADDRESS)
#define MY_GATEWAY_CLIENT_MODE //!< gateway client mode
#endif
#if defined(MY_USE_UDP) && !defined(MY_GATEWAY_CLIENT_MODE)
#error You must specify MY_CONTROLLER_IP_ADDRESS or MY_CONTROLLER_URL_ADDRESS for UDP
#endif
// Set MQTT defaults if not set
#if !defined(MY_MQTT_PUBLISH_TOPIC_PREFIX)
#define MY_MQTT_PUBLISH_TOPIC_PREFIX "mygateway1-out"
#endif
#if !defined(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX)
#define MY_MQTT_SUBSCRIBE_TOPIC_PREFIX "mygateway1-in"
#endif
#if !defined(MY_MQTT_CLIENT_ID)
#define MY_MQTT_CLIENT_ID "mysensors-1"
#endif
#if defined(MY_GATEWAY_MQTT_CLIENT)
#if defined(MY_SENSOR_NETWORK)
// We assume that a gateway having a radio also should act as repeater
#define MY_REPEATER_FEATURE
#endif
// GATEWAY - COMMON FUNCTIONS
// We support MQTT Client using W5100, ESP8266, GSM modems supported by TinyGSM library and Linux
#if !defined(MY_GATEWAY_CLIENT_MODE) && !defined(MY_GATEWAY_TINYGSM)
#error You must specify MY_CONTROLLER_IP_ADDRESS or MY_CONTROLLER_URL_ADDRESS
#endif
#if defined(MY_GATEWAY_TINYGSM) && !defined(MY_GATEWAY_MQTT_CLIENT)
// TinyGSM currently only supports MQTTClient mode.
#error MY_GATEWAY_TINYGSM only works with MY_GATEWAY_MQTT_CLIENT
#endif
#include "core/MyGatewayTransport.cpp"
#include "core/MyProtocol.cpp"
#if defined(MY_GATEWAY_TINYGSM)
#include "drivers/TinyGSM/TinyGsmClient.h"
#endif
#if defined(MY_GATEWAY_LINUX)
#include "hal/architecture/Linux/drivers/core/EthernetClient.h"
#include "hal/architecture/Linux/drivers/core/EthernetServer.h"
#include "hal/architecture/Linux/drivers/core/IPAddress.h"
#endif
#include "drivers/PubSubClient/PubSubClient.cpp"
#include "core/MyGatewayTransportMQTTClient.cpp"
#elif defined(MY_GATEWAY_FEATURE)
// GATEWAY - COMMON FUNCTIONS
#include "core/MyGatewayTransport.cpp"
#include "core/MyProtocol.cpp"
// GATEWAY - CONFIGURATION
#if defined(MY_SENSOR_NETWORK)
// We assume that a gateway having a radio also should act as repeater
#define MY_REPEATER_FEATURE
#endif
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
// GATEWAY - ESP8266 / ESP32
#include "core/MyGatewayTransportEthernet.cpp"
#elif defined(MY_GATEWAY_LINUX)
// GATEWAY - Generic Linux
#if defined(MY_USE_UDP)
#error UDP mode is not available for Linux
#endif
#include "hal/architecture/Linux/drivers/core/EthernetClient.h"
#include "hal/architecture/Linux/drivers/core/EthernetServer.h"
#include "hal/architecture/Linux/drivers/core/IPAddress.h"
#include "core/MyGatewayTransportEthernet.cpp"
#elif defined(MY_GATEWAY_W5100)
// GATEWAY - W5100
#include "core/MyGatewayTransportEthernet.cpp"
#elif defined(MY_GATEWAY_ENC28J60)
// GATEWAY - ENC28J60
#if defined(MY_USE_UDP)
#error UDP mode is not available for ENC28J60
#endif
#include "core/MyGatewayTransportEthernet.cpp"
#elif defined(MY_GATEWAY_SERIAL)
// GATEWAY - SERIAL
#include "core/MyGatewayTransportSerial.cpp"
#endif
#endif
// TRANSPORT
#ifndef DOXYGEN
// count enabled transports
#if defined(MY_RADIO_RF24)
#define __RF24CNT 1 //!< __RF24CNT
#else
#define __RF24CNT 0 //!< __RF24CNT
#endif
#if defined(MY_RADIO_NRF5_ESB)
#define __NRF5ESBCNT 1 //!< __NRF5ESBCNT
#else
#define __NRF5ESBCNT 0 //!< __NRF5ESBCNT
#endif
#if defined(MY_RADIO_RFM69)
#define __RFM69CNT 1 //!< __RFM69CNT
#else
#define __RFM69CNT 0 //!< __RFM69CNT
#endif
#if defined(MY_RADIO_RFM95)
#define __RFM95CNT 1 //!< __RFM95CNT
#else
#define __RFM95CNT 0 //!< __RFM95CNT
#endif
#if defined(MY_RS485)
#define __RS485CNT 1 //!< __RS485CNT
#else
#define __RS485CNT 0 //!< __RS485CNT
#endif
#if (__RF24CNT + __NRF5ESBCNT + __RFM69CNT + __RFM95CNT + __RS485CNT > 1)
#error Only one forward link driver can be activated
#endif
#endif //DOXYGEN
// SANITY CHECK
#if defined(MY_REPEATER_FEATURE) || defined(MY_GATEWAY_FEATURE)
#define MY_TRANSPORT_SANITY_CHECK //!< enable regular transport sanity checks
#endif
// TRANSPORT INCLUDES
#if defined(MY_RADIO_RF24) || defined(MY_RADIO_NRF5_ESB) || defined(MY_RADIO_RFM69) || defined(MY_RADIO_RFM95) || defined(MY_RS485)
#include "hal/transport/MyTransportHAL.h"
#include "core/MyTransport.h"
// PARENT CHECK
#if defined(MY_PARENT_NODE_IS_STATIC) && (MY_PARENT_NODE_ID == AUTO)
#error Parent is static but no parent ID defined, set MY_PARENT_NODE_ID.
#endif
#if defined(MY_TRANSPORT_DONT_CARE_MODE)
#error MY_TRANSPORT_DONT_CARE_MODE is deprecated, set MY_TRANSPORT_WAIT_READY_MS instead!
#endif
// RAM ROUTING TABLE
#if defined(MY_RAM_ROUTING_TABLE_FEATURE) && defined(MY_REPEATER_FEATURE)
// activate feature based on architecture
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_NRF5) || defined(ARDUINO_ARCH_STM32F1) || defined(TEENSYDUINO) || defined(__linux__)
#define MY_RAM_ROUTING_TABLE_ENABLED
#elif defined(ARDUINO_ARCH_AVR)
#if defined(__avr_atmega1280__) || defined(__avr_atmega1284__) || defined(__avr_atmega2560__)
// >4kb, enable it
#define MY_RAM_ROUTING_TABLE_ENABLED
#else
// memory limited, enable with care
// #define MY_RAM_ROUTING_TABLE_ENABLED
#endif // __avr_atmega1280__, __avr_atmega1284__, __avr_atmega2560__
#endif // ARDUINO_ARCH_AVR
#endif
#ifdef DOXYGEN
/**
* @def MY_RAM_ROUTING_TABLE_ENABLED
* @brief Automatically set if RAM routing table is enabled
*
* @see MY_RAM_ROUTING_TABLE_FEATURE
*/
#define MY_RAM_ROUTING_TABLE_ENABLED
#endif
// SOFTSERIAL
#if defined(MY_GSM_TX) != defined(MY_GSM_RX)
#error Both, MY_GSM_TX and MY_GSM_RX need to be defined when using SoftSerial
#endif
#if defined(MY_GATEWAY_TINYGSM) && !defined(SerialAT) && (!defined(MY_GSM_TX) || !defined(MY_GSM_RX))
#error You need to define either SerialAT or MY_GSM_RX and MY_GSM_TX pins
#endif
// POWER PIN
#ifndef DOXYGEN
#if defined(MY_RF24_POWER_PIN) || defined(MY_RFM69_POWER_PIN) || defined(MY_RFM95_POWER_PIN) || defined(MY_RADIO_NRF5_ESB)
#define RADIO_CAN_POWER_OFF (true)
#else
#define RADIO_CAN_POWER_OFF (false)
#endif
#endif
// Transport drivers
#if defined(MY_RADIO_RF24)
#include "hal/transport/RF24/driver/RF24.cpp"
#include "hal/transport/RF24/MyTransportRF24.cpp"
#elif defined(MY_RADIO_NRF5_ESB)
#if !defined(ARDUINO_ARCH_NRF5)
#error No support for nRF5 radio on this platform
#endif
#include "hal/transport/NRF5_ESB/driver/Radio.cpp"
#include "hal/transport/NRF5_ESB/driver/Radio_ESB.cpp"
#include "hal/transport/NRF5_ESB/MyTransportNRF5_ESB.cpp"
#elif defined(MY_RS485)
#if !defined(MY_RS485_HWSERIAL)
#if defined(__linux__)
#error You must specify MY_RS485_HWSERIAL for RS485 transport
#endif
#include "drivers/AltSoftSerial/AltSoftSerial.cpp"
#endif
#include "hal/transport/RS485/MyTransportRS485.cpp"
#elif defined(MY_RADIO_RFM69)
#if defined(MY_RFM69_NEW_DRIVER)
#include "hal/transport/RFM69/driver/new/RFM69_new.cpp"
#else
#include "hal/transport/RFM69/driver/old/RFM69_old.cpp"
#endif
#include "hal/transport/RFM69/MyTransportRFM69.cpp"
#elif defined(MY_RADIO_RFM95)
#include "hal/transport/RFM95/driver/RFM95.cpp"
#include "hal/transport/RFM95/MyTransportRFM95.cpp"
#endif
#if (defined(MY_RF24_ENABLE_ENCRYPTION) && defined(MY_RADIO_RF24)) || (defined(MY_NRF5_ESB_ENABLE_ENCRYPTION) && defined(MY_RADIO_NRF5_ESB)) || (defined(MY_RFM69_ENABLE_ENCRYPTION) && defined(MY_RADIO_RFM69)) || (defined(MY_RFM95_ENABLE_ENCRYPTION) && defined(MY_RADIO_RFM95))
#define MY_TRANSPORT_ENCRYPTION //!< ïnternal flag
#endif
#include "hal/transport/MyTransportHAL.cpp"
// PASSIVE MODE
#if defined(MY_PASSIVE_NODE) && !defined(DOXYGEN)
#define MY_TRANSPORT_UPLINK_CHECK_DISABLED
#define MY_PARENT_NODE_IS_STATIC
#undef MY_REGISTRATION_FEATURE
#undef MY_SIGNING_FEATURE
#undef MY_OTA_FIRMWARE_FEATURE
#if defined(MY_GATEWAY_FEATURE) || defined(MY_REPEATER_FEATURE)
#error This node is configured as GW/repeater, MY_PASSIVE_NODE cannot be set simultaneously
#endif
#if (MY_NODE_ID == AUTO)
#error MY_PASSIVE_NODE configuration requires setting MY_NODE_ID
#endif
#endif
#include "core/MyTransport.cpp"
#endif
// Make sure to disable child features when parent feature is disabled
#if !defined(MY_SENSOR_NETWORK)
#undef MY_OTA_FIRMWARE_FEATURE
#undef MY_REPEATER_FEATURE
#undef MY_SIGNING_NODE_WHITELISTING
#undef MY_SIGNING_FEATURE
#endif
#if !defined(MY_GATEWAY_FEATURE)
#undef MY_INCLUSION_MODE_FEATURE
#undef MY_INCLUSION_BUTTON_FEATURE
#endif
#if !defined(MY_CORE_ONLY)
#if !defined(MY_GATEWAY_FEATURE) && !defined(MY_SENSOR_NETWORK)
#error No forward link or gateway feature activated. This means nowhere to send messages! Pretty pointless.
#endif
#endif
#include "core/MyCapabilities.h"
#include "core/MyMessage.cpp"
#include "core/MySplashScreen.cpp"
#include "core/MySensorsCore.cpp"
// HW mains
#if defined(ARDUINO_ARCH_AVR)
#include "hal/architecture/AVR/MyMainAVR.cpp"
#elif defined(ARDUINO_ARCH_SAMD)
#include "hal/architecture/SAMD/MyMainSAMD.cpp"
#elif defined(ARDUINO_ARCH_ESP8266)
#include "hal/architecture/ESP8266/MyMainESP8266.cpp"
#elif defined(ARDUINO_ARCH_NRF5)
#include "hal/architecture/NRF5/MyMainNRF5.cpp"
#elif defined(ARDUINO_ARCH_ESP32)
#include "hal/architecture/ESP32/MyMainESP32.cpp"
#elif defined(__linux__)
#include "hal/architecture/Linux/MyMainLinuxGeneric.cpp"
#elif defined(ARDUINO_ARCH_STM32F1)
#include "hal/architecture/STM32F1/MyMainSTM32F1.cpp"
#elif defined(__arm__) && defined(TEENSYDUINO)
#include "hal/architecture/Teensy3/MyMainTeensy3.cpp"
#endif
#endif
/** @}*/

19
lib/MySensors/README.md Normal file
View File

@@ -0,0 +1,19 @@
MySensors Library v2.3.2
Please visit www.mysensors.org for more information
Current version in Arduino IDE [![arduino-library-badge](https://www.ardu-badge.com/badge/MySensors.svg)](https://www.ardu-badge.com/MySensors)
Documentation
-------------
[master](https://www.mysensors.org/apidocs/index.html) [development](https://www.mysensors.org/apidocs-beta/index.html)
CI statuses
-----------
Current build status of master branch: [![Build Status](https://ci.mysensors.org/job/MySensors/job/MySensors/job/master/badge/icon)](https://ci.mysensors.org/job/MySensors/job/MySensors/job/master/)
Current build status of development branch: [![Build Status](https://ci.mysensors.org/job/MySensors/job/MySensors/job/development/badge/icon)](https://ci.mysensors.org/job/MySensors/job/MySensors/job/development/)
Current build status of master branch (nightly build of Arduino IDE): [![Build Status](https://ci.mysensors.org/job/MySensors-nightly-IDE/job/MySensors/job/master/badge/icon)](https://ci.mysensors.org/job/MySensors-nightly-IDE/job/MySensors/job/master/)
Current build status of development branch (nightly build of Arduino IDE): [![Build Status](https://ci.mysensors.org/job/MySensors-nightly-IDE/job/MySensors/job/development/badge/icon)](https://ci.mysensors.org/job/MySensors-nightly-IDE/job/MySensors/job/development/)

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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 MyCapabilities.h
* @ingroup MyCapabilities
*/
#ifndef MyCapabilities_h
#define MyCapabilities_h
/**
* @defgroup MyCapabilities Node capabilities indicator
* @ingroup MyConfigGrp
*
* @brief MySensors capabilities indications.
*
* At node startup, a capabilities string is shown as part of the initialization logs.
* This string indicate what configuration the node is running with.
*
* The string symbols are ordered in the following way:
* | Setting | Reset | Radio | OTA | Node | Architecture | Signing | Buffering | Encryption
* |-----------|-------------------|-------------------|--------------------|------------------|------------------|------------------|-------------------|-----------------
* | Indicator | @ref MY_CAP_RESET | @ref MY_CAP_RADIO | @ref MY_CAP_OTA_FW | @ref MY_CAP_TYPE | @ref MY_CAP_ARCH | @ref MY_CAP_SIGN | @ref MY_CAP_RXBUF | @ref MY_CAP_ENCR
*
* @see MY_CAPABILITIES
*
* @{
*/
// Remote reset
/**
* @def MY_CAP_RESET
* @brief Indicate the remote reset setting.
*
* @see MY_DISABLE_REMOTE_RESET
*
* | Setting | Indicator
* |------------|----------
* | Enabled | R
* | Disabled | N
*/
#if defined(MY_DISABLE_REMOTE_RESET)
#define MY_CAP_RESET "N"
#else
#define MY_CAP_RESET "R"
#endif
// OTA firmware update feature
/**
* @def MY_CAP_OTA_FW
* @brief Indicate the OTA update setting.
*
* @see MY_OTA_FIRMWARE_FEATURE
*
* | Setting | Indicator
* |------------|----------
* | Enabled | O
* | Disabled | N
*/
#if defined(MY_OTA_FIRMWARE_FEATURE)
#define MY_CAP_OTA_FW "O"
#else
#define MY_CAP_OTA_FW "N"
#endif
// Transport
/**
* @def MY_CAP_RADIO
* @brief Indicate the type of transport selected.
*
* @see MY_RADIO_RF24, MY_RADIO_NRF5_ESB, MY_RADIO_RFM69, MY_RFM69_NEW_DRIVER, MY_RADIO_RFM95, MY_RS485
*
* | Radio | Indicator
* |--------------|----------
* | nRF24/nRF5 | N
* | %RFM69 (old) | R
* | %RFM69 (new) | P
* | RFM95 | L
* | RS485 | S
* | None | -
*/
#if defined(MY_RADIO_RF24) || defined(MY_RADIO_NRF5_ESB)
#define MY_CAP_RADIO "N"
#elif defined(MY_RADIO_RFM69)
#if !defined(MY_RFM69_NEW_DRIVER)
// old RFM69 driver
#define MY_CAP_RADIO "R"
#else
// new RFM69 driver
#define MY_CAP_RADIO "P"
#endif
#elif defined(MY_RADIO_RFM95)
#define MY_CAP_RADIO "L"
#elif defined(MY_RS485)
#define MY_CAP_RADIO "S"
#else
#define MY_CAP_RADIO "-"
#endif
// Node type
/**
* @def MY_CAP_TYPE
* @brief Indicate the type of node.
*
* @see MY_GATEWAY_FEATURE, MY_REPEATER_FEATURE, MY_PASSIVE_NODE
*
* | Node type | Indicator
* |-----------|----------
* | Gateway | G
* | Repeater | R
* | Passive | P
* | Node | N
*/
#if defined(MY_GATEWAY_FEATURE)
#define MY_CAP_TYPE "G"
#elif defined(MY_REPEATER_FEATURE)
#define MY_CAP_TYPE "R"
#elif defined(MY_PASSIVE_NODE)
#define MY_CAP_TYPE "P"
#else
#define MY_CAP_TYPE "N"
#endif
// Architecture
/**
* @def MY_CAP_ARCH
* @brief Indicate the architecture.
*
* @see ARDUINO_ARCH_SAMD, ARDUINO_ARCH_NRF5, ARDUINO_ARCH_ESP8266, ARDUINO_ARCH_AVR, ARDUINO_ARCH_STM32F1, TEENSYDUINO
*
* | Architecture | Indicator
* |--------------|----------
* | SAMD | S
* | nRF5 | N
* | ESP8266 | E
* | AVR | A
* | STM32F1 | F
* | TEENSY | T
* | Linux | L
* | Unknown | -
*/
#if defined(ARDUINO_ARCH_SAMD)
#define MY_CAP_ARCH "S"
#elif defined(ARDUINO_ARCH_NRF5)
#define MY_CAP_ARCH "N"
#elif defined(ARDUINO_ARCH_ESP8266)
#define MY_CAP_ARCH "E"
#elif defined(ARDUINO_ARCH_ESP32)
#define MY_CAP_ARCH "F"
#elif defined(ARDUINO_ARCH_AVR)
#define MY_CAP_ARCH "A"
#elif defined(ARDUINO_ARCH_STM32F1)
#define MY_CAP_ARCH "F"
#elif defined(__arm__) && defined(TEENSYDUINO)
#define MY_CAP_ARCH "T"
#elif defined(__linux__)
#define MY_CAP_ARCH "L"
#else
#define MY_CAP_ARCH "-"
#endif
// Signing
/**
* @def MY_CAP_SIGN
* @brief Indicate the signing backend used.
*
* @see MY_SIGNING_ATSHA204, MY_SIGNING_SOFT
*
* | Signing backend | Indicator
* |-----------------|----------
* | ATSHA204 | A
* | Software | S
* | No signing | -
*/
#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
// RX queue
/**
* @def MY_CAP_RXBUF
* @brief Indicate the rx message buffer setting.
*
* @see MY_RX_MESSAGE_BUFFER_FEATURE
*
* | Setting | Indicator
* |------------|----------
* | Enabled | Q
* | Disabled | -
*/
#if defined(MY_RX_MESSAGE_BUFFER_FEATURE)
#define MY_CAP_RXBUF "Q"
#else
#define MY_CAP_RXBUF "-"
#endif
// Radio encryption
/**
* @def MY_CAP_ENCR
* @brief Indicate the encryption setting.
*
* @see MY_ENCRYPTION_FEATURE
*
* | Setting | Indicator
* |------------|----------
* | Enabled | X
* | Disabled | -
*/
#if defined(MY_ENCRYPTION_FEATURE)
#define MY_CAP_ENCR "X"
#else
#define MY_CAP_ENCR "-"
#endif
/**
* @def MY_CAPABILITIES
* @brief This is the resulting capabilities string.
*
* @see MY_CAP_RESET, MY_CAP_RADIO, MY_CAP_OTA_FW, MY_CAP_TYPE, MY_CAP_ARCH, MY_CAP_SIGN, MY_CAP_RXBUF, MY_CAP_ENCR
*/
#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 MY_CAP_ENCR
/** @}*/ // End of MyCapabilities group
#endif /* MyCapabilities_h */

View File

@@ -0,0 +1,92 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 MyEepromAddresses.h
* @brief Eeprom addresses for MySensors library data
*
* @defgroup MyEepromAddressesgrp MyEepromAddresses
* @ingroup internals
* @{
*
*/
#ifndef MyEepromAddresses_h
#define MyEepromAddresses_h
// EEPROM variable sizes, in bytes
#define SIZE_NODE_ID (1u) //!< Size node ID
#define SIZE_PARENT_NODE_ID (1u) //!< Size parent node ID
#define SIZE_DISTANCE (1u) //!< Size GW distance
#define SIZE_ROUTES (256u) //!< Size routing table
#define SIZE_CONTROLLER_CONFIG (23u) //!< Size controller config
#define SIZE_PERSONALIZATION_CHECKSUM (1u) //!< Size personalization checksum
#define SIZE_FIRMWARE_TYPE (2u) //!< Size firmware type
#define SIZE_FIRMWARE_VERSION (2u) //!< Size firmware version
#define SIZE_FIRMWARE_BLOCKS (2u) //!< Size firmware blocks
#define SIZE_FIRMWARE_CRC (2u) //!< Size firmware CRC
#define SIZE_SIGNING_REQUIREMENT_TABLE (32u) //!< Size signing requirement table
#define SIZE_WHITELIST_REQUIREMENT_TABLE (32u) //!< Size whitelist requirement table
#define SIZE_SIGNING_SOFT_HMAC_KEY (32u) //!< Size soft signing HMAC key
#define SIZE_SIGNING_SOFT_SERIAL (9u) //!< Size soft signing serial
#define SIZE_RF_ENCRYPTION_AES_KEY (16u) //!< Size RF AES encryption key
#define SIZE_NODE_LOCK_COUNTER (1u) //!< Size node lock counter
/** @brief EEPROM start address */
#define EEPROM_START (0u)
/** @brief Address node ID */
#define EEPROM_NODE_ID_ADDRESS EEPROM_START
/** @brief Address parent node ID */
#define EEPROM_PARENT_NODE_ID_ADDRESS (EEPROM_NODE_ID_ADDRESS + SIZE_NODE_ID)
/** @brief Address distance to GW */
#define EEPROM_DISTANCE_ADDRESS (EEPROM_PARENT_NODE_ID_ADDRESS + SIZE_PARENT_NODE_ID)
/** @brief Address routing table */
#define EEPROM_ROUTES_ADDRESS (EEPROM_DISTANCE_ADDRESS + SIZE_DISTANCE)
/** @brief Address configuration bytes sent by controller */
#define EEPROM_CONTROLLER_CONFIG_ADDRESS (EEPROM_ROUTES_ADDRESS + SIZE_ROUTES)
/** @brief Personalization checksum (set by SecurityPersonalizer.ino) */
#define EEPROM_PERSONALIZATION_CHECKSUM_ADDRESS (EEPROM_CONTROLLER_CONFIG_ADDRESS + SIZE_CONTROLLER_CONFIG)
/** @brief Address firmware type */
#define EEPROM_FIRMWARE_TYPE_ADDRESS (EEPROM_PERSONALIZATION_CHECKSUM_ADDRESS + SIZE_PERSONALIZATION_CHECKSUM)
/** @brief Address firmware version */
#define EEPROM_FIRMWARE_VERSION_ADDRESS (EEPROM_FIRMWARE_TYPE_ADDRESS + SIZE_FIRMWARE_TYPE)
/** @brief Address firmware blocks */
#define EEPROM_FIRMWARE_BLOCKS_ADDRESS (EEPROM_FIRMWARE_VERSION_ADDRESS + SIZE_FIRMWARE_VERSION)
/** @brief Address firmware CRC */
#define EEPROM_FIRMWARE_CRC_ADDRESS (EEPROM_FIRMWARE_BLOCKS_ADDRESS + SIZE_FIRMWARE_BLOCKS)
/** @brief Address signing requirement table */
#define EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS (EEPROM_FIRMWARE_CRC_ADDRESS + SIZE_FIRMWARE_CRC)
/** @brief Address whitelist requirement table */
#define EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS (EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS + SIZE_SIGNING_REQUIREMENT_TABLE)
/** @brief Address soft signing HMAC key. This is set with @ref SecurityPersonalizer.ino */
#define EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS (EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS + SIZE_WHITELIST_REQUIREMENT_TABLE)
/** @brief Address soft signing serial key. This is set with @ref SecurityPersonalizer.ino */
#define EEPROM_SIGNING_SOFT_SERIAL_ADDRESS (EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS + SIZE_SIGNING_SOFT_HMAC_KEY)
/** @brief Address RF AES encryption key. This is set with @ref SecurityPersonalizer.ino */
#define EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS (EEPROM_SIGNING_SOFT_SERIAL_ADDRESS + SIZE_SIGNING_SOFT_SERIAL)
/** @brief Address node lock counter. This is set with @ref SecurityPersonalizer.ino */
#define EEPROM_NODE_LOCK_COUNTER_ADDRESS (EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS + SIZE_RF_ENCRYPTION_AES_KEY)
/** @brief First free address for sketch static configuration */
#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_NODE_LOCK_COUNTER_ADDRESS + SIZE_NODE_LOCK_COUNTER)
#endif // MyEepromAddresses_h
/** @}*/

View File

@@ -0,0 +1,69 @@
/*
* 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/MySensors/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);
// global variables
extern MyMessage _msg;
extern MyMessage _msgTmp;
inline void gatewayTransportProcess(void)
{
if (gatewayTransportAvailable()) {
_msg = gatewayTransportReceive();
if (_msg.getDestination() == GATEWAY_ADDRESS) {
// Check if sender requests an echo
if (_msg.getRequestEcho()) {
// Copy message
_msgTmp = _msg;
// Reply without echo flag, otherwise we would end up in an eternal loop
_msgTmp.setRequestEcho(false);
_msgTmp.setEcho(true);
_msgTmp.setSender(getNodeId());
_msgTmp.setDestination(_msg.getSender());
gatewayTransportSend(_msgTmp);
}
if (_msg.getCommand() == C_INTERNAL) {
if (_msg.getType() == I_VERSION) {
// Request for version. Create the response
gatewayTransportSend(buildGw(_msgTmp, I_VERSION).set(MYSENSORS_LIBRARY_VERSION));
#ifdef MY_INCLUSION_MODE_FEATURE
} else if (_msg.getType() == I_INCLUSION_MODE) {
// Request to change inclusion mode
inclusionModeSet(atoi(_msg.data) == 1);
#endif
} else {
(void)_processInternalCoreMessage();
}
} else {
// Call incoming message callback if available
if (receive) {
receive(_msg);
}
}
} else {
#if defined(MY_SENSOR_NETWORK)
transportSendRoute(_msg);
#endif
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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/MySensors/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 MyGatewayTransport.h
*
* @defgroup MyGatewayTransportgrp MyGatewayTransport
* @ingroup internals
* @{
*
* Gateway transport-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE
* - [!] Exclamation mark is prepended in case of error
* - SYSTEM:
* - <b>GWT</b>: messages emitted by MyGatewayTransport
* - SUB SYSTEMS:
* - GWT:<b>TIN</b> from @ref gatewayTransportInit()
* - GWT:<b>TPS</b> from @ref gatewayTransportSend()
* - GWT:<b>IMQ</b> from incomingMQTT()
* - GWT:<b>RMQ</b> from reconnectMQTT()
* - GWT:<b>TPC</b> from gatewayTransportConnect()
* - GWT:<b>RFC</b> from _readFromClient()
* - GWT:<b>TSA</b> from @ref gatewayTransportAvailable()
* - GWT:<b>TRC</b> from @ref gatewayTransportReceive()
*
* Gateway transport debug log messages :
*
* |E| SYS | SUB | Message | Comment
* |-|-----|-------|---------------------------|---------------------------------------------------------------------
* | | GWT | TIN | CONNECTING... | Connecting to router
* | | GWT | TIN | IP=%%s | IP address [%%s] obtained
* |!| GWT | TIN | DHCP FAIL | DHCP request failed
* | | GWT | TIN | ETH OK | Connected to network
* |!| GWT | TIN | ETH FAIL | Connection failed
* | | GWT | TPS | TOPIC=%%s,MSG SENT | MQTT message sent on topic [%%s]
* | | GWT | TPS | ETH OK | Connected to network
* |!| GWT | TPS | ETH FAIL | Connection failed
* | | GWT | IMQ | TOPIC=%%s,MSG RECEIVE | MQTT message received on topic [%%s]
* | | GWT | RMQ | CONNECTING... | Connecting to MQTT broker
* | | GWT | RMQ | OK | Connected to MQTT broker
* |!| GWT | RMQ | FAIL | Connection to MQTT broker failed
* | | GWT | TPC | CONNECTING... | Obtaining IP address
* | | GWT | TPC | IP=%%s | IP address [%%s] obtained
* |!| GWT | TPC | DHCP FAIL | DHCP request failed
* | | GWT | RFC | C=%%d,MSG=%%s | Received message [%%s] from client [%%d]
* |!| GWT | RFC | C=%%d,MSG TOO LONG | Received message from client [%%d] too long
* | | GWT | TSA | UDP MSG=%%s | Received UDP message [%%s]
* | | GWT | TSA | ETH OK | Connected to network
* |!| GWT | TSA | ETH FAIL | Connection failed
* | | GWT | TSA | C=%d,DISCONNECTED | Client [%%d] disconnected
* | | GWT | TSA | C=%d,CONNECTED | Client [%%d] connected
* |!| GWT | TSA | NO FREE SLOT | No free slot for client
* |!| GWT | TRC | IP RENEW FAIL | IP renewal failed
*
* @brief API declaration for MyGatewayTransport
*
*/
#ifndef MyGatewayTransport_h
#define MyGatewayTransport_h
#include "MyProtocol.h"
#include "MySensorsCore.h"
#define MSG_GW_STARTUP_COMPLETE "Gateway startup complete." //!< Gateway startup message
#if defined(MY_DEBUG_VERBOSE_GATEWAY)
#define GATEWAY_DEBUG(x,...) DEBUG_OUTPUT(x, ##__VA_ARGS__) //!< debug output
#else
#define GATEWAY_DEBUG(x,...) //!< debug NULL
#endif
/**
* @brief Process gateway-related messages
*/
void gatewayTransportProcess(void);
/**
* @brief Initialize gateway transport driver
* @return true if transport initialized
*/
bool gatewayTransportInit(void);
/**
* @brief Send message to controller
* @param message to send
* @return true if message delivered
*/
bool gatewayTransportSend(MyMessage &message);
/**
* @brief Check if a new message is available from controller
* @return true if message available
*/
bool gatewayTransportAvailable(void);
/**
* @brief Pick up last message received from controller
* @return message
*/
MyMessage& gatewayTransportReceive(void);
#endif /* MyGatewayTransportEthernet_h */
/** @}*/

View File

@@ -0,0 +1,495 @@
/*
* 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/MySensors/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"
// global variables
extern MyMessage _msgTmp;
// housekeeping, remove for 3.0.0
#ifdef MY_ESP8266_SSID
#warning MY_ESP8266_SSID is deprecated, use MY_WIFI_SSID instead!
#define MY_WIFI_SSID MY_ESP8266_SSID
#undef MY_ESP8266_SSID // cleanup
#endif
#ifdef MY_ESP8266_PASSWORD
#warning MY_ESP8266_PASSWORD is deprecated, use MY_WIFI_PASSWORD instead!
#define MY_WIFI_PASSWORD MY_ESP8266_PASSWORD
#undef MY_ESP8266_PASSWORD // cleanup
#endif
#ifdef MY_ESP8266_BSSID
#warning MY_ESP8266_BSSID is deprecated, use MY_WIFI_BSSID instead!
#define MY_WIFI_BSSID MY_ESP8266_BSSID
#undef MY_ESP8266_BSSID // cleanup
#endif
#ifdef MY_ESP8266_HOSTNAME
#warning MY_ESP8266_HOSTNAME is deprecated, use MY_HOSTNAME instead!
#define MY_HOSTNAME MY_ESP8266_HOSTNAME
#undef MY_ESP8266_HOSTNAME // cleanup
#endif
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
#if !defined(MY_WIFI_SSID)
#error ESP8266/ESP32 gateway: MY_WIFI_SSID not defined!
#endif
#endif
#if defined(MY_CONTROLLER_IP_ADDRESS)
#define _ethernetControllerIP IPAddress(MY_CONTROLLER_IP_ADDRESS)
#endif
#if defined(MY_IP_ADDRESS)
#define _ethernetGatewayIP IPAddress(MY_IP_ADDRESS)
#if defined(MY_IP_GATEWAY_ADDRESS)
#define _gatewayIp IPAddress(MY_IP_GATEWAY_ADDRESS)
#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
// Assume the gateway will be the machine on the same network as the local IP
// but with last octet being '1'
#define _gatewayIp IPAddress(_ethernetGatewayIP[0], _ethernetGatewayIP[1], _ethernetGatewayIP[2], 1)
#endif /* End of MY_IP_GATEWAY_ADDRESS */
#if defined(MY_IP_SUBNET_ADDRESS)
#define _subnetIp IPAddress(MY_IP_SUBNET_ADDRESS)
#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
#define _subnetIp IPAddress(255, 255, 255, 0)
#endif /* End of MY_IP_SUBNET_ADDRESS */
#endif /* End of MY_IP_ADDRESS */
uint8_t _ethernetGatewayMAC[] = { MY_MAC_ADDRESS };
uint16_t _ethernetGatewayPort = MY_PORT;
MyMessage _ethernetMsg;
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
typedef struct {
// Suppress the warning about unused members in this struct because it is used through a complex
// set of preprocessor directives
// cppcheck-suppress unusedStructMember
char string[MY_GATEWAY_MAX_RECEIVE_LENGTH];
// cppcheck-suppress unusedStructMember
uint8_t idx;
} inputBuffer;
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
// Some re-defines to make code more readable below
#define EthernetServer WiFiServer
#define EthernetClient WiFiClient
#define EthernetUDP WiFiUDP
#endif
#if defined(MY_GATEWAY_CLIENT_MODE)
#if defined(MY_USE_UDP)
EthernetUDP _ethernetServer;
#endif /* End of MY_USE_UDP */
#elif defined(MY_GATEWAY_LINUX) /* Elif part of MY_GATEWAY_CLIENT_MODE */
EthernetServer _ethernetServer(_ethernetGatewayPort, MY_GATEWAY_MAX_CLIENTS);
#else /* Else part of MY_GATEWAY_CLIENT_MODE */
EthernetServer _ethernetServer(_ethernetGatewayPort);
#endif /* End of MY_GATEWAY_CLIENT_MODE */
#if defined(MY_GATEWAY_CLIENT_MODE)
static inputBuffer inputString;
#if defined(MY_USE_UDP)
// Nothing to do here
#else
static EthernetClient client = EthernetClient();
#endif /* End of MY_USE_UDP */
#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32) || defined(MY_GATEWAY_LINUX)
static EthernetClient clients[MY_GATEWAY_MAX_CLIENTS];
static bool clientsConnected[MY_GATEWAY_MAX_CLIENTS];
static inputBuffer inputString[MY_GATEWAY_MAX_CLIENTS];
#else /* Else part of MY_GATEWAY_CLIENT_MODE */
static EthernetClient client = EthernetClient();
static inputBuffer inputString;
#endif /* End of MY_GATEWAY_CLIENT_MODE */
// On W5100 boards with SPI_EN exposed we can use the real SPI bus together with radio
// (if we enable it during usage)
void _w5100_spi_en(const bool enable)
{
#if defined(MY_W5100_SPI_EN)
if (enable) {
// Pull up pin
hwPinMode(MY_W5100_SPI_EN, INPUT);
hwDigitalWrite(MY_W5100_SPI_EN, HIGH);
} else {
// Ground pin
hwPinMode(MY_W5100_SPI_EN, OUTPUT);
hwDigitalWrite(MY_W5100_SPI_EN, LOW);
}
#else
(void)enable;
#endif
}
#if !defined(MY_IP_ADDRESS) && defined(MY_GATEWAY_W5100)
void gatewayTransportRenewIP(void)
{
/* renew/rebind IP address
0 - nothing happened
1 - renew failed
2 - renew success
3 - rebind failed
4 - rebind success
*/
static uint32_t _nextIPRenewal = hwMillis() + MY_IP_RENEWAL_INTERVAL_MS;
const uint32_t now = hwMillis();
// http://playground.arduino.cc/Code/TimingRollover
if ((int32_t)(now - _nextIPRenewal) < 0) {
return;
}
if (Ethernet.maintain() & ~(0x06)) {
GATEWAY_DEBUG(PSTR("!GWT:TRC:IP RENEW FAIL\n"));
return;
}
_w5100_spi_en(false);
_nextIPRenewal = now + MY_IP_RENEWAL_INTERVAL_MS;
}
#endif
bool gatewayTransportInit(void)
{
_w5100_spi_en(true);
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
// Turn off access point
WiFi.mode(WIFI_STA);
#if defined(MY_GATEWAY_ESP8266)
WiFi.hostname(MY_HOSTNAME);
#elif defined(MY_GATEWAY_ESP32)
WiFi.setHostname(MY_HOSTNAME);
#endif
#ifdef MY_IP_ADDRESS
WiFi.config(_ethernetGatewayIP, _gatewayIp, _subnetIp);
#endif
(void)WiFi.begin(MY_WIFI_SSID, MY_WIFI_PASSWORD, 0, MY_WIFI_BSSID);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
GATEWAY_DEBUG(PSTR("GWT:TIN:CONNECTING...\n"));
}
GATEWAY_DEBUG(PSTR("GWT:TIN:IP: %s\n"), WiFi.localIP().toString().c_str());
#elif defined(MY_GATEWAY_LINUX)
// Nothing to do here
#else
#if defined(MY_IP_GATEWAY_ADDRESS) && defined(MY_IP_SUBNET_ADDRESS)
// DNS server set to gateway ip
Ethernet.begin(_ethernetGatewayMAC, _ethernetGatewayIP, _gatewayIp, _gatewayIp, _subnetIp);
#elif defined(MY_IP_ADDRESS)
Ethernet.begin(_ethernetGatewayMAC, _ethernetGatewayIP);
#else /* Else part of MY_IP_GATEWAY_ADDRESS && MY_IP_SUBNET_ADDRESS */
// Get IP address from DHCP
if (!Ethernet.begin(_ethernetGatewayMAC)) {
GATEWAY_DEBUG(PSTR("!GWT:TIN:DHCP FAIL\n"));
_w5100_spi_en(false);
return false;
}
#endif /* End of MY_IP_GATEWAY_ADDRESS && MY_IP_SUBNET_ADDRESS */
GATEWAY_DEBUG(PSTR("GWT:TIN:IP=%" PRIu8 ".%" PRIu8 ".%" PRIu8 ".%" PRIu8 "\n"),
Ethernet.localIP()[0],
Ethernet.localIP()[1], Ethernet.localIP()[2], Ethernet.localIP()[3]);
// give the Ethernet interface a second to initialize
delay(1000);
#endif /* MY_GATEWAY_ESP8266 / MY_GATEWAY_ESP32 */
#if defined(MY_GATEWAY_CLIENT_MODE)
#if defined(MY_USE_UDP)
_ethernetServer.begin(_ethernetGatewayPort);
#else /* Else part of MY_USE_UDP */
#if defined(MY_GATEWAY_LINUX) && defined(MY_IP_ADDRESS)
client.bind(_ethernetGatewayIP);
#endif /* End of MY_GATEWAY_LINUX && MY_IP_ADDRESS */
#if defined(MY_CONTROLLER_URL_ADDRESS)
if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
#else
if (client.connect(_ethernetControllerIP, MY_PORT)) {
#endif /* End of MY_CONTROLLER_URL_ADDRESS */
GATEWAY_DEBUG(PSTR("GWT:TIN:ETH OK\n"));
_w5100_spi_en(false);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(F(MSG_GW_STARTUP_COMPLETE)));
_w5100_spi_en(true);
presentNode();
} else {
client.stop();
GATEWAY_DEBUG(PSTR("!GWT:TIN:ETH FAIL\n"));
}
#endif /* End of MY_USE_UDP */
#else /* Else part of MY_GATEWAY_CLIENT_MODE */
#if defined(MY_GATEWAY_LINUX) && defined(MY_IP_ADDRESS)
_ethernetServer.begin(_ethernetGatewayIP);
#else
// we have to use pointers due to the constructor of EthernetServer
_ethernetServer.begin();
#endif /* End of MY_GATEWAY_LINUX && MY_IP_ADDRESS */
#endif /* End of MY_GATEWAY_CLIENT_MODE */
_w5100_spi_en(false);
return true;
}
bool gatewayTransportSend(MyMessage &message)
{
int nbytes = 0;
char *_ethernetMessage = protocolMyMessage2Serial(message);
setIndication(INDICATION_GW_TX);
_w5100_spi_en(true);
#if defined(MY_GATEWAY_CLIENT_MODE)
#if defined(MY_USE_UDP)
#if defined(MY_CONTROLLER_URL_ADDRESS)
_ethernetServer.beginPacket(MY_CONTROLLER_URL_ADDRESS, MY_PORT);
#else
_ethernetServer.beginPacket(_ethernetControllerIP, MY_PORT);
#endif /* End of MY_CONTROLLER_URL_ADDRESS */
_ethernetServer.write((uint8_t *)_ethernetMessage, strlen(_ethernetMessage));
// returns 1 if the packet was sent successfully
nbytes = _ethernetServer.endPacket();
#else /* Else part of MY_USE_UDP */
if (!client.connected()) {
client.stop();
#if defined(MY_CONTROLLER_URL_ADDRESS)
if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
#else
if (client.connect(_ethernetControllerIP, MY_PORT)) {
#endif /* End of MY_CONTROLLER_URL_ADDRESS */
GATEWAY_DEBUG(PSTR("GWT:TPS:ETH OK\n"));
_w5100_spi_en(false);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_STARTUP_COMPLETE));
_w5100_spi_en(true);
presentNode();
} else {
// connecting to the server failed!
GATEWAY_DEBUG(PSTR("!GWT:TPS:ETH FAIL\n"));
_w5100_spi_en(false);
return false;
}
}
nbytes = client.write((const uint8_t *)_ethernetMessage, strlen(_ethernetMessage));
#endif /* End of MY_USE_UDP */
#else /* Else part of MY_GATEWAY_CLIENT_MODE */
// Send message to connected clients
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) {
if (clients[i] && clients[i].connected()) {
nbytes += clients[i].write((uint8_t *)_ethernetMessage, strlen(_ethernetMessage));
}
}
#else /* Else part of MY_GATEWAY_ESPxx*/
nbytes = _ethernetServer.write(_ethernetMessage);
#endif /* End of MY_GATEWAY_ESPxx */
#endif /* End of MY_GATEWAY_CLIENT_MODE */
_w5100_spi_en(false);
return (nbytes > 0);
}
#if defined(MY_USE_UDP)
// Nothing to do here
#else
#if (defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32) || defined(MY_GATEWAY_LINUX)) && !defined(MY_GATEWAY_CLIENT_MODE)
bool _readFromClient(uint8_t i)
{
while (clients[i].connected() && clients[i].available()) {
const 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;
GATEWAY_DEBUG(PSTR("GWT:RFC:C=%" PRIu8 ",MSG=%s\n"), i, inputString[i].string);
inputString[i].idx = 0;
if (protocolSerial2MyMessage(_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
GATEWAY_DEBUG(PSTR("!GWT:RFC:C=%" PRIu8 ",MSG 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 /* Else part of MY_GATEWAY_ESP8266 || MY_GATEWAY_LINUX || !MY_GATEWAY_CLIENT_MODE */
bool _readFromClient(void)
{
while (client.connected() && client.available()) {
const 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;
GATEWAY_DEBUG(PSTR("GWT:RFC:MSG=%s\n"), inputString.string);
inputString.idx = 0;
if (protocolSerial2MyMessage(_ethernetMsg, inputString.string)) {
return true;
}
} else {
// add it to the inputString:
inputString.string[inputString.idx++] = inChar;
}
} else {
// Incoming message too long. Throw away
GATEWAY_DEBUG(PSTR("!GWT:RFC:MSG 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 /* End of MY_GATEWAY_ESP8266 || MY_GATEWAY_LINUX || !MY_GATEWAY_CLIENT_MODE */
#endif /* End of MY_USE_UDP */
bool gatewayTransportAvailable(void)
{
_w5100_spi_en(true);
#if !defined(MY_IP_ADDRESS) && defined(MY_GATEWAY_W5100)
// renew IP address using DHCP
gatewayTransportRenewIP();
#endif
#if defined(MY_GATEWAY_CLIENT_MODE)
#if defined(MY_USE_UDP)
int packet_size = _ethernetServer.parsePacket();
if (packet_size) {
_ethernetServer.read(inputString.string, MY_GATEWAY_MAX_RECEIVE_LENGTH);
inputString.string[packet_size] = 0;
GATEWAY_DEBUG(PSTR("GWT:TSA:UDP MSG=%s\n"), inputString.string);
_w5100_spi_en(false);
const bool ok = protocolSerial2MyMessage(_ethernetMsg, inputString.string);
if (ok) {
setIndication(INDICATION_GW_RX);
}
return ok;
}
#else /* Else part of MY_USE_UDP */
if (!client.connected()) {
client.stop();
#if defined(MY_CONTROLLER_URL_ADDRESS)
if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
#else
if (client.connect(_ethernetControllerIP, MY_PORT)) {
#endif /* End of MY_CONTROLLER_URL_ADDRESS */
GATEWAY_DEBUG(PSTR("GWT:TSA:ETH OK\n"));
_w5100_spi_en(false);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(F(MSG_GW_STARTUP_COMPLETE)));
_w5100_spi_en(true);
presentNode();
} else {
GATEWAY_DEBUG(PSTR("!GWT:TSA:ETH FAIL\n"));
_w5100_spi_en(false);
return false;
}
}
if (_readFromClient()) {
setIndication(INDICATION_GW_RX);
_w5100_spi_en(false);
return true;
}
#endif /* End of MY_USE_UDP */
#else /* Else part of MY_GATEWAY_CLIENT_MODE */
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32) || defined(MY_GATEWAY_LINUX)
// ESP8266/ESP32: 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]) {
GATEWAY_DEBUG(PSTR("GWT:TSA:C=%" PRIu8 ",DISCONNECTED\n"), i);
clients[i].stop();
}
//check if there are any new clients
if (_ethernetServer.hasClient()) {
clients[i] = _ethernetServer.available();
inputString[i].idx = 0;
GATEWAY_DEBUG(PSTR("GWT:TSA:C=%" PRIu8 ",CONNECTED\n"), i);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_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
GATEWAY_DEBUG(PSTR("!GWT:TSA:NO FREE SLOT\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 /* Else part of MY_GATEWAY_ESP8266 || MY_GATEWAY_LINUX */
// 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;
GATEWAY_DEBUG(PSTR("GWT:TSA:ETH OK\n"));
_w5100_spi_en(false);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_STARTUP_COMPLETE));
_w5100_spi_en(true);
presentNode();
}
}
if (client) {
if (!client.connected()) {
GATEWAY_DEBUG(PSTR("!GWT:TSA:ETH FAIL\n"));
client.stop();
} else {
if (_readFromClient()) {
setIndication(INDICATION_GW_RX);
_w5100_spi_en(false);
return true;
}
}
}
#endif /* End of MY_GATEWAY_ESP8266 || MY_GATEWAY_LINUX */
#endif /* End of MY_GATEWAY_CLIENT_MODE */
_w5100_spi_en(false);
return false;
}
MyMessage& gatewayTransportReceive(void)
{
// Return the last parsed message
return _ethernetMsg;
}

View File

@@ -0,0 +1,298 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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"
// housekeeping, remove for 3.0.0
#ifdef MY_ESP8266_SSID
#warning MY_ESP8266_SSID is deprecated, use MY_WIFI_SSID instead!
#define MY_WIFI_SSID MY_ESP8266_SSID
#undef MY_ESP8266_SSID // cleanup
#endif
#ifdef MY_ESP8266_PASSWORD
#warning MY_ESP8266_PASSWORD is deprecated, use MY_WIFI_PASSWORD instead!
#define MY_WIFI_PASSWORD MY_ESP8266_PASSWORD
#undef MY_ESP8266_PASSWORD // cleanup
#endif
#ifdef MY_ESP8266_BSSID
#warning MY_ESP8266_BSSID is deprecated, use MY_WIFI_BSSID instead!
#define MY_WIFI_BSSID MY_ESP8266_BSSID
#undef MY_ESP8266_BSSID // cleanup
#endif
#ifdef MY_ESP8266_HOSTNAME
#warning MY_ESP8266_HOSTNAME is deprecated, use MY_HOSTNAME instead!
#define MY_HOSTNAME MY_ESP8266_HOSTNAME
#undef MY_ESP8266_HOSTNAME // cleanup
#endif
#ifndef MY_MQTT_USER
#define MY_MQTT_USER NULL
#endif
#ifndef MY_MQTT_PASSWORD
#define MY_MQTT_PASSWORD NULL
#endif
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
#if !defined(MY_WIFI_SSID)
#error ESP8266/ESP32 MQTT gateway: MY_WIFI_SSID not defined!
#endif
#endif
#if defined MY_CONTROLLER_IP_ADDRESS
#define _brokerIp IPAddress(MY_CONTROLLER_IP_ADDRESS)
#endif
#if defined(MY_IP_ADDRESS)
#define _MQTT_clientIp IPAddress(MY_IP_ADDRESS)
#if defined(MY_IP_GATEWAY_ADDRESS)
#define _gatewayIp IPAddress(MY_IP_GATEWAY_ADDRESS)
#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
// Assume the gateway will be the machine on the same network as the local IP
// but with last octet being '1'
#define _gatewayIp IPAddress(_MQTT_clientIp[0], _MQTT_clientIp[1], _MQTT_clientIp[2], 1)
#endif /* End of MY_IP_GATEWAY_ADDRESS */
#if defined(MY_IP_SUBNET_ADDRESS)
#define _subnetIp IPAddress(MY_IP_SUBNET_ADDRESS)
#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
#define _subnetIp IPAddress(255, 255, 255, 0)
#endif /* End of MY_IP_SUBNET_ADDRESS */
#endif /* End of MY_IP_ADDRESS */
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
#define EthernetClient WiFiClient
#elif defined(MY_GATEWAY_LINUX)
// Nothing to do here
#else
uint8_t _MQTT_clientMAC[] = { MY_MAC_ADDRESS };
#endif /* End of MY_GATEWAY_ESPxy */
#if defined(MY_GATEWAY_TINYGSM)
#if defined(MY_GSM_RX) && defined(MY_GSM_TX)
SoftwareSerial SerialAT(MY_GSM_RX, MY_GSM_TX);
#endif
static TinyGsm modem(SerialAT);
static TinyGsmClient _MQTT_ethClient(modem);
#if defined(MY_GSM_BAUDRATE)
uint32_t rate = MY_GSM_BAUDRATE;
#else /* Else part of MY_GSM_BAUDRATE */
uint32_t rate = 0;
#endif /* End of MY_GSM_BAUDRATE */
#else /* Else part of MY_GATEWAY_TINYGSM */
static EthernetClient _MQTT_ethClient;
#endif /* End of MY_GATEWAY_TINYGSM */
static PubSubClient _MQTT_client(_MQTT_ethClient);
static bool _MQTT_connecting = true;
static bool _MQTT_available = false;
static MyMessage _MQTT_msg;
bool gatewayTransportSend(MyMessage &message)
{
if (!_MQTT_client.connected()) {
return false;
}
setIndication(INDICATION_GW_TX);
char *topic = protocolMyMessage2MQTT(MY_MQTT_PUBLISH_TOPIC_PREFIX, message);
GATEWAY_DEBUG(PSTR("GWT:TPS:TOPIC=%s,MSG SENT\n"), topic);
#if defined(MY_MQTT_CLIENT_PUBLISH_RETAIN)
const bool retain = message.getCommand() == C_SET ||
(message.getCommand() == C_INTERNAL && message.getType() == I_BATTERY_LEVEL);
#else
const bool retain = false;
#endif /* End of MY_MQTT_CLIENT_PUBLISH_RETAIN */
return _MQTT_client.publish(topic, message.getString(_convBuffer), retain);
}
void incomingMQTT(char *topic, uint8_t *payload, unsigned int length)
{
GATEWAY_DEBUG(PSTR("GWT:IMQ:TOPIC=%s, MSG RECEIVED\n"), topic);
_MQTT_available = protocolMQTT2MyMessage(_MQTT_msg, topic, payload, length);
setIndication(INDICATION_GW_RX);
}
bool reconnectMQTT(void)
{
GATEWAY_DEBUG(PSTR("GWT:RMQ:CONNECTING...\n"));
// Attempt to connect
if (_MQTT_client.connect(MY_MQTT_CLIENT_ID, MY_MQTT_USER, MY_MQTT_PASSWORD)) {
GATEWAY_DEBUG(PSTR("GWT:RMQ:OK\n"));
// Send presentation of locally attached sensors (and node if applicable)
presentNode();
// Once connected, publish subscribe
if (__builtin_constant_p(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX)) {
// to save some memory
_MQTT_client.subscribe(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX "/+/+/+/+/+");
} else {
char inTopic[strlen(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) + strlen("/+/+/+/+/+")];
(void)strncpy(inTopic, MY_MQTT_SUBSCRIBE_TOPIC_PREFIX, strlen(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) + 1);
(void)strcat(inTopic, "/+/+/+/+/+");
_MQTT_client.subscribe(inTopic);
}
return true;
}
delay(1000);
GATEWAY_DEBUG(PSTR("!GWT:RMQ:FAIL\n"));
return false;
}
bool gatewayTransportConnect(void)
{
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
if (WiFi.status() != WL_CONNECTED) {
GATEWAY_DEBUG(PSTR("GWT:TPC:CONNECTING...\n"));
delay(1000);
return false;
}
GATEWAY_DEBUG(PSTR("GWT:TPC:IP=%s\n"), WiFi.localIP().toString().c_str());
#elif defined(MY_GATEWAY_LINUX)
#if defined(MY_IP_ADDRESS)
_MQTT_ethClient.bind(_MQTT_clientIp);
#endif /* End of MY_IP_ADDRESS */
#elif defined(MY_GATEWAY_TINYGSM)
GATEWAY_DEBUG(PSTR("GWT:TPC:IP=%s\n"), modem.getLocalIP().c_str());
#else
#if defined(MY_IP_ADDRESS)
Ethernet.begin(_MQTT_clientMAC, _MQTT_clientIp);
#else /* Else part of MY_IP_ADDRESS */
// Get IP address from DHCP
if (!Ethernet.begin(_MQTT_clientMAC)) {
GATEWAY_DEBUG(PSTR("!GWT:TPC:DHCP FAIL\n"));
_MQTT_connecting = false;
return false;
}
#endif /* End of MY_IP_ADDRESS */
GATEWAY_DEBUG(PSTR("GWT:TPC:IP=%" PRIu8 ".%" PRIu8 ".%" PRIu8 ".%" PRIu8 "\n"),
Ethernet.localIP()[0],
Ethernet.localIP()[1], Ethernet.localIP()[2], Ethernet.localIP()[3]);
// give the Ethernet interface a second to initialize
delay(1000);
#endif
return true;
}
bool gatewayTransportInit(void)
{
_MQTT_connecting = true;
#if defined(MY_GATEWAY_TINYGSM)
#if !defined(MY_GSM_BAUDRATE)
rate = TinyGsmAutoBaud(SerialAT);
#endif /* End of MY_GSM_BAUDRATE */
SerialAT.begin(rate);
delay(3000);
modem.restart();
#if defined(MY_GSM_PIN) && !defined(TINY_GSM_MODEM_ESP8266)
modem.simUnlock(MY_GSM_PIN);
#endif /* End of MY_GSM_PIN */
#ifndef TINY_GSM_MODEM_ESP8266
if (!modem.waitForNetwork()) {
GATEWAY_DEBUG(PSTR("!GWT:TIN:ETH FAIL\n"));
while (true);
}
GATEWAY_DEBUG(PSTR("GWT:TIN:ETH OK\n"));
if (!modem.gprsConnect(MY_GSM_APN, MY_GSM_USR, MY_GSM_PSW)) {
GATEWAY_DEBUG(PSTR("!GWT:TIN:ETH FAIL\n"));
while (true);
}
GATEWAY_DEBUG(PSTR("GWT:TIN:ETH OK\n"));
delay(1000);
#else /* Else part of TINY_GSM_MODEM_ESP8266 */
if (!modem.networkConnect(MY_GSM_SSID, MY_GSM_PSW)) {
GATEWAY_DEBUG(PSTR("!GWT:TIN:ETH FAIL\n"));
while (true);
}
GATEWAY_DEBUG(PSTR("GWT:TIN:ETH OK\n"));
delay(1000);
#endif /* End of TINY_GSM_MODEM_ESP8266 */
#endif /* End of MY_GATEWAY_TINYGSM */
#if defined(MY_CONTROLLER_IP_ADDRESS)
_MQTT_client.setServer(_brokerIp, MY_PORT);
#else
_MQTT_client.setServer(MY_CONTROLLER_URL_ADDRESS, MY_PORT);
#endif /* End of MY_CONTROLLER_IP_ADDRESS */
_MQTT_client.setCallback(incomingMQTT);
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
// Turn off access point
WiFi.mode(WIFI_STA);
#if defined(MY_GATEWAY_ESP8266)
WiFi.hostname(MY_HOSTNAME);
#elif defined(MY_GATEWAY_ESP32)
WiFi.setHostname(MY_HOSTNAME);
#endif
#if defined(MY_IP_ADDRESS)
WiFi.config(_MQTT_clientIp, _gatewayIp, _subnetIp);
#endif /* End of MY_IP_ADDRESS */
(void)WiFi.begin(MY_WIFI_SSID, MY_WIFI_PASSWORD, 0, MY_WIFI_BSSID);
#endif
gatewayTransportConnect();
_MQTT_connecting = false;
return true;
}
bool gatewayTransportAvailable(void)
{
if (_MQTT_connecting) {
return false;
}
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_ESP32)
if (WiFi.status() != WL_CONNECTED) {
#if defined(MY_GATEWAY_ESP32)
(void)gatewayTransportInit();
#endif
return false;
}
#endif
if (!_MQTT_client.connected()) {
//reinitialise client
if (gatewayTransportConnect()) {
reconnectMQTT();
}
return false;
}
_MQTT_client.loop();
return _MQTT_available;
}
MyMessage & gatewayTransportReceive(void)
{
// Return the last parsed message
_MQTT_available = false;
return _MQTT_msg;
}

View File

@@ -0,0 +1,78 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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"
// global variables
extern MyMessage _msgTmp;
char _serialInputString[MY_GATEWAY_MAX_RECEIVE_LENGTH]; // A buffer for incoming commands from serial interface
uint8_t _serialInputPos;
MyMessage _serialMsg;
bool gatewayTransportSend(MyMessage &message) {
setIndication(INDICATION_GW_TX);
// MY_SERIALDEVICE.print(protocolMyMessage2Serial(message));
// Serial print is always successful
return true;
}
bool gatewayTransportInit(void) {
(void)gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_STARTUP_COMPLETE));
// Send presentation of locally attached sensors (and node if applicable)
presentNode();
return true;
}
bool gatewayTransportAvailable(void) {
while (MY_SERIALDEVICE.available()) {
// get the new byte:
const 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;
const bool ok = protocolSerial2MyMessage(_serialMsg, _serialInputString);
if (ok) {
setIndication(INDICATION_GW_RX);
}
_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(void) {
// Return the last parsed message
return _serialMsg;
}

View File

@@ -0,0 +1,41 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MyHelperFunctions.h"
static uint8_t convertH2I(const char c)
{
if (c <= '9') {
return c - '0';
} else if (c >= 'a') {
return c - 'a' + 10;
} else {
return c - 'A' + 10;
}
}
static char convertI2H(const uint8_t i)
{
const uint8_t k = i & 0x0F;
if (k <= 9) {
return '0' + k;
} else {
return 'A' + k - 10;
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 MyHelperFunctions_h
#define MyHelperFunctions_h
/**
* Single character hex conversion
* @param c hex char
* @return byte representation of the paramter
*/
static uint8_t convertH2I(const char c) __attribute__((unused));
/**
* Lower nibble byte to hex conversion
* @param i byte
* @return hex char representation of the parameter
*/
static char convertI2H(const uint8_t i) __attribute__((unused));
#endif

View File

@@ -0,0 +1,72 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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"
// global variables
extern MyMessage _msgTmp;
unsigned long _inclusionStartTime;
bool _inclusionMode;
inline void inclusionInit()
{
_inclusionMode = false;
#if defined(MY_INCLUSION_BUTTON_FEATURE)
// Setup digital in that triggers inclusion mode
hwPinMode(MY_INCLUSION_MODE_BUTTON_PIN, INPUT_PULLUP);
#endif
#if defined (MY_INCLUSION_LED_PIN)
// Setup LED pin that indicates inclusion mode
hwPinMode(MY_INCLUSION_LED_PIN, OUTPUT);
hwDigitalWrite(MY_INCLUSION_LED_PIN, LED_OFF);
#endif
}
void inclusionModeSet(bool newMode)
{
if (newMode != _inclusionMode) {
_inclusionMode = newMode;
// Send back mode change to controller
gatewayTransportSend(buildGw(_msgTmp, I_INCLUSION_MODE).set((uint8_t)(_inclusionMode?1:0)));
if (_inclusionMode) {
_inclusionStartTime = hwMillis();
}
}
#if defined (MY_INCLUSION_LED_PIN)
hwDigitalWrite(MY_INCLUSION_LED_PIN, _inclusionMode ? LED_ON : LED_OFF);
#endif
}
inline void inclusionProcess()
{
#ifdef MY_INCLUSION_BUTTON_FEATURE
if (!_inclusionMode && hwDigitalRead(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);
}
}

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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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

View File

@@ -0,0 +1,52 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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"
#if defined(MY_DEFAULT_TX_LED_PIN)|| defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
#include "MyLeds.h"
#endif
void setIndication( const indication_t ind )
{
#if defined(MY_DEFAULT_TX_LED_PIN)
if ((INDICATION_TX == ind) || (INDICATION_GW_TX == ind)) {
ledsBlinkTx(1);
} else
#endif
#if defined(MY_DEFAULT_RX_LED_PIN)
if ((INDICATION_RX == ind) || (INDICATION_GW_RX == ind)) {
ledsBlinkRx(1);
} else
#endif
#if defined(MY_DEFAULT_ERR_LED_PIN)
if (ind > INDICATION_ERR_START) {
// Number of blinks indicates which error occurred.
ledsBlinkErr(ind-INDICATION_ERR_START);
}
#endif
indication(ind);
}
#if !defined(MY_INDICATION_HANDLER)
void indication(indication_t)
{
// empty function, resolves AVR-specific GCC optimization bug (<5.5) if handler not used
// see here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77326
}
#endif

View File

@@ -0,0 +1,80 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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_CHECK_UPLINK, //!< Check uplink
INDICATION_REQ_REGISTRATION, //!< Request node registration.
INDICATION_GOT_REGISTRATION, //!< Got registration response.
INDICATION_REBOOT, //!< Rebooting node.
INDICATION_PRESENT, //!< Presenting node to gateway.
INDICATION_CLEAR_ROUTING, //!< Clear routing 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_FW_UPDATE_RX_ERR, //!< Received wrong piece of firmware data.
INDICATION_ERR_START = 100,
INDICATION_ERR_HW_INIT, //!< HW initialization error
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_CHECK_UPLINK, //!< Failed to check uplink
INDICATION_ERR_SIGN, //!< Error signing.
INDICATION_ERR_LENGTH, //!< Invalid message length.
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 );
#endif

View File

@@ -0,0 +1,119 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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;
inline void ledsInit()
{
// initialize counters
countRx = 0;
countTx = 0;
countErr = 0;
// Setup led pins
#if defined(MY_DEFAULT_RX_LED_PIN)
hwPinMode(MY_DEFAULT_RX_LED_PIN, OUTPUT);
#endif
#if defined(MY_DEFAULT_TX_LED_PIN)
hwPinMode(MY_DEFAULT_TX_LED_PIN, OUTPUT);
#endif
#if defined(MY_DEFAULT_ERR_LED_PIN)
hwPinMode(MY_DEFAULT_ERR_LED_PIN, OUTPUT);
#endif
prevTime = hwMillis() -
LED_PROCESS_INTERVAL_MS; // Subtract some, to make sure leds gets updated on first run.
ledsProcess();
}
void ledsProcess()
{
// Just return if it is not the time...
if ((hwMillis() - prevTime) < LED_PROCESS_INTERVAL_MS) {
return;
}
prevTime = hwMillis();
#if defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
uint8_t state;
#endif
// For an On/Off ratio of 4, the pattern repeated will be [on, on, on, off]
// until the counter becomes 0.
#if defined(MY_DEFAULT_RX_LED_PIN)
if (countRx) {
--countRx;
}
state = (countRx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
hwDigitalWrite(MY_DEFAULT_RX_LED_PIN, state);
#endif
#if defined(MY_DEFAULT_TX_LED_PIN)
if (countTx) {
--countTx;
}
state = (countTx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
hwDigitalWrite(MY_DEFAULT_TX_LED_PIN, state);
#endif
#if defined(MY_DEFAULT_ERR_LED_PIN)
if (countErr) {
--countErr;
}
state = (countErr & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
hwDigitalWrite(MY_DEFAULT_ERR_LED_PIN, state);
#endif
}
void ledsBlinkRx(uint8_t cnt)
{
if (!countRx) {
countRx = cnt*LED_ON_OFF_RATIO;
}
ledsProcess();
}
void ledsBlinkTx(uint8_t cnt)
{
if(!countTx) {
countTx = cnt*LED_ON_OFF_RATIO;
}
ledsProcess();
}
void ledsBlinkErr(uint8_t cnt)
{
if(!countErr) {
countErr = cnt*LED_ON_OFF_RATIO;
}
ledsProcess();
}
bool ledsBlinking()
{
return countRx || countTx || countErr;
}

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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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
#if defined(MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
#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
/**
* Test if any LED is currently blinking.
* @return true when one or more LEDs are blinking, false otherwise.
*/
bool ledsBlinking();
#else
// Remove led functions if feature is disabled
#define ledBlinkTx(x,...)
#define ledBlinkRx(x,...)
#define ledBlinkErr(x,...)
#endif
#endif

View File

@@ -0,0 +1,462 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MyHelperFunctions.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
MyMessage::MyMessage(void)
{
this->clear();
}
MyMessage::MyMessage(const uint8_t _sensorId, const mysensors_data_t _dataType)
{
this->clear();
(void)this->setSensor(_sensorId);
(void)this->setType(static_cast<uint8_t>(_dataType));
}
void MyMessage::clear(void)
{
this->last = 0u;
this->sender = 0u;
this->destination = GATEWAY_ADDRESS; // Gateway is default destination
this->version_length = 0u;
this->command_echo_payload = 0u;
this->type = 0u;
this->sensor = 0u;
// clear data buffer
(void)memset((void *)this->data, 0u, sizeof(this->data));
// set message protocol version
(void)this->setVersion();
}
uint8_t MyMessage::getHeaderSize(void) const
{
return (uint8_t)HEADER_SIZE;
}
uint8_t MyMessage::getMaxPayloadSize(void) const
{
return (uint8_t)MAX_PAYLOAD_SIZE;
}
uint8_t MyMessage::getExpectedMessageSize(void) const
{
return this->getHeaderSize() + (this->getSigned() ? this->getMaxPayloadSize() : this->getLength());
}
bool MyMessage::isProtocolVersionValid(void) const
{
return (this->getVersion() == V2_MYS_HEADER_PROTOCOL_VERSION);
}
uint8_t MyMessage::getType(void) const
{
return this->type;
}
MyMessage& MyMessage::setType(const uint8_t messageType)
{
this->type = messageType;
return *this;
}
uint8_t MyMessage::getLast(void) const
{
return this->last;
}
MyMessage& MyMessage::setLast(const uint8_t lastId)
{
this->last = lastId;
return *this;
}
uint8_t MyMessage::getSender(void) const
{
return this->sender;
}
MyMessage& MyMessage::setSender(const uint8_t senderId)
{
this->sender = senderId;
return *this;
}
uint8_t MyMessage::getSensor(void) const
{
return this->sensor;
}
MyMessage& MyMessage::setSensor(const uint8_t sensorId)
{
this->sensor = sensorId;
return *this;
}
uint8_t MyMessage::getDestination(void) const
{
return this->destination;
}
MyMessage& MyMessage::setDestination(const uint8_t destinationId)
{
this->destination = destinationId;
return *this;
}
// TODO: Remove before v3 is released, use isEcho instead
bool MyMessage::isAck(void) const
{
return this->isEcho();
}
bool MyMessage::isEcho(void) const
{
return (bool)BF_GET(this->command_echo_payload, V2_MYS_HEADER_CEP_ECHO_POS,
V2_MYS_HEADER_CEP_ECHO_SIZE);
}
MyMessage& MyMessage::setEcho(const bool echo)
{
BF_SET(this->command_echo_payload, echo, V2_MYS_HEADER_CEP_ECHO_POS,
V2_MYS_HEADER_CEP_ECHO_SIZE);
return *this;
}
bool MyMessage::getRequestEcho(void) const
{
return (bool)BF_GET(this->command_echo_payload, V2_MYS_HEADER_CEP_ECHOREQUEST_POS,
V2_MYS_HEADER_CEP_ECHOREQUEST_SIZE);
}
MyMessage& MyMessage::setRequestEcho(const bool requestEcho)
{
BF_SET(this->command_echo_payload, requestEcho, V2_MYS_HEADER_CEP_ECHOREQUEST_POS,
V2_MYS_HEADER_CEP_ECHOREQUEST_SIZE);
return *this;
}
uint8_t MyMessage::getVersion(void) const
{
return (uint8_t)BF_GET(this->version_length, V2_MYS_HEADER_VSL_VERSION_POS,
V2_MYS_HEADER_VSL_VERSION_SIZE);
}
MyMessage& MyMessage::setVersion(void)
{
BF_SET(this->version_length, V2_MYS_HEADER_PROTOCOL_VERSION, V2_MYS_HEADER_VSL_VERSION_POS,
V2_MYS_HEADER_VSL_VERSION_SIZE);
return *this;
}
mysensors_command_t MyMessage::getCommand(void) const
{
return static_cast<mysensors_command_t>(BF_GET(this->command_echo_payload,
V2_MYS_HEADER_CEP_COMMAND_POS, V2_MYS_HEADER_CEP_COMMAND_SIZE));
}
MyMessage& MyMessage::setCommand(const mysensors_command_t command)
{
BF_SET(this->command_echo_payload, static_cast<uint8_t>(command), V2_MYS_HEADER_CEP_COMMAND_POS,
V2_MYS_HEADER_CEP_COMMAND_SIZE);
return *this;
}
mysensors_payload_t MyMessage::getPayloadType(void) const
{
return static_cast<mysensors_payload_t>(BF_GET(this->command_echo_payload,
V2_MYS_HEADER_CEP_PAYLOADTYPE_POS, V2_MYS_HEADER_CEP_PAYLOADTYPE_SIZE));
}
MyMessage& MyMessage::setPayloadType(const mysensors_payload_t payloadType)
{
BF_SET(this->command_echo_payload, static_cast<uint8_t>(payloadType),
V2_MYS_HEADER_CEP_PAYLOADTYPE_POS, V2_MYS_HEADER_CEP_PAYLOADTYPE_SIZE);
return *this;
}
bool MyMessage::getSigned(void) const
{
return (bool)BF_GET(this->version_length, V2_MYS_HEADER_VSL_SIGNED_POS,
V2_MYS_HEADER_VSL_SIGNED_SIZE);
}
MyMessage& MyMessage::setSigned(const bool signedFlag)
{
BF_SET(this->version_length, signedFlag, V2_MYS_HEADER_VSL_SIGNED_POS,
V2_MYS_HEADER_VSL_SIGNED_SIZE);
return *this;
}
uint8_t MyMessage::getLength(void) const
{
uint8_t length = BF_GET(this->version_length, V2_MYS_HEADER_VSL_LENGTH_POS,
V2_MYS_HEADER_VSL_LENGTH_SIZE);
// limit length
if (length > MAX_PAYLOAD_SIZE) {
length = MAX_PAYLOAD_SIZE;
}
return length;
}
MyMessage& MyMessage::setLength(const uint8_t length)
{
uint8_t finalLength = length;
// limit length
if (finalLength > MAX_PAYLOAD_SIZE) {
finalLength = MAX_PAYLOAD_SIZE;
}
BF_SET(this->version_length, finalLength, V2_MYS_HEADER_VSL_LENGTH_POS,
V2_MYS_HEADER_VSL_LENGTH_SIZE);
return *this;
}
/* Getters for payload converted to desired form */
void* MyMessage::getCustom(void) const
{
return (void *)this->data;
}
const char* MyMessage::getString(void) const
{
if (this->getPayloadType() == P_STRING) {
return this->data;
} else {
return NULL;
}
}
char* MyMessage::getCustomString(char *buffer) const
{
if (buffer != NULL) {
for (uint8_t i = 0; i < this->getLength(); i++) {
buffer[i * 2] = convertI2H(this->data[i] >> 4);
buffer[(i * 2) + 1] = convertI2H(this->data[i]);
}
buffer[this->getLength() * 2] = '\0';
return buffer;
} else {
return NULL;
}
}
char* MyMessage::getStream(char *buffer) const
{
if (buffer != NULL) {
if (this->getCommand() == C_STREAM) {
return this->getCustomString(buffer);
}
return buffer;
} else {
return NULL;
}
}
char* MyMessage::getString(char *buffer) const
{
if (buffer != NULL) {
const uint8_t payloadType = this->getPayloadType();
if (payloadType == P_STRING) {
(void)strncpy(buffer, this->data, this->getLength());
buffer[this->getLength()] = 0;
} else if (payloadType == P_BYTE) {
(void)itoa(bValue, buffer, 10);
} else if (payloadType == P_INT16) {
(void)itoa(iValue, buffer, 10);
} else if (payloadType == P_UINT16) {
(void)utoa(uiValue, buffer, 10);
} else if (payloadType == P_LONG32) {
(void)ltoa(lValue, buffer, 10);
} else if (payloadType == P_ULONG32) {
(void)ultoa(ulValue, buffer, 10);
} else if (payloadType == P_FLOAT32) {
(void)dtostrf(fValue, 2, min(fPrecision, (uint8_t)8u), buffer);
} else if (payloadType == P_CUSTOM) {
return getCustomString(buffer);
}
return buffer;
} else {
return NULL;
}
}
bool MyMessage::getBool(void) const
{
return (bool)this->getByte();
}
uint8_t MyMessage::getByte(void) const
{
if (this->getPayloadType() == P_BYTE) {
return (uint8_t)this->data[0];
} else if (this->getPayloadType() == P_STRING) {
return (uint8_t)atoi(this->data);
} else {
return 0;
}
}
float MyMessage::getFloat(void) const
{
if (this->getPayloadType() == P_FLOAT32) {
return this->fValue;
} else if (this->getPayloadType() == P_STRING) {
return (float)atof(this->data);
} else {
return 0;
}
}
int32_t MyMessage::getLong(void) const
{
if (this->getPayloadType() == P_LONG32) {
return this->lValue;
} else if (this->getPayloadType() == P_STRING) {
return (int32_t)atol(this->data);
} else {
return 0;
}
}
uint32_t MyMessage::getULong(void) const
{
if (this->getPayloadType() == P_ULONG32) {
return this->ulValue;
} else if (this->getPayloadType() == P_STRING) {
return (uint32_t)atol(this->data);
} else {
return 0;
}
}
int16_t MyMessage::getInt(void) const
{
if (this->getPayloadType() == P_INT16) {
return this->iValue;
} else if (this->getPayloadType() == P_STRING) {
return (int16_t)atoi(this->data);
} else {
return 0;
}
}
uint16_t MyMessage::getUInt(void) const
{
if (this->getPayloadType() == P_UINT16) {
return this->uiValue;
} else if (this->getPayloadType() == P_STRING) {
return (uint16_t)atoi(this->data);
} else {
return 0;
}
}
MyMessage& MyMessage::set(const void* value, const size_t _length)
{
(void)this->setLength((value != NULL) ? _length : 0);
(void)this->setPayloadType(P_CUSTOM);
(void)memcpy((void *)this->data, value, this->getLength());
return *this;
}
MyMessage& MyMessage::set(const char* value)
{
(void)this->setLength((value != NULL) ? strlen(value) : 0);
(void)this->setPayloadType(P_STRING);
(void)strncpy(this->data, value, this->getLength());
// null terminate string
this->data[this->getLength()] = 0;
return *this;
}
#if !defined(__linux__)
MyMessage& MyMessage::set(const __FlashStringHelper* value)
{
(void)this->setLength((value != NULL) ? strlen_P(reinterpret_cast<const char *>(value)) : 0);
(void)this->setPayloadType(P_STRING);
(void)strncpy_P(this->data, reinterpret_cast<const char *>(value), this->getLength());
// null terminate string
this->data[this->getLength()] = 0;
return *this;
}
#endif
MyMessage& MyMessage::set(const bool value)
{
return this->set((uint8_t)value);
}
MyMessage& MyMessage::set(const uint8_t value)
{
(void)this->setLength(1u);
(void)this->setPayloadType(P_BYTE);
this->bValue = value;
return *this;
}
MyMessage& MyMessage::set(const float value, const uint8_t decimals)
{
(void)this->setLength(5u); // 32 bit float + persi
(void)this->setPayloadType(P_FLOAT32);
this->fValue = value;
this->fPrecision = decimals;
return *this;
}
MyMessage& MyMessage::set(const uint32_t value)
{
(void)this->setLength(4u);
(void)this->setPayloadType(P_ULONG32);
this->ulValue = value;
return *this;
}
MyMessage& MyMessage::set(const int32_t value)
{
(void)this->setLength(4u);
(void)this->setPayloadType(P_LONG32);
this->lValue = value;
return *this;
}
MyMessage& MyMessage::set(const uint16_t value)
{
(void)this->setLength(2u);
(void)this->setPayloadType(P_UINT16);
this->uiValue = value;
return *this;
}
MyMessage& MyMessage::set(const int16_t value)
{
(void)this->setLength(2u);
(void)this->setPayloadType(P_INT16);
this->iValue = value;
return *this;
}

View File

@@ -0,0 +1,671 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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
* @ingroup publics
* @{
*
* @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 <stdint.h>
#endif
#define V2_MYS_HEADER_PROTOCOL_VERSION (2u) //!< Protocol version
#define V2_MYS_HEADER_SIZE (7u) //!< Header size
#define V2_MYS_HEADER_MAX_MESSAGE_SIZE (32u) //!< Max payload size
#define V2_MYS_HEADER_VSL_VERSION_POS (0) //!< bitfield position version
#define V2_MYS_HEADER_VSL_VERSION_SIZE (2u) //!< size version field
#define V2_MYS_HEADER_VSL_SIGNED_POS (2u) //!< bitfield position signed field
#define V2_MYS_HEADER_VSL_SIGNED_SIZE (1u) //!< size signed field
#define V2_MYS_HEADER_VSL_LENGTH_POS (3u) //!< bitfield position length field
#define V2_MYS_HEADER_VSL_LENGTH_SIZE (5u) //!< size length field
#define V2_MYS_HEADER_CEP_COMMAND_POS (0) //!< bitfield position command field
#define V2_MYS_HEADER_CEP_COMMAND_SIZE (3u) //!< size command field
#define V2_MYS_HEADER_CEP_ECHOREQUEST_POS (3u) //!< bitfield position echo request field
#define V2_MYS_HEADER_CEP_ECHOREQUEST_SIZE (1u) //!< size echo request field
#define V2_MYS_HEADER_CEP_ECHO_POS (4u) //!< bitfield position echo field
#define V2_MYS_HEADER_CEP_ECHO_SIZE (1u) //!< size echo field
#define V2_MYS_HEADER_CEP_PAYLOADTYPE_POS (5u) //!< bitfield position payload type field
#define V2_MYS_HEADER_CEP_PAYLOADTYPE_SIZE (3u) //!< size payload type field
#define MAX_MESSAGE_SIZE V2_MYS_HEADER_MAX_MESSAGE_SIZE //!< The maximum size of a message (including header)
#define HEADER_SIZE V2_MYS_HEADER_SIZE //!< The size of the header
#define MAX_PAYLOAD_SIZE (MAX_MESSAGE_SIZE - HEADER_SIZE) //!< The maximum size of a payload depends on #MAX_MESSAGE_SIZE and #HEADER_SIZE
// deprecated in 3.0.0
#define MAX_PAYLOAD MAX_PAYLOAD_SIZE //!< \deprecated in 3.0.0 The maximum size of a payload depends on #MAX_MESSAGE_SIZE 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.
C_RESERVED_5 = 5, //!< C_RESERVED_5
C_RESERVED_6 = 6, //!< C_RESERVED_6
C_INVALID_7 = 7 //!< C_INVALID_7
} mysensors_command_t;
#if !DOXYGEN // Hide until we migrate
/// @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
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
} mysensors_sensor_t;
/// @brief Type of sensor data (for set/req/echo 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
V_PERCENTAGE = 3, //!< S_DIMMER. Used for sending a percentage value 0-100 (%).
V_DIMMER = 3, //!< \deprecated Same as V_PERCENTAGE
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
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]
} mysensors_data_t;
#endif
/// @brief Type of internal messages (for internal messages)
typedef enum {
I_BATTERY_LEVEL = 0, //!< Battery level
I_TIME = 1, //!< Time (request/response)
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 (request/response)
I_FIND_PARENT_REQUEST = 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_REQUEST = 18, //!< Heartbeat request
I_PRESENTATION = 19, //!< Presentation message
I_DISCOVER_REQUEST = 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
I_SIGNAL_REPORT_REQUEST = 29, //!< Device signal strength request
I_SIGNAL_REPORT_REVERSE = 30, //!< Internal
I_SIGNAL_REPORT_RESPONSE = 31, //!< Device signal strength response (RSSI)
I_PRE_SLEEP_NOTIFICATION = 32, //!< Message sent before node is going to sleep
I_POST_SLEEP_NOTIFICATION = 33 //!< Message sent after node woke up (if enabled)
} mysensors_internal_t;
/// @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
ST_FIRMWARE_CONFIRM = 6, //!< Mark running firmware as valid (MyOTAFirmwareUpdateNVM + mcuboot)
ST_FIRMWARE_RESPONSE_RLE = 7, //!< Response FW block with run length encoded data
} mysensors_stream_t;
/// @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
} mysensors_payload_t;
#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
// deprecated in 3.0.0
#define mSetVersion(_message, _version) _message.setVersion(_version) //!< \deprecated Set version field
#define mGetVersion(_message) _message.getVersion() //!< \deprecated Get version field
#define mSetSigned(_message, _signed) _message.setSigned(_signed) //!< \deprecated Set signed field
#define mGetSigned(_message) _message.getSigned() //!< \deprecated Get signed field
#define mSetLength(_message,_length) _message.setLength(_length) //!< \deprecated Set length field
#define mGetLength(_message) _message.getLength() //!< \deprecated Get length field
#define mSetCommand(_message, _command) _message.setCommand(_command) //!< \deprecated Set command field
#define mGetCommand(_message) _message.getCommand() //!< \deprecated Get command field
#define mSetRequestEcho(_message, _requestEcho) _message.setRequestEcho(_requestEcho) //!< \deprecated Set echo request field
#define mGetRequestEcho(_message) _message.getRequestEcho() //!< \deprecated Get echo request field
#define mSetEcho(_message, _echo) _message.setEcho(_echo) //!< \deprecated Set echo field
#define mGetEcho(_message) _message.getEcho() //!< \deprecated Get echo field
#define mSetPayloadType(_message, _payloadType) _message.setPayloadType(_payloadType) //!< \deprecated Set payload type field
#define mGetPayloadType(_message) _message.getPayloadType() //!< \deprecated Get payload type field
#if defined(__cplusplus) || defined(DOXYGEN)
/**
* @brief MyMessage is used to create, manipulate, send and read MySensors messages
*/
class MyMessage
{
private:
char* getCustomString(char *buffer) const;
public:
/**
* Default constructor
*/
MyMessage(void);
/**
* Constructor
* @param sensorId id of the child sensor for this message
* @param dataType
*/
MyMessage(const uint8_t sensorId, const mysensors_data_t dataType);
/**
* @brief Clear message contents.
*/
void clear(void);
/**
* 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_SIZE + 1. This is to be able to fit hex-conversion of a full binary payload.
* @param buffer pointer to a buffer that's at least 2 * MAX_PAYLOAD_SIZE + 1 bytes large
*/
char* getStream(char *buffer) const;
/**
* @brief Copy the payload into the supplied buffer
*/
char* getString(char *buffer) const;
/**
* @brief Get payload as string
* @return pointer to a char array storing the string
*/
const char* getString(void) const;
/**
* @brief Get custom payload
* @return pointer to the raw payload
*/
void* getCustom(void) const;
/**
* @brief Get bool payload
* @return a bool with the value of the payload (true/false)
*/
bool getBool(void) const;
/**
* @brief Get unsigned 8-bit integer payload
* @return the value of the payload, 0 to 255
*/
uint8_t getByte(void) const;
/**
* @brief Get float payload
* @return the floating-point value of the payload
*/
float getFloat(void) const;
/**
* @brief Get signed 16-bit integer payload
* @return the value of the payload, 32768 to 32767
*/
int16_t getInt(void) const;
/**
* @brief Get unsigned 16-bit integer payload
* @return the value of the payload, 0 to 65535
*/
uint16_t getUInt(void) const;
/**
* @brief Get signed 32-bit integer payload
* @return the value of the payload, 2147483648 to 2147483647
*/
int32_t getLong(void) const;
/**
* @brief Get unsigned 32-bit integer payload
* @return the value of the payload, 0 to 4294967295
*/
uint32_t getULong(void) const;
/**
* @brief getHeaderSize
* @return the size of the header
*/
uint8_t getHeaderSize(void) const;
/**
* @brief getMaxPayloadSize
* @return the max. size of the payload
*/
uint8_t getMaxPayloadSize(void) const;
/**
* @brief getExpectedMessageSize
* @return the expected message size based on header information
*/
uint8_t getExpectedMessageSize(void) const;
/**
* @brief isProtocolVersionValid
* @return true if the protocol version is valid
*/
bool isProtocolVersionValid(void) const;
/**
* @brief Getter for echo request
* @return echo request
*/
bool getRequestEcho(void) const;
/**
* @brief Setter for echo request
* @param requestEcho
*/
MyMessage& setRequestEcho(const bool requestEcho);
/**
* @brief Getter for version
* @return version
*/
uint8_t getVersion(void) const;
/**
* @brief Setter for version
*/
MyMessage& setVersion(void);
/**
* @brief Getter for length
* @return length
*/
uint8_t getLength(void) const;
/**
* @brief Setter for length
* @param length
*/
MyMessage& setLength(const uint8_t length);
/**
* @brief Getter for command type
* @return #mysensors_command_t
*/
mysensors_command_t getCommand(void) const;
/**
* @brief Setter for command type
* @param command
*/
MyMessage& setCommand(const mysensors_command_t command);
/**
* @brief Getter for payload type
* @return payload type
*/
mysensors_payload_t getPayloadType(void) const;
/**
* @brief Setter for payload type
* @param payloadType
*/
MyMessage& setPayloadType(const mysensors_payload_t payloadType);
/**
* @brief Getter for sign field
* @return sign field
*/
bool getSigned(void) const;
/**
* @brief Setter for sign field
* @param signedFlag
*/
MyMessage& setSigned(const bool signedFlag);
/**
* \deprecated use isEcho()
* @brief Getter for echo-flag.
* @return true if this is an echoed message
*/
bool isAck(void) const;
/**
* @brief Getter for echo-flag.
* @return true if this is an echoed message
*/
bool isEcho(void) const;
/**
* @brief Setter for echo-flag.
* @param echo true if this an echo message
*/
MyMessage& setEcho(const bool echo);
/**
* @brief Get message type
* @return messageType
*/
uint8_t getType(void) const;
/**
* @brief Set message type
* @param messageType
*/
MyMessage& setType(const uint8_t messageType);
/**
* @brief Get last ID
* @return lastId
*/
uint8_t getLast(void) const;
/**
* @brief Set last ID
* @param lastId
*/
MyMessage& setLast(const uint8_t lastId);
/**
* @brief Get sender ID
* @return sender
*/
uint8_t getSender(void) const;
/**
* @brief Set sender ID
* @param senderId
*/
MyMessage& setSender(const uint8_t senderId);
/**
* @brief Get sensor ID of message
* @return sensorId
*/
uint8_t getSensor(void) const;
/**
* @brief Set which child sensor this message belongs to
* @param sensorId
*/
MyMessage& setSensor(const uint8_t sensorId);
/**
* @brief Get destination
* @return destinationId
*/
uint8_t getDestination(void) const;
/**
* @brief Set final destination node id for this message
* @param destinationId
*/
MyMessage& setDestination(const uint8_t destinationId);
/**
* @brief Set entire payload
* @param payload pointer to the buffer where the payload is stored
* @param length of the payload
*/
MyMessage& set(const void* payload, const size_t length);
/**
* @brief Set payload to character array
* @param value pointer to the character array. The array must be null-terminated.
*/
MyMessage& set(const char* value);
#if !defined(__linux__)
/**
* @brief Set payload to character array from flash
* @param value pointer to the character array. The array must be null-terminated.
*/
MyMessage& set(const __FlashStringHelper* value);
#endif
/**
* @brief Set payload to decimal number
* @param value float
* @param decimals number of decimals to include
*/
MyMessage& set(const float value, const uint8_t decimals);
/**
* @brief Set payload to bool value
* @param value true or false
*/
MyMessage& set(const bool value);
/**
* @brief Set payload to unsigned 8-bit integer value
* @param value (0 to 255)
*/
MyMessage& set(const uint8_t value);
/**
* @brief Set payload to unsigned 32-bit integer value
* @param value (0 to 4294967295)
*/
MyMessage& set(const uint32_t value);
/**
* @brief Set payload to signed 32-bit integer value
* @param value (2147483648 to 2147483647)
*/
MyMessage& set(const int32_t value);
/**
* @brief Set payload to unsigned 16-bit integer value
* @param value (0 to 65535)
*/
MyMessage& set(const uint16_t value);
/**
* @brief Set payload to signed 16-bit integer value
* @param value (32768 to 32767)
*/
MyMessage& set(const 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
/**
* 2 bit - Protocol version<br>
* 1 bit - Signed flag<br>
* 5 bit - Length of payload
*/
uint8_t version_length;
/**
* 3 bit - Command type<br>
* 1 bit - Request an echo - Indicator that receiver should echo the message back to the sender<br>
* 1 bit - Is echo message - Indicator that this is the echoed message<br>
* 3 bit - Payload data type
*/
uint8_t command_echo_payload;
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; //!< unsigned byte value (8-bit)
uint16_t uiValue; //!< unsigned integer value (16-bit)
int16_t iValue; //!< signed integer value (16-bit)
uint32_t ulValue; //!< unsigned long value (32-bit)
int32_t lValue; //!< signed long value (32-bit)
struct { //!< Float messages
float fValue;
uint8_t fPrecision; //!< Number of decimals when serializing
};
char data[MAX_PAYLOAD_SIZE + 1]; //!< Buffer for raw payload data
} __attribute__((packed)); //!< Doxygen will complain without this comment
#if defined(__cplusplus) || defined(DOXYGEN)
} __attribute__((packed));
#else
};
uint8_t array[HEADER_SIZE + MAX_PAYLOAD_SIZE + 1]; //!< buffer for entire message
} __attribute__((packed)) MyMessage;
#endif
#endif
/** @}*/

View File

@@ -0,0 +1,287 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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"
// global variables
extern MyMessage _msg;
extern MyMessage _msgTmp;
// local variables
#ifdef MY_OTA_USE_I2C_EEPROM
I2CEeprom _flash(MY_OTA_I2C_ADDR);
#elif !defined(MCUBOOT_PRESENT)
SPIFlash _flash(MY_OTA_FLASH_SS, MY_OTA_FLASH_JDECID);
#endif
// Map flash functions
#ifndef MCUBOOT_PRESENT
#define _flash_initialize() _flash.initialize()
#define _flash_readByte(addr) _flash.readByte(addr)
#define _flash_writeBytes( dstaddr, data, size) _flash.writeBytes( dstaddr, data, size)
#define _flash_blockErase32K(num) _flash.blockErase32K(num)
#define _flash_busy() _flash.busy()
#else
#define _flash_initialize() true
#define _flash_readByte(addr) (*((uint8_t *)(addr)))
#define _flash_blockErase32K(num) Flash.erase((uint32_t *)FLASH_AREA_IMAGE_1_OFFSET_0, FLASH_AREA_IMAGE_1_SIZE_0)
#define _flash_busy() false
#endif
LOCAL nodeFirmwareConfig_t _nodeFirmwareConfig;
LOCAL bool _firmwareUpdateOngoing = false;
LOCAL uint32_t _firmwareLastRequest;
LOCAL uint16_t _firmwareBlock;
LOCAL uint8_t _firmwareRetry;
LOCAL bool _firmwareResponse(uint16_t block, uint8_t *data);
LOCAL void readFirmwareSettings(void)
{
hwReadConfigBlock((void*)&_nodeFirmwareConfig, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS,
sizeof(nodeFirmwareConfig_t));
}
LOCAL void firmwareOTAUpdateRequest(void)
{
const uint32_t enterMS = hwMillis();
if (_firmwareUpdateOngoing && (enterMS - _firmwareLastRequest > MY_OTA_RETRY_DELAY)) {
if (!_firmwareRetry) {
setIndication(INDICATION_ERR_FW_TIMEOUT);
OTA_DEBUG(PSTR("!OTA:FRQ:FW UPD FAIL\n")); // fw update failed
// Give up. We have requested MY_OTA_RETRY times without any packet in return.
_firmwareUpdateOngoing = false;
return;
}
_firmwareRetry--;
_firmwareLastRequest = enterMS;
// Time to (re-)request firmware block from controller
requestFirmwareBlock_t firmwareRequest;
firmwareRequest.type = _nodeFirmwareConfig.type;
firmwareRequest.version = _nodeFirmwareConfig.version;
firmwareRequest.block = (_firmwareBlock - 1);
OTA_DEBUG(PSTR("OTA:FRQ:FW REQ,T=%04" PRIX16 ",V=%04" PRIX16 ",B=%04" PRIX16 "\n"),
_nodeFirmwareConfig.type,
_nodeFirmwareConfig.version, _firmwareBlock - 1); // request FW update block
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_REQUEST,
false).set(&firmwareRequest, sizeof(requestFirmwareBlock_t)));
}
}
LOCAL bool firmwareOTAUpdateProcess(void)
{
if (_msg.getType() == ST_FIRMWARE_CONFIG_RESPONSE) {
if(_firmwareUpdateOngoing) {
OTA_DEBUG(PSTR("!OTA:FWP:UPDO\n")); // FW config response received, FW update already ongoing
return true;
}
nodeFirmwareConfig_t *firmwareConfigResponse = (nodeFirmwareConfig_t *)_msg.data;
// compare with current node configuration, if they differ, start FW fetch process
if (memcmp(&_nodeFirmwareConfig, firmwareConfigResponse, sizeof(nodeFirmwareConfig_t))) {
setIndication(INDICATION_FW_UPDATE_START);
OTA_DEBUG(PSTR("OTA:FWP:UPDATE\n")); // FW update initiated
// copy new FW config
(void)memcpy(&_nodeFirmwareConfig, firmwareConfigResponse, sizeof(nodeFirmwareConfig_t));
// Init flash
if (!_flash_initialize()) {
setIndication(INDICATION_ERR_FW_FLASH_INIT);
OTA_DEBUG(PSTR("!OTA:FWP:FLASH INIT FAIL\n")); // failed to initialise flash
_firmwareUpdateOngoing = false;
} else {
// erase lower 32K -> max flash size for ATMEGA328
_flash_blockErase32K(0);
// wait until flash erased
while ( _flash_busy() ) {}
_firmwareBlock = _nodeFirmwareConfig.blocks;
_firmwareUpdateOngoing = true;
// reset flags
_firmwareRetry = MY_OTA_RETRY + 1;
_firmwareLastRequest = 0;
}
return true;
}
OTA_DEBUG(PSTR("OTA:FWP:UPDATE SKIPPED\n")); // FW update skipped, no newer version available
} else if (_msg.getType() == ST_FIRMWARE_RESPONSE) {
// extract FW block
replyFirmwareBlock_t *firmwareResponse = (replyFirmwareBlock_t *)_msg.data;
// Proceed firmware data
return _firmwareResponse(firmwareResponse->block, firmwareResponse->data);
#ifdef FIRMWARE_PROTOCOL_31
} else if (_msg.getType() == ST_FIRMWARE_RESPONSE_RLE) {
// RLE encoded block
// extract FW block
replyFirmwareBlockRLE_t *firmwareResponse = (replyFirmwareBlockRLE_t *)_msg.data;
uint8_t data[FIRMWARE_BLOCK_SIZE];
for (uint8_t i=0; i<FIRMWARE_BLOCK_SIZE; i++) {
data[i]=firmwareResponse->data;
}
while ((_firmwareBlock) && (firmwareResponse->number_of_blocks)) {
_firmwareResponse(firmwareResponse->block, data);
firmwareResponse->number_of_blocks--;
firmwareResponse->block--;
}
return true;
#endif
} else {
#ifdef MCUBOOT_PRESENT
if (_msg.getType() == ST_FIRMWARE_CONFIRM) {
if (*(uint16_t *)MCUBOOT_IMAGE_0_MAGIC_ADDR == ((uint16_t)MCUBOOT_IMAGE_MAGIC)) {
if (*(uint8_t *)(MCUBOOT_IMAGE_0_IMG_OK_ADDR) != MCUBOOT_IMAGE_0_IMG_OK_BYTE) {
// Calculate data word to write
uint32_t *img_ok_base_addr = (uint32_t *)(MCUBOOT_IMAGE_0_IMG_OK_ADDR & ~3); // align word wise
uint32_t img_ok_data = *img_ok_base_addr;
// Set copy of MCUBOOT_IMAGE_0_IMG_OK_ADDR to MCUBOOT_IMAGE_0_IMG_OK_BYTE (0x01)
uint8_t *img_ok_array = (uint8_t *)&img_ok_data;
*(img_ok_array + (MCUBOOT_IMAGE_0_IMG_OK_ADDR % 4)) = MCUBOOT_IMAGE_0_IMG_OK_BYTE;
// Write word back
Flash.write(img_ok_base_addr, img_ok_data);
}
OTA_DEBUG(PSTR("!OTA:FWP:IMAGE CONFIRMED\n"));
} else {
OTA_DEBUG(PSTR("!OTA:FWP:INVALID MCUBOOT MAGIC\n"));
}
}
#endif
}
return false;
}
LOCAL void presentBootloaderInformation(void)
{
requestFirmwareConfig_t *requestFirmwareConfig = (requestFirmwareConfig_t *)_msgTmp.data;
_msgTmp.setLength(sizeof(requestFirmwareConfig_t));
_msgTmp.setCommand(C_STREAM);
_msgTmp.setPayloadType(P_CUSTOM);
// copy node settings to reqFWConfig
(void)memcpy(requestFirmwareConfig, &_nodeFirmwareConfig, sizeof(nodeFirmwareConfig_t));
// add bootloader information
requestFirmwareConfig->BLVersion = MY_OTA_BOOTLOADER_VERSION;
#ifdef FIRMWARE_PROTOCOL_31
requestFirmwareConfig->blockSize = FIRMWARE_BLOCK_SIZE;
#ifndef MCUBOOT_PRESENT
requestFirmwareConfig->img_commited = 0x2;
requestFirmwareConfig->img_revision = 0x00;
requestFirmwareConfig->img_build_num = 0x00;
#else
requestFirmwareConfig->img_commited = *((uint8_t*)(MCUBOOT_IMAGE_0_IMG_OK_ADDR));
requestFirmwareConfig->img_revision = *((uint16_t*)(MCUBOOT_IMAGE_0_IMG_REVISION_ADDR));
requestFirmwareConfig->img_build_num = *((uint16_t*)(MCUBOOT_IMAGE_0_IMG_BUILD_NUM_ADDR));
#endif
#endif
_firmwareUpdateOngoing = false;
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM,
ST_FIRMWARE_CONFIG_REQUEST, false));
}
LOCAL bool isFirmwareUpdateOngoing(void)
{
return _firmwareUpdateOngoing;
}
// do a crc16 on the whole received firmware
LOCAL bool transportIsValidFirmware(void)
{
// init crc
uint16_t crc = ~0;
for (uint32_t i = 0; i < _nodeFirmwareConfig.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);
}
}
}
OTA_DEBUG(PSTR("OTA:CRC:B=%04" PRIX16 ",C=%04" PRIX16 ",F=%04" PRIX16 "\n"),
_nodeFirmwareConfig.blocks,crc,
_nodeFirmwareConfig.crc);
return crc == _nodeFirmwareConfig.crc;
}
LOCAL bool _firmwareResponse(uint16_t block, uint8_t *data)
{
if (_firmwareUpdateOngoing) {
OTA_DEBUG(PSTR("OTA:FWP:RECV B=%04" PRIX16 "\n"), block); // received FW block
if (block != _firmwareBlock - 1) {
OTA_DEBUG(PSTR("!OTA:FWP:WRONG FWB\n")); // received FW block
// wrong firmware block received
setIndication(INDICATION_FW_UPDATE_RX_ERR);
// no further processing required
return true;
}
setIndication(INDICATION_FW_UPDATE_RX);
// Save block to flash
#ifdef MCUBOOT_PRESENT
uint32_t addr = ((size_t)(((_firmwareBlock - 1) * FIRMWARE_BLOCK_SIZE)) + (size_t)(
FIRMWARE_START_OFFSET));
if (addr<FLASH_AREA_IMAGE_SCRATCH_OFFSET_0) {
Flash.write_block( (uint32_t *)addr, (uint32_t *)data, FIRMWARE_BLOCK_SIZE>>2);
}
#else
_flash_writeBytes( ((_firmwareBlock - 1) * FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET,
data, FIRMWARE_BLOCK_SIZE);
#endif
// wait until flash written
while (_flash_busy()) {}
#ifdef OTA_EXTRA_FLASH_DEBUG
{
char prbuf[8];
uint32_t addr = ((_firmwareBlock - 1) * FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET;
OTA_DEBUG(PSTR("OTA:FWP:FL DUMP "));
sprintf_P(prbuf,PSTR("%04" PRIX16 ":"), (uint16_t)addr);
MY_SERIALDEVICE.print(prbuf);
for(uint8_t i=0; i<FIRMWARE_BLOCK_SIZE; i++) {
uint8_t data = _flash_readByte(addr + i);
sprintf_P(prbuf,PSTR("%02" PRIX8 ""), (uint8_t)data);
MY_SERIALDEVICE.print(prbuf);
}
OTA_DEBUG(PSTR("\n"));
}
#endif
_firmwareBlock--;
if (!_firmwareBlock) {
// We're done! Do a checksum and reboot.
OTA_DEBUG(PSTR("OTA:FWP:FW END\n")); // received FW block
_firmwareUpdateOngoing = false;
if (transportIsValidFirmware()) {
OTA_DEBUG(PSTR("OTA:FWP:CRC OK\n")); // FW checksum ok
// Write the new firmware config to eeprom
hwWriteConfigBlock((void*)&_nodeFirmwareConfig, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS,
sizeof(nodeFirmwareConfig_t));
#ifndef MCUBOOT_PRESENT
// All seems ok, write size and signature to flash (DualOptiboot will pick this up and flash it)
const uint16_t firmwareSize = FIRMWARE_BLOCK_SIZE * _nodeFirmwareConfig.blocks;
const uint8_t OTAbuffer[FIRMWARE_START_OFFSET] = {'F','L','X','I','M','G',':', (uint8_t)(firmwareSize >> 8), (uint8_t)(firmwareSize & 0xff),':'};
_flash_writeBytes(0, OTAbuffer, FIRMWARE_START_OFFSET);
// wait until flash ready
while (_flash_busy()) {}
#endif
hwReboot();
} else {
setIndication(INDICATION_ERR_FW_CHECKSUM);
OTA_DEBUG(PSTR("!OTA:FWP:CRC FAIL\n"));
}
}
// reset flags
_firmwareRetry = MY_OTA_RETRY + 1;
_firmwareLastRequest = 0;
} else {
OTA_DEBUG(PSTR("!OTA:FWP:NO UPDATE\n"));
}
return true;
}

View File

@@ -0,0 +1,200 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 MyOTAFirmwareUpdate.h
*
* @defgroup MyOTAFirmwaregrp MyOTAFirmwareUpdate
* @ingroup internals
* @{
*
* MyOTAFirmwareUpdate-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE
* - [!] Exclamation mark is prepended in case of error or warning
* - SYSTEM:
* - <b>OTA</b> messages emitted by MyOTAFirmwareUpdate
* - SUB SYSTEMS:
* - OTA:<b>FRQ</b> from @ref firmwareOTAUpdateRequest()
* - OTA:<b>FWP</b> from @ref firmwareOTAUpdateProcess()
*
* MyOTAFirmwareUpdate debug log messages:
*
* |E| SYS | SUB | Message | Comment
* |-|-----|-----|-----------------------------|----------------------------------------------------------------------------
* | | OTA | FWP | UPDATE | FW update initiated
* |!| OTA | FWP | UPDO | FW config response received, FW update already ongoing
* |!| OTA | FWP | FLASH INIT FAIL | Failed to initialise flash
* | | OTA | FWP | UPDATE SKIPPED | FW update skipped, no newer version available
* | | OTA | FWP | RECV B=%04X | Received FW block (B)
* |!| OTA | FWP | WRONG FWB | Wrong FW block received
* | | OTA | FWP | FW END | FW received, proceed to CRC verification
* | | OTA | FWP | CRC OK | FW CRC verification OK
* |!| OTA | FWP | CRC FAIL | FW CRC verification failed
* | | OTA | FRQ | FW REQ,T=%04X,V=%04X,B=%04X | Request FW update, FW type (T), version (V), block (B)
* |!| OTA | FRQ | FW UPD FAIL | FW update failed
* | | OTA | CRC | B=%04X,C=%04X,F=%04X | FW CRC verification. FW blocks (B), calculated CRC (C), FW CRC (F)
*
*
* @brief API declaration for MyOTAFirmwareUpdate
*/
#ifndef MyOTAFirmwareUpdate_h
#define MyOTAFirmwareUpdate_h
#include "MySensorsCore.h"
#ifdef MCUBOOT_PRESENT
#include "generated_dts_board.h"
#define FIRMWARE_PROTOCOL_31
#endif
#define LOCAL static //!< static
#if MAX_PAYLOAD_SIZE >= 22
#define FIRMWARE_BLOCK_SIZE (16u) //!< Size of each firmware block
#else
#define FIRMWARE_BLOCK_SIZE (8u) //!< Size of each firmware block
#ifndef FIRMWARE_PROTOCOL_31
#define FIRMWARE_PROTOCOL_31
#endif
#endif
#ifndef MY_OTA_RETRY
#define MY_OTA_RETRY (5u) //!< Number of times to request a fw block before giving up
#endif
#ifndef MY_OTA_RETRY_DELAY
#define MY_OTA_RETRY_DELAY (500u) //!< Number of milliseconds before re-requesting a FW block
#endif
#ifndef MCUBOOT_PRESENT
#define FIRMWARE_START_OFFSET (10u) //!< Start offset for firmware in flash (DualOptiboot wants to keeps a signature first)
#else
#define FIRMWARE_START_OFFSET (FLASH_AREA_IMAGE_1_OFFSET_0) //!< Use offset from generated_dts_board.h (mcuboot)
#endif
#define MY_OTA_BOOTLOADER_MAJOR_VERSION (3u) //!< Bootloader version major
#ifdef FIRMWARE_PROTOCOL_31
#define MY_OTA_BOOTLOADER_MINOR_VERSION (1u) //!< Bootloader version minor
#else
#define MY_OTA_BOOTLOADER_MINOR_VERSION (0u) //!< Bootloader version minor
#endif
#define MY_OTA_BOOTLOADER_VERSION (MY_OTA_BOOTLOADER_MINOR_VERSION * 256 + MY_OTA_BOOTLOADER_MAJOR_VERSION) //!< Bootloader version
#if defined(MY_DEBUG_VERBOSE_OTA_UPDATE)
#define OTA_DEBUG(x,...) DEBUG_OUTPUT(x, ##__VA_ARGS__) //!< debug
//#define OTA_EXTRA_FLASH_DEBUG //!< Dumps flash after each FW block
#else
#define OTA_DEBUG(x,...) //!< debug NULL
#endif
#if defined(DOXYGEN) && !defined(FIRMWARE_PROTOCOL_31)
/**
* @brief Enabled FOTA 3.1 protocol extensions
*
* Supports smaller FIRMWARE_BLOCK_SIZE, RLE and NVM for nRF5 with mcuboot. The
* extension is enabled per default when mcuboot is present or full FIRMWARE_BLOCK_SIZE
* exeeds MAX_PAYLOAD_SIZE.
*/
#define FIRMWARE_PROTOCOL_31
#endif
/**
* @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_t;
/**
* @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
#ifdef FIRMWARE_PROTOCOL_31
uint8_t blockSize; //!< Blocksize, when protocol version >= 3.1 is reported. Otherwhise the blocksize is 16
uint8_t img_commited; //!< mcuboot image_ok attribute commited firmware=0x01(mcuboot)|0x02(DualOptiboot), when protocol version >= 3.1 is reported
uint16_t img_revision; //!< mcuboot revision attribute, when protocol version >= 3.1 is reported
uint32_t img_build_num; //!< mcuboot build_num attribute, when protocol version >= 3.1 is reported
#endif
} __attribute__((packed)) requestFirmwareConfig_t;
/**
* @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)) requestFirmwareBlock_t;
/**
* @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)) replyFirmwareBlock_t;
/**
* @brief FW block reply structure (RLE)
*/
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t block; //!< Block index
uint16_t number_of_blocks; //!< Number of blocks to fill with data
uint8_t data; //!< Block data
} __attribute__((packed)) replyFirmwareBlockRLE_t;
/**
* @brief Read firmware settings from EEPROM
*
* Current firmware settings (type, version, crc, blocks) are read into _fc
*/
LOCAL void readFirmwareSettings(void);
/**
* @brief Handle OTA FW update requests
*/
LOCAL void firmwareOTAUpdateRequest(void);
/**
* @brief Handle OTA FW update responses
*
* This function handles incoming OTA FW packets and stores them to external flash (Sensebender)
*/
LOCAL bool firmwareOTAUpdateProcess(void);
/**
* @brief Validate uploaded FW CRC
*
* This function verifies if uploaded FW CRC is valid
*/
LOCAL bool transportIsValidFirmware(void);
/**
* @brief Present bootloader/FW information upon startup
*/
LOCAL void presentBootloaderInformation(void);
#endif
/** @}*/

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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MyOTALogging.h"
#ifdef MY_OTA_LOG_SENDER_FEATURE
// global variables
static bool inOTALog = false;
void OTALog(uint8_t logNode, const bool requestEcho, const char *fmt, ... )
{
// Avoid recursion
if (inOTALog==true) {
return;
}
inOTALog = true;
MyMessage msg(NODE_SENSOR_ID, V_CUSTOM);
char fmtBuffer[MY_SERIAL_OUTPUT_SIZE];
va_list args;
// create message
va_start (args, fmt );
int n = vsnprintf_P(fmtBuffer, sizeof(fmtBuffer), fmt, args);
va_end (args);
// Check number of chars
if (n<1) {
// Nothing to send
inOTALog = false;
return;
}
// Add \n to the end of string
if (n>(int)(sizeof(fmtBuffer)-2)) {
// String is truncated
n = sizeof(fmtBuffer)-2;
}
// add LF if not set
if (fmtBuffer[n-1]!='\n') {
fmtBuffer[n++]='\n';
fmtBuffer[n++]=0;
}
// Configure message
msg.setSender(getNodeId());
msg.setDestination(logNode);
msg.setCommand(C_INTERNAL);
msg.setType(I_LOG_MESSAGE);
msg.setRequestEcho(requestEcho);
// Send package
for (int pos = 0; pos < n; pos += MAX_PAYLOAD_SIZE) {
uint8_t length = strlen(&fmtBuffer[pos]);
if (length > MAX_PAYLOAD_SIZE) {
length = MAX_PAYLOAD_SIZE;
}
(void)_sendRoute(msg.set((char*)&fmtBuffer[pos]));
}
inOTALog = false;
}
#endif
#ifdef MY_OTA_LOG_RECEIVER_FEATURE
// Global variables
char OTALogfmtBuffer[MY_SERIAL_OUTPUT_SIZE];
int OTALogfmtBufferPos = 0;
uint8_t OTALogBufferNode = BROADCAST_ADDRESS;
uint8_t OTALogBufferSensor = 0;
void OTALogPrintPrefix()
{
char prefix[37];
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
snprintf_P(prefix, sizeof(prefix),
PSTR("%" PRId8 ";%" PRId8 ";%" PRId8 ";0;%" PRId8 ";%" PRIu32 " "),
OTALogBufferNode, OTALogBufferSensor, C_INTERNAL, I_LOG_MESSAGE, hwMillis());
MY_SERIALDEVICE.print(prefix);
}
void OTALogFlushBuffer()
{
OTALogfmtBuffer[0] = 0;
OTALogfmtBufferPos = 0;
OTALogBufferNode = BROADCAST_ADDRESS;
}
inline void OTALogPrint(const MyMessage &message)
{
// Ignore log messages via broadcast
if (message.destination == BROADCAST_ADDRESS) {
return;
}
// FLush buffer, when node id changes
if ((OTALogBufferNode!=BROADCAST_ADDRESS) && ((OTALogBufferNode != message.getSender()) ||
(OTALogBufferSensor != message.getSensor()))) {
OTALogPrintPrefix();
MY_SERIALDEVICE.print(OTALogfmtBuffer);
MY_SERIALDEVICE.println("...");
OTALogFlushBuffer();
}
// Add data to buffer
const char *str = message.getString();
strncpy(&OTALogfmtBuffer[OTALogfmtBufferPos], str,
sizeof(OTALogfmtBuffer)-OTALogfmtBufferPos);
OTALogfmtBufferPos += strlen(str);
// Store node ID and sensor ID
OTALogBufferNode = message.getSender();
OTALogBufferSensor = message.getSensor();
// Print out buffered lines ending with \n
char *EOLpos;
while (EOLpos = strchr(OTALogfmtBuffer,'\n'), EOLpos != NULL) {
// Add end of string
EOLpos[0]=0;
// Print out line
OTALogPrintPrefix();
MY_SERIALDEVICE.println(OTALogfmtBuffer);
// Check if more content in buffer
int lenAfterEOL = (size_t)&OTALogfmtBuffer[OTALogfmtBufferPos]-(size_t)EOLpos-2;
if (lenAfterEOL>0) {
// More lines, move string to the beginning of the buffer
strcpy((char*)&OTALogfmtBuffer[0], (char*)&EOLpos[1]);
// calculate OTALogfmtBufferPos
OTALogfmtBufferPos -= (size_t)EOLpos-(size_t)&OTALogfmtBuffer[0]+2;
// Security check
if ((OTALogfmtBufferPos<=0) || (OTALogfmtBufferPos>=(int)sizeof(OTALogfmtBuffer))) {
MY_SERIALDEVICE.print("Sec:");
MY_SERIALDEVICE.println(OTALogfmtBufferPos);
OTALogFlushBuffer();
}
} else {
// End of message, prepare new one
OTALogFlushBuffer();
}
}
}
#endif

View File

@@ -0,0 +1,73 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 MyOTALogging.h
*
* @brief API declaration for MyOTALogging
* @defgroup MyOTALogginggrp MyOTALogging
* @ingroup internals
* @{
*
* @brief Enables ending and receiving debug messages over the air.
*/
#ifndef MyOTALogging_h
#define MyOTALogging_h
#include "MySensorsCore.h"
#include "MyTransport.h"
/**
* @brief Send a log message to a node
*
* If MY_OTA_LOG_RECEIVER_FEATURE is enabled on the destination node, the given
* message is printed to the serial port. You have to define MY_OTA_LOG_SENDER_FEATURE
* or MY_DEBUG_OTA to enable the OTALog() function.
*
* Output format of each line:
* Node ID;CHILD_NODE_ID;C_INTERNAL;I_LOG_MESSAGE;hwMillis() MESSAGE
*
* You will see the hwMillis() of the receiving node. After each \n character, a
* new debug message line starts. Incomplete messages are ending with '...'
*
* @param logNode Destination node ID
* @param echo Enable or disable echo flag
* @param fmt printf format string
* @param ... arguments
*/
void OTALog(uint8_t logNode, bool echo, const char *fmt, ... );
/**
* @brief Handles output of OTA log or debug messages
*
* This function is used by MyTransport.cpp
*
* Output format of each line:
* Node ID;CHILD_NODE_ID;C_INTERNAL;I_LOG_MESSAGE;hwMillis() MESSAGE
*
* You will see the hwMillis() of the receiving node. After each \n character, a
* new debug message line starts. Incomplete messages are ending with '...'
*
* @param message Message buffer to use.
*/
inline void OTALogPrint(const MyMessage &message);
#endif /* MyOTALogging_h */
/** @}*/

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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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

View File

@@ -0,0 +1,167 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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"
#include "MyHelperFunctions.h"
#include <string.h>
char _fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH];
char _convBuffer[MAX_PAYLOAD_SIZE * 2 + 1];
bool protocolSerial2MyMessage(MyMessage &message, char *inputString)
{
char *str, *p;
uint8_t index = 0;
mysensors_command_t command = C_INVALID_7;
message.setSender(GATEWAY_ADDRESS);
message.setLast(GATEWAY_ADDRESS);
message.setEcho(false);
// Extract command data coming on serial line
for (str = strtok_r(inputString, ";", &p); // split using semicolon
str && index < 5; // loop while str is not null an max 4 times
str = strtok_r(NULL, ";", &p), index++ // get subsequent tokens
) {
switch (index) {
case 0: // Radio id (destination)
message.setDestination(atoi(str));
break;
case 1: // Child id
message.setSensor(atoi(str));
break;
case 2: // Message type
command = static_cast<mysensors_command_t>(atoi(str));
message.setCommand(command);
break;
case 3: // Should we request echo from destination?
message.setRequestEcho(atoi(str) ? 1 : 0);
break;
case 4: // Data type
message.setType(atoi(str));
break;
}
}
// payload
if (str == NULL) {
// no payload, set default value
message.set((uint8_t)0);
} else if (command == C_STREAM) {
// stream payload
uint8_t bvalue[MAX_PAYLOAD_SIZE];
uint8_t blen = 0;
while (*str) {
uint8_t val;
val = convertH2I(*str++) << 4;
val += convertH2I(*str++);
bvalue[blen] = val;
blen++;
}
message.set(bvalue, blen);
} else {
// regular payload
char *value = str;
// Remove trailing carriage return and newline character (if it exists)
const uint8_t lastCharacter = strlen(value) - 1;
if (value[lastCharacter] == '\r' || value[lastCharacter] == '\n') {
value[lastCharacter] = '\0';
}
message.set(value);
}
return (index == 5);
}
char *protocolMyMessage2Serial(MyMessage &message)
{
(void)snprintf_P(_fmtBuffer, (uint8_t)MY_GATEWAY_MAX_SEND_LENGTH,
PSTR("%" PRIu8 ";%" PRIu8 ";%" PRIu8 ";%" PRIu8 ";%" PRIu8 ";%s\n"), message.getSender(),
message.getSensor(), message.getCommand(), message.isEcho(), message.getType(),
message.getString(_convBuffer));
return _fmtBuffer;
}
char *protocolMyMessage2MQTT(const char *prefix, MyMessage &message)
{
(void)snprintf_P(_fmtBuffer, (uint8_t)MY_GATEWAY_MAX_SEND_LENGTH,
PSTR("%s/%" PRIu8 "/%" PRIu8 "/%" PRIu8 "/%" PRIu8 "/%" PRIu8 ""), prefix,
message.getSender(), message.getSensor(), message.getCommand(), message.isEcho(),
message.getType());
return _fmtBuffer;
}
bool protocolMQTT2MyMessage(MyMessage &message, char *topic, uint8_t *payload,
const unsigned int length)
{
char *str, *p;
uint8_t index = 0;
message.setSender(GATEWAY_ADDRESS);
message.setLast(GATEWAY_ADDRESS);
message.setEcho(false);
for (str = strtok_r(topic + strlen(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) + 1, "/", &p);
str && index < 5;
str = strtok_r(NULL, "/", &p), index++
) {
switch (index) {
case 0:
// Node id
message.setDestination(atoi(str));
break;
case 1:
// Sensor id
message.setSensor(atoi(str));
break;
case 2: {
// Command type
const mysensors_command_t command = static_cast<mysensors_command_t>(atoi(str));
message.setCommand(command);
// Add payload
if (command == C_STREAM) {
uint8_t bvalue[MAX_PAYLOAD_SIZE];
uint8_t blen = 0;
while (*payload) {
uint8_t val;
val = convertH2I(*payload++) << 4;
val += convertH2I(*payload++);
bvalue[blen] = val;
blen++;
}
message.set(bvalue, blen);
} else {
// terminate string
char *value = (char *)payload;
value[length] = '\0';
message.set((const char*)payload);
}
break;
}
case 3:
// Echo flag
message.setRequestEcho(atoi(str) ? 1 : 0);
break;
case 4:
// Sub type
message.setType(atoi(str));
break;
}
}
// Return true if input valid
return (index == 5);
}

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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 protocolSerial2MyMessage(MyMessage &message, char *inputString);
// Format MyMessage to the protocol representation
char *protocolMyMessage2Serial(MyMessage &message);
#endif

View File

@@ -0,0 +1,833 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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"
// debug output
#if defined(MY_DEBUG_VERBOSE_CORE)
#define CORE_DEBUG(x,...) DEBUG_OUTPUT(x, ##__VA_ARGS__) //!< debug
#else
#define CORE_DEBUG(x,...) //!< debug NULL
#endif
// message buffers
MyMessage _msg; // Buffer for incoming messages
MyMessage _msgTmp; // Buffer for temporary messages (acks and nonces among others)
// core configuration
static coreConfig_t _coreConfig;
#if defined(MY_DEBUG_VERBOSE_CORE)
static uint8_t waitLock = 0;
static uint8_t processLock = 0;
#endif
#if defined(DEBUG_OUTPUT_ENABLED)
char _convBuf[MAX_PAYLOAD_SIZE * 2 + 1];
#endif
// Callback for transport=ok transition
void _callbackTransportReady(void)
{
if (!_coreConfig.presentationSent) {
#if !defined(MY_GATEWAY_FEATURE) // GW calls presentNode() when client connected
presentNode();
#endif
_registerNode();
_coreConfig.presentationSent = true;
}
}
void _process(void)
{
#if defined(MY_DEBUG_VERBOSE_CORE)
if (processLock) {
CORE_DEBUG(PSTR("!MCO:PRO:RC=%" PRIu8 "\n"), processLock); // recursive call detected
}
processLock++;
#endif
doYield();
#if defined(MY_INCLUSION_MODE_FEATURE)
inclusionProcess();
#endif
#if defined(MY_GATEWAY_FEATURE)
gatewayTransportProcess();
#endif
#if defined(MY_SENSOR_NETWORK)
transportProcess();
#endif
#if defined(__linux__)
// To avoid high cpu usage
usleep(10000); // 10ms
#endif
#if defined(MY_DEBUG_VERBOSE_CORE)
processLock--;
#endif
}
void _infiniteLoop(void)
{
#if defined(__linux__)
exit(1);
#else
while(1) {
doYield();
}
#endif
}
void _begin(void)
{
#if defined(MY_CORE_ONLY)
// initialize HW and run setup if present
(void)hwInit();
if (setup) {
setup();
}
return;
#endif
// reset wdt
hwWatchdogReset();
if (preHwInit) {
preHwInit();
}
const bool hwInitResult = hwInit();
#if !defined(MY_SPLASH_SCREEN_DISABLED) && !defined(MY_GATEWAY_FEATURE)
displaySplashScreen();
#endif
#if defined(F_CPU)
CORE_DEBUG(PSTR("MCO:BGN:INIT " MY_NODE_TYPE ",CP=" MY_CAPABILITIES ",FQ=%" PRIu16 ",REL=%"
PRIu8 ",VER="
MYSENSORS_LIBRARY_VERSION "\n"), (uint16_t)(F_CPU/1000000UL),
MYSENSORS_LIBRARY_VERSION_PRERELEASE_NUMBER);
#else
CORE_DEBUG(PSTR("MCO:BGN:INIT " MY_NODE_TYPE ",CP=" MY_CAPABILITIES ",FQ=NA,REL=%"
PRIu8 ",VER="
MYSENSORS_LIBRARY_VERSION "\n"), MYSENSORS_LIBRARY_VERSION_PRERELEASE_NUMBER);
#endif
if (!hwInitResult) {
CORE_DEBUG(PSTR("!MCO:BGN:HW ERR\n"));
setIndication(INDICATION_ERR_HW_INIT);
_infiniteLoop();
}
// set defaults
_coreConfig.presentationSent = false;
// Call sketch before() (if defined)
if (before) {
CORE_DEBUG(PSTR("MCO:BGN:BFR\n")); // before callback
before();
}
#if defined(MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
ledsInit();
#endif
signerInit();
// Read latest received controller configuration from EEPROM
// Note: _coreConfig.isMetric is bool, hence empty EEPROM (=0xFF) evaluates to true (default)
hwReadConfigBlock((void *)&_coreConfig.controllerConfig, (void *)EEPROM_CONTROLLER_CONFIG_ADDRESS,
sizeof(controllerConfig_t));
#if defined(MY_OTA_FIRMWARE_FEATURE)
// Read firmware config from EEPROM, i.e. type, version, CRC, blocks
readFirmwareSettings();
#endif
#if defined(MY_SENSOR_NETWORK)
// Save static parent ID in eeprom (used by bootloader)
hwWriteConfig(EEPROM_PARENT_NODE_ID_ADDRESS, MY_PARENT_NODE_ID);
// Initialise transport layer
transportInitialise();
// Register transport=ready callback
transportRegisterReadyCallback(_callbackTransportReady);
// wait until transport is ready
(void)transportWaitUntilReady(MY_TRANSPORT_WAIT_READY_MS);
#endif
_checkNodeLock();
#if defined(MY_GATEWAY_FEATURE)
#if defined(MY_INCLUSION_BUTTON_FEATURE)
inclusionInit();
#endif
// initialise the transport driver
if (!gatewayTransportInit()) {
setIndication(INDICATION_ERR_INIT_GWTRANSPORT);
CORE_DEBUG(PSTR("!MCO:BGN:TSP FAIL\n"));
// Nothing more we can do
_infiniteLoop();
}
#endif
// Call sketch setup() (if defined)
if (setup) {
CORE_DEBUG(PSTR("MCO:BGN:STP\n")); // setup callback
setup();
}
#if defined(MY_SENSOR_NETWORK)
CORE_DEBUG(PSTR("MCO:BGN:INIT OK,TSP=%" PRIu8 "\n"), isTransportReady() &&
transportHALSanityCheck());
#else
// no sensor network defined, call presentation & registration
_callbackTransportReady();
CORE_DEBUG(PSTR("MCO:BGN:INIT OK,TSP=NA\n"));
#endif
// reset wdt before handing over to loop
hwWatchdogReset();
}
void _registerNode(void)
{
#if defined (MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE)
CORE_DEBUG(PSTR("MCO:REG:REQ\n")); // registration request
setIndication(INDICATION_REQ_REGISTRATION);
_coreConfig.nodeRegistered = MY_REGISTRATION_DEFAULT;
uint8_t counter = MY_REGISTRATION_RETRIES;
// only proceed if register response received or retries exceeded
do {
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_REGISTRATION_REQUEST).set(MY_CORE_VERSION));
} while (!wait(2000, C_INTERNAL, I_REGISTRATION_RESPONSE) && counter--);
#else
_coreConfig.nodeRegistered = true;
CORE_DEBUG(PSTR("MCO:REG:NOT NEEDED\n"));
#endif
}
void presentNode(void)
{
setIndication(INDICATION_PRESENT);
// Present node and request config
#if defined(MY_GATEWAY_FEATURE)
// Send presentation for this gateway device
#if defined(MY_REPEATER_FEATURE)
(void)present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE);
#else
(void)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)
(void)present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE);
#else
(void)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
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_CONFIG).set(getParentNodeId()));
// Wait configuration reply.
(void)wait(2000, C_INTERNAL, I_CONFIG);
#endif
if (presentation) {
presentation();
}
}
uint8_t getNodeId(void)
{
uint8_t result;
#if defined(MY_GATEWAY_FEATURE)
result = GATEWAY_ADDRESS;
#elif defined(MY_SENSOR_NETWORK)
result = transportGetNodeId();
#else
result = VALUE_NOT_DEFINED;
#endif
return result;
}
uint8_t getParentNodeId(void)
{
uint8_t result;
#if defined(MY_GATEWAY_FEATURE)
result = VALUE_NOT_DEFINED; // GW doesn't have a parent
#elif defined(MY_SENSOR_NETWORK)
result = transportGetParentNodeId();
#else
result = VALUE_NOT_DEFINED;
#endif
return result;
}
uint8_t getDistanceGW(void)
{
uint8_t result;
#if defined(MY_GATEWAY_FEATURE)
result = 0;
#elif defined(MY_SENSOR_NETWORK)
result = transportGetDistanceGW();
#else
result = VALUE_NOT_DEFINED;
#endif
return result;
}
controllerConfig_t getControllerConfig(void)
{
return _coreConfig.controllerConfig;
}
bool _sendRoute(MyMessage &message)
{
#if defined(MY_CORE_ONLY)
(void)message;
#endif
#if defined(MY_GATEWAY_FEATURE)
if (message.getDestination() == getNodeId()) {
// 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_SENSOR_NETWORK)
return transportSendRoute(message);
#else
return false;
#endif
}
bool send(MyMessage &message, const bool requestEcho)
{
message.setSender(getNodeId());
message.setCommand(C_SET);
message.setRequestEcho(requestEcho);
#if defined(MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE)
if (_coreConfig.nodeRegistered) {
return _sendRoute(message);
} else {
CORE_DEBUG(PSTR("!MCO:SND:NODE NOT REG\n")); // node not registered
return false;
}
#else
return _sendRoute(message);
#endif
}
bool sendBatteryLevel(const uint8_t value, const bool requestEcho)
{
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_BATTERY_LEVEL,
requestEcho).set(value));
}
bool sendHeartbeat(const bool requestEcho)
{
#if defined(MY_SENSOR_NETWORK)
const uint32_t heartbeat = transportGetHeartbeat();
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_HEARTBEAT_RESPONSE,
requestEcho).set(heartbeat));
#elif defined(MY_GATEWAY_FEATURE)
const uint32_t heartbeat = hwMillis();
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_HEARTBEAT_RESPONSE,
requestEcho).set(heartbeat));
#else
(void)requestEcho;
return false;
#endif
}
bool present(const uint8_t childSensorId, const mysensors_sensor_t sensorType,
const char *description,
const bool requestEcho)
{
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, childSensorId, C_PRESENTATION,
static_cast<uint8_t>(sensorType),
requestEcho).set(childSensorId == NODE_SENSOR_ID ? MYSENSORS_LIBRARY_VERSION : description));
}
#if !defined(__linux__)
bool present(const uint8_t childSensorId, const mysensors_sensor_t sensorType,
const __FlashStringHelper *description,
const bool requestEcho)
{
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, childSensorId, C_PRESENTATION,
static_cast<uint8_t>(sensorType),
requestEcho).set(childSensorId == NODE_SENSOR_ID ? F(" MYSENSORS_LIBRARY_VERSION "): description));
}
#endif
bool sendSketchInfo(const char *name, const char *version, const bool requestEcho)
{
bool result = true;
if (name) {
result &= _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_NAME,
requestEcho).set(name));
}
if (version) {
result &= _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_VERSION,
requestEcho).set(version));
}
return result;
}
#if !defined(__linux__)
bool sendSketchInfo(const __FlashStringHelper *name, const __FlashStringHelper *version,
const bool requestEcho)
{
bool result = true;
if (name) {
result &= _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_NAME,
requestEcho).set(name));
}
if (version) {
result &= _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_VERSION,
requestEcho).set(version));
}
return result;
}
#endif
bool request(const uint8_t childSensorId, const uint8_t variableType, const uint8_t destination)
{
return _sendRoute(build(_msgTmp, destination, childSensorId, C_REQ, variableType).set(""));
}
bool requestTime(const bool requestEcho)
{
return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_TIME,
requestEcho).set(""));
}
// Message delivered through _msg
bool _processInternalCoreMessage(void)
{
const uint8_t type = _msg.getType();
if (_msg.getSender() == GATEWAY_ADDRESS) {
if (type == I_REBOOT) {
#if !defined(MY_DISABLE_REMOTE_RESET)
setIndication(INDICATION_REBOOT);
// WDT fuse should be enabled
hwReboot();
#endif
} else if (type == I_REGISTRATION_RESPONSE) {
#if defined (MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE)
_coreConfig.nodeRegistered = _msg.getBool();
setIndication(INDICATION_GOT_REGISTRATION);
CORE_DEBUG(PSTR("MCO:PIM:NODE REG=%" PRIu8 "\n"), _coreConfig.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
_coreConfig.controllerConfig.isMetric = _msg.data[0] == 0x00 ||
_msg.data[0] == 'M'; // metric if null terminated or M
hwWriteConfigBlock((void*)&_coreConfig.controllerConfig, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS,
sizeof(controllerConfig_t));
} else if (type == I_PRESENTATION) {
// Re-send node presentation to controller
presentNode();
} else if (type == I_HEARTBEAT_REQUEST) {
(void)sendHeartbeat();
} else if (type == I_VERSION) {
#if !defined(MY_GATEWAY_FEATURE)
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_VERSION).set(MYSENSORS_LIBRARY_VERSION_INT));
#endif
} else if (type == I_TIME) {
// Deliver time to callback
if (receiveTime) {
receiveTime(_msg.getULong());
}
} else if (type == I_CHILDREN) {
if (_msg.data[0] == 'C') {
#if defined(MY_REPEATER_FEATURE) && defined(MY_SENSOR_NETWORK)
// Clears child relay data for this node
setIndication(INDICATION_CLEAR_ROUTING);
transportClearRoutingTable();
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CHILDREN).set("OK"));
#endif
}
} else if (type == I_DEBUG) {
#if defined(MY_SPECIAL_DEBUG)
const char debug_msg = _msg.data[0];
if (debug_msg == 'R') { // routing table
#if defined(MY_REPEATER_FEATURE) && defined(MY_SENSOR_NETWORK)
transportReportRoutingTable();
#endif
} else if (debug_msg == 'V') { // CPU voltage
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_DEBUG).set(hwCPUVoltage()));
} else if (debug_msg == 'F') { // CPU frequency in 1/10Mhz
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_DEBUG).set(hwCPUFrequency()));
} else if (debug_msg == 'M') { // free memory
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_DEBUG).set(hwFreeMem()));
} else if (debug_msg == 'E') { // clear MySensors eeprom area and reboot
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG).set("OK"));
for (uint16_t i = EEPROM_START; i<EEPROM_LOCAL_CONFIG_ADDRESS; i++) {
hwWriteConfig(i, 0xFF);
}
setIndication(INDICATION_REBOOT);
hwReboot();
}
#endif
} else {
return false; // further processing required
}
} else {
// sender is a node
if (type == I_REGISTRATION_REQUEST) {
#if defined(MY_GATEWAY_FEATURE)
// registration requests are exclusively handled by GW/Controller
#if !defined(MY_REGISTRATION_CONTROLLER)
bool approveRegistration;
#if defined(MY_CORE_COMPATIBILITY_CHECK)
approveRegistration = (_msg.getByte() >= MY_CORE_MIN_VERSION);
#else
// auto registration if version compatible
approveRegistration = true;
#endif
#if (F_CPU>16*1000000ul)
// delay for fast GW and slow nodes
delay(5);
#endif
(void)_sendRoute(build(_msgTmp, _msg.getSender(), NODE_SENSOR_ID, C_INTERNAL,
I_REGISTRATION_RESPONSE).set(approveRegistration));
#else
return false; // processing of this request via controller
#endif
#endif
} else {
return false; // further processing required
}
}
return true; // if not GW or no further processing required
}
void saveState(const uint8_t pos, const uint8_t value)
{
hwWriteConfig(EEPROM_LOCAL_CONFIG_ADDRESS+pos, value);
}
uint8_t loadState(const uint8_t pos)
{
return hwReadConfig(EEPROM_LOCAL_CONFIG_ADDRESS+pos);
}
void wait(const uint32_t waitingMS)
{
#if defined(MY_DEBUG_VERBOSE_CORE)
if (waitLock) {
CORE_DEBUG(PSTR("!MCO:WAI:RC=%" PRIu8 "\n"), waitLock); // recursive call detected
}
waitLock++;
#endif
const uint32_t enteringMS = hwMillis();
while (hwMillis() - enteringMS < waitingMS) {
_process();
}
#if defined(MY_DEBUG_VERBOSE_CORE)
waitLock--;
#endif
}
bool wait(const uint32_t waitingMS, const mysensors_command_t cmd)
{
#if defined(MY_DEBUG_VERBOSE_CORE)
if (waitLock) {
CORE_DEBUG(PSTR("!MCO:WAI:RC=%" PRIu8 "\n"), waitLock); // recursive call detected
}
waitLock++;
#endif
const uint32_t enteringMS = hwMillis();
// invalidate cmd
//_msg.setCommand(!cmd);
_msg.setCommand(C_INVALID_7);
bool expectedResponse = false;
while ((hwMillis() - enteringMS < waitingMS) && !expectedResponse) {
_process();
expectedResponse = (_msg.getCommand() == cmd);
}
#if defined(MY_DEBUG_VERBOSE_CORE)
waitLock--;
#endif
return expectedResponse;
}
bool wait(const uint32_t waitingMS, const mysensors_command_t cmd, const uint8_t msgType)
{
#if defined(MY_DEBUG_VERBOSE_CORE)
if (waitLock) {
CORE_DEBUG(PSTR("!MCO:WAI:RC=%" PRIu8 "\n"), waitLock); // recursive call detected
}
waitLock++;
#endif
const uint32_t enteringMS = hwMillis();
// invalidate cmd
//_msg.setCommand(!cmd);
_msg.setCommand(C_INVALID_7);
bool expectedResponse = false;
while ( (hwMillis() - enteringMS < waitingMS) && !expectedResponse ) {
_process();
expectedResponse = (_msg.getCommand() == cmd && _msg.getType() == msgType);
}
#if defined(MY_DEBUG_VERBOSE_CORE)
waitLock--;
#endif
return expectedResponse;
}
void doYield(void)
{
hwWatchdogReset();
yield();
#if defined (MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
ledsProcess();
#endif
}
int8_t _sleep(const uint32_t sleepingMS, const bool smartSleep, const uint8_t interrupt1,
const uint8_t mode1, const uint8_t interrupt2, const uint8_t mode2)
{
CORE_DEBUG(PSTR("MCO:SLP:MS=%" PRIu32 ",SMS=%" PRIu8 ",I1=%" PRIu8 ",M1=%" PRIu8 ",I2=%" PRIu8
",M2=%" PRIu8 "\n"), sleepingMS, smartSleep,
interrupt1, mode1, interrupt2, mode2);
// repeater feature: sleeping not possible
#if defined(MY_REPEATER_FEATURE)
(void)smartSleep;
(void)interrupt1;
(void)mode1;
(void)interrupt2;
(void)mode2;
CORE_DEBUG(PSTR("!MCO:SLP:REP\n")); // sleeping not possible, repeater feature enabled
wait(sleepingMS);
return MY_SLEEP_NOT_POSSIBLE;
#else
uint32_t sleepingTimeMS = sleepingMS;
#if defined(MY_SENSOR_NETWORK)
// Do not sleep if transport not ready
if (!isTransportReady()) {
CORE_DEBUG(PSTR("!MCO:SLP:TNR\n")); // sleeping not possible, transport not ready
const uint32_t sleepEnterMS = hwMillis();
uint32_t sleepDeltaMS = 0;
while (!isTransportReady() && (sleepDeltaMS < sleepingTimeMS) &&
(sleepDeltaMS < MY_SLEEP_TRANSPORT_RECONNECT_TIMEOUT_MS)) {
_process();
sleepDeltaMS = hwMillis() - sleepEnterMS;
}
// sleep remainder
if (sleepDeltaMS < sleepingTimeMS) {
sleepingTimeMS -= sleepDeltaMS; // calculate remaining sleeping time
CORE_DEBUG(PSTR("MCO:SLP:MS=%" PRIu32 "\n"), sleepingTimeMS);
} else {
// no sleeping time left
return MY_SLEEP_NOT_POSSIBLE;
}
}
// OTA FW feature: do not sleep if FW update ongoing
#if defined(MY_OTA_FIRMWARE_FEATURE)
while (isFirmwareUpdateOngoing() && sleepingTimeMS) {
CORE_DEBUG(PSTR("!MCO:SLP:FWUPD\n")); // sleeping not possible, FW update ongoing
wait(1000ul);
sleepingTimeMS = sleepingTimeMS >= 1000ul ? sleepingTimeMS - 1000ul : 1000ul;
}
#endif // MY_OTA_FIRMWARE_FEATURE
if (smartSleep) {
// sleeping time left?
if (sleepingTimeMS > 0 && sleepingTimeMS < ((uint32_t)MY_SMART_SLEEP_WAIT_DURATION_MS)) {
wait(sleepingMS);
CORE_DEBUG(PSTR("!MCO:SLP:NTL\n")); // sleeping not possible, no time left
return MY_SLEEP_NOT_POSSIBLE;
}
// notify controller about going to sleep, payload indicates smartsleep waiting time in MS
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_PRE_SLEEP_NOTIFICATION).set((uint32_t)MY_SMART_SLEEP_WAIT_DURATION_MS));
wait(MY_SMART_SLEEP_WAIT_DURATION_MS); // listen for incoming messages
#if defined(MY_OTA_FIRMWARE_FEATURE)
// check if during smart sleep waiting period a FOTA request was received
if (isFirmwareUpdateOngoing()) {
CORE_DEBUG(PSTR("!MCO:SLP:FWUPD\n")); // sleeping not possible, FW update ongoing
return MY_SLEEP_NOT_POSSIBLE;
}
#endif // MY_OTA_FIRMWARE_FEATURE
}
#else
(void)smartSleep;
#endif // MY_SENSOR_NETWORK
#if defined(MY_SENSOR_NETWORK)
transportDisable();
#endif
setIndication(INDICATION_SLEEP);
#if defined (MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
// Wait until leds finish their blinking pattern
while (ledsBlinking()) {
doYield();
}
#endif
int8_t result = MY_SLEEP_NOT_POSSIBLE; // default
if (interrupt1 != INTERRUPT_NOT_DEFINED && interrupt2 != INTERRUPT_NOT_DEFINED) {
// both IRQs
result = hwSleep(interrupt1, mode1, interrupt2, mode2, sleepingTimeMS);
} else if (interrupt1 != INTERRUPT_NOT_DEFINED && interrupt2 == INTERRUPT_NOT_DEFINED) {
// one IRQ
result = hwSleep(interrupt1, mode1, sleepingTimeMS);
} else if (interrupt1 == INTERRUPT_NOT_DEFINED && interrupt2 == INTERRUPT_NOT_DEFINED) {
// no IRQ
result = hwSleep(sleepingTimeMS);
}
setIndication(INDICATION_WAKEUP);
CORE_DEBUG(PSTR("MCO:SLP:WUP=%" PRIi8 "\n"), result); // sleep wake-up
#if defined(MY_SENSOR_NETWORK)
transportReInitialise();
#endif
if (smartSleep) {
// notify controller about waking up, payload indicates sleeping time in MS
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL,
I_POST_SLEEP_NOTIFICATION).set(sleepingTimeMS));
}
return result;
#endif
}
// sleep functions
int8_t sleep(const uint32_t sleepingMS, const bool smartSleep)
{
return _sleep(sleepingMS, smartSleep);
}
int8_t sleep(const uint8_t interrupt, const uint8_t mode, const uint32_t sleepingMS,
const bool smartSleep)
{
return _sleep(sleepingMS, smartSleep, interrupt, mode);
}
int8_t sleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2,
const uint8_t mode2, const uint32_t sleepingMS, const bool smartSleep)
{
return _sleep(sleepingMS, smartSleep, interrupt1, mode1, interrupt2, mode2);
}
// deprecated smartSleep() functions
int8_t smartSleep(const uint32_t sleepingMS)
{
// compatibility
return _sleep(sleepingMS, true);
}
int8_t smartSleep(const uint8_t interrupt, const uint8_t mode, const uint32_t sleepingMS)
{
// compatibility
return _sleep(sleepingMS, true, interrupt, mode);
}
int8_t smartSleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2,
const uint8_t mode2, const uint32_t sleepingMS)
{
// compatibility
return _sleep(sleepingMS, true, interrupt1, mode1, interrupt2, mode2);
}
uint32_t getSleepRemaining(void)
{
return hwGetSleepRemaining();
}
void _nodeLock(const char *str)
{
#ifdef MY_NODE_LOCK_FEATURE
// Make sure EEPROM is updated to locked status
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER_ADDRESS, 0);
while (1) {
setIndication(INDICATION_ERR_LOCKED);
CORE_DEBUG(PSTR("MCO:NLK:NODE LOCKED. TO UNLOCK, GND PIN %" PRIu8 " AND RESET\n"),
MY_NODE_UNLOCK_PIN);
doYield();
(void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID,C_INTERNAL, I_LOCKED).set(str));
#if defined(MY_SENSOR_NETWORK)
transportSleep();
CORE_DEBUG(PSTR("MCO:NLK:TSL\n")); // sleep transport
#endif
setIndication(INDICATION_SLEEP);
(void)hwSleep((uint32_t)1000*60*30); // Sleep for 30 min before resending LOCKED message
setIndication(INDICATION_WAKEUP);
}
#else
(void)str;
#endif
}
void _checkNodeLock(void)
{
#ifdef MY_NODE_LOCK_FEATURE
// Check if node has been locked down
if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER_ADDRESS) == 0) {
// Node is locked, check if unlock pin is asserted, else hang the node
hwPinMode(MY_NODE_UNLOCK_PIN, INPUT_PULLUP);
// Make a short delay so we are sure any large external nets are fully pulled
uint32_t enter = hwMillis();
while (hwMillis() - enter < 2) {}
if (hwDigitalRead(MY_NODE_UNLOCK_PIN) == 0) {
// Pin is grounded, reset lock counter
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER_ADDRESS, MY_NODE_LOCK_COUNTER_MAX);
// Disable pullup
hwPinMode(MY_NODE_UNLOCK_PIN, INPUT);
setIndication(INDICATION_ERR_LOCKED);
CORE_DEBUG(PSTR("MCO:BGN:NODE UNLOCKED\n"));
} else {
// Disable pullup
hwPinMode(MY_NODE_UNLOCK_PIN, INPUT);
_nodeLock("LDB"); //Locked during boot
}
} else if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER_ADDRESS) == 0xFF) {
// Reset value
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER_ADDRESS, MY_NODE_LOCK_COUNTER_MAX);
}
#endif
}
#if DOXYGEN
#endif

View File

@@ -0,0 +1,509 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 or warning
* - 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 _processInternalCoreMessage()
* - MCO:<b>NLK</b> from @ref _nodeLock()
*
* MySensorsCore debug log messages:
*
* |E| SYS | SUB | Message | Comment
* |-|-----|-----|---------------------------------------------|-----------------------------------------------------------------------------------------------------------------
* |!| MCO | BGN | HW ERR | Error HW initialization (e.g. ext. EEPROM)
* | | MCO | BGN | INIT %%s,CP=%%s,FQ=%%d,REL=%%d,VER=%%s | Core initialization, capabilities (CP), CPU frequency [Mhz] (FQ), release number (REL), library version (VER)
* | | MCO | BGN | BFR | Callback before()
* | | MCO | BGN | STP | Callback setup()
* | | MCO | BGN | INIT OK,TSP=%%d | Core initialised, transport status (TSP): 0=not initialised, 1=initialised, NA=not available
* | | 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 | WAI | RC=%%d | Recursive call detected in wait(), level (RC)
* | | MCO | SLP | MS=%%lu,SMS=%%d,I1=%%d,M1=%%d,I2=%%d,M2=%%d | Sleep node, time (MS), smartSleep (SMS), Int1 (I1), Mode1 (M1), Int2 (I2), Mode2 (M2)
* | | MCO | SLP | WUP=%%d | Node woke-up, reason/IRQ (WUP)
* |!| MCO | SLP | NTL | Sleeping not possible, no time left
* |!| MCO | SLP | FWUPD | Sleeping not possible, FW update ongoing
* |!| MCO | SLP | REP | Sleeping not possible, repeater feature enabled
* |!| MCO | SLP | TNR | Transport not ready, attempt to reconnect until timeout (@ref MY_SLEEP_TRANSPORT_RECONNECT_TIMEOUT_MS)
* | | MCO | NLK | NODE LOCKED. UNLOCK: GND PIN %%d AND RESET | Node locked during booting, see signing chapter for additional information
* | | MCO | NLK | TSL | Set transport to sleep
*
* @brief API declaration for MySensorsCore
*/
#ifndef MySensorsCore_h
#define MySensorsCore_h
#include "Version.h"
#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 ((uint8_t)255) //!< 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
#define MY_WAKE_UP_BY_TIMER ((int8_t)-1) //!< Sleeping wake up by timer
#define MY_SLEEP_NOT_POSSIBLE ((int8_t)-2) //!< Sleeping not possible
#define INTERRUPT_NOT_DEFINED ((uint8_t)255) //!< _sleep() param: no interrupt defined
#define MODE_NOT_DEFINED ((uint8_t)255) //!< _sleep() param: no mode defined
#define VALUE_NOT_DEFINED ((uint8_t)255) //!< Value not defined
#define FUNCTION_NOT_SUPPORTED ((uint16_t)0) //!< Function not supported
/**
* @brief Controller configuration
*
* This structure stores controller-related configurations
*/
typedef struct {
uint8_t isMetric; //!< Flag indicating if metric or imperial measurements are used
} controllerConfig_t;
/**
* @brief Node core configuration
*/
typedef struct {
controllerConfig_t controllerConfig; //!< Controller config
// 8 bit
bool nodeRegistered : 1; //!< Flag node registered
bool presentationSent : 1; //!< Flag presentation sent
uint8_t reserved : 6; //!< reserved
} coreConfig_t;
// **** public functions ********
/**
* Return this nodes id.
*/
uint8_t getNodeId(void);
/**
* Return the parent node id.
*/
uint8_t getParentNodeId(void);
/**
* Sends node information to the gateway.
*/
void presentNode(void);
/**
* 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 requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool present(const uint8_t sensorId, const mysensors_sensor_t sensorType,
const char *description = "",
const bool requestEcho = false);
#if !defined(__linux__)
bool present(const uint8_t childSensorId, const mysensors_sensor_t sensorType,
const __FlashStringHelper *description,
const bool requestEcho = false);
#endif
/**
* 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 requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool sendSketchInfo(const char *name, const char *version, const bool requestEcho = false);
#if !defined(__linux__)
bool sendSketchInfo(const __FlashStringHelper *name, const __FlashStringHelper *version,
const bool requestEcho = false);
#endif
/**
* Sends a message to gateway or one of the other nodes in the radio network
* @param msg Message to send
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool send(MyMessage &msg, const bool requestEcho = false);
/**
* Send this nodes battery level to gateway.
* @param level Level between 0-100(%)
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool sendBatteryLevel(const uint8_t level, const bool requestEcho = 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.
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool sendHeartbeat(const bool requestEcho = false);
/**
* Send this nodes signal strength to gateway.
* @param level Signal strength can be RSSI if the radio provide it, or another kind of calculation
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool sendSignalStrength(const int16_t level, const bool requestEcho = false);
/**
* Send this nodes TX power level to gateway.
* @param level For instance, can be TX power level in dbm
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool sendTXPowerLevel(const uint8_t level, const bool requestEcho = false);
/**
* 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
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool request(const uint8_t childSensorId, const uint8_t variableType,
const uint8_t destination = GATEWAY_ADDRESS);
/**
* Requests time from controller. Answer will be delivered to receiveTime function in sketch.
* @param requestEcho Set this to true if you want destination node to echo the message back to this node.
* Default is not to request echo. If set to true, the final destination will echo back the
* contents of the message, triggering the receive() function on the original node with a copy of
* the message, with message.isEcho() set to true and sender/destination switched.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool requestTime(const bool requestEcho = false);
/**
* Returns the most recent node configuration received from controller
*/
controllerConfig_t getControllerConfig(void);
/**
* 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(const uint8_t pos, const 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(const 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 waitingMS Number of milliseconds to wait.
*/
void wait(const uint32_t waitingMS);
/**
* 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 waitingMS Number of milliseconds to wait.
* @param cmd Command of incoming message.
* @return True if specified message received
*/
bool wait(const uint32_t waitingMS, const mysensors_command_t cmd);
/**
* 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 waitingMS Number of milliseconds to wait.
* @param cmd Command of incoming message.
* @param msgtype Message type.
* @return True if specified message received
*/
bool wait(const uint32_t waitingMS, const mysensors_command_t cmd, const uint8_t msgtype);
/**
* Function to allow scheduler to do some work.
* @remark Internally it will call yield, kick the watchdog and update led states.
*/
void doYield(void);
/**
* Sleep (PowerDownMode) the MCU and radio. Wake up on timer.
* @param sleepingMS Number of milliseconds to sleep.
* @param smartSleep Set True if sending heartbeat and process incoming messages before going to sleep.
* @return @ref MY_WAKE_UP_BY_TIMER if timer woke it up, @ref MY_SLEEP_NOT_POSSIBLE if not possible (e.g. ongoing FW update)
*/
int8_t sleep(const uint32_t sleepingMS, const bool smartSleep = false);
/**
* 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 sleepingMS Number of milliseconds to sleep or 0 to sleep forever
* @param smartSleep Set True if sending heartbeat and process incoming messages before going to sleep
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t sleep(const uint8_t interrupt, const uint8_t mode, const uint32_t sleepingMS = 0,
const bool smartSleep = false);
/**
* 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 sleepingMS Number of milliseconds to sleep or 0 to sleep forever
* @param smartSleep Set True if sending heartbeat and process incoming messages before going to sleep.
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t sleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2,
const uint8_t mode2, const uint32_t sleepingMS = 0, const bool smartSleep = false);
/**
* \deprecated Use sleep(ms, true) instead
* Same as sleep(), send heartbeat and process incoming messages before going to sleep.
* Specify the time to wait for incoming messages by defining @ref MY_SMART_SLEEP_WAIT_DURATION_MS to a time (ms).
* @param sleepingMS Number of milliseconds to sleep.
* @return @ref MY_WAKE_UP_BY_TIMER if timer woke it up, @ref MY_SLEEP_NOT_POSSIBLE if not possible (e.g. ongoing FW update)
*/
int8_t smartSleep(const uint32_t sleepingMS);
/**
* \deprecated Use sleep(interrupt, mode, ms, true) instead
* Same as sleep(), send heartbeat and process incoming messages before going to sleep.
* Specify the time to wait for incoming messages by defining @ref MY_SMART_SLEEP_WAIT_DURATION_MS to a time (ms).
* @param interrupt Interrupt that should trigger the wakeup
* @param mode RISING, FALLING, CHANGE
* @param sleepingMS Number of milliseconds to sleep or 0 to sleep forever
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t smartSleep(const uint8_t interrupt, const uint8_t mode, const uint32_t sleepingMS = 0);
/**
* \deprecated Use sleep(interrupt1, mode1, interrupt2, mode2, ms, true) instead
* Same as sleep(), send heartbeat and process incoming messages before going to sleep.
* Specify the time to wait for incoming messages by defining @ref MY_SMART_SLEEP_WAIT_DURATION_MS to a time (ms).
* @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 sleepingMS Number of milliseconds to sleep or 0 to sleep forever
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t smartSleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2,
const uint8_t mode2, const uint32_t sleepingMS = 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 sleepingMS Number of milliseconds to sleep or 0 to sleep forever
* @param interrupt1 (optional) First interrupt that should trigger the wakeup
* @param mode1 (optional) Mode for first interrupt (RISING, FALLING, CHANGE)
* @param interrupt2 (optional) Second interrupt that should trigger the wakeup
* @param mode2 (optional) Mode for second interrupt (RISING, FALLING, CHANGE)
* @param smartSleep (optional) Set True if sending heartbeat and process incoming messages before going to sleep.
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t _sleep(const uint32_t sleepingMS, const bool smartSleep = false,
const uint8_t interrupt1 = INTERRUPT_NOT_DEFINED, const uint8_t mode1 = MODE_NOT_DEFINED,
const uint8_t interrupt2 = INTERRUPT_NOT_DEFINED, const uint8_t mode2 = MODE_NOT_DEFINED);
/**
* Return the sleep time remaining after waking up from sleep.
* Depending on the CPU architecture, the remaining time can be seconds off (e.g. upto roughly 8 seconds on AVR).
* @return Time remaining, in ms, when wake from sleep by an interrupt, 0 by timer (@ref MY_WAKE_UP_BY_TIMER), undefined otherwise.
*/
uint32_t getSleepRemaining(void);
// **** private functions ********
/**
* @defgroup MyLockgrp MyNodeLock
* @ingroup internals
* @brief API declaration for MyNodeLock
* @{
*/
/**
* @brief Lock a node and transmit provided message with 30m intervals
*
* This function is called if suspicious activity has exceeded the threshold (see
* @ref MY_NODE_LOCK_COUNTER_MAX). Unlocking with a normal Arduino bootloader require erasing the EEPROM
* while unlocking with a custom bootloader require holding @ref MY_NODE_UNLOCK_PIN low during power on/reset.
*
* @param str The string to transmit.
*/
void _nodeLock(const char *str);
/**
* @brief Check node lock status and prevent node execution if locked.
*/
void _checkNodeLock(void);
/** @}*/ // Node lock group
/**
* @brief Node initialisation
*/
void _begin(void);
/**
* @brief Main framework process
*/
void _process(void);
/**
* @brief Processes internal core message
* @return True if no further processing required
*/
bool _processInternalCoreMessage(void);
/**
* @brief Puts node to a infinite loop if unrecoverable situation detected
*/
void _infiniteLoop(void);
/**
* @brief Handles registration request
*/
void _registerNode(void);
/**
* @brief Sends message according to routing table
* @param message
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool _sendRoute(MyMessage &message);
/**
* @brief Callback for incoming messages
*/
void receive(const MyMessage&) __attribute__((weak));
/**
* @brief Callback for incoming time messages
*/
void receiveTime(uint32_t) __attribute__((weak));
/**
* @brief Node presentation
*/
void presentation(void) __attribute__((weak));
/**
* @brief Called before node initialises
*/
void before(void) __attribute__((weak));
/**
* @brief Called before any hardware initialisation is done
*/
void preHwInit(void) __attribute__((weak));
/**
* @brief Called after node initialises but before main loop
*/
void setup(void) __attribute__((weak));
/**
* @brief Main loop
*/
void loop(void) __attribute__((weak));
// Inline function and macros
static inline MyMessage& build(MyMessage &msg, const uint8_t destination, const uint8_t sensor,
const mysensors_command_t command, const uint8_t type, const bool requestEcho = false)
{
msg.setSender(getNodeId());
msg.setDestination(destination);
msg.setSensor(sensor);
msg.setType(type);
msg.setCommand(command);
msg.setRequestEcho(requestEcho);
msg.setEcho(false);
return msg;
}
static inline MyMessage& buildGw(MyMessage &msg, const uint8_t type)
{
msg.setSender(GATEWAY_ADDRESS);
msg.setDestination(GATEWAY_ADDRESS);
msg.setSensor(NODE_SENSOR_ID);
msg.setType(type);
msg.setCommand(C_INTERNAL);
msg.setRequestEcho(false);
msg.setEcho(false);
return msg;
}
#endif
/** @}*/

View File

@@ -0,0 +1,587 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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"
#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_OUTPUT(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 to reqire signatures!
#endif
#if defined(MY_SIGNING_SOFT) && defined(MY_SIGNING_ATSHA204)
#error You have to pick one and only one signing backend
#endif
#ifdef MY_SIGNING_FEATURE
static uint8_t _doSign[32]; // Bitfield indicating which sensors require signed communication
static uint8_t _doWhitelist[32]; // Bitfield indicating which sensors require salted signatures
static MyMessage _msgSign; // Buffer for message to sign.
static uint8_t _signingNonceStatus;
static bool stateValid = false;
#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 signerSignMsg
enum { SIGN_WAITING_FOR_NONCE = 0, SIGN_OK = 1 };
// Macros for manipulating signing requirement tables
#define DO_SIGN(node) (~_doSign[node>>3]&(1<<node%8))
#define SET_SIGN(node) (_doSign[node>>3]&=~(1<<node%8))
#if defined(MY_SIGNING_WEAK_SECURITY)
#define CLEAR_SIGN(node) (_doSign[node>>3]|=(1<<node%8))
#endif
#define DO_WHITELIST(node) (~_doWhitelist[node>>3]&(1<<node%8))
#define SET_WHITELIST(node) (_doWhitelist[node>>3]&=~(1<<node%8))
#if defined(MY_SIGNING_WEAK_SECURITY)
#define CLEAR_WHITELIST(node) (_doWhitelist[node>>3]|=(1<<node%8))
#endif
#if defined(MY_SIGNING_SOFT)
extern bool 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);
#define signerBackendInit signerAtsha204SoftInit
#define signerBackendCheckTimer signerAtsha204SoftCheckTimer
#define signerBackendGetNonce signerAtsha204SoftGetNonce
#define signerBackendPutNonce signerAtsha204SoftPutNonce
#define signerBackendVerifyMsg signerAtsha204SoftVerifyMsg
#define signerBackendSignMsg signerAtsha204SoftSignMsg
#elif defined(MY_SIGNING_ATSHA204)
extern bool 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);
#define signerBackendInit signerAtsha204Init
#define signerBackendCheckTimer signerAtsha204CheckTimer
#define signerBackendGetNonce signerAtsha204GetNonce
#define signerBackendPutNonce signerAtsha204PutNonce
#define signerBackendVerifyMsg signerAtsha204VerifyMsg
#define signerBackendSignMsg signerAtsha204SignMsg
#endif
static bool skipSign(MyMessage &msg);
#else // not MY_SIGNING_FEATURE
#define signerBackendCheckTimer() true
#endif // MY_SIGNING_FEATURE
static void prepareSigningPresentation(MyMessage &msg, uint8_t destination);
static bool signerInternalProcessPresentation(MyMessage &msg);
static bool signerInternalProcessNonceRequest(MyMessage &msg);
static bool signerInternalProcessNonceResponse(MyMessage &msg);
#if (defined(MY_ENCRYPTION_FEATURE) || defined(MY_SIGNING_FEATURE)) &&\
!defined(MY_SIGNING_SIMPLE_PASSWD)
static bool signerInternalValidatePersonalization(void);
#endif
void signerInit(void)
{
#if defined(MY_SIGNING_FEATURE)
stateValid = true;
#endif
#if (defined (MY_ENCRYPTION_FEATURE) || defined (MY_SIGNING_FEATURE)) &&\
!defined (MY_SIGNING_SIMPLE_PASSWD)
// Suppress this warning since it is only fixed on Linux builds and this keeps the code more tidy
// cppcheck-suppress knownConditionTrueFalse
if (!signerInternalValidatePersonalization()) {
SIGN_DEBUG(PSTR("!SGN:PER:TAMPERED\n"));
#if defined(MY_SIGNING_FEATURE)
stateValid = false;
#endif
} else {
SIGN_DEBUG(PSTR("SGN:PER:OK\n"));
}
#endif
#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 (!signerBackendInit()) {
SIGN_DEBUG(PSTR("!SGN:INI:BND FAIL\n"));
} else {
SIGN_DEBUG(PSTR("SGN:INI:BND OK\n"));
}
#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("SGN:PRE:SGN REQ\n")); // Signing required
#else
SIGN_DEBUG(PSTR("SGN:PRE:SGN NREQ\n")); // Signing not required
#endif
#if defined(MY_SIGNING_NODE_WHITELISTING)
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_WHITELISTING;
SIGN_DEBUG(PSTR("SGN:PRE:WHI REQ\n")); // Whitelisting required
#else
SIGN_DEBUG(PSTR("SGN:PRE:WHI NREQ\n")); // Whitelisting not required
#endif
if (!_sendRoute(msg)) {
SIGN_DEBUG(PSTR("!SGN:PRE:XMT,TO=%" PRIu8 " FAIL\n"),
destination); // Failed to transmit presentation!
} else {
SIGN_DEBUG(PSTR("SGN:PRE:XMT,TO=%" PRIu8 "\n"), destination); // Transmitted signing presentation!
}
if (destination == GATEWAY_ADDRESS) {
SIGN_DEBUG(PSTR("SGN:PRE:WAIT GW\n")); // Waiting for GW to send signing preferences...
wait(2000, C_INTERNAL, I_SIGNING_PRESENTATION);
}
}
bool signerProcessInternal(MyMessage &msg)
{
bool ret;
switch (msg.getType()) {
case I_SIGNING_PRESENTATION:
ret = signerInternalProcessPresentation(msg);
break;
case I_NONCE_REQUEST:
ret = signerInternalProcessNonceRequest(msg);
break;
case I_NONCE_RESPONSE:
ret = signerInternalProcessNonceResponse(msg);
break;
default:
ret = false; // Let the transport process this message further as it is not related to signing
break;
}
return ret;
}
bool signerCheckTimer(void)
{
return signerBackendCheckTimer();
}
bool signerSignMsg(MyMessage &msg)
{
#if defined(MY_SIGNING_FEATURE)
bool ret;
// If destination is known to require signed messages and we are the sender,
// sign this message unless it is identified as an exception
if (DO_SIGN(msg.getDestination()) && msg.getSender() == getNodeId()) {
if (skipSign(msg)) {
ret = true;
} else {
// Before starting, validate that our state is good, or signing will fail
if (!stateValid) {
SIGN_DEBUG(PSTR("!SGN:SGN:STATE\n")); // Signing system is not in a valid state
ret = false;
} else {
// Send nonce-request
_signingNonceStatus=SIGN_WAITING_FOR_NONCE;
if (!_sendRoute(build(_msgSign, msg.getDestination(), msg.getSensor(), C_INTERNAL,
I_NONCE_REQUEST).set(""))) {
SIGN_DEBUG(PSTR("!SGN:SGN:NCE REQ,TO=%" PRIu8 " FAIL\n"),
msg.getDestination()); // Failed to transmit nonce request!
ret = false;
} else {
SIGN_DEBUG(PSTR("SGN:SGN:NCE REQ,TO=%" PRIu8 "\n"), msg.getDestination()); // Nonce requested
// 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 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("!SGN:SGN:NCE TMO\n")); // Timeout waiting for nonce!
ret = false;
} else {
if (_signingNonceStatus == SIGN_OK) {
// process() received a nonce and signerProcessInternal successfully signed the message
msg = _msgSign; // Write the signed message back
SIGN_DEBUG(PSTR("SGN:SGN:SGN\n")); // Message to send has been signed
ret = true;
// 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 {
SIGN_DEBUG(PSTR("!SGN:SGN:SGN FAIL\n")); // Message to send could not be signed!
ret = false;
}
}
}
}
}
} else if (getNodeId() == msg.getSender()) {
msg.setSigned(false); // Message is not supposed to be signed, make sure it is marked unsigned
SIGN_DEBUG(PSTR("SGN:SGN:NREQ=%" PRIu8 "\n"),
msg.getDestination()); // Do not sign message as it is not req
ret = true;
} else {
SIGN_DEBUG(PSTR("SGN:SGN:%" PRIu8 "!=%" PRIu8 " NUS\n"), msg.getSender(),
getNodeId()); // Will not sign message since it was from someone else
ret = true;
}
return ret;
#else
(void)msg;
return true;
#endif // MY_SIGNING_FEATURE
}
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 (or just a strict gw)
// and we are the destination...
#if defined(MY_SIGNING_WEAK_SECURITY)
if ((!MY_IS_GATEWAY || DO_SIGN(msg.getSender())) && msg.getDestination() == getNodeId()) {
#else
if (msg.getDestination() == getNodeId()) {
#endif
// Internal messages of certain types are not verified
if (skipSign(msg)) {
verificationResult = true;
} else if (!msg.getSigned()) {
// Got unsigned message that should have been signed
SIGN_DEBUG(PSTR("!SGN:VER:NSG\n")); // Message is not signed, but it should have been!
verificationResult = false;
} else {
// Before starting, validate that our state is good, or signing will fail
if (!stateValid) {
SIGN_DEBUG(PSTR("!SGN:VER:STATE\n")); // Signing system is not in a valid state
verificationResult = false;
} else {
if (!signerBackendVerifyMsg(msg)) {
SIGN_DEBUG(PSTR("!SGN:VER:FAIL\n")); // Signature verification failed!
verificationResult = false;
} else {
SIGN_DEBUG(PSTR("SGN:VER:OK\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("SGN:VER:LEFT=%" PRIu8 "\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
msg.setSigned(false); // Clear the sign-flag now as verification is completed
}
}
#else
(void)msg;
#endif // MY_SIGNING_REQUEST_SIGNATURES
return verificationResult;
}
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;
}
#if defined(MY_SIGNING_FEATURE)
// Helper function to centralize signing/verification exceptions
static bool skipSign(MyMessage &msg)
{
bool ret = false;
if (msg.isEcho()) {
ret = true;
} else if (msg.getCommand() == C_INTERNAL &&
(msg.getType() == I_SIGNING_PRESENTATION ||
msg.getType() == I_REGISTRATION_REQUEST ||
msg.getType() == I_NONCE_REQUEST || msg.getType() == I_NONCE_RESPONSE ||
msg.getType() == I_ID_REQUEST || msg.getType() == I_ID_RESPONSE ||
msg.getType() == I_FIND_PARENT_REQUEST || msg.getType() == I_FIND_PARENT_RESPONSE ||
msg.getType() == I_HEARTBEAT_REQUEST || msg.getType() == I_HEARTBEAT_RESPONSE ||
msg.getType() == I_PING || msg.getType() == I_PONG ||
msg.getType() == I_DISCOVER_REQUEST || msg.getType() == I_DISCOVER_RESPONSE ||
msg.getType() == I_LOG_MESSAGE)) {
ret = true;
} else if (msg.getCommand() == C_STREAM &&
(msg.getType() == ST_SOUND ||
msg.getType() == ST_IMAGE ||
msg.getType() == ST_FIRMWARE_REQUEST || msg.getType() == ST_FIRMWARE_RESPONSE )) {
ret = true;
}
if (ret) {
SIGN_DEBUG(PSTR("SGN:SKP:%s CMD=%" PRIu8 ",TYPE=%" PRIu8 "\n"), msg.isEcho() ? "ECHO" : "MSG",
msg.getCommand(),
msg.getType()); //Skip signing/verification of this message
}
return ret;
}
#endif
// Helper to prepare a signing presentation message
static void prepareSigningPresentation(MyMessage &msg, uint8_t destination)
{
// Only supports version 1 for now
(void)build(msg, destination, NODE_SENSOR_ID, C_INTERNAL, I_SIGNING_PRESENTATION).set("");
msg.setLength(2);
msg.setPayloadType(P_CUSTOM); // displayed as hex
msg.data[0] = SIGNING_PRESENTATION_VERSION_1;
msg.data[1] = 0;
}
// Helper to process presentation messages
static bool signerInternalProcessPresentation(MyMessage &msg)
{
const uint8_t sender = msg.getSender();
#if defined(MY_SIGNING_FEATURE)
if (msg.data[0] != SIGNING_PRESENTATION_VERSION_1) {
SIGN_DEBUG(PSTR("!SGN:PRE:VER=%" PRIu8 "\n"),
msg.data[0]); // Unsupported signing presentation version
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("SGN:PRE:SGN REQ,FROM=%" PRIu8 "\n"), sender); // Node require signatures
SET_SIGN(sender);
} else {
#if defined(MY_SIGNING_WEAK_SECURITY)
// We received an indicator that the sender does not require us to sign messages we send to it
SIGN_DEBUG(PSTR("SGN:PRE:SGN NREQ,FROM=%" PRIu8 "\n"), sender); // Node does not require signatures
CLEAR_SIGN(sender);
#else
if (DO_SIGN(sender)) {
SIGN_DEBUG(PSTR("!SGN:PRE:SGN NREQ,FROM=%" PRIu8 " REJ\n"),
sender); // Node does not require signatures but used to do so
}
#endif
}
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("SGN:PRE:WHI REQ,FROM=%" PRIu8 "\n"), sender); // Node require whitelisting
SET_WHITELIST(sender);
} else {
#if defined(MY_SIGNING_WEAK_SECURITY)
// We received an indicator that the sender does not require us to sign messages we send to it
SIGN_DEBUG(PSTR("SGN:PRE:WHI NREQ,FROM=%" PRIu8 "\n"),
sender); // Node does not require whitelisting
CLEAR_WHITELIST(sender);
#else
if (DO_WHITELIST(sender)) {
SIGN_DEBUG(PSTR("!SGN:PRE:WHI NREQ,FROM=%" PRIu8 " REJ\n"),
sender); // Node does not require whitelisting but used to do so
}
#endif
}
// 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 unless we explicitly configure it to
#if defined(MY_GATEWAY_FEATURE)
prepareSigningPresentation(msg, sender);
#if defined(MY_SIGNING_REQUEST_SIGNATURES)
#if defined(MY_SIGNING_WEAK_SECURITY)
if (DO_SIGN(sender)) {
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES;
}
#else
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES;
#endif
#endif // MY_SIGNING_REQUEST_SIGNATURES
#if defined(MY_SIGNING_NODE_WHITELISTING)
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_WHITELISTING;
#endif // MY_SIGNING_NODE_WHITELISTING
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_SIGNATURES) {
SIGN_DEBUG(PSTR("SGN:PRE:SGN REQ,TO=%" PRIu8 "\n"),
sender); // Inform node that we require signatures
} else {
SIGN_DEBUG(PSTR("SGN:PRE:SGN NREQ,TO=%" PRIu8 "\n"),
sender); // Inform node that we do not require signatures
}
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_WHITELISTING) {
SIGN_DEBUG(PSTR("SGN:PRE:WHI REQ,TO=%" PRIu8 "\n"),
sender); // Inform node that we require whitelisting
} else {
SIGN_DEBUG(PSTR("SGN:PRE:WHI NREQ,TO=%" PRIu8 "\n"),
sender); // Inform node that we do not require whitelisting
}
if (!_sendRoute(msg)) {
SIGN_DEBUG(PSTR("!SGN:PRE:XMT,TO=%" PRIu8 " FAIL\n"),
sender); // Failed to transmit signing presentation!
} else {
SIGN_DEBUG(PSTR("SGN:PRE:XMT,TO=%" PRIu8 "\n"), sender);
}
#endif // MY_GATEWAY_FEATURE
#else // not MY_SIGNING_FEATURE
#if 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
prepareSigningPresentation(msg, sender);
SIGN_DEBUG(PSTR("SGN:PRE:NSUP,TO=%" PRIu8 "\n"),
sender); // Informing node that we do not support signing
if (!_sendRoute(msg)) {
SIGN_DEBUG(PSTR("!SGN:PRE:XMT,TO=%" PRIu8 " FAIL\n"),
sender); // Failed to transmit signing presentation!
} else {
SIGN_DEBUG(PSTR("SGN:PRE:XMT,TO=%" PRIu8 "\n"), sender);
}
#else // not MY_GATEWAY_FEATURE
// If we act as a node and do not have the signing feature then we just silently drop any signing
// presentation messages received
(void)msg;
(void)sender;
// Received signing presentation, but signing is not supported (message ignored)
SIGN_DEBUG(
PSTR("SGN:PRE:NSUP\n"));
#endif // not MY_GATEWAY_FEATURE
#endif // not MY_SIGNING_FEATURE
return true; // No need to further process I_SIGNING_PRESENTATION
}
// Helper to process nonce request messages
static bool signerInternalProcessNonceRequest(MyMessage &msg)
{
#if defined(MY_SIGNING_FEATURE)
#if defined(MY_NODE_LOCK_FEATURE)
nof_nonce_requests++;
SIGN_DEBUG(PSTR("SGN:NCE:LEFT=%" PRIu8 "\n"),
MY_NODE_LOCK_COUNTER_MAX-nof_nonce_requests); // Nonce requests left until lockdown
if (nof_nonce_requests >= MY_NODE_LOCK_COUNTER_MAX) {
_nodeLock("TMNR"); // Too many nonces requested
}
#endif // MY_NODE_LOCK_FEATURE
if (signerBackendGetNonce(msg)) {
if (!_sendRoute(build(msg, msg.getSender(), NODE_SENSOR_ID, C_INTERNAL, I_NONCE_RESPONSE))) {
SIGN_DEBUG(PSTR("!SGN:NCE:XMT,TO=%" PRIu8 " FAIL\n"), msg.getSender()); // Failed to transmit nonce!
} else {
SIGN_DEBUG(PSTR("SGN:NCE:XMT,TO=%" PRIu8 "\n"), msg.getSender());
}
} else {
SIGN_DEBUG(PSTR("!SGN:NCE:GEN\n")); // Failed to generate nonce!
}
#else // not MY_SIGNING_FEATURE
(void)msg;
SIGN_DEBUG(
PSTR("SGN:NCE:NSUP (DROPPED)\n")); // Received nonce request/response without signing support
#endif // MY_SIGNING_FEATURE
return true; // No need to further process I_NONCE_REQUEST
}
// Helper to process nonce response messages
static bool signerInternalProcessNonceResponse(MyMessage &msg)
{
#if defined(MY_SIGNING_FEATURE)
// Proceed with signing if nonce has been received
SIGN_DEBUG(PSTR("SGN:NCE:FROM=%" PRIu8 "\n"), msg.getSender());
if (msg.getSender() != _msgSign.getDestination()) {
SIGN_DEBUG(PSTR("SGN:NCE:%" PRIu8 "!=%" PRIu8 " (DROPPED)\n"), _msgSign.getDestination(),
msg.getSender());
} else {
signerBackendPutNonce(msg);
if (signerBackendSignMsg(_msgSign)) {
// _msgSign now contains the signed message pending transmission
_signingNonceStatus = SIGN_OK;
}
}
#else
(void)msg;
SIGN_DEBUG(
PSTR("SGN:NCE:NSUP (DROPPED)\n")); // Received nonce request/response without signing support
#endif
return true; // No need to further process I_NONCE_RESPONSE
}
#if (defined(MY_ENCRYPTION_FEATURE) || defined(MY_SIGNING_FEATURE)) &&\
!defined(MY_SIGNING_SIMPLE_PASSWD)
static bool signerInternalValidatePersonalization(void)
{
#ifdef __linux__
// Personalization on linux is a bit more crude
return true;
#else
uint8_t buffer[SIZE_SIGNING_SOFT_HMAC_KEY + SIZE_RF_ENCRYPTION_AES_KEY + SIZE_SIGNING_SOFT_SERIAL];
uint8_t hash[32];
uint8_t checksum;
hwReadConfigBlock((void*)buffer, (void*)EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS,
SIZE_SIGNING_SOFT_HMAC_KEY);
hwReadConfigBlock((void*)&buffer[SIZE_SIGNING_SOFT_HMAC_KEY],
(void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, SIZE_RF_ENCRYPTION_AES_KEY);
hwReadConfigBlock((void*)&buffer[SIZE_SIGNING_SOFT_HMAC_KEY + SIZE_RF_ENCRYPTION_AES_KEY],
(void*)EEPROM_SIGNING_SOFT_SERIAL_ADDRESS, SIZE_SIGNING_SOFT_SERIAL);
hwReadConfigBlock((void*)&checksum, (void*)EEPROM_PERSONALIZATION_CHECKSUM_ADDRESS,
SIZE_PERSONALIZATION_CHECKSUM);
SHA256(hash, buffer, sizeof(buffer));
return (checksum == hash[0]);
#endif
}
#endif

View File

@@ -0,0 +1,907 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 MySigninggrpPub Message signing
* @ingroup publics
* @{
*
* @brief The message signing infrastructure provides message authenticity to users by signing
* MySensors messages.
*
* Signing support created by Patrick "Anticimex" Fallberg.
*
* @section MySigninggrptoc Table of contents
*
* @ref MySigninggrphowuse <br>
* @ref MySigningwhitelisting <br>
* @ref MySigninglimitations <br>
* @ref MySigningusecases <br>
* @ref MySigningtechnical <br>
* @ref MySigninggrpbackground <br>
* @ref MySigninggrphow <br>
* @ref MySigninggrpencryption <br>
*
* @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
* choice in your sketch. Currently, two compatible backends are supported; @ref MY_SIGNING_ATSHA204
* (hardware backed) and @ref MY_SIGNING_SOFT (software backed). There also exist a simplified
* variant (@ref MY_SIGNING_SIMPLE_PASSWD) of the software backend which only require one setting
* to activate.
*
* If you use hardware backed signing, then connect the device as follows:
* @image html MySigning/wiring.png
* @note The pull-up resistor is optional but recommended.
* @note If you change the default pin (A3) make sure you use a pin that supports input/output
* (ex. A6 & A7 on a Pro Mini are input only pins).
*
* To use signing, you need to perform three major steps which are described below.
*
* <b>Firstly</b>, you need to make sure to pick a backend to use.
* @code{.cpp}
* //#define MY_SIGNING_SOFT
* //#define MY_SIGNING_SIMPLE_PASSWD
* #define MY_SIGNING_ATSHA204
* #include <MySensors.h>
* ...
* @endcode
* Make sure to set the define before the inclusion of MySensors.h.
* It is ok to mix @ref MY_SIGNING_SOFT and @ref MY_SIGNING_ATSHA204 in a network.
* They are fully compatible. It is however not recommended to use @ref MY_SIGNING_SOFT on nodes
* that are publicly accessible (more on that later).
*
* If you use @ref MY_SIGNING_SOFT or @ref MY_SIGNING_ATSHA204 you also need to decide if the node
* (or gateway) in question require messages to be signed in addition to the ability to generate
* signatures for other nodes.
* This has to be set by at least one of the nodes 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. Note that when set in a gateway, the
* gateway will require ALL nodes in the network to sign messages.
* If this behaviour is undesired, enable the flag @ref MY_SIGNING_WEAK_SECURITY which will allow
* the gateway to only require signatures from nodes that in turn require signatures. It will also
* allow the gateway (and all nodes) to "downgrade" security by clearing the signing/whitelisting
* requirements (whitelisting is described later on in the @ref MySigningwhitelisting section) in
* the EEPROM if a node presents itself as not having any security requirements.
* If @ref MY_SIGNING_WEAK_SECURITY is not set, any node that has presented itself with
* signing/whitelisting requirements will be permanently marked as such by the receiver
* (typically the gateway). The only way then to reset/revert this requirement is to clear the
* EEPROM at the receiver (or disable @ref MY_SIGNING_REQUEST_SIGNATURES, but the preference will be
* remembered if the request flag is re-enabled before EEPROM is cleared).<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,
* @ref MY_SIGNING_SOFT nor @ref MY_SIGNING_SIMPLE_PASSWD is set)
* 2. Node does support signing but don't require messages sent to it to be signed (neither
* @ref MY_SIGNING_REQUEST_SIGNATURES nor @ref MY_SIGNING_SIMPLE_PASSWD is set)
* 3. Node does support signing and require messages sent to it to be signed (@ref
* MY_SIGNING_SOFT or @ref MY_SIGNING_ATSHA204 together with @ref MY_SIGNING_REQUEST_SIGNATURES or
* @ref MY_SIGNING_SIMPLE_PASSWD are 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.<br>
* If you use an official MySensors board (like the SenseBender GW) you do not need to set the pin,
* this is configured automatically by the Arduino board definition files.
*
* 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 signing backend, an unconnected analog pin is required on boards that
* does not provide a hardware based random generator unit 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. The same configuration possibilities exist as with the other
* configuration options.
*
* @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.
*
* <b>Thirdly</b>, if you use a signing backend and you don't use @ref MY_SIGNING_SIMPLE_PASSWD, you
* need to personalize the node.
*
* @anchor personalization If you use the ATSHA204A (@ref MY_SIGNING_ATSHA204), 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 @ref MY_SIGNING_SOFT, but then the values are
* stored in EEPROM.
*
* To personalize a ATSHA204A do the following procedure:
* 1. Enable @ref GENERATE_KEYS_ATSHA204A<br>
* This will lock the ATSHA204A and generate random keys for HMAC (signing) and %AES (encryption).
* Copy the keys generated and replace the corresponding definitions under
* "User defined key data", specifically @ref MY_HMAC_KEY and @ref MY_AES_KEY.
* 2. Disable @ref GENERATE_KEYS_ATSHA204A and enable @ref PERSONALIZE_ATSHA204A<br>
* This will store the HMAC key to the ATSHA204A and the %AES key to EEPROM. It will also write
* a checksum of the personalization data in EEPROM to be able to detect if the data is
* altered.<br>
* Personalization is now complete.
*
* To personalize for software signing do the following procedure:
* 1. Enable @ref GENERATE_KEYS_SOFT<br>
* This will generate random keys for HMAC (signing) and %AES (encryption).
* Copy the keys generated and replace the corresponding definitions under
* "User defined key data", specifically @ref MY_HMAC_KEY and @ref MY_AES_KEY.
* 2. Disable @ref GENERATE_KEYS_SOFT and enable @ref PERSONALIZE_SOFT<br>
* This will store the HMAC key and the %AES key to EEPROM. It will also write
* a checksum of the personalization data in EEPROM to be able to detect if the data is
* altered.<br>
* Personalization is now complete.
*
* If you want to use soft signing and you want to use whitelisting (the ability to revoke/ban
* compromised nodes in the network) and your target does not provide a unique device ID, you have
* to generate a unique serial and store it in EEPROM. This can be done by replacing
* @ref PERSONALIZE_SOFT in step 2 above with @ref PERSONALIZE_SOFT_RANDOM_SERIAL. See the output
* under "Hardware security peripherals" to determine if this is necessary.
*
* When you have personalized your first device after step 2 above, you can run the same sketch on
* all devices in your network that needs to be personalized in a compatible manner. Pick
* @ref PERSONALIZE_ATSHA204A or @ref PERSONALIZE_SOFT as needed by the hardware. When the
* personalization has finished, you just program the sketch you plan to use (with the appropriate
* signing flags set).
*
* If you are using a Raspberry PI-based gateway, personalizaion is done slightly differently:
* 1. Generate keys, execute @c mysgw with arguments
* * To generate HMAC key @verbatim --gen-soft-hmac-key @endverbatim
* * To generate %AES key @verbatim --gen-aes-key @endverbatim
* * To generate a soft serial number @verbatim --gen-soft-serial @endverbatim
* 2. Update the gateway config file with the generated keys/valeus
* * For HMAC key @verbatim soft_hmac_key=<DATA> @endverbatim
* * For %AES key @verbatim aes_key=<DATA> @endverbatim
* * For soft serial number @verbatim soft_serial_key=<DATA> @endverbatim
*
* You are now set and ready to use message signing in your network.
* As of now, the following restrictions will be applied to your nodes:
* * If a node does require signing, any unsigned message sent to the node will be rejected.
* This also applies to the gateway.
* * Your radio communication quality is expected to work fine (if any NACK happen on a signing
* related message, it will fail, and enabling signing will put maximum strain on your RF link as
* maximum sized packets are transmitted in the network). See @ref MySigningTroubleshootinggrp.
* * 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 a node or gateway reboots, the other nodes does
* not have to retransmit a signing presentation to the node for the node to start expecting signed
* messages from other nodes.<br>
* * By default, the signing preferences are not "downgradeable". That is, any node that at any
* point in time has indicated a signing requirement will not be able to revert this requirement at
* the receiving end (except by manual erase of the EEPROM).<br>
* If you for some reason need to be able to downgrade the security requirements, you can set
* @ref MY_SIGNING_WEAK_SECURITY at the receiver to allow it to downgrade the security expectations
* of the node in question.<br>
* You then need to reset your transmitting node, to force it to transmit updated signing
* preferences.
*
* @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 (considered physically inaccessible) that controls your
* garage door motor.<br>
* 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. Optionally, your keyfob sends a signed message to your gateway (which
* require signatures) and the gateway in turn sends a signed message to your garage door.
*
* 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. You could now repersonalize all your node to switch to a
* different PSK but this obviously is a hassle. 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
* (by uploading a new sketch), 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 @ref MY_SIGNING_ATSHA204 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. You need to remember that the gateway will remember if
* a node has presented it with a whitelisting requirement as described above, if you at some point
* decide to remove the whitelist requirement.<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 (@ref MY_SIGNING_ATSHA204 always has unique serials).
*
* @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>
* Most importantly, if you use @ref MY_SIGNING_SOFT your PSK will be stored in EEPROM and will
* therefore be accessible by anyone with physical access to your node. Therefore it is <b>NOT</b>
* recommended to use @ref MY_SIGNING_SOFT on nodes that are placed in a public place or worn on
* on your person (like a keyfob).<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 need to make your node require signed messages but you do not necessarily need to make your
* gateway require signed messages (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 be configured with a whitelist (and it has 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
*
* @section MySigningtechnical The technical stuff
*
* The following sequence diagram illustrate how messages are passed in a MySensors network with
* respect to signing:
* @image html MySigning/signingsequence.png
*
* None of this activity is “visible” to you (as the sensor sketch implementor). All you need to do
* is to set your preferences in your sketch and personalize accordingly.
* 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 (except possibly preventing them from arriving).
*
* 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
*
* 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 at the moment. 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
*
* Exactly how this is done can be reviewed in the source for the software backend
* (MySigningAtsha204Soft.cpp) 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 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 switch,
* 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
*
* 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
*
* 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
* 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 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 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.<br>
* This might change in the future as more powerful platforms emerge which permit more complex
* security schemes and better hardware acceleration.
*/
/** @}*/
/**
* @defgroup MySigninggrp MySigning
* @ingroup internals
* @{
*
* @brief API declaration for MySigning signing backend
*
* @see MySigninggrpPub
*/
/**
* @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 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 function 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
* requiring signing to not requiring 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 handshaking 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 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 MySigningDebugMessages Signing related debug messages
* @ingroup MySigninggrpPub
* @{
*
* @brief Explanation of the abstract signing related debug messages
*
* MySigning-related log messages, format: [!]SYSTEM:SUB SYSTEM:MESSAGE
* - [!] Exclamation mark is prepended in case of error or warning
* - SYSTEM:
* - <b>SGN</b> messages emitted by MySigning
* - SUB SYSTEMS:
* - SGN:<b>INI</b> from @ref signerInit
* - SGN:<b>PER</b> from @ref signerInit
* - SGN:<b>PRE</b> from @ref signerPresentation
* - SGN:<b>SGN</b> from @ref signerSignMsg
* - SGN:<b>VER</b> from @ref signerVerifyMsg
* - SGN:<b>SKP</b> from @ref signerSignMsg or @ref signerVerifyMsg (skipSign)
* - SGN:<b>NCE</b> from @ref signerProcessInternal (signerInternalProcessNonceRequest)
* - SGN:<b>BND</b> from the signing backends
*
* MySigning debug log messages:
*
* |E| SYS | SUB | Message | Comment
* |-|-----|-----|--------------------------|----------------------------------------------------------------------------
* | | SGN | INI | BND OK | Backend has initialized ok
* |!| SGN | INI | BND FAIL | Backend has not initialized ok
* | | SGN | PER | OK | Personalization data is ok
* |!| SGN | PER | TAMPERED | Personalization data has been tampered
* | | SGN | PRE | SGN REQ | Signing required
* | | SGN | PRE | SGN REQ,TO='node' | Tell 'node' that we require signing
* | | SGN | PRE | SGN REQ,FROM='node' | Node 'node' require signing
* | | SGN | PRE | SGN NREQ | Signing not required
* | | SGN | PRE | SGN REQ,TO='node' | Tell 'node' that we do not require signing
* | | SGN | PRE | SGN NREQ,FROM='node' | Node 'node' does not require signing
* |!| SGN | PRE | SGN NREQ,FROM='node' REJ | Node 'node' does not require signing but used to (requirement remain unchanged)
* | | SGN | PRE | WHI REQ | Whitelisting required
* | | SGN | PRE | WHI REQ,TO='node' | Tell 'node' that we require whitelisting
* | | SGN | PRE | WHI REQ,FROM='node' | Node 'node' require whitelisting
* | | SGN | PRE | WHI NREQ | Whitelisting not required
* | | SGN | PRE | WHI NREQ,TO='node' | Tell 'node' that we do not require whitelisting
* | | SGN | PRE | WHI NREQ,FROM='node' | Node 'node' does not require whitelisting
* |!| SGN | PRE | WHI NREQ,FROM='node' REJ | Node 'node' does not require whitelisting but used to (requirement remain unchanged)
* | | SGN | PRE | XMT,TO='node' | Presentation data transmitted to 'node'
* |!| SGN | PRE | XMT,TO='node' FAIL | Presentation data not properly transmitted to 'node'
* | | SGN | PRE | WAIT GW | Waiting for gateway presentation data
* |!| SGN | PRE | VER='version' | Presentation version 'version' is not supported
* | | SGN | PRE | NSUP | Received signing presentation but signing is not supported
* | | SGN | PRE | NSUP,TO='node' | Informing 'node' that we do not support signing
* | | SGN | SGN | NCE REQ,TO='node' | Nonce request transmitted to 'node'
* |!| SGN | SGN | NCE REQ,TO='node' FAIL | Nonce request not properly transmitted to 'node'
* |!| SGN | SGN | NCE TMO | Timeout waiting for nonce
* | | SGN | SGN | SGN | Message signed
* |!| SGN | SGN | SGN FAIL | Message failed to be signed
* | | SGN | SGN | NREQ='node' | 'node' does not require signed messages
* | | SGN | SGN | 'sender'!='us' NUS | Will not sign because 'sender' is not 'us' (repeater)
* |!| SGN | SGN | STATE | Security system in a invalid state (personalization data tampered)
* |!| SGN | VER | NSG | Message was not signed, but it should have been
* |!| SGN | VER | FAIL | Verification failed
* | | SGN | VER | OK | Verification succeeded
* | | SGN | VER | LEFT='number' | 'number' of failed verifications left in a row before node is locked
* |!| SGN | VER | STATE | Security system in a invalid state (personalization data tampered)
* | | SGN | SKP | MSG CMD='cmd',TYPE='type'| Message with command 'cmd' and type 'type' does not need to be signed
* | | SGN | SKP | ECHO CMD='cmd',TYPE='type'| ECHO messages does not need to be signed
* | | SGN | NCE | LEFT='number' | 'number' of nonce requests between successful verifications left before node is locked
* | | SGN | NCE | XMT,TO='node' | Nonce data transmitted to 'node'
* |!| SGN | NCE | XMT,TO='node' FAIL | Nonce data not properly transmitted to 'node'
* |!| SGN | NCE | GEN | Failed to generate nonce
* | | SGN | NCE | NSUP (DROPPED) | Ignored nonce/request for nonce (signing not supported)
* | | SGN | NCE | FROM='node' | Received nonce from 'node'
* | | SGN | NCE | 'sender'!='dst' (DROPPED)| Ignoring nonce as it did not come from the designation of the message to sign
* |!| SGN | BND | INIT FAIL | Failed to initialize signing backend
* |!| SGN | BND | PWD<8 | Signing password too short
* |!| SGN | BND | PER | Backend not personalized
* |!| SGN | BND | SER | Could not get device unique serial from backend
* |!| SGN | BND | TMR | Backend timed out
* |!| SGN | BND | SIG,SIZE,'message'>'max' | Refusing to sign 'message' because it is bigger than 'max' allowed size
* | | SGN | BND | SIG WHI,ID='id' | Salting message with our 'id'
* | | SGN | BND | SIG WHI,SERIAL='serial' | Salting message with our 'serial'
* |!| SGN | BND | VER ONGOING | Verification failed, no ongoing session
* |!| SGN | BND | VER,IDENT='identifier' | Verification failed, 'identifier' is unknown
* | | SGN | BND | VER WHI,ID='sender' | 'sender' found in whitelist
* | | SGN | BND | VER WHI,SERIAL='serial' | Expecting 'serial' for this sender
* |!| SGN | BND | VER WHI,ID='id' MISSING | 'id' not found in whitelist
* | | SGN | BND | NONCE='nonce' | Calculating signature using 'nonce'
* | | SGN | BND | HMAC='hmac' | Calculated signature is 'hmac'
*/
/** @}*/
/**
* @defgroup MySigningTroubleshootinggrp Signing troubleshooting
* @ingroup MySigninggrpPub
* @{
*
* @brief Typical signing related failure cases and how to solve them
*
* @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 often 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 performance.<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 performance.<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
*
* @subsection MySigningTroubleshootingSymptomStTampered Signing backend reports tampered even after personalization
*
* The signing backend validates that the secure elements in EEPROM remain unmodified after personalization using a checksum. If the check fails,
* the backend reports
* @code
* !SGN:PER:Tampered
* @endcode
* This usually indicate that the sketch has modified the secure elements in EEPROM, but if you experience this even after a node is freshly
* personalized on a atmega device, it could be that the EESAVE fuse bit is not set which mean that the EEPROM is erased when a new firmware is flashed.
* You will need to enable the EESAVE bit in order to have the security personalization persist in the node.
*/
/** @}*/

View File

@@ -0,0 +1,357 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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"
#include "MyHelperFunctions.h"
#ifdef MY_SIGNING_ATSHA204
#define SIGNING_IDENTIFIER (1) //HMAC-SHA256
#if defined(MY_DEBUG_VERBOSE_SIGNING)
#define SIGN_DEBUG(x,...) DEBUG_OUTPUT(x, ##__VA_ARGS__)
#else
#define SIGN_DEBUG(x,...)
#endif
static unsigned long _signing_timestamp;
static bool _signing_verification_ongoing = false;
static uint8_t _signing_verifying_nonce[32+9+1];
static uint8_t _signing_signing_nonce[32+9+1];
static uint8_t _signing_temp_message[SHA_MSG_SIZE];
static uint8_t _signing_rx_buffer[SHA204_RSP_SIZE_MAX];
static uint8_t _signing_tx_buffer[SHA204_CMD_SIZE_MAX];
static uint8_t* const _signing_hmac = &_signing_rx_buffer[SHA204_BUFFER_POS_DATA];
static uint8_t _signing_node_serial_info[9];
#ifdef MY_SIGNING_NODE_WHITELISTING
static const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING;
#endif
static bool init_ok = false;
static void signerCalculateSignature(MyMessage &msg, bool signing);
static uint8_t* signerAtsha204AHmac(const uint8_t* nonce, const uint8_t* data);
static uint8_t* signerSha256(const uint8_t* data, size_t sz);
bool signerAtsha204Init(void)
{
init_ok = true;
atsha204_init(MY_SIGNING_ATSHA204_PIN);
(void)atsha204_wakeup(_signing_temp_message);
// Read the configuration lock flag to determine if device is personalized or not
if (atsha204_read(_signing_tx_buffer, _signing_rx_buffer,
SHA204_ZONE_CONFIG, 0x15<<2) != SHA204_SUCCESS) {
SIGN_DEBUG(PSTR("!SGN:BND:INIT FAIL\n")); //Could not read ATSHA204A lock config
init_ok = false;
} else if (_signing_rx_buffer[SHA204_BUFFER_POS_DATA+3] != 0x00) {
SIGN_DEBUG(PSTR("!SGN:BND:PER\n")); //ATSHA204A not personalized
init_ok = false;
}
if (init_ok) {
// Get and cache the serial of the ATSHA204A
if (atsha204_getSerialNumber(_signing_node_serial_info) != SHA204_SUCCESS) {
SIGN_DEBUG(PSTR("!SGN:BND:SER\n")); //Could not get ATSHA204A serial
init_ok = false;
}
}
return init_ok;
}
bool signerAtsha204CheckTimer(void)
{
if (!init_ok) {
return false;
}
if (_signing_verification_ongoing) {
unsigned long time_now = hwMillis();
// If timestamp is taken so late a rollover can take place during the timeout,
// offset both timestamp and current time to make sure no rollover takes place during the
// timeout
if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < _signing_timestamp) {
_signing_timestamp += MY_VERIFICATION_TIMEOUT_MS;
time_now += MY_VERIFICATION_TIMEOUT_MS;
}
if (time_now > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) {
SIGN_DEBUG(PSTR("!SGN:BND:TMR\n")); //Verification timeout
// Purge nonce
memset(_signing_signing_nonce, 0xAA, 32);
memset(_signing_verifying_nonce, 0xAA, 32);
_signing_verification_ongoing = false;
return false;
}
}
return true;
}
bool signerAtsha204GetNonce(MyMessage &msg)
{
if (!init_ok) {
return false;
}
// 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) {
return false;
}
for (int i = 0; i < 32; i++) {
_signing_verifying_nonce[i] = _signing_rx_buffer[SHA204_BUFFER_POS_DATA+i] ^ (hwMillis()&0xFF);
}
(void)memcpy((void *)_signing_verifying_nonce, (const void *)signerSha256(_signing_verifying_nonce,
32),
min((uint8_t)MAX_PAYLOAD_SIZE, 32u));
// We just idle the chip now since we expect to use it soon when the signed message arrives
atsha204_idle();
if (MAX_PAYLOAD_SIZE < 32) {
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
(void)memset((void *)&_signing_verifying_nonce[MAX_PAYLOAD_SIZE], 0xAA, 32u - MAX_PAYLOAD_SIZE);
}
// Transfer the first part of the nonce to the message
msg.set(_signing_verifying_nonce, min((uint8_t)MAX_PAYLOAD_SIZE, 32u));
_signing_verification_ongoing = true;
_signing_timestamp = hwMillis(); // Set timestamp to determine when to purge nonce
return true;
}
void signerAtsha204PutNonce(MyMessage &msg)
{
if (!init_ok) {
return;
}
(void)memcpy((void *)_signing_signing_nonce, (const void *)msg.getCustom(),
min((uint8_t)MAX_PAYLOAD_SIZE, 32u));
if (MAX_PAYLOAD_SIZE < 32u) {
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
(void)memset((void *)&_signing_signing_nonce[MAX_PAYLOAD_SIZE], 0xAA, 32u - MAX_PAYLOAD_SIZE);
}
}
bool signerAtsha204SignMsg(MyMessage &msg)
{
// If we cannot fit any signature in the message, refuse to sign it
if (msg.getLength() > MAX_PAYLOAD_SIZE - 2) {
SIGN_DEBUG(PSTR("!SGN:BND:SIG,SIZE,%" PRIu8 ">%" PRIu8 "\n"), msg.getLength(),
MAX_PAYLOAD_SIZE - 2); //Message too large
return false;
}
// Calculate signature of message
msg.setSigned(true); // make sure signing flag is set before signature is calculated
signerCalculateSignature(msg, true);
#if defined(MY_SIGNING_NODE_WHITELISTING)
if (DO_WHITELIST(msg.destination)) {
// Salt the signature with the senders nodeId and the unique serial of the ATSHA device
// We can reuse the nonce buffer now since it is no longer needed
memcpy(_signing_signing_nonce, _signing_hmac, 32);
_signing_signing_nonce[32] = msg.getSender();
memcpy(&_signing_signing_nonce[33], _signing_node_serial_info, 9);
// We can 'void' sha256 because the hash is already put in the correct place
(void)signerSha256(_signing_signing_nonce, 32+1+9);
SIGN_DEBUG(PSTR("SGN:BND:SIG WHI,ID=%" PRIu8 "\n"), msg.getSender());
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(_signing_node_serial_info, 9);
SIGN_DEBUG(PSTR("SGN:BND:SIG WHI,SERIAL=%s\n"), hwDebugPrintStr);
#endif
}
#endif
// Put device back to sleep
atsha204_sleep();
// 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
(void)memcpy((void *)&msg.data[msg.getLength()], (const void *)_signing_hmac,
min(MAX_PAYLOAD_SIZE - msg.getLength(), 32));
return true;
}
bool signerAtsha204VerifyMsg(MyMessage &msg)
{
if (!_signing_verification_ongoing) {
SIGN_DEBUG(PSTR("!SGN:BND:VER ONGOING\n"));
return false;
} else {
// Make sure we have not expired
if (!signerCheckTimer()) {
return false;
}
_signing_verification_ongoing = false;
if (msg.data[msg.getLength()] != SIGNING_IDENTIFIER) {
SIGN_DEBUG(PSTR("!SGN:BND:VER,IDENT=%" PRIu8 "\n"), msg.data[msg.getLength()]);
return false;
}
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.getSender()) {
// We can reuse the nonce buffer now since it is no longer needed
memcpy(_signing_verifying_nonce, _signing_hmac, 32);
_signing_verifying_nonce[32] = msg.getSender();
memcpy(&_signing_verifying_nonce[33], _signing_whitelist[j].serial, 9);
// We can 'void' sha256 because the hash is already put in the correct place
(void)signerSha256(_signing_verifying_nonce, 32+1+9);
SIGN_DEBUG(PSTR("SGN:BND:VER WHI,ID=%" PRIu8 "\n"), msg.getSender());
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(_signing_whitelist[j].serial, 9);
SIGN_DEBUG(PSTR("SGN:BND:VER WHI,SERIAL=%s\n"), hwDebugPrintStr);
#endif
break;
}
}
if (j == NUM_OF(_signing_whitelist)) {
SIGN_DEBUG(PSTR("!SGN:BND:VER WHI,ID=%" PRIu8 " MISSING\n"), msg.getSender());
// 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_hmac[0] = SIGNING_IDENTIFIER;
// Compare the calculated signature with the provided signature
if (signerMemcmp(&msg.data[msg.getLength()], _signing_hmac,
min(MAX_PAYLOAD_SIZE - msg.getLength(), 32))) {
return false;
} else {
return true;
}
}
}
// Helper to calculate signature of msg (returned in _signing_rx_buffer[SHA204_BUFFER_POS_DATA])
// (=_signing_hmac)
static void signerCalculateSignature(MyMessage &msg, bool signing)
{
// Signature is calculated on everything expect the first byte in the header
uint16_t bytes_left = msg.getLength()+HEADER_SIZE-1;
int16_t current_pos = 1-(int16_t)HEADER_SIZE; // Start at the second byte in the header
uint8_t* nonce = signing ? _signing_signing_nonce : _signing_verifying_nonce;
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(nonce, 32);
SIGN_DEBUG(PSTR("SGN:BND:NONCE=%s\n"), hwDebugPrintStr);
#endif
while (bytes_left) {
uint16_t bytes_to_include = min(bytes_left, 32);
(void)atsha204_wakeup(_signing_temp_message); // Issue wakeup to reset watchdog
memset(_signing_temp_message, 0, 32);
memcpy(_signing_temp_message, (uint8_t*)&msg.data[current_pos], bytes_to_include);
// We can 'void' signerAtsha204AHmac because the HMAC is already put in the correct place
(void)signerAtsha204AHmac(nonce, _signing_temp_message);
// Purge nonce when used
memset(nonce, 0xAA, 32);
bytes_left -= bytes_to_include;
current_pos += bytes_to_include;
if (bytes_left > 0) {
// We will do another pass, use current HMAC as nonce for the next HMAC
memcpy(nonce, _signing_hmac, 32);
atsha204_idle(); // Idle the chip to allow the wakeup call to reset the watchdog
}
}
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(_signing_hmac, 32);
SIGN_DEBUG(PSTR("SGN:BND:HMAC=%s\n"), hwDebugPrintStr);
#endif
}
// Helper to calculate a ATSHA204A specific HMAC-SHA256 using provided 32 byte nonce and data
// (zero padded to 32 bytes)
// The pointer to the HMAC is returned, but the HMAC is also stored in
// _signing_rx_buffer[SHA204_BUFFER_POS_DATA] (=_signing_hmac)
static uint8_t* signerAtsha204AHmac(const uint8_t* nonce, const uint8_t* data)
{
// Program the data to sign into the ATSHA204
(void)atsha204_execute(SHA204_WRITE, SHA204_ZONE_DATA | SHA204_ZONE_COUNT_FLAG, 8 << 3, 32,
(uint8_t*)data,
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, 32, (uint8_t*)nonce,
NONCE_COUNT_LONG, _signing_tx_buffer, NONCE_RSP_SIZE_SHORT,
_signing_rx_buffer);
// 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);
return &_signing_rx_buffer[SHA204_BUFFER_POS_DATA];
}
// 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);
return &_signing_rx_buffer[SHA204_BUFFER_POS_DATA];
}
#endif //MY_SIGNING_ATSHA204

View File

@@ -0,0 +1,374 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 "MyHelperFunctions.h"
#ifdef MY_SIGNING_SOFT
#define SIGNING_IDENTIFIER (1) //HMAC-SHA256
#if defined(MY_DEBUG_VERBOSE_SIGNING)
#define SIGN_DEBUG(x,...) DEBUG_OUTPUT(x, ##__VA_ARGS__)
#else
#define SIGN_DEBUG(x,...)
#endif
static unsigned long _signing_timestamp;
static bool _signing_verification_ongoing = false;
static bool _signing_init_ok = false;
static uint8_t _signing_verifying_nonce[32+9+1];
static uint8_t _signing_nonce[32+9+1];
static uint8_t _signing_hmac_key[SIZE_SIGNING_SOFT_HMAC_KEY];
static uint8_t _signing_hmac[32];
static uint8_t _signing_node_serial_info[SIZE_SIGNING_SOFT_SERIAL];
#ifdef MY_SIGNING_NODE_WHITELISTING
static const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING;
#endif
static void signerCalculateSignature(MyMessage &msg, const bool signing);
static void signerAtsha204AHmac(uint8_t *dest, const uint8_t *nonce, const uint8_t *data);
bool signerAtsha204SoftInit(void)
{
_signing_init_ok = true;
// initialize pseudo-RNG
hwRandomNumberInit();
// Set secrets
#ifdef MY_SIGNING_SIMPLE_PASSWD
if (strnlen(MY_SIGNING_SIMPLE_PASSWD, 32) < 8) {
SIGN_DEBUG(PSTR("!SGN:BND:PWD<8\n")); //Password is too short to be acceptable
_signing_init_ok = false;
} else {
(void)memset((void *)_signing_hmac_key, 0x00, sizeof(_signing_hmac_key));
(void)memcpy((void *)_signing_hmac_key, MY_SIGNING_SIMPLE_PASSWD, strnlen(MY_SIGNING_SIMPLE_PASSWD,
32));
(void)memset((void *)_signing_node_serial_info, 0x00, sizeof(_signing_node_serial_info));
(void)memcpy((void *)_signing_node_serial_info, MY_SIGNING_SIMPLE_PASSWD,
strnlen(MY_SIGNING_SIMPLE_PASSWD, 8));
_signing_node_serial_info[8] = getNodeId();
}
#else
hwReadConfigBlock((void *)_signing_hmac_key, (void *)EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS,
SIZE_SIGNING_SOFT_HMAC_KEY);
hwReadConfigBlock((void *)_signing_node_serial_info, (void *)EEPROM_SIGNING_SOFT_SERIAL_ADDRESS,
SIZE_SIGNING_SOFT_SERIAL);
#endif
uint16_t chk = 0;
for (uint8_t i = 0; i < SIZE_SIGNING_SOFT_SERIAL; i++) {
chk += _signing_node_serial_info[i];
}
if (chk==SIZE_SIGNING_SOFT_SERIAL *
0xFF) { // test if == { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }
unique_id_t uniqueID;
// There is no serial, attempt to get unique ID for serial instead
if (hwUniqueID(&uniqueID)) {
// There is a unique serial, use that
(void)memcpy((void *)_signing_node_serial_info, (const void *)uniqueID, SIZE_SIGNING_SOFT_SERIAL);
}
}
return _signing_init_ok;
}
bool signerAtsha204SoftCheckTimer(void)
{
if (!_signing_init_ok) {
return false;
}
if (_signing_verification_ongoing) {
unsigned long time_now = hwMillis();
// If timestamp is taken so late a rollover can take place during the timeout,
// offset both timestamp and current time to make sure no rollover takes place during the
// timeout
if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < _signing_timestamp) {
_signing_timestamp += MY_VERIFICATION_TIMEOUT_MS;
time_now += MY_VERIFICATION_TIMEOUT_MS;
}
if (time_now > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) {
SIGN_DEBUG(PSTR("!SGN:BND:TMR\n")); //Verification timeout
// Purge nonce
(void)memset((void *)_signing_nonce, 0xAA, sizeof(_signing_nonce));
(void)memset((void *)_signing_verifying_nonce, 0xAA, sizeof(_signing_verifying_nonce));
_signing_verification_ongoing = false;
return false;
}
}
return true;
}
bool signerAtsha204SoftGetNonce(MyMessage &msg)
{
if (!_signing_init_ok) {
return false;
}
#ifdef MY_HW_HAS_GETENTROPY
// Try to get MAX_PAYLOAD_SIZE random bytes
while (hwGetentropy(&_signing_verifying_nonce, MAX_PAYLOAD_SIZE) != MAX_PAYLOAD_SIZE);
#else
// 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
uint8_t randBuffer[32];
for (uint8_t i = 0; i < sizeof(randBuffer); i++) {
randBuffer[i] = random(256) ^ (hwMillis() & 0xFF);
}
SHA256(_signing_verifying_nonce, randBuffer, sizeof(randBuffer));
#endif
if (MAX_PAYLOAD_SIZE < 32) {
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
(void)memset((void *)&_signing_verifying_nonce[MAX_PAYLOAD_SIZE], 0xAA, 32u - MAX_PAYLOAD_SIZE);
}
// Transfer the first part of the nonce to the message
msg.set(_signing_verifying_nonce, MIN((uint8_t)MAX_PAYLOAD_SIZE, (uint8_t)32));
_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)
{
if (!_signing_init_ok) {
return;
}
(void)memcpy((void *)_signing_nonce, (const void *)msg.getCustom(), MIN((uint8_t)MAX_PAYLOAD_SIZE,
(uint8_t)32));
if (MAX_PAYLOAD_SIZE < 32) {
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
(void)memset((void *)&_signing_nonce[MAX_PAYLOAD_SIZE], 0xAA, 32u - MAX_PAYLOAD_SIZE);
}
}
bool signerAtsha204SoftSignMsg(MyMessage &msg)
{
// If we cannot fit any signature in the message, refuse to sign it
if (msg.getLength() > MAX_PAYLOAD_SIZE - 2u) {
SIGN_DEBUG(PSTR("!SGN:BND:SIG,SIZE,%" PRIu8 ">%" PRIu8 "\n"), msg.getLength(),
MAX_PAYLOAD_SIZE - 2); //Message too large
return false;
}
// Calculate signature of message
msg.setSigned(true); // make sure signing flag is set before signature is calculated
signerCalculateSignature(msg, true);
#if defined(MY_SIGNING_NODE_WHITELISTING)
if (DO_WHITELIST(msg.getDestination())) {
// Salt the signature with the senders nodeId and the (hopefully) unique serial The Creator has
// provided. We can reuse the nonce buffer now since it is no longer needed
(void)memcpy((void *)_signing_nonce, (const void *)_signing_hmac, 32);
_signing_nonce[32] = msg.getSender();
(void)memcpy((void *)&_signing_nonce[33], (const void *)_signing_node_serial_info, 9);
SHA256(_signing_hmac, _signing_nonce, 32+1+9);
SIGN_DEBUG(PSTR("SGN:BND:SIG WHI,ID=%" PRIu8 "\n"), msg.getSender());
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(_signing_node_serial_info, 9);
SIGN_DEBUG(PSTR("SGN:BND:SIG WHI,SERIAL=%s\n"), hwDebugPrintStr);
#endif
}
#endif
// 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
(void)memcpy((void *)&msg.data[msg.getLength()], (const void *)_signing_hmac,
MIN((uint8_t)(MAX_PAYLOAD_SIZE - msg.getLength()), (uint8_t)32));
return true;
}
bool signerAtsha204SoftVerifyMsg(MyMessage &msg)
{
if (!_signing_verification_ongoing) {
SIGN_DEBUG(PSTR("!SGN:BND:VER ONGOING\n"));
return false;
} else {
// Make sure we have not expired
if (!signerCheckTimer()) {
return false;
}
_signing_verification_ongoing = false;
if (msg.data[msg.getLength()] != SIGNING_IDENTIFIER) {
SIGN_DEBUG(PSTR("!SGN:BND:VER,IDENT=%" PRIu8 "\n"), msg.data[msg.getLength()]);
return false;
}
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.getSender()) {
// We can reuse the nonce buffer now since it is no longer needed
(void)memcpy((void *)_signing_verifying_nonce, (const void *)_signing_hmac, 32);
_signing_verifying_nonce[32] = msg.getSender();
(void)memcpy((void *)&_signing_verifying_nonce[33], (const void *)_signing_whitelist[j].serial, 9);
SHA256(_signing_hmac, _signing_verifying_nonce, 32+1+9);
SIGN_DEBUG(PSTR("SGN:BND:VER WHI,ID=%" PRIu8 "\n"), msg.getSender());
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(_signing_whitelist[j].serial, 9);
SIGN_DEBUG(PSTR("SGN:BND:VER WHI,SERIAL=%s\n"), hwDebugPrintStr);
#endif
break;
}
}
if (j == NUM_OF(_signing_whitelist)) {
SIGN_DEBUG(PSTR("!SGN:BND:VER WHI,ID=%" PRIu8 " MISSING\n"), msg.getSender());
return false;
}
#endif
// Overwrite the first byte in the signature with the signing identifier
_signing_hmac[0] = SIGNING_IDENTIFIER;
// Compare the calculated signature with the provided signature
if (signerMemcmp(&msg.data[msg.getLength()], _signing_hmac,
MIN((uint8_t)(MAX_PAYLOAD_SIZE - msg.getLength()), (uint8_t)32))) {
return false;
} else {
return true;
}
}
}
// Helper to calculate signature of msg (returned in _signing_hmac)
static void signerCalculateSignature(MyMessage &msg, const bool signing)
{
// Signature is calculated on everything expect the first byte in the header
uint8_t bytes_left = msg.getLength()+HEADER_SIZE-1;
int16_t current_pos = 1-(int16_t)HEADER_SIZE; // Start at the second byte in the header
uint8_t* nonce = signing ? _signing_nonce : _signing_verifying_nonce;
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(nonce, 32);
SIGN_DEBUG(PSTR("SGN:BND:NONCE=%s\n"), hwDebugPrintStr);
#endif
uint8_t _signing_temp_message[32];
while (bytes_left) {
uint8_t bytes_to_include = MIN(bytes_left, (uint8_t)32);
(void)memset((void *)_signing_temp_message, 0x00, sizeof(_signing_temp_message));
(void)memcpy((void *)_signing_temp_message, (const void *)&msg.data[current_pos], bytes_to_include);
signerAtsha204AHmac(_signing_hmac, nonce, _signing_temp_message);
// Purge nonce when used
(void)memset((void *)nonce, 0xAA, 32);
bytes_left -= bytes_to_include;
current_pos += bytes_to_include;
if (bytes_left) {
// We will do another pass, use current HMAC as nonce for the next HMAC
(void)memcpy((void *)nonce, (const void *)_signing_hmac, 32);
}
}
#ifdef MY_DEBUG_VERBOSE_SIGNING
hwDebugBuf2Str(_signing_hmac, 32);
SIGN_DEBUG(PSTR("SGN:BND:HMAC=%s\n"), hwDebugPrintStr);
#endif
}
// Helper to calculate a ATSHA204A specific HMAC-SHA256 using provided 32 byte nonce and data
// (zero padded to 32 bytes)
// The pointer to the HMAC is returned, but the HMAC is also stored in _signing_hmac
static void signerAtsha204AHmac(uint8_t *dest, const uint8_t *nonce, const uint8_t *data)
{
// 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
#if defined(MY_CRYPTO_SHA256_ASM)
static uint8_t _signing_buffer[96]; // static for AVR ASM SHA256
#else
uint8_t _signing_buffer[96];
#endif
// Calculate message digest first
(void)memset((void *)_signing_buffer, 0x00, sizeof(_signing_buffer));
(void)memcpy((void *)_signing_buffer, (const void *)data, 32);
_signing_buffer[0 + 32] = 0x15; // OPCODE
_signing_buffer[1 + 32] = 0x02; // param1
_signing_buffer[2 + 32] = 0x08; // param2(1)
//_signing_buffer[3 + 32] = 0x00; // param2(2)
_signing_buffer[4 + 32] = 0xEE; // SN[8]
_signing_buffer[5 + 32] = 0x01; // SN[0]
_signing_buffer[6 + 32] = 0x23; // SN[1]
// _signing_buffer[7 + 32..31 + 32] => 0x00;
(void)memcpy((void *)&_signing_buffer[64], (const void *)nonce, 32);
SHA256(_signing_hmac, _signing_buffer, 96);
// Feed "message" to HMAC calculator
(void)memset((void *)_signing_buffer, 0x00, sizeof(_signing_buffer));
(void)memcpy((void *)&_signing_buffer[32], (const void *)_signing_hmac, 32);
_signing_buffer[0 + 64] = 0x11; // OPCODE
_signing_buffer[1 + 64] = 0x04; // Mode
//_signing_buffer[2 + 64] = 0x00; // SlotID(1)
//_signing_buffer[3 + 64] = 0x00; // SlotID(2)
//_signing_buffer[4 + 64..14 + 64] => 0x00; // 11 bytes zeroes
_signing_buffer[15 + 64] = 0xEE; // SN[8]
//_signing_buffer[16 + 64..19 + 64] => 0x00; // 4 bytes zeroes
_signing_buffer[20 + 64] = 0x01;
_signing_buffer[21 + 64] = 0x23;
//_signing_buffer[22 + 64] = 0x00; // SN[0]
//_signing_buffer[23 + 64] = 0x00; // SN[1]
SHA256HMAC(dest, _signing_hmac_key, 32, _signing_buffer, 88);
}
#endif //MY_SIGNING_SOFT

View File

@@ -0,0 +1,52 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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"
void displaySplashScreen(void)
{
#if !defined(MY_DISABLED_SERIAL) && defined(MY_DEBUGDEVICE)
static const uint8_t splashScreen[] PROGMEM = {
0x20, 0x7C, 0x5F, 0x5c, 0x2F, 0x60, 0x2C, 0x0A, // substitution matrix
0x07, 0x02, 0x20, 0x02, 0x20, 0xB2, 0x87, 0x10, 0x03, 0x40, 0x01, 0x20, 0x00, 0x24, 0x02, 0x22,
0x10, 0x02, 0x22, 0x02, 0x02, 0x20, 0x02, 0x22, 0x00, 0x22, 0x20, 0x02, 0x02, 0x20, 0x22, 0x27,
0x10, 0x13, 0x41, 0x01, 0x01, 0x01, 0x03, 0x22, 0x20, 0x30, 0x40, 0x20, 0x30, 0x52, 0x03, 0x40,
0x22, 0x14, 0x02, 0x03, 0x10, 0x52, 0x24, 0x02, 0x21, 0x71, 0x01, 0x00, 0x10, 0x10, 0x12, 0x10,
0x12, 0x22, 0x10, 0x10, 0x02, 0x24, 0x01, 0x01, 0x03, 0x22, 0x03, 0x00, 0x20, 0x01, 0x01, 0x00,
0x32, 0x20, 0x37, 0x12, 0x10, 0x01, 0x21, 0x32, 0x26, 0x01, 0x28, 0x40, 0x32, 0x22, 0x12, 0x10,
0x12, 0x12, 0x22, 0x43, 0x22, 0x24, 0x12, 0x10, 0x01, 0x22, 0x24, 0x70, 0xC1, 0x22, 0x24,
0x0F, 0x0F
};
uint8_t pos = 16;
char display = 0;
while (pos<sizeof(splashScreen)*2) {
uint8_t val = pgm_read_byte(&(splashScreen[pos>>1]));
val = (pos % 2) ? val & 0xF : val >> 4;
pos++;
if (val<8) {
display = pgm_read_byte(&(splashScreen[val]));
}
const uint8_t rep = val > 7 ? val - 5 : 1;
for (uint8_t c = 0; c<rep; c++) {
MY_DEBUGDEVICE.print(display);
}
}
MY_DEBUGDEVICE.println(F(MYSENSORS_LIBRARY_VERSION "\n"));
#endif
}

View File

@@ -0,0 +1,28 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 MySplashScreen_h
#define MySplashScreen_h
/**
* Display MySensors splash screen.
*/
void displaySplashScreen(void);
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,573 @@
/*
* 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-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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:<b>INIT</b> from <b>stInit</b> Initialize transport and radio
* - TSM:<b>FPAR</b> from <b>stParent</b> Find parent
* - TSM:<b>ID</b> from <b>stID</b> Check/request node ID, if dynamic node ID set
* - TSM:<b>UPL</b> from <b>stUplink</b> Verify uplink connection by pinging GW
* - TSM:<b>READY</b> from <b>stReady</b> Transport is ready and fully operational
* - TSM:<b>FAIL</b> from <b>stFailure</b> Failure in transport link or transport HW
* - Transport support function (<b>TSF</b>)
* - TSF:<b>CKU</b> from @ref transportCheckUplink(), checks connection to GW
* - TSF:<b>SID</b> from @ref transportAssignNodeID(), assigns node ID
* - TSF:<b>PNG</b> from @ref transportPingNode(), pings a node
* - TSF:<b>WUR</b> from @ref transportWaitUntilReady(), waits until transport is ready
* - TSF:<b>CRT</b> from @ref transportClearRoutingTable(), clears routing table stored in EEPROM
* - TSF:<b>LRT</b> from @ref transportLoadRoutingTable(), loads RAM routing table from EEPROM (only GW/repeaters)
* - TSF:<b>SRT</b> from @ref transportSaveRoutingTable(), saves RAM routing table to EEPROM (only GW/repeaters)
* - TSF:<b>MSG</b> from @ref transportProcessMessage(), processes incoming message
* - TSF:<b>SAN</b> from @ref transportInvokeSanityCheck(), calls transport-specific sanity check
* - TSF:<b>RTE</b> from @ref transportRouteMessage(), sends message
* - TSF:<b>SND</b> from @ref transportSendRoute(), sends message if transport is ready (exposed)
* - TSF:<b>TDI</b> from @ref transportDisable()
* - TSF:<b>TRI</b> from @ref transportReInitialise()
* - TSF:<b>SIR</b> from @ref transportSignalReport()
*
* Transport debug log messages:
*
* |E| SYS | SUB | Message | Comment
* |-|-----|-------|---------------------------|---------------------------------------------------------------------
* | | TSM | INIT | | <b>Transition to stInit state</b>
* | | TSM | INIT | STATID=%%d | Node ID is static
* | | TSM | INIT | TSP OK | Transport device configured and fully operational
* | | TSM | INIT | TSP PSM | Transport passive mode set
* | | 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, no ID received from controller
* | | TSM | UPL | | <b>Transition to stUplink state</b>
* | | TSM | UPL | OK | Uplink OK, GW returned ping
* | | TSF | UPL | DGWC,O=%%d,N=%%d | Uplink check revealed changed network topology, old distance (O), new distance (N)
* |!| TSM | UPL | FAIL | Uplink check failed, i.e. GW could not be pinged
* | | TSM | READY | SRT | Save routing table
* | | TSM | READY | ID=%%d,PAR=%%d,DIS=%%d | <b>Transition to stReady</b> Transport ready, node ID (ID), parent node ID (PAR), distance to GW (DIS)
* |!| 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 | FAIL | CNT=%%d | <b>Transition to stFailure state</b>, consecutive failure counter (CNT)
* | | TSM | FAIL | DIS | Disable transport
* | | TSM | FAIL | RE-INIT | Attempt to re-initialize transport
* | | TSF | CKU | OK | Uplink OK
* | | TSF | CKU | OK,FCTRL | Uplink OK, flood control prevents pinging GW in too short intervals
* | | TSF | CKU | DGWC,O=%%d,N=%%d | Uplink check revealed changed network topology, old distance (O), new distance (N)
* | | TSF | CKU | FAIL | No reply received when checking uplink
* | | TSF | SID | OK,ID=%%d | Node ID assigned
* |!| TSF | SID | FAIL,ID=%%d | Assigned ID is invalid
* | | TSF | PNG | SEND,TO=%%d | Send ping to destination (TO)
* | | TSF | WUR | MS=%%lu | Wait until transport ready, timeout (MS)
* | | TSF | MSG | ECHO REQ | ECHO message requested
* | | TSF | MSG | ECHO | ECHO 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 | RCV CB | Hand over message to @ref receive() callback function
* | | TSF | MSG | REL MSG | Relay message
* | | TSF | MSG | REL PxNG,HP=%%d | Relay PING/PONG message, increment hop counter (HP)
* |!| 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 | MSG | ID TK INVALID | Token for ID request invalid
* | | TSF | SAN | OK | Sanity check passed
* |!| TSF | SAN | FAIL | Sanity check failed, attempt to re-initialize radio
* | | TSF | CRT | OK | Clearing routing table successful
* | | TSF | LRT | OK | Loading routing table successful
* | | TSF | SRT | OK | Saving routing table successful
* |!| TSF | RTE | FPAR ACTIVE | Finding parent active, message not sent
* |!| TSF | RTE | DST %%d UNKNOWN | Routing for destination (DST) unknown, send message to parent
* | | TSF | RTE | N2N OK | Node-to-node communication succeeded
* |!| TSF | RTE | N2N FAIL | Node-to-node communication failed, handing over to parent for re-routing
* | | TSF | RRT | ROUTE N=%%d,R=%%d | Routing table, messages to node (N) are routed via node (R)
* |!| TSF | SND | TNR | Transport not ready, message cannot be sent
* | | TSF | TDI | TSL | Set transport to sleep
* | | TSF | TDI | TPD | Power down transport
* | | TSF | TRI | TRI | Reinitialise transport
* | | TSF | TRI | TSB | Set transport to standby
* | | TSF | SIR | CMD=%d,VAL=%d | Get signal report
*
*
* Incoming / outgoing messages:
*
* See <a href="https://www.mysensors.org/download/serial_api_20">here</a> for more detail on the format and definitions.
*
* 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
*
* Prepended char:
* - <b>none</b>=sending OK
* - <b>!</b>=error sending
* - <b>?</b>=sending status unknown
* 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>sg</b>=signing flag
* - <b>ft</b>=failed uplink transmission counter
* - <b>st</b>=send status, OK=success, NACK=no radio ACK received
*
* @startuml
* state top as "Transport" {
* state Init
* state Failure
* state Ready
* state Parent
* state ID
* state Uplink
* }
*
* [*] --> Init
* Init : entry / Read config from eeprom
* Init --> Failure : [! transportInit()\n|| ID == 0\n|| ID == 255 ]
* Init --> Ready : [MY_GATEWAY_FEATURE]
* Init --> Parent : [else]
*
* Parent : entry / Broadcast Find Parent
* Parent --> ID : [MY_PARENT_NODE_IS_STATIC\n|| MY_PASSIVE_NODE\n|| Parent found]
* Parent --> Parent : [timeout\n&& retries left]
* Parent --> Failure : [timeout\n&& no retries left]
*
* ID : entry / Request Node ID
* ID --> Uplink : [ID valid]
* ID --> ID : [timeout\n&& retries left]
* ID --> Failure : [timeout\n&& no retries left]
*
* Uplink : entry / Check uplink (PING)
* Uplink --> Uplink : [timeout\n&& retries left]
* Uplink --> Parent : [timeout\n&& no retries left]
* Uplink --> Ready : [MY_TRANSPORT_UPLINK_CHECK_DISABLED\n|| Uplink ok (PONG)]
*
* Ready : entry / Transport ready callback
* Ready : MY_GATEWAY_FEATURE && Network discovery required / Send discovery
* Ready --> Parent : [!MY_PARENT_NODE_IS_STATIC\n&& Uplink failure overflow]
*
* Failure : entry / Disable transport
* Failure --> Init : [timeout]
* top --> Failure : [MY_TRANSPORT_SANITY_CHECK\n&& !transportSanityCheck]
* @enduml
*
* @brief API declaration for MyTransport
*
*/
#ifndef MyTransport_h
#define MyTransport_h
#include "hal/transport/MyTransportHAL.h"
#ifndef MY_TRANSPORT_MAX_TX_FAILURES
#if defined(MY_REPEATER_FEATURE)
#define MY_TRANSPORT_MAX_TX_FAILURES (10u) //!< search for a new parent node after this many transmission failures, higher threshold for repeating nodes
#else
#define MY_TRANSPORT_MAX_TX_FAILURES (5u) //!< search for a new parent node after this many transmission failures, lower threshold for non-repeating nodes
#endif
#endif
#ifndef MY_TRANSPORT_MAX_TSM_FAILURES
#define MY_TRANSPORT_MAX_TSM_FAILURES (7u) //!< Max. number of consecutive TSM failure state entries (3bits)
#endif
#ifndef MY_TRANSPORT_TIMEOUT_FAILURE_STATE_MS
#define MY_TRANSPORT_TIMEOUT_FAILURE_STATE_MS (10*1000ul) //!< Duration failure state (in ms)
#endif
#ifndef MY_TRANSPORT_TIMEOUT_EXT_FAILURE_STATE_MS
#define MY_TRANSPORT_TIMEOUT_EXT_FAILURE_STATE_MS (60*1000ul) //!< Duration extended failure state (in ms)
#endif
#ifndef MY_TRANSPORT_STATE_TIMEOUT_MS
#define MY_TRANSPORT_STATE_TIMEOUT_MS (2*1000ul) //!< general state timeout (in ms)
#endif
#ifndef MY_TRANSPORT_CHKUPL_INTERVAL_MS
#define MY_TRANSPORT_CHKUPL_INTERVAL_MS (10*1000ul) //!< Interval to re-check uplink (in ms)
#endif
#ifndef MY_TRANSPORT_STATE_RETRIES
#define MY_TRANSPORT_STATE_RETRIES (3u) //!< retries before switching to FAILURE
#endif
#define AUTO (255u) //!< ID 255 is reserved
#define BROADCAST_ADDRESS (255u) //!< broadcasts are addressed to ID 255
#define DISTANCE_INVALID (255u) //!< invalid distance when searching for parent
#define MAX_HOPS (254u) //!< maximal number of hops for ping/pong
#define INVALID_HOPS (255u) //!< invalid hops
#define MAX_SUBSEQ_MSGS (5u) //!< Maximum number of subsequently processed messages in FIFO (to prevent transport deadlock if HW issue)
#define UPLINK_QUALITY_WEIGHT (0.05f) //!< UPLINK_QUALITY_WEIGHT
// parent node check
#if defined(MY_PARENT_NODE_IS_STATIC) && !defined(MY_PARENT_NODE_ID)
#error MY_PARENT_NODE_IS_STATIC but no MY_PARENT_NODE_ID defined!
#endif
#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 Callback type
*/
typedef void(*transportCallback_t)(void);
/**
* @brief Node configuration
*
* This structure stores node-related configurations
*/
typedef struct {
uint8_t nodeId; //!< Current node id
uint8_t parentNodeId; //!< Where this node sends its messages
uint8_t distanceGW; //!< This nodes distance to sensor net gateway (number of hops)
uint8_t passiveMode : 1; //!< Passive mode
uint8_t reserved : 7; //!< Reserved
} transportConfig_t;
/**
* @brief SM state
*
* This structure stores SM state definitions
*/
typedef struct {
void(*Transition)(void); //!< state transition function
void(*Update)(void); //!< state update function
} transportState_t;
/**
* @brief Datatype for internal RSSI storage
*/
typedef int16_t transportRSSI_t; //!< Datatype for internal RSSI storage
// helper macro for conversion
#define transportInternalToRSSI(__value) ((int16_t)__value >> 4) //!< Convert internal RSSI to RSSI
#define transportRSSItoInternal(__value) ((transportRSSI_t)__value << 4) //!< Convert RSSI to internal RSSI
/**
* @brief Status variables and SM state
*
* This structure stores transport status and SM variables
*/
typedef struct {
// SM variables
transportState_t *currentState; //!< pointer to current FSM state
uint32_t stateEnter; //!< state enter timepoint
// general transport variables
uint32_t lastUplinkCheck; //!< last uplink check, required to prevent GW flooding
// 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 stateRetries : 3; //!< retries / state re-enter (max 7)
// 8 bits
uint8_t failedUplinkTransmissions : 4; //!< counter failed uplink transmissions (max 15)
uint8_t failureCounter : 3; //!< counter for TSM failures (max 7)
bool msgReceived : 1; //!< flag message received
uint8_t pingResponse; //!< stores I_PONG hops
#if defined(MY_SIGNAL_REPORT_ENABLED)
transportRSSI_t uplinkQualityRSSI; //!< Uplink quality, internal RSSI representation
#endif
} transportSM_t;
/**
* @brief RAM routing table
*/
typedef struct {
uint8_t route[SIZE_ROUTES]; //!< route for node
} routingTable_t;
// PRIVATE functions
/**
* @brief Initialize SM variables and transport HW
*/
void stInitTransition(void);
/**
* @brief Initialize transport
*/
void stInitUpdate(void);
/**
* @brief Find parent
*/
void stParentTransition(void);
/**
* @brief Verify find parent responses
*/
void stParentUpdate(void);
/**
* @brief Send ID request
*/
void stIDTransition(void);
/**
* @brief Verify ID response and GW link
*/
void stIDUpdate(void);
/**
* @brief Send uplink ping request
*/
void stUplinkTransition(void);
/**
* @brief Verify uplink response
*/
void stUplinkUpdate(void);
/**
* @brief Set transport OK
*/
void stReadyTransition(void);
/**
* @brief Monitor transport link
*/
void stReadyUpdate(void);
/**
* @brief Transport failure and power down radio
*/
void stFailureTransition(void);
/**
* @brief Re-initialize transport after timeout
*/
void stFailureUpdate(void);
/**
* @brief Switch SM state
* @param newState New state to switch SM to
*/
void transportSwitchSM(transportState_t &newState);
/**
* @brief Update SM state
*/
void transportUpdateSM(void);
/**
* @brief Request time in current SM state
* @return ms in current state
*/
uint32_t transportTimeInState(void);
/**
* @brief Call transport driver sanity check
*/
void transportInvokeSanityCheck(void);
/**
* @brief Process all pending messages in RX FIFO
*/
void transportProcessFIFO(void);
/**
* @brief Receive message from RX FIFO and process
*/
void transportProcessMessage(void);
/**
* @brief Assign node ID
* @param newNodeId New node ID
* @return true if node ID is valid and successfully assigned
*/
bool transportAssignNodeID(const uint8_t newNodeId);
/**
* @brief Wait and process messages for a defined amount of time until specified message received
* @param waitingMS 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(const uint32_t waitingMS, const uint8_t cmd, const 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(const 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(const 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(const bool force = false);
// PUBLIC functions
/**
* @brief Wait until transport is ready
* @param waitingMS timeout in MS, set 0 (default) for no timeout, i.e. wait indefinitely. For a node in standalone mode (optional network connection) set >0 to allow a node entering the main loop() function.
* @return true if transport is ready
*/
bool transportWaitUntilReady(const uint32_t waitingMS = 0);
/**
* @brief Initialize transport and SM
*/
void transportInitialise(void);
/**
* @brief Process FIFO msg and update SM
*/
void transportProcess(void);
/**
* @brief Flag transport ready
* @return true if transport is initialized and ready
*/
bool isTransportReady(void);
/**
* @brief Flag searching parent ongoing
* @return true if transport is searching for parent
*/
bool isTransportSearchingParent(void);
/**
* @brief Flag TSM extended failure
* @return true if TSM had too many consecutive failure state entries
*/
bool isTransportExtendedFailure(void);
/**
* @brief Flag valid message received
* @return true if valid message received, needs to be reset if used
*/
bool isMessageReceived(void);
/**
* @brief Reset message received flag
*/
void resetMessageReceived(void);
/**
* @brief Clear routing table
*/
void transportClearRoutingTable(void);
/**
* @brief Return heart beat
* @return MS in current state
*/
uint32_t transportGetHeartbeat(void);
/**
* @brief Load routing table from EEPROM to RAM.
* Only for GW devices with enough RAM, i.e. ESP8266, RPI Sensebender GW, etc.
* Atmega328 has only limited amount of RAM
*/
void transportLoadRoutingTable(void);
/**
* @brief Save routing table to EEPROM.
*/
void transportSaveRoutingTable(void);
/**
* @brief Update routing table
* @param node
* @param route
*/
void transportSetRoute(const uint8_t node, const uint8_t route);
/**
* @brief Load route to node
* @param node
* @return route to node
*/
uint8_t transportGetRoute(const uint8_t node);
/**
* @brief Reports content of routing table
*/
void transportReportRoutingTable(void);
/**
* @brief Get node ID
* @return node ID
*/
uint8_t transportGetNodeId(void);
/**
* @brief Get parent node ID
* @return parent node ID
*/
uint8_t transportGetParentNodeId(void);
/**
* @brief Get distance to GW
* @return distance (=hops) to GW
*/
uint8_t transportGetDistanceGW(void);
/**
* @brief Toggle passive mode, i.e. transport does not wait for ACK
* @param OnOff
*/
void transportTogglePassiveMode(const bool OnOff);
/**
* @brief Disable transport, if xxx_POWER_PIN is defined, transport is powered down, else send to sleep
*/
void transportDisable(void);
/**
* @brief Reinitialise transport. Put transport to standby - If xxx_POWER_PIN set, power up and go to standby
*/
void transportReInitialise(void);
/**
* @brief Get transport signal report
* @param command:
* R = RSSI (if available) of incoming @ref I_SIGNAL_REPORT_REQUEST message (from last hop)
* R! = RSSI (if available) of ACK to @ref I_SIGNAL_REPORT_REVERSE message received from last hop
* S = SNR (if available) of incoming @ref I_SIGNAL_REPORT_REQUEST message (from last hop)
* S! = SNR (if available) of ACK to @ref I_SIGNAL_REPORT_REVERSE message received from last hop
* P = TX powerlevel in %
* T = TX powerlevel in dBm
* U = Uplink quality (via ACK from parent node), avg. RSSI
* @return Signal report (if report is not available, INVALID_RSSI, INVALID_SNR, INVALID_PERCENT, or INVALID_LEVEL is sent instead)
*/
int16_t transportSignalReport(const char command) __attribute__((unused));
/**
* @brief Get transport signal report
* @param signalReport
* @return report
*/
int16_t transportGetSignalReport(const signalReport_t signalReport) __attribute__((unused));
#endif // MyTransport_h
/** @}*/

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 Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2019 Sensnology AB
* Full contributor list: https://github.com/mysensors/MySensors/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 Version.h
*
* @defgroup Versiongrp Version
* @ingroup internals
* @{
*
* This file defines the MySensors library version number
* Please adjust for new releases.
* These helper macros generate a numerical and alphanumerical (see http://www.semver.org) representation of the library version number, i.e
*
* | SemVer | Numerical | Comments
* |-------------|-------------|------------------
* | 2.1.0 | 0x020100FF | final
* | 2.1.1-beta | 0x02010100 | first pre-release
* | 2.1.1 | 0x020101FF | final
* | 2.2.0-beta | 0x02020000 | first pre-release
* | 2.2.0-rc.1 | 0x02020001 |
* | 2.2.0-rc.2 | 0x02020002 |
* | 2.2.0 | 0x020200FF | final
*/
#ifndef Version_h
#define Version_h
#define STR_HELPER(x) #x //!< Helper macro, STR_HELPER()
#define STR(x) STR_HELPER(x) //!< Helper macro, STR()
#define MYSENSORS_LIBRARY_VERSION_MAJOR 2 //!< Major release version
#define MYSENSORS_LIBRARY_VERSION_MINOR 3 //!< Minor release version
#define MYSENSORS_LIBRARY_VERSION_PATCH 2 //!< Patch version
#define MYSENSORS_LIBRARY_VERSION_PRERELEASE "" //!< Pre-release suffix, i.e. alpha, beta, rc.1, etc
#define MYSENSORS_LIBRARY_VERSION_PRERELEASE_NUMBER 0xFF //!< incremental counter, starting at 0x00. 0xFF for final release
#if (MYSENSORS_LIBRARY_VERSION_PRERELEASE_NUMBER != 0xFF)
#define MYSENSORS_LIBRARY_VERSION STR(MYSENSORS_LIBRARY_VERSION_MAJOR) "." STR(MYSENSORS_LIBRARY_VERSION_MINOR) "." STR(MYSENSORS_LIBRARY_VERSION_PATCH) "-" MYSENSORS_LIBRARY_VERSION_PRERELEASE //!< pre-release versioning
#else
#define MYSENSORS_LIBRARY_VERSION STR(MYSENSORS_LIBRARY_VERSION_MAJOR) "." STR(MYSENSORS_LIBRARY_VERSION_MINOR) "." STR(MYSENSORS_LIBRARY_VERSION_PATCH) //!< final release versioning
#endif
#define MYSENSORS_LIBRARY_VERSION_INT ( ((uint32_t)MYSENSORS_LIBRARY_VERSION_MAJOR) << 24 | ((uint32_t)MYSENSORS_LIBRARY_VERSION_MINOR) << 16 | ((uint32_t)MYSENSORS_LIBRARY_VERSION_PATCH) << 8 | ((uint32_t)MYSENSORS_LIBRARY_VERSION_PRERELEASE_NUMBER) ) //!< numerical versioning
#endif // Version_h
/** @}*/

View File

@@ -0,0 +1,594 @@
#include "Arduino.h"
#include "ATSHA204.h"
/* Local data and function prototypes */
static uint8_t device_pin;
#ifdef ARDUINO_ARCH_AVR
static volatile uint8_t *device_port_DDR, *device_port_OUT, *device_port_IN;
#endif
static void sha204c_calculate_crc(uint8_t length, uint8_t *data, uint8_t *crc);
static uint8_t sha204c_check_crc(uint8_t *response);
static void swi_set_signal_pin(uint8_t is_high);
static uint8_t swi_receive_bytes(uint8_t count, uint8_t *buffer);
static uint8_t swi_send_bytes(uint8_t count, uint8_t *buffer);
static uint8_t swi_send_byte(uint8_t value);
static uint8_t sha204p_receive_response(uint8_t size, uint8_t *response);
static uint8_t sha204c_resync(uint8_t size, uint8_t *response);
static uint8_t sha204c_send_and_receive(uint8_t *tx_buffer, uint8_t rx_size, uint8_t *rx_buffer,
uint8_t execution_delay, uint8_t execution_timeout);
/* SWI bit bang functions */
static void swi_set_signal_pin(uint8_t is_high)
{
SHA204_SET_OUTPUT();
if (is_high) {
SHA204_POUT_HIGH();
} else {
SHA204_POUT_LOW();
}
}
static uint8_t swi_send_bytes(uint8_t count, uint8_t *buffer)
{
uint8_t i, bit_mask;
// Disable interrupts while sending.
noInterrupts(); //swi_disable_interrupts();
// Set signal pin as output.
SHA204_POUT_HIGH();
SHA204_SET_OUTPUT();
// Wait turn around time.
delayMicroseconds(RX_TX_DELAY); //RX_TX_DELAY;
for (i = 0; i < count; i++) {
for (bit_mask = 1; bit_mask > 0; bit_mask <<= 1) {
if (bit_mask & buffer[i]) {
SHA204_POUT_LOW(); //*device_port_OUT &= ~device_pin;
delayMicroseconds(BIT_DELAY); //BIT_DELAY_1;
SHA204_POUT_HIGH(); //*device_port_OUT |= device_pin;
delayMicroseconds(7*BIT_DELAY); //BIT_DELAY_7;
} else {
// Send a zero bit.
SHA204_POUT_LOW(); //*device_port_OUT &= ~device_pin;
delayMicroseconds(BIT_DELAY); //BIT_DELAY_1;
SHA204_POUT_HIGH(); //*device_port_OUT |= device_pin;
delayMicroseconds(BIT_DELAY); //BIT_DELAY_1;
SHA204_POUT_LOW(); //*device_port_OUT &= ~device_pin;
delayMicroseconds(BIT_DELAY); //BIT_DELAY_1;
SHA204_POUT_HIGH(); //*device_port_OUT |= device_pin;
delayMicroseconds(5*BIT_DELAY); //BIT_DELAY_5;
}
}
}
interrupts(); //swi_enable_interrupts();
return SWI_FUNCTION_RETCODE_SUCCESS;
}
static uint8_t swi_send_byte(uint8_t value)
{
return swi_send_bytes(1, &value);
}
static uint8_t swi_receive_bytes(uint8_t count, uint8_t *buffer)
{
uint8_t status = SWI_FUNCTION_RETCODE_SUCCESS;
uint8_t i;
uint8_t bit_mask;
uint8_t pulse_count;
uint8_t timeout_count;
// Disable interrupts while receiving.
noInterrupts(); //swi_disable_interrupts();
// Configure signal pin as input.
SHA204_SET_INPUT();
// Receive bits and store in buffer.
for (i = 0; i < count; i++) {
for (bit_mask = 1; bit_mask > 0; bit_mask <<= 1) {
pulse_count = 0;
// Make sure that the variable below is big enough.
// Change it to uint16_t if 255 is too small, but be aware that
// the loop resolution decreases on an 8-bit controller in that case.
timeout_count = START_PULSE_TIME_OUT;
// Detect start bit.
while (--timeout_count > 0) {
// Wait for falling edge.
if (SHA204_PIN_READ() == 0) {
break;
}
}
if (timeout_count == 0) {
status = SWI_FUNCTION_RETCODE_TIMEOUT;
break;
}
do {
// Wait for rising edge.
if (SHA204_PIN_READ() != 0) {
// For an Atmel microcontroller this might be faster than "pulse_count++".
pulse_count = 1;
break;
}
} while (--timeout_count > 0);
if (pulse_count == 0) {
status = SWI_FUNCTION_RETCODE_TIMEOUT;
break;
}
// Trying to measure the time of start bit and calculating the timeout
// for zero bit detection is not accurate enough for an 8 MHz 8-bit CPU.
// So let's just wait the maximum time for the falling edge of a zero bit
// to arrive after we have detected the rising edge of the start bit.
timeout_count = ZERO_PULSE_TIME_OUT;
// Detect possible edge indicating zero bit.
do {
if (SHA204_PIN_READ() == 0) {
// For an Atmel microcontroller this might be faster than "pulse_count++".
pulse_count = 2;
break;
}
} while (--timeout_count > 0);
// Wait for rising edge of zero pulse before returning. Otherwise we might interpret
// its rising edge as the next start pulse.
if (pulse_count == 2) {
do {
if (SHA204_PIN_READ() != 0) {
break;
}
} while (timeout_count-- > 0);
}
// Update byte at current buffer index.
else {
buffer[i] |= bit_mask; // received "one" bit
}
}
if (status != SWI_FUNCTION_RETCODE_SUCCESS) {
break;
}
}
interrupts(); //swi_enable_interrupts();
if (status == SWI_FUNCTION_RETCODE_TIMEOUT) {
if (i > 0) {
// Indicate that we timed out after having received at least one byte.
status = SWI_FUNCTION_RETCODE_RX_FAIL;
}
}
return status;
}
/* Physical functions */
static uint8_t sha204p_receive_response(uint8_t size, uint8_t *response)
{
uint8_t i;
uint8_t ret_code;
for (i = 0; i < size; i++) {
response[i] = 0;
}
(void) swi_send_byte(SHA204_SWI_FLAG_TX);
ret_code = swi_receive_bytes(size, response);
if (ret_code == SWI_FUNCTION_RETCODE_SUCCESS || ret_code == SWI_FUNCTION_RETCODE_RX_FAIL) {
uint8_t count_byte;
count_byte = response[SHA204_BUFFER_POS_COUNT];
if ((count_byte < SHA204_RSP_SIZE_MIN) || (count_byte > size)) {
return SHA204_INVALID_SIZE;
}
return SHA204_SUCCESS;
}
// Translate error so that the Communication layer
// can distinguish between a real error or the
// device being busy executing a command.
if (ret_code == SWI_FUNCTION_RETCODE_TIMEOUT) {
return SHA204_RX_NO_RESPONSE;
} else {
return SHA204_RX_FAIL;
}
}
/* Communication functions */
static uint8_t sha204c_resync(uint8_t size, uint8_t *response)
{
// Try to re-synchronize without sending a Wake token
// (step 1 of the re-synchronization process).
delay(SHA204_SYNC_TIMEOUT);
uint8_t ret_code = sha204p_receive_response(size, response);
if (ret_code == SHA204_SUCCESS) {
return ret_code;
}
// We lost communication. Send a Wake pulse and try
// to receive a response (steps 2 and 3 of the
// re-synchronization process).
atsha204_sleep();
ret_code = atsha204_wakeup(response);
// Translate a return value of success into one
// that indicates that the device had to be woken up
// and might have lost its TempKey.
return (ret_code == SHA204_SUCCESS ? SHA204_RESYNC_WITH_WAKEUP : ret_code);
}
static uint8_t sha204c_send_and_receive(uint8_t *tx_buffer, uint8_t rx_size, uint8_t *rx_buffer,
uint8_t execution_delay, uint8_t execution_timeout)
{
uint8_t ret_code = SHA204_FUNC_FAIL;
uint8_t ret_code_resync;
uint8_t n_retries_send;
uint8_t n_retries_receive;
uint8_t i;
uint8_t status_byte;
uint8_t count = tx_buffer[SHA204_BUFFER_POS_COUNT];
uint8_t count_minus_crc = count - SHA204_CRC_SIZE;
uint16_t execution_timeout_us = (uint16_t) (execution_timeout * 1000) + SHA204_RESPONSE_TIMEOUT;
volatile uint16_t timeout_countdown;
// Append CRC.
sha204c_calculate_crc(count_minus_crc, tx_buffer, tx_buffer + count_minus_crc);
// Retry loop for sending a command and receiving a response.
n_retries_send = SHA204_RETRY_COUNT + 1;
while ((n_retries_send-- > 0) && (ret_code != SHA204_SUCCESS)) {
// Send command.
ret_code = swi_send_byte(SHA204_SWI_FLAG_CMD);
if (ret_code != SWI_FUNCTION_RETCODE_SUCCESS) {
ret_code = SHA204_COMM_FAIL;
} else {
ret_code = swi_send_bytes(count, tx_buffer);
}
if (ret_code != SHA204_SUCCESS) {
if (sha204c_resync(rx_size, rx_buffer) == SHA204_RX_NO_RESPONSE) {
return ret_code; // The device seems to be dead in the water.
} else {
continue;
}
}
// Wait minimum command execution time and then start polling for a response.
delay(execution_delay);
// Retry loop for receiving a response.
n_retries_receive = SHA204_RETRY_COUNT + 1;
while (n_retries_receive-- > 0) {
// Reset response buffer.
for (i = 0; i < rx_size; i++) {
rx_buffer[i] = 0;
}
// Poll for response.
timeout_countdown = execution_timeout_us;
do {
ret_code = sha204p_receive_response(rx_size, rx_buffer);
timeout_countdown -= SHA204_RESPONSE_TIMEOUT;
} while ((timeout_countdown > SHA204_RESPONSE_TIMEOUT) && (ret_code == SHA204_RX_NO_RESPONSE));
if (ret_code == SHA204_RX_NO_RESPONSE) {
// We did not receive a response. Re-synchronize and send command again.
if (sha204c_resync(rx_size, rx_buffer) == SHA204_RX_NO_RESPONSE) {
// The device seems to be dead in the water.
return ret_code;
} else {
break;
}
}
// Check whether we received a valid response.
if (ret_code == SHA204_INVALID_SIZE) {
// We see 0xFF for the count when communication got out of sync.
ret_code_resync = sha204c_resync(rx_size, rx_buffer);
if (ret_code_resync == SHA204_SUCCESS) {
// We did not have to wake up the device. Try receiving response again.
continue;
}
if (ret_code_resync == SHA204_RESYNC_WITH_WAKEUP) {
// We could re-synchronize, but only after waking up the device.
// Re-send command.
break;
} else {
// We failed to re-synchronize.
return ret_code;
}
}
// We received a response of valid size.
// Check the consistency of the response.
ret_code = sha204c_check_crc(rx_buffer);
if (ret_code == SHA204_SUCCESS) {
// Received valid response.
if (rx_buffer[SHA204_BUFFER_POS_COUNT] > SHA204_RSP_SIZE_MIN) {
// Received non-status response. We are done.
return ret_code;
}
// Received status response.
status_byte = rx_buffer[SHA204_BUFFER_POS_STATUS];
// Translate the three possible device status error codes
// into library return codes.
if (status_byte == SHA204_STATUS_BYTE_PARSE) {
return SHA204_PARSE_ERROR;
}
if (status_byte == SHA204_STATUS_BYTE_EXEC) {
return SHA204_CMD_FAIL;
}
if (status_byte == SHA204_STATUS_BYTE_COMM) {
// In case of the device status byte indicating a communication
// error this function exits the retry loop for receiving a response
// and enters the overall retry loop
// (send command / receive response).
ret_code = SHA204_STATUS_CRC;
break;
}
// Received status response from CheckMAC, DeriveKey, GenDig,
// Lock, Nonce, Pause, UpdateExtra, or Write command.
return ret_code;
}
else {
// Received response with incorrect CRC.
ret_code_resync = sha204c_resync(rx_size, rx_buffer);
if (ret_code_resync == SHA204_SUCCESS) {
// We did not have to wake up the device. Try receiving response again.
continue;
}
if (ret_code_resync == SHA204_RESYNC_WITH_WAKEUP) {
// We could re-synchronize, but only after waking up the device.
// Re-send command.
break;
} else {
// We failed to re-synchronize.
return ret_code;
}
} // block end of check response consistency
} // block end of receive retry loop
} // block end of send and receive retry loop
return ret_code;
}
/* CRC Calculator and Checker */
static void sha204c_calculate_crc(uint8_t length, uint8_t *data, uint8_t *crc)
{
uint8_t counter;
uint16_t crc_register = 0;
uint16_t polynom = 0x8005;
uint8_t shift_register;
uint8_t data_bit, crc_bit;
for (counter = 0; counter < length; counter++) {
for (shift_register = 0x01; shift_register > 0x00; shift_register <<= 1) {
data_bit = (data[counter] & shift_register) ? 1 : 0;
crc_bit = crc_register >> 15;
// Shift CRC to the left by 1.
crc_register <<= 1;
if ((data_bit ^ crc_bit) != 0) {
crc_register ^= polynom;
}
}
}
crc[0] = (uint8_t) (crc_register & 0x00FF);
crc[1] = (uint8_t) (crc_register >> 8);
}
static uint8_t sha204c_check_crc(uint8_t *response)
{
uint8_t crc[SHA204_CRC_SIZE];
uint8_t count = response[SHA204_BUFFER_POS_COUNT];
count -= SHA204_CRC_SIZE;
sha204c_calculate_crc(count, response, crc);
return (crc[0] == response[count] && crc[1] == response[count + 1])
? SHA204_SUCCESS : SHA204_BAD_CRC;
}
/* Public functions */
void atsha204_init(uint8_t pin)
{
#if defined(ARDUINO_ARCH_AVR)
device_pin = digitalPinToBitMask(pin); // Find the bit value of the pin
uint8_t port = digitalPinToPort(pin); // temoporarily used to get the next three registers
// Point to data direction register port of pin
device_port_DDR = portModeRegister(port);
// Point to output register of pin
device_port_OUT = portOutputRegister(port);
// Point to input register of pin
device_port_IN = portInputRegister(port);
#else
device_pin = pin;
#endif
}
void atsha204_idle(void)
{
swi_send_byte(SHA204_SWI_FLAG_IDLE);
}
void atsha204_sleep(void)
{
swi_send_byte(SHA204_SWI_FLAG_SLEEP);
}
uint8_t atsha204_wakeup(uint8_t *response)
{
swi_set_signal_pin(0);
delayMicroseconds(10*SHA204_WAKEUP_PULSE_WIDTH);
swi_set_signal_pin(1);
delay(SHA204_WAKEUP_DELAY);
uint8_t ret_code = sha204p_receive_response(SHA204_RSP_SIZE_MIN, response);
if (ret_code != SHA204_SUCCESS) {
return ret_code;
}
// Verify status response.
if (response[SHA204_BUFFER_POS_COUNT] != SHA204_RSP_SIZE_MIN) {
ret_code = SHA204_INVALID_SIZE;
} else if (response[SHA204_BUFFER_POS_STATUS] != SHA204_STATUS_BYTE_WAKEUP) {
ret_code = SHA204_COMM_FAIL;
} else {
if ((response[SHA204_RSP_SIZE_MIN - SHA204_CRC_SIZE] != 0x33)
|| (response[SHA204_RSP_SIZE_MIN + 1 - SHA204_CRC_SIZE] != 0x43)) {
ret_code = SHA204_BAD_CRC;
}
}
if (ret_code != SHA204_SUCCESS) {
delay(SHA204_COMMAND_EXEC_MAX);
}
return ret_code;
}
uint8_t atsha204_execute(uint8_t op_code, uint8_t param1, uint16_t param2,
uint8_t datalen1, uint8_t *data1, uint8_t tx_size, uint8_t *tx_buffer, uint8_t rx_size,
uint8_t *rx_buffer)
{
uint8_t poll_delay, poll_timeout, response_size;
uint8_t *p_buffer;
uint8_t len;
(void)tx_size;
// Supply delays and response size.
switch (op_code) {
case SHA204_GENDIG:
poll_delay = GENDIG_DELAY;
poll_timeout = GENDIG_EXEC_MAX - GENDIG_DELAY;
response_size = GENDIG_RSP_SIZE;
break;
case SHA204_HMAC:
poll_delay = HMAC_DELAY;
poll_timeout = HMAC_EXEC_MAX - HMAC_DELAY;
response_size = HMAC_RSP_SIZE;
break;
case SHA204_NONCE:
poll_delay = NONCE_DELAY;
poll_timeout = NONCE_EXEC_MAX - NONCE_DELAY;
response_size = param1 == NONCE_MODE_PASSTHROUGH
? NONCE_RSP_SIZE_SHORT : NONCE_RSP_SIZE_LONG;
break;
case SHA204_RANDOM:
poll_delay = RANDOM_DELAY;
poll_timeout = RANDOM_EXEC_MAX - RANDOM_DELAY;
response_size = RANDOM_RSP_SIZE;
break;
case SHA204_SHA:
poll_delay = SHA_DELAY;
poll_timeout = SHA_EXEC_MAX - SHA_DELAY;
response_size = param1 == SHA_INIT
? SHA_RSP_SIZE_SHORT : SHA_RSP_SIZE_LONG;
break;
case SHA204_WRITE:
poll_delay = WRITE_DELAY;
poll_timeout = WRITE_EXEC_MAX - WRITE_DELAY;
response_size = WRITE_RSP_SIZE;
break;
default:
poll_delay = 0;
poll_timeout = SHA204_COMMAND_EXEC_MAX;
response_size = rx_size;
}
// Assemble command.
len = datalen1 + SHA204_CMD_SIZE_MIN;
p_buffer = tx_buffer;
*p_buffer++ = len;
*p_buffer++ = op_code;
*p_buffer++ = param1;
*p_buffer++ = param2 & 0xFF;
*p_buffer++ = param2 >> 8;
if (datalen1 > 0) {
memcpy(p_buffer, data1, datalen1);
p_buffer += datalen1;
}
sha204c_calculate_crc(len - SHA204_CRC_SIZE, tx_buffer, p_buffer);
// Send command and receive response.
return sha204c_send_and_receive(&tx_buffer[0], response_size,
&rx_buffer[0], poll_delay, poll_timeout);
}
uint8_t atsha204_getSerialNumber(uint8_t * response)
{
uint8_t readCommand[READ_COUNT];
uint8_t readResponse[READ_4_RSP_SIZE];
/* read from bytes 0->3 of config zone */
uint8_t returnCode = atsha204_read(readCommand, readResponse, SHA204_ZONE_CONFIG, ADDRESS_SN03);
if (!returnCode) {
for (int i=0; i<4; i++) {// store bytes 0-3 into respones array
response[i] = readResponse[SHA204_BUFFER_POS_DATA+i];
}
/* read from bytes 8->11 of config zone */
returnCode = atsha204_read(readCommand, readResponse, SHA204_ZONE_CONFIG, ADDRESS_SN47);
for (int i=4; i<8; i++) {// store bytes 4-7 of SN into response array
response[i] = readResponse[SHA204_BUFFER_POS_DATA+(i-4)];
}
if (!returnCode) {
/* Finally if last two reads were successful, read byte 8 of the SN */
returnCode = atsha204_read(readCommand, readResponse, SHA204_ZONE_CONFIG, ADDRESS_SN8);
response[8] = readResponse[SHA204_BUFFER_POS_DATA]; // Byte 8 of SN should always be 0xEE
}
}
return returnCode;
}
uint8_t atsha204_read(uint8_t *tx_buffer, uint8_t *rx_buffer, uint8_t zone, uint16_t address)
{
uint8_t rx_size;
address >>= 2;
tx_buffer[SHA204_COUNT_IDX] = READ_COUNT;
tx_buffer[SHA204_OPCODE_IDX] = SHA204_READ;
tx_buffer[READ_ZONE_IDX] = zone;
tx_buffer[READ_ADDR_IDX] = (uint8_t) (address & SHA204_ADDRESS_MASK);
tx_buffer[READ_ADDR_IDX + 1] = 0;
rx_size = (zone & SHA204_ZONE_COUNT_FLAG) ? READ_32_RSP_SIZE : READ_4_RSP_SIZE;
return sha204c_send_and_receive(&tx_buffer[0], rx_size, &rx_buffer[0], READ_DELAY,
READ_EXEC_MAX - READ_DELAY);
}

View File

@@ -0,0 +1,253 @@
#ifndef ATSHA204_H
#define ATSHA204_H
#if !DOXYGEN
#include <Arduino.h>
/* This is a scaled down variant of the ATSHA204 library, tweaked to meet the specific needs of the MySensors library. */
/* Library return codes */
#define SHA204_SUCCESS ((uint8_t) 0x00) //!< Function succeeded.
#define SHA204_PARSE_ERROR ((uint8_t) 0xD2) //!< response status byte indicates parsing error
#define SHA204_CMD_FAIL ((uint8_t) 0xD3) //!< response status byte indicates command execution error
#define SHA204_STATUS_CRC ((uint8_t) 0xD4) //!< response status byte indicates CRC error
#define SHA204_STATUS_UNKNOWN ((uint8_t) 0xD5) //!< response status byte is unknown
#define SHA204_FUNC_FAIL ((uint8_t) 0xE0) //!< Function could not execute due to incorrect condition / state.
#define SHA204_GEN_FAIL ((uint8_t) 0xE1) //!< unspecified error
#define SHA204_BAD_PARAM ((uint8_t) 0xE2) //!< bad argument (out of range, null pointer, etc.)
#define SHA204_INVALID_ID ((uint8_t) 0xE3) //!< invalid device id, id not set
#define SHA204_INVALID_SIZE ((uint8_t) 0xE4) //!< Count value is out of range or greater than buffer size.
#define SHA204_BAD_CRC ((uint8_t) 0xE5) //!< incorrect CRC received
#define SHA204_RX_FAIL ((uint8_t) 0xE6) //!< Timed out while waiting for response. Number of bytes received is > 0.
#define SHA204_RX_NO_RESPONSE ((uint8_t) 0xE7) //!< Not an error while the Command layer is polling for a command response.
#define SHA204_RESYNC_WITH_WAKEUP ((uint8_t) 0xE8) //!< re-synchronization succeeded, but only after generating a Wake-up
#define SHA204_COMM_FAIL ((uint8_t) 0xF0) //!< Communication with device failed. Same as in hardware dependent modules.
#define SHA204_TIMEOUT ((uint8_t) 0xF1) //!< Timed out while waiting for response. Number of bytes received is 0.
/* bitbang_config.h */
#define PORT_ACCESS_TIME (630) //! time it takes to toggle the pin at CPU clock of 16 MHz (ns)
#define START_PULSE_WIDTH (4340) //! width of start pulse (ns)
#define BIT_DELAY (4) //! delay macro for width of one pulse (start pulse or zero pulse, in ns)
#define RX_TX_DELAY (15) //! turn around time when switching from receive to transmit
#define START_PULSE_TIME_OUT (255) //! This value is decremented while waiting for the falling edge of a start pulse.
#define ZERO_PULSE_TIME_OUT (26) //! This value is decremented while waiting for the falling edge of a zero pulse.
/* swi_phys.h */
#define SWI_FUNCTION_RETCODE_SUCCESS ((uint8_t) 0x00) //!< Communication with device succeeded.
#define SWI_FUNCTION_RETCODE_TIMEOUT ((uint8_t) 0xF1) //!< Communication timed out.
#define SWI_FUNCTION_RETCODE_RX_FAIL ((uint8_t) 0xF9) //!< Communication failed after at least one byte was received.
/* sha204_physical.h */
#define SHA204_RSP_SIZE_MIN ((uint8_t) 4) //!< minimum number of bytes in response
#define SHA204_RSP_SIZE_MAX ((uint8_t) 35) //!< maximum size of response packet
#define SHA204_BUFFER_POS_COUNT (0) //!< buffer index of count byte in command or response
#define SHA204_BUFFER_POS_DATA (1) //!< buffer index of data in response
#define SHA204_WAKEUP_PULSE_WIDTH (uint8_t) (6.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5) //! width of Wakeup pulse in 10 us units
#define SHA204_WAKEUP_DELAY (uint8_t) (3.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5) //! delay between Wakeup pulse and communication in ms
/* sha204_swi.c */
#define SHA204_SWI_FLAG_CMD ((uint8_t) 0x77) //!< flag preceding a command
#define SHA204_SWI_FLAG_TX ((uint8_t) 0x88) //!< flag requesting a response
#define SHA204_SWI_FLAG_IDLE ((uint8_t) 0xBB) //!< flag requesting to go into Idle mode
#define SHA204_SWI_FLAG_SLEEP ((uint8_t) 0xCC) //!< flag requesting to go into Sleep mode
/* sha204_comm_marshaling.h */
// command op-code definitions
#define SHA204_GENDIG ((uint8_t) 0x15) //!< GenDig command op-code
#define SHA204_HMAC ((uint8_t) 0x11) //!< HMAC command op-code
#define SHA204_NONCE ((uint8_t) 0x16) //!< Nonce command op-code
#define SHA204_RANDOM ((uint8_t) 0x1B) //!< Random command op-code
#define SHA204_READ ((uint8_t) 0x02) //!< Read command op-code
#define SHA204_SHA ((uint8_t) 0x47) //!< SHA command op-code
#define SHA204_WRITE ((uint8_t) 0x12) //!< Write command op-code
// packet size definitions
#define SHA204_RSP_SIZE_VAL ((uint8_t) 7) //!< size of response packet containing four bytes of data
// definitions for command packet indexes common to all commands
#define SHA204_COUNT_IDX ( 0) //!< command packet index for count
#define SHA204_OPCODE_IDX ( 1) //!< command packet index for op-code
#define SHA204_PARAM1_IDX ( 2) //!< command packet index for first parameter
#define SHA204_PARAM2_IDX ( 3) //!< command packet index for second parameter
#define SHA204_DATA_IDX ( 5) //!< command packet index for second parameter
// zone definitions
#define SHA204_ZONE_CONFIG ((uint8_t) 0x00) //!< Configuration zone
#define SHA204_ZONE_OTP ((uint8_t) 0x01) //!< OTP (One Time Programming) zone
#define SHA204_ZONE_DATA ((uint8_t) 0x02) //!< Data zone
#define SHA204_ZONE_MASK ((uint8_t) 0x03) //!< Zone mask
#define SHA204_ZONE_COUNT_FLAG ((uint8_t) 0x80) //!< Zone bit 7 set: Access 32 bytes, otherwise 4 bytes.
#define SHA204_ZONE_ACCESS_4 ((uint8_t) 4) //!< Read or write 4 bytes.
#define SHA204_ZONE_ACCESS_32 ((uint8_t) 32) //!< Read or write 32 bytes.
#define SHA204_ADDRESS_MASK_CONFIG ( 0x001F) //!< Address bits 5 to 7 are 0 for Configuration zone.
#define SHA204_ADDRESS_MASK_OTP ( 0x000F) //!< Address bits 4 to 7 are 0 for OTP zone.
#define SHA204_ADDRESS_MASK ( 0x007F) //!< Address bit 7 to 15 are always 0.
// GenDig command definitions
#define GENDIG_ZONE_IDX SHA204_PARAM1_IDX //!< GenDig command index for zone
#define GENDIG_KEYID_IDX SHA204_PARAM2_IDX //!< GenDig command index for key id
#define GENDIG_DATA_IDX SHA204_DATA_IDX //!< GenDig command index for optional data
#define GENDIG_COUNT SHA204_CMD_SIZE_MIN //!< GenDig command packet size without "other data"
#define GENDIG_COUNT_DATA (11) //!< GenDig command packet size with "other data"
#define GENDIG_OTHER_DATA_SIZE (4) //!< GenDig size of "other data"
#define GENDIG_ZONE_CONFIG ((uint8_t) 0) //!< GenDig zone id config
#define GENDIG_ZONE_OTP ((uint8_t) 1) //!< GenDig zone id OTP
#define GENDIG_ZONE_DATA ((uint8_t) 2) //!< GenDig zone id data
// HMAC command definitions
#define HMAC_MODE_IDX SHA204_PARAM1_IDX //!< HMAC command index for mode
#define HMAC_KEYID_IDX SHA204_PARAM2_IDX //!< HMAC command index for key id
#define HMAC_COUNT SHA204_CMD_SIZE_MIN //!< HMAC command packet size
#define HMAC_MODE_MASK ((uint8_t) 0x74) //!< HMAC mode bits 0, 1, 3, and 7 are 0.
#define HMAC_MODE_SOURCE_FLAG_MATCH ((uint8_t) 0x04) //!< HMAC mode bit 2: match TempKey.SourceFlag
// Nonce command definitions
#define NONCE_MODE_IDX SHA204_PARAM1_IDX //!< Nonce command index for mode
#define NONCE_PARAM2_IDX SHA204_PARAM2_IDX //!< Nonce command index for 2. parameter
#define NONCE_INPUT_IDX SHA204_DATA_IDX //!< Nonce command index for input data
#define NONCE_COUNT_SHORT (27) //!< Nonce command packet size for 20 bytes of data
#define NONCE_COUNT_LONG (39) //!< Nonce command packet size for 32 bytes of data
#define NONCE_MODE_MASK ((uint8_t) 3) //!< Nonce mode bits 2 to 7 are 0.
#define NONCE_MODE_SEED_UPDATE ((uint8_t) 0x00) //!< Nonce mode: update seed
#define NONCE_MODE_NO_SEED_UPDATE ((uint8_t) 0x01) //!< Nonce mode: do not update seed
#define NONCE_MODE_INVALID ((uint8_t) 0x02) //!< Nonce mode 2 is invalid.
#define NONCE_MODE_PASSTHROUGH ((uint8_t) 0x03) //!< Nonce mode: pass-through
#define NONCE_NUMIN_SIZE (20) //!< Nonce data length
#define NONCE_NUMIN_SIZE_PASSTHROUGH (32) //!< Nonce data length in pass-through mode (mode = 3)
// Random command definitions
#define RANDOM_MODE_IDX SHA204_PARAM1_IDX //!< Random command index for mode
#define RANDOM_PARAM2_IDX SHA204_PARAM2_IDX //!< Random command index for 2. parameter
#define RANDOM_COUNT SHA204_CMD_SIZE_MIN //!< Random command packet size
#define RANDOM_SEED_UPDATE ((uint8_t) 0x00) //!< Random mode for automatic seed update
#define RANDOM_NO_SEED_UPDATE ((uint8_t) 0x01) //!< Random mode for no seed update
// Read command definitions
#define READ_ZONE_IDX SHA204_PARAM1_IDX //!< Read command index for zone
#define READ_ADDR_IDX SHA204_PARAM2_IDX //!< Read command index for address
#define READ_COUNT SHA204_CMD_SIZE_MIN //!< Read command packet size
#define READ_ZONE_MASK ((uint8_t) 0x83) //!< Read zone bits 2 to 6 are 0.
#define READ_ZONE_MODE_32_BYTES ((uint8_t) 0x80) //!< Read mode: 32 bytes
// SHA command definitions
#define SHA_MODE_IDX SHA204_PARAM1_IDX //!< SHA command index for mode
#define SHA_PARAM2_IDX SHA204_PARAM2_IDX //!< SHA command index for 2. parameter
#define SHA_COUNT_SHORT SHA204_CMD_SIZE_MIN //!< SHA command packet size for init
#define SHA_COUNT_LONG (71) //!< SHA command packet size for calculation
#define SHA_MSG_SIZE (64) //!< SHA message data size
#define SHA_INIT ((uint8_t) 0x00) //!< SHA mode for init
#define SHA_CALC ((uint8_t) 0x01) //!< SHA mode for calculation
// Write command definitions
#define WRITE_ZONE_IDX SHA204_PARAM1_IDX //!< Write command index for zone
#define WRITE_ADDR_IDX SHA204_PARAM2_IDX //!< Write command index for address
#define WRITE_VALUE_IDX SHA204_DATA_IDX //!< Write command index for data
#define WRITE_MAC_VS_IDX ( 9) //!< Write command index for MAC following short data
#define WRITE_MAC_VL_IDX (37) //!< Write command index for MAC following long data
#define WRITE_COUNT_SHORT (11) //!< Write command packet size with short data and no MAC
#define WRITE_COUNT_LONG (39) //!< Write command packet size with long data and no MAC
#define WRITE_COUNT_SHORT_MAC (43) //!< Write command packet size with short data and MAC
#define WRITE_COUNT_LONG_MAC (71) //!< Write command packet size with long data and MAC
#define WRITE_MAC_SIZE (32) //!< Write MAC size
#define WRITE_ZONE_MASK ((uint8_t) 0xC3) //!< Write zone bits 2 to 5 are 0.
#define WRITE_ZONE_WITH_MAC ((uint8_t) 0x40) //!< Write zone bit 6: write encrypted with MAC
// Response size definitions
#define GENDIG_RSP_SIZE SHA204_RSP_SIZE_MIN //!< response size of GenDig command
#define HMAC_RSP_SIZE SHA204_RSP_SIZE_MAX //!< response size of HMAC command
#define NONCE_RSP_SIZE_SHORT SHA204_RSP_SIZE_MIN //!< response size of Nonce command with mode[0:1] = 3
#define NONCE_RSP_SIZE_LONG SHA204_RSP_SIZE_MAX //!< response size of Nonce command
#define RANDOM_RSP_SIZE SHA204_RSP_SIZE_MAX //!< response size of Random command
#define READ_4_RSP_SIZE SHA204_RSP_SIZE_VAL //!< response size of Read command when reading 4 bytes
#define READ_32_RSP_SIZE SHA204_RSP_SIZE_MAX //!< response size of Read command when reading 32 bytes
#define SHA_RSP_SIZE_SHORT SHA204_RSP_SIZE_MIN //!< response size of SHA command with mode[0:1] = 0
#define SHA_RSP_SIZE_LONG SHA204_RSP_SIZE_MAX //!< response size of SHA command
#define WRITE_RSP_SIZE SHA204_RSP_SIZE_MIN //!< response size of Write command
// command timing definitions for minimum execution times (ms)
#define GENDIG_DELAY ((uint8_t) (11.0 * CPU_CLOCK_DEVIATION_NEGATIVE - 0.5))
#define HMAC_DELAY ((uint8_t) (27.0 * CPU_CLOCK_DEVIATION_NEGATIVE - 0.5))
#define NONCE_DELAY ((uint8_t) (22.0 * CPU_CLOCK_DEVIATION_NEGATIVE - 0.5))
#define RANDOM_DELAY ((uint8_t) (11.0 * CPU_CLOCK_DEVIATION_NEGATIVE - 0.5))
#define READ_DELAY ((uint8_t) ( 0.4 * CPU_CLOCK_DEVIATION_NEGATIVE - 0.5))
#define SHA_DELAY ((uint8_t) (11.0 * CPU_CLOCK_DEVIATION_NEGATIVE - 0.5))
#define WRITE_DELAY ((uint8_t) ( 4.0 * CPU_CLOCK_DEVIATION_NEGATIVE - 0.5))
// command timing definitions for maximum execution times (ms)
#define GENDIG_EXEC_MAX ((uint8_t) (43.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5))
#define HMAC_EXEC_MAX ((uint8_t) (69.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5))
#define NONCE_EXEC_MAX ((uint8_t) (60.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5))
#define RANDOM_EXEC_MAX ((uint8_t) (50.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5))
#define READ_EXEC_MAX ((uint8_t) ( 4.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5))
#define SHA_EXEC_MAX ((uint8_t) (22.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5))
#define WRITE_EXEC_MAX ((uint8_t) (42.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5))
/* from sha204_config.h */
#define CPU_CLOCK_DEVIATION_POSITIVE (1.01)
#define CPU_CLOCK_DEVIATION_NEGATIVE (0.99)
#define SHA204_RETRY_COUNT (1)
#define SWI_RECEIVE_TIME_OUT ((uint16_t) 163) //! #START_PULSE_TIME_OUT in us instead of loop counts
#define SWI_US_PER_BYTE ((uint16_t) 313) //! It takes 312.5 us to send a byte (9 single-wire bits / 230400 Baud * 8 flag bits).
#define SHA204_SYNC_TIMEOUT ((uint8_t) 85)//! delay before sending a transmit flag in the synchronization routine
#define SHA204_RESPONSE_TIMEOUT ((uint16_t) SWI_RECEIVE_TIME_OUT + SWI_US_PER_BYTE) //! SWI response timeout is the sum of receive timeout and the time it takes to send the TX flag.
/* from sha204_comm.h */
#define SHA204_COMMAND_EXEC_MAX ((uint8_t) (69.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5)) //! maximum command delay
#define SHA204_CMD_SIZE_MIN ((uint8_t) 7) //! minimum number of bytes in command (from count byte to second CRC byte)
#ifndef SHA204_CMD_SIZE_MAX
#define SHA204_CMD_SIZE_MAX ((uint8_t) SHA_COUNT_LONG) //! maximum size of command packet (SHA)
#endif
#define SHA204_CRC_SIZE ((uint8_t) 2) //! number of CRC bytes
#define SHA204_BUFFER_POS_STATUS (1) //! buffer index of status byte in status response
#define SHA204_BUFFER_POS_DATA (1) //! buffer index of first data byte in data response
#define SHA204_STATUS_BYTE_WAKEUP ((uint8_t) 0x11) //! command parse error
#define SHA204_STATUS_BYTE_PARSE ((uint8_t) 0x03) //! command parse error
#define SHA204_STATUS_BYTE_EXEC ((uint8_t) 0x0F) //! command execution error
#define SHA204_STATUS_BYTE_COMM ((uint8_t) 0xFF) //! communication error
/* EEPROM Addresses */
/* Configuration Zone */
#define ADDRESS_SN03 0 // SN[0:3] are bytes 0->3 of configuration zone
#define ADDRESS_RevNum 4 // bytes 4->7 of config zone are RevNum
#define ADDRESS_SN47 8 // SN[4:7] are bytes 8->11 of config zone
#define ADDRESS_SN8 12 // SN[8] is byte 12 of config zone, should be 0xEE
#define ADDRESS_I2CEN 14 // I2C Enable, bit 0 represents I2C enable status
#define ADDRESS_I2CADD 16 // Defines I2C address of SHA204
#define ADDRESS_OTPMODE 18 // Sets the One-time-programmable mode
#define ADDRESS_SELECTOR 19 // Controls writability of Selector
#define SHA204_SERIAL_SZ 9 // The number of bytes the serial number consists of
/* Low level HW access macros */
/* function calls is not working, as it will have too much overhead */
#if !defined(ARDUINO_ARCH_AVR) // For everything else than AVR use pinMode / digitalWrite
#define SHA204_SET_OUTPUT() pinMode(device_pin, OUTPUT)
#define SHA204_SET_INPUT() pinMode(device_pin, INPUT)
#define SHA204_POUT_HIGH() digitalWrite(device_pin, HIGH)
#define SHA204_POUT_LOW() digitalWrite(device_pin, LOW)
#define SHA204_PIN_READ() digitalRead(device_pin)
#else
#define SHA204_SET_INPUT() *device_port_DDR &= ~device_pin
#define SHA204_SET_OUTPUT() *device_port_DDR |= device_pin
#define SHA204_POUT_HIGH() *device_port_OUT |= device_pin
#define SHA204_POUT_LOW() *device_port_OUT &= ~device_pin
#define SHA204_PIN_READ() (*device_port_IN & device_pin)
#endif
void atsha204_init(uint8_t pin);
void atsha204_idle(void);
void atsha204_sleep(void);
uint8_t atsha204_wakeup(uint8_t *response);
uint8_t atsha204_execute(uint8_t op_code, uint8_t param1, uint16_t param2,
uint8_t datalen1, uint8_t *data1, uint8_t tx_size,
uint8_t *tx_buffer, uint8_t rx_size, uint8_t *rx_buffer);
uint8_t atsha204_getSerialNumber(uint8_t *response);
uint8_t atsha204_read(uint8_t *tx_buffer, uint8_t *rx_buffer, uint8_t zone, uint16_t address);
#endif
#endif

View File

@@ -0,0 +1,377 @@
/* An Alternative Software Serial Library
* http://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
* Copyright (c) 2014 PJRC.COM, LLC, Paul Stoffregen, paul@pjrc.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
// Revisions are now tracked on GitHub
// https://github.com/PaulStoffregen/AltSoftSerial
//
// Version 1.2: Support Teensy 3.x
//
// Version 1.1: Improve performance in receiver code
//
// Version 1.0: Initial Release
#include "AltSoftSerial.h"
#include "config/AltSoftSerial_Boards.h"
#include "config/AltSoftSerial_Timers.h"
/****************************************/
/** Initialization **/
/****************************************/
static uint16_t ticks_per_bit=0;
bool AltSoftSerial::timing_error=false;
static uint8_t rx_state;
static uint8_t rx_byte;
static uint8_t rx_bit = 0;
static uint16_t rx_target;
static uint16_t rx_stop_ticks=0;
static volatile uint8_t rx_buffer_head;
static volatile uint8_t rx_buffer_tail;
#define RX_BUFFER_SIZE 80
static volatile uint8_t rx_buffer[RX_BUFFER_SIZE];
static volatile uint8_t tx_state=0;
static uint8_t tx_byte;
static uint8_t tx_bit;
static volatile uint8_t tx_buffer_head;
static volatile uint8_t tx_buffer_tail;
#define TX_BUFFER_SIZE 68
static volatile uint8_t tx_buffer[TX_BUFFER_SIZE];
#ifndef INPUT_PULLUP
#define INPUT_PULLUP INPUT
#endif
#define MAX_COUNTS_PER_BIT 6241 // 65536 / 10.5
void AltSoftSerial::init(uint32_t cycles_per_bit)
{
//Serial.printf("cycles_per_bit = %d\n", cycles_per_bit);
if (cycles_per_bit < MAX_COUNTS_PER_BIT) {
CONFIG_TIMER_NOPRESCALE();
} else {
cycles_per_bit /= 8;
//Serial.printf("cycles_per_bit/8 = %d\n", cycles_per_bit);
if (cycles_per_bit < MAX_COUNTS_PER_BIT) {
CONFIG_TIMER_PRESCALE_8();
} else {
#if defined(CONFIG_TIMER_PRESCALE_256)
cycles_per_bit /= 32;
//Serial.printf("cycles_per_bit/256 = %d\n", cycles_per_bit);
if (cycles_per_bit < MAX_COUNTS_PER_BIT) {
CONFIG_TIMER_PRESCALE_256();
} else {
return; // baud rate too low for AltSoftSerial
}
#elif defined(CONFIG_TIMER_PRESCALE_128)
cycles_per_bit /= 16;
//Serial.printf("cycles_per_bit/128 = %d\n", cycles_per_bit);
if (cycles_per_bit < MAX_COUNTS_PER_BIT) {
CONFIG_TIMER_PRESCALE_128();
} else {
return; // baud rate too low for AltSoftSerial
}
#else
return; // baud rate too low for AltSoftSerial
#endif
}
}
ticks_per_bit = cycles_per_bit;
rx_stop_ticks = cycles_per_bit * 37 / 4;
hwPinMode(INPUT_CAPTURE_PIN, INPUT_PULLUP);
hwDigitalWrite(OUTPUT_COMPARE_A_PIN, HIGH);
hwPinMode(OUTPUT_COMPARE_A_PIN, OUTPUT);
rx_state = 0;
rx_buffer_head = 0;
rx_buffer_tail = 0;
tx_state = 0;
tx_buffer_head = 0;
tx_buffer_tail = 0;
ENABLE_INT_INPUT_CAPTURE();
}
void AltSoftSerial::end(void)
{
DISABLE_INT_COMPARE_B();
DISABLE_INT_INPUT_CAPTURE();
flushInput();
flushOutput();
DISABLE_INT_COMPARE_A();
// TODO: restore timer to original settings?
}
/****************************************/
/** Transmission **/
/****************************************/
void AltSoftSerial::writeByte(uint8_t b)
{
uint8_t intr_state, head;
head = tx_buffer_head + 1;
if (head >= TX_BUFFER_SIZE) {
head = 0;
}
while (tx_buffer_tail == head) ; // wait until space in buffer
intr_state = SREG;
cli();
if (tx_state) {
tx_buffer[head] = b;
tx_buffer_head = head;
} else {
tx_state = 1;
tx_byte = b;
tx_bit = 0;
ENABLE_INT_COMPARE_A();
CONFIG_MATCH_CLEAR();
SET_COMPARE_A(GET_TIMER_COUNT() + 16);
}
SREG = intr_state;
}
ISR(COMPARE_A_INTERRUPT)
{
uint8_t state, byte, bit, head, tail;
uint16_t target;
state = tx_state;
byte = tx_byte;
target = GET_COMPARE_A();
while (state < 10) {
target += ticks_per_bit;
if (state < 9) {
bit = byte & 1;
} else {
bit = 1; // stopbit
}
byte >>= 1;
state++;
if (bit != tx_bit) {
if (bit) {
CONFIG_MATCH_SET();
} else {
CONFIG_MATCH_CLEAR();
}
SET_COMPARE_A(target);
tx_bit = bit;
tx_byte = byte;
tx_state = state;
// TODO: how to detect timing_error?
return;
}
}
head = tx_buffer_head;
tail = tx_buffer_tail;
if (head == tail) {
if (state == 10) {
// Wait for final stop bit to finish
tx_state = 11;
SET_COMPARE_A(target + ticks_per_bit);
} else {
tx_state = 0;
CONFIG_MATCH_NORMAL();
DISABLE_INT_COMPARE_A();
}
} else {
if (++tail >= TX_BUFFER_SIZE) {
tail = 0;
}
tx_buffer_tail = tail;
tx_byte = tx_buffer[tail];
tx_bit = 0;
CONFIG_MATCH_CLEAR();
if (state == 10) {
SET_COMPARE_A(target + ticks_per_bit);
} else {
SET_COMPARE_A(GET_TIMER_COUNT() + 16);
}
tx_state = 1;
// TODO: how to detect timing_error?
}
}
void AltSoftSerial::flushOutput(void)
{
while (tx_state) /* wait */ ;
}
/****************************************/
/** Reception **/
/****************************************/
ISR(CAPTURE_INTERRUPT)
{
uint8_t state, bit;
uint16_t capture;
capture = GET_INPUT_CAPTURE();
bit = rx_bit;
if (bit) {
CONFIG_CAPTURE_FALLING_EDGE();
rx_bit = 0;
} else {
CONFIG_CAPTURE_RISING_EDGE();
rx_bit = 0x80;
}
state = rx_state;
if (state == 0) {
if (!bit) {
uint16_t end = capture + rx_stop_ticks;
SET_COMPARE_B(end);
ENABLE_INT_COMPARE_B();
rx_target = capture + ticks_per_bit + ticks_per_bit/2;
rx_state = 1;
}
} else {
uint16_t target = rx_target;
const uint16_t offset_overflow = 65535 - ticks_per_bit;
while (1) {
const uint16_t offset = capture - target;
if (offset > offset_overflow) {
break;
}
rx_byte = (rx_byte >> 1) | rx_bit;
target += ticks_per_bit;
state++;
if (state >= 9) {
DISABLE_INT_COMPARE_B();
uint8_t head = rx_buffer_head + 1;
if (head >= RX_BUFFER_SIZE) {
head = 0;
}
if (head != rx_buffer_tail) {
rx_buffer[head] = rx_byte;
rx_buffer_head = head;
}
CONFIG_CAPTURE_FALLING_EDGE();
rx_bit = 0;
rx_state = 0;
return;
}
}
rx_target = target;
rx_state = state;
}
//if (GET_TIMER_COUNT() - capture > ticks_per_bit) AltSoftSerial::timing_error = true;
}
ISR(COMPARE_B_INTERRUPT)
{
uint8_t head, state, bit;
DISABLE_INT_COMPARE_B();
CONFIG_CAPTURE_FALLING_EDGE();
state = rx_state;
bit = rx_bit ^ 0x80;
while (state < 9) {
rx_byte = (rx_byte >> 1) | bit;
state++;
}
head = rx_buffer_head + 1;
if (head >= RX_BUFFER_SIZE) {
head = 0;
}
if (head != rx_buffer_tail) {
rx_buffer[head] = rx_byte;
rx_buffer_head = head;
}
rx_state = 0;
CONFIG_CAPTURE_FALLING_EDGE();
rx_bit = 0;
}
int AltSoftSerial::read(void)
{
uint8_t head, tail, out;
head = rx_buffer_head;
tail = rx_buffer_tail;
if (head == tail) {
return -1;
}
if (++tail >= RX_BUFFER_SIZE) {
tail = 0;
}
out = rx_buffer[tail];
rx_buffer_tail = tail;
return out;
}
int AltSoftSerial::peek(void)
{
uint8_t head, tail;
head = rx_buffer_head;
tail = rx_buffer_tail;
if (head == tail) {
return -1;
}
if (++tail >= RX_BUFFER_SIZE) {
tail = 0;
}
return rx_buffer[tail];
}
int AltSoftSerial::available(void)
{
uint8_t head, tail;
head = rx_buffer_head;
tail = rx_buffer_tail;
if (head >= tail) {
return head - tail;
}
return RX_BUFFER_SIZE + head - tail;
}
void AltSoftSerial::flushInput(void)
{
rx_buffer_head = rx_buffer_tail;
}
#ifdef ALTSS_USE_FTM0
void ftm0_isr(void)
{
uint32_t flags = FTM0_STATUS;
FTM0_STATUS = 0;
if (flags & (1<<0) && (FTM0_C0SC & 0x40)) {
altss_compare_b_interrupt();
}
if (flags & (1<<5)) {
altss_capture_interrupt();
}
if (flags & (1<<6) && (FTM0_C6SC & 0x40)) {
altss_compare_a_interrupt();
}
}
#endif

View File

@@ -0,0 +1,117 @@
/* An Alternative Software Serial Library
* http://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
* Copyright (c) 2014 PJRC.COM, LLC, Paul Stoffregen, paul@pjrc.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef AltSoftSerial_h
#define AltSoftSerial_h
#include <inttypes.h>
#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#include "pins_arduino.h"
#endif
#if defined(__arm__) && defined(CORE_TEENSY)
#define ALTSS_BASE_FREQ F_BUS
#else
#define ALTSS_BASE_FREQ F_CPU
#endif
/** AltSoftSerial class */
class AltSoftSerial : public Stream
{
public:
AltSoftSerial() { } //!< Constructor
~AltSoftSerial()
{
end(); //!< Destructor
}
static void begin(uint32_t baud)
{
init((ALTSS_BASE_FREQ + baud / 2) / baud); //!< begin
}
static void end(); //!< end
int peek(); //!< peek
int read(); //!< read
int available(); //!< available
#if ARDUINO >= 100
size_t write(uint8_t byte)
{
writeByte(byte); //!< write
return 1;
}
void flush()
{
flushOutput(); //!< flush
}
#else
void write(uint8_t byte)
{
writeByte(byte); //!< write
}
void flush()
{
flushInput(); //!< flush
}
#endif
using Print::write;
static void flushInput(); //!< flushInput
static void flushOutput(); //!< flushOutput
// for drop-in compatibility with NewSoftSerial, rxPin & txPin ignored
AltSoftSerial(uint8_t rxPin, uint8_t txPin, bool inverse = false)
{
(void)rxPin; //!< AltSoftSerial
(void)txPin;
(void)inverse;
}
bool listen()
{
return false; //!< listen
}
bool isListening()
{
return true; //!< isListening
}
bool overflow()
{
bool r = timing_error; //!< overflow
timing_error = false;
return r;
}
static int library_version()
{
return 1; //!< library_version
}
static void enable_timer0(bool enable)
{
(void)enable; //!< enable_timer0
}
static bool timing_error; //!< timing_error
private:
static void init(uint32_t cycles_per_bit);
static void writeByte(uint8_t byte);
};
#endif

View File

@@ -0,0 +1,9 @@
# AltSoftSerial Library
Improved software emulated serial, using hardware timers for precise signal
timing and availability of CPU time for other libraries to respond to interrupts
during data AltSoftSerial data transmission and reception.
http://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
![AltSoftSerial on Teensy 2.0](http://www.pjrc.com/teensy/td_libs_AltSoftSerial_2.jpg)

View File

@@ -0,0 +1,147 @@
/* An Alternative Software Serial Library
* http://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
* Copyright (c) 2014 PJRC.COM, LLC, Paul Stoffregen, paul@pjrc.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
// Teensy 2.0
//
#if defined(__AVR_ATmega32U4__) && defined(CORE_TEENSY)
//#define ALTSS_USE_TIMER1
//#define INPUT_CAPTURE_PIN 22 // receive
//#define OUTPUT_COMPARE_A_PIN 14 // transmit
//#define OUTPUT_COMPARE_B_PIN 15 // unusable PWM
//#define OUTPUT_COMPARE_C_PIN 4 // unusable PWM
#define ALTSS_USE_TIMER3
#define INPUT_CAPTURE_PIN 10 // receive
#define OUTPUT_COMPARE_A_PIN 9 // transmit
// Teensy++ 2.0
//
#elif defined(__AVR_AT90USB1286__) && defined(CORE_TEENSY)
#define ALTSS_USE_TIMER1
#define INPUT_CAPTURE_PIN 4 // receive
#define OUTPUT_COMPARE_A_PIN 25 // transmit
#define OUTPUT_COMPARE_B_PIN 26 // unusable PWM
#define OUTPUT_COMPARE_C_PIN 27 // unusable PWM
//#define ALTSS_USE_TIMER3
//#define INPUT_CAPTURE_PIN 17 // receive
//#define OUTPUT_COMPARE_A_PIN 16 // transmit
//#define OUTPUT_COMPARE_B_PIN 15 // unusable PWM
//#define OUTPUT_COMPARE_C_PIN 14 // unusable PWM
// Teensy 3.x
//
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)
#define ALTSS_USE_FTM0
#define INPUT_CAPTURE_PIN 20 // receive (FTM0_CH5)
#define OUTPUT_COMPARE_A_PIN 21 // transmit (FTM0_CH6)
#define OUTPUT_COMPARE_B_PIN 22 // unusable PWM (FTM0_CH0)
#define OUTPUT_COMPARE_C_PIN 23 // PWM usable fixed freq
#define OUTPUT_COMPARE_D_PIN 5 // PWM usable fixed freq
#define OUTPUT_COMPARE_E_PIN 6 // PWM usable fixed freq
#define OUTPUT_COMPARE_F_PIN 9 // PWM usable fixed freq
#define OUTPUT_COMPARE_G_PIN 10 // PWM usable fixed freq
// Wiring-S
//
#elif defined(__AVR_ATmega644P__) && defined(WIRING)
#define ALTSS_USE_TIMER1
#define INPUT_CAPTURE_PIN 6 // receive
#define OUTPUT_COMPARE_A_PIN 5 // transmit
#define OUTPUT_COMPARE_B_PIN 4 // unusable PWM
// Arduino Uno, Duemilanove, LilyPad, etc
//
#elif defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__)
#define ALTSS_USE_TIMER1
#define INPUT_CAPTURE_PIN 8 // receive
#define OUTPUT_COMPARE_A_PIN 9 // transmit
#define OUTPUT_COMPARE_B_PIN 10 // unusable PWM
// Arduino Leonardo & Yun (from Cristian Maglie)
//
#elif defined(ARDUINO_AVR_YUN) || defined(ARDUINO_AVR_LEONARDO) || defined(__AVR_ATmega32U4__)
//#define ALTSS_USE_TIMER1
//#define INPUT_CAPTURE_PIN 4 // receive
//#define OUTPUT_COMPARE_A_PIN 9 // transmit
//#define OUTPUT_COMPARE_B_PIN 10 // unusable PWM
//#define OUTPUT_COMPARE_C_PIN 11 // unusable PWM
#define ALTSS_USE_TIMER3
#define INPUT_CAPTURE_PIN 13 // receive
#define OUTPUT_COMPARE_A_PIN 5 // transmit
// Arduino Mega
//
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
//#define ALTSS_USE_TIMER4
//#define INPUT_CAPTURE_PIN 49 // receive
//#define OUTPUT_COMPARE_A_PIN 6 // transmit
//#define OUTPUT_COMPARE_B_PIN 7 // unusable PWM
//#define OUTPUT_COMPARE_C_PIN 8 // unusable PWM
#define ALTSS_USE_TIMER5
#define INPUT_CAPTURE_PIN 48 // receive
#define OUTPUT_COMPARE_A_PIN 46 // transmit
#define OUTPUT_COMPARE_B_PIN 45 // unusable PWM
#define OUTPUT_COMPARE_C_PIN 44 // unusable PWM
// EnviroDIY Mayfly, Sodaq Mbili
#elif defined ARDUINO_AVR_ENVIRODIY_MAYFLY || defined ARDUINO_AVR_SODAQ_MBILI
#define ALTSS_USE_TIMER1
#define INPUT_CAPTURE_PIN 6 // receive
#define OUTPUT_COMPARE_A_PIN 5 // transmit
#define OUTPUT_COMPARE_B_PIN 4 // unusable PWM
// Sanguino, Mighty 1284
#elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__)
#define ALTSS_USE_TIMER1
#define INPUT_CAPTURE_PIN 14 // receive
#define OUTPUT_COMPARE_A_PIN 13 // transmit
#define OUTPUT_COMPARE_B_PIN 12 // unusable PWM
// Unknown board
#else
#error "Please define your board timer and pins"
#endif

View File

@@ -0,0 +1,187 @@
/* An Alternative Software Serial Library
* http://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
* Copyright (c) 2014 PJRC.COM, LLC, Paul Stoffregen, paul@pjrc.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#if defined(ALTSS_USE_TIMER1)
#define CONFIG_TIMER_NOPRESCALE() (TIMSK1 = 0, TCCR1A = 0, TCCR1B = (1<<ICNC1) | (1<<CS10))
#define CONFIG_TIMER_PRESCALE_8() (TIMSK1 = 0, TCCR1A = 0, TCCR1B = (1<<ICNC1) | (1<<CS11))
#define CONFIG_TIMER_PRESCALE_256() (TIMSK1 = 0, TCCR1A = 0, TCCR1B = (1<<ICNC1) | (1<<CS12))
#define CONFIG_MATCH_NORMAL() (TCCR1A = TCCR1A & ~((1<<COM1A1) | (1<<COM1A0)))
#define CONFIG_MATCH_TOGGLE() (TCCR1A = (TCCR1A & ~(1<<COM1A1)) | (1<<COM1A0))
#define CONFIG_MATCH_CLEAR() (TCCR1A = (TCCR1A | (1<<COM1A1)) & ~(1<<COM1A0))
#define CONFIG_MATCH_SET() (TCCR1A = TCCR1A | ((1<<COM1A1) | (1<<COM1A0)))
#define CONFIG_CAPTURE_FALLING_EDGE() (TCCR1B &= ~(1<<ICES1))
#define CONFIG_CAPTURE_RISING_EDGE() (TCCR1B |= (1<<ICES1))
#define ENABLE_INT_INPUT_CAPTURE() (TIFR1 = (1<<ICF1), TIMSK1 = (1<<ICIE1))
#define ENABLE_INT_COMPARE_A() (TIFR1 = (1<<OCF1A), TIMSK1 |= (1<<OCIE1A))
#define ENABLE_INT_COMPARE_B() (TIFR1 = (1<<OCF1B), TIMSK1 |= (1<<OCIE1B))
#define DISABLE_INT_INPUT_CAPTURE() (TIMSK1 &= ~(1<<ICIE1))
#define DISABLE_INT_COMPARE_A() (TIMSK1 &= ~(1<<OCIE1A))
#define DISABLE_INT_COMPARE_B() (TIMSK1 &= ~(1<<OCIE1B))
#define GET_TIMER_COUNT() (TCNT1)
#define GET_INPUT_CAPTURE() (ICR1)
#define GET_COMPARE_A() (OCR1A)
#define GET_COMPARE_B() (OCR1B)
#define SET_COMPARE_A(val) (OCR1A = (val))
#define SET_COMPARE_B(val) (OCR1B = (val))
#define CAPTURE_INTERRUPT TIMER1_CAPT_vect
#define COMPARE_A_INTERRUPT TIMER1_COMPA_vect
#define COMPARE_B_INTERRUPT TIMER1_COMPB_vect
#elif defined(ALTSS_USE_TIMER3)
#define CONFIG_TIMER_NOPRESCALE() (TIMSK3 = 0, TCCR3A = 0, TCCR3B = (1<<ICNC3) | (1<<CS30))
#define CONFIG_TIMER_PRESCALE_8() (TIMSK3 = 0, TCCR3A = 0, TCCR3B = (1<<ICNC3) | (1<<CS31))
#define CONFIG_TIMER_PRESCALE_256() (TIMSK3 = 0, TCCR3A = 0, TCCR3B = (1<<ICNC3) | (1<<CS32))
#define CONFIG_MATCH_NORMAL() (TCCR3A = TCCR3A & ~((1<<COM3A1) | (1<<COM3A0)))
#define CONFIG_MATCH_TOGGLE() (TCCR3A = (TCCR3A & ~(1<<COM3A1)) | (1<<COM3A0))
#define CONFIG_MATCH_CLEAR() (TCCR3A = (TCCR3A | (1<<COM3A1)) & ~(1<<COM3A0))
#define CONFIG_MATCH_SET() (TCCR3A = TCCR3A | ((1<<COM3A1) | (1<<COM3A0)))
#define CONFIG_CAPTURE_FALLING_EDGE() (TCCR3B &= ~(1<<ICES3))
#define CONFIG_CAPTURE_RISING_EDGE() (TCCR3B |= (1<<ICES3))
#define ENABLE_INT_INPUT_CAPTURE() (TIFR3 = (1<<ICF3), TIMSK3 = (1<<ICIE3))
#define ENABLE_INT_COMPARE_A() (TIFR3 = (1<<OCF3A), TIMSK3 |= (1<<OCIE3A))
#define ENABLE_INT_COMPARE_B() (TIFR3 = (1<<OCF3B), TIMSK3 |= (1<<OCIE3B))
#define DISABLE_INT_INPUT_CAPTURE() (TIMSK3 &= ~(1<<ICIE3))
#define DISABLE_INT_COMPARE_A() (TIMSK3 &= ~(1<<OCIE3A))
#define DISABLE_INT_COMPARE_B() (TIMSK3 &= ~(1<<OCIE3B))
#define GET_TIMER_COUNT() (TCNT3)
#define GET_INPUT_CAPTURE() (ICR3)
#define GET_COMPARE_A() (OCR3A)
#define GET_COMPARE_B() (OCR3B)
#define SET_COMPARE_A(val) (OCR3A = (val))
#define SET_COMPARE_B(val) (OCR3B = (val))
#define CAPTURE_INTERRUPT TIMER3_CAPT_vect
#define COMPARE_A_INTERRUPT TIMER3_COMPA_vect
#define COMPARE_B_INTERRUPT TIMER3_COMPB_vect
#elif defined(ALTSS_USE_TIMER4)
#define CONFIG_TIMER_NOPRESCALE() (TIMSK4 = 0, TCCR4A = 0, TCCR4B = (1<<ICNC4) | (1<<CS40))
#define CONFIG_TIMER_PRESCALE_8() (TIMSK4 = 0, TCCR4A = 0, TCCR4B = (1<<ICNC4) | (1<<CS41))
#define CONFIG_TIMER_PRESCALE_256() (TIMSK4 = 0, TCCR4A = 0, TCCR4B = (1<<ICNC4) | (1<<CS42))
#define CONFIG_MATCH_NORMAL() (TCCR4A = TCCR4A & ~((1<<COM4A1) | (1<<COM4A0)))
#define CONFIG_MATCH_TOGGLE() (TCCR4A = (TCCR4A & ~(1<<COM4A1)) | (1<<COM4A0))
#define CONFIG_MATCH_CLEAR() (TCCR4A = (TCCR4A | (1<<COM4A1)) & ~(1<<COM4A0))
#define CONFIG_MATCH_SET() (TCCR4A = TCCR4A | ((1<<COM4A1) | (1<<COM4A0)))
#define CONFIG_CAPTURE_FALLING_EDGE() (TCCR4B &= ~(1<<ICES4))
#define CONFIG_CAPTURE_RISING_EDGE() (TCCR4B |= (1<<ICES4))
#define ENABLE_INT_INPUT_CAPTURE() (TIFR4 = (1<<ICF4), TIMSK4 = (1<<ICIE4))
#define ENABLE_INT_COMPARE_A() (TIFR4 = (1<<OCF4A), TIMSK4 |= (1<<OCIE4A))
#define ENABLE_INT_COMPARE_B() (TIFR4 = (1<<OCF4B), TIMSK4 |= (1<<OCIE4B))
#define DISABLE_INT_INPUT_CAPTURE() (TIMSK4 &= ~(1<<ICIE4))
#define DISABLE_INT_COMPARE_A() (TIMSK4 &= ~(1<<OCIE4A))
#define DISABLE_INT_COMPARE_B() (TIMSK4 &= ~(1<<OCIE4B))
#define GET_TIMER_COUNT() (TCNT4)
#define GET_INPUT_CAPTURE() (ICR4)
#define GET_COMPARE_A() (OCR4A)
#define GET_COMPARE_B() (OCR4B)
#define SET_COMPARE_A(val) (OCR4A = (val))
#define SET_COMPARE_B(val) (OCR4B = (val))
#define CAPTURE_INTERRUPT TIMER4_CAPT_vect
#define COMPARE_A_INTERRUPT TIMER4_COMPA_vect
#define COMPARE_B_INTERRUPT TIMER4_COMPB_vect
#elif defined(ALTSS_USE_TIMER5)
#define CONFIG_TIMER_NOPRESCALE() (TIMSK5 = 0, TCCR5A = 0, TCCR5B = (1<<ICNC5) | (1<<CS50))
#define CONFIG_TIMER_PRESCALE_8() (TIMSK5 = 0, TCCR5A = 0, TCCR5B = (1<<ICNC5) | (1<<CS51))
#define CONFIG_TIMER_PRESCALE_256() (TIMSK5 = 0, TCCR5A = 0, TCCR5B = (1<<ICNC5) | (1<<CS52))
#define CONFIG_MATCH_NORMAL() (TCCR5A = TCCR5A & ~((1<<COM5A1) | (1<<COM5A0)))
#define CONFIG_MATCH_TOGGLE() (TCCR5A = (TCCR5A & ~(1<<COM5A1)) | (1<<COM5A0))
#define CONFIG_MATCH_CLEAR() (TCCR5A = (TCCR5A | (1<<COM5A1)) & ~(1<<COM5A0))
#define CONFIG_MATCH_SET() (TCCR5A = TCCR5A | ((1<<COM5A1) | (1<<COM5A0)))
#define CONFIG_CAPTURE_FALLING_EDGE() (TCCR5B &= ~(1<<ICES5))
#define CONFIG_CAPTURE_RISING_EDGE() (TCCR5B |= (1<<ICES5))
#define ENABLE_INT_INPUT_CAPTURE() (TIFR5 = (1<<ICF5), TIMSK5 = (1<<ICIE5))
#define ENABLE_INT_COMPARE_A() (TIFR5 = (1<<OCF5A), TIMSK5 |= (1<<OCIE5A))
#define ENABLE_INT_COMPARE_B() (TIFR5 = (1<<OCF5B), TIMSK5 |= (1<<OCIE5B))
#define DISABLE_INT_INPUT_CAPTURE() (TIMSK5 &= ~(1<<ICIE5))
#define DISABLE_INT_COMPARE_A() (TIMSK5 &= ~(1<<OCIE5A))
#define DISABLE_INT_COMPARE_B() (TIMSK5 &= ~(1<<OCIE5B))
#define GET_TIMER_COUNT() (TCNT5)
#define GET_INPUT_CAPTURE() (ICR5)
#define GET_COMPARE_A() (OCR5A)
#define GET_COMPARE_B() (OCR5B)
#define SET_COMPARE_A(val) (OCR5A = (val))
#define SET_COMPARE_B(val) (OCR5B = (val))
#define CAPTURE_INTERRUPT TIMER5_CAPT_vect
#define COMPARE_A_INTERRUPT TIMER5_COMPA_vect
#define COMPARE_B_INTERRUPT TIMER5_COMPB_vect
#elif defined(ALTSS_USE_FTM0)
// CH5 = input capture (input, pin 20)
// CH6 = compare a (output, pin 21)
// CH0 = compare b (input timeout)
#define CONFIG_TIMER_NOPRESCALE() FTM0_SC = 0; FTM0_CNT = 0; FTM0_MOD = 0xFFFF; \
FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); \
digitalWriteFast(21, HIGH); \
NVIC_SET_PRIORITY(IRQ_FTM0, 48); \
FTM0_C0SC = 0x18; \
NVIC_ENABLE_IRQ(IRQ_FTM0);
#define CONFIG_TIMER_PRESCALE_8() FTM0_SC = 0; FTM0_CNT = 0; FTM0_MOD = 0xFFFF; \
FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(3); \
digitalWriteFast(21, HIGH); \
NVIC_SET_PRIORITY(IRQ_FTM0, 48); \
FTM0_C0SC = 0x18; \
NVIC_ENABLE_IRQ(IRQ_FTM0);
#define CONFIG_TIMER_PRESCALE_128() FTM0_SC = 0; FTM0_CNT = 0; FTM0_MOD = 0xFFFF; \
FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(7); \
digitalWriteFast(21, HIGH); \
NVIC_SET_PRIORITY(IRQ_FTM0, 48); \
FTM0_C0SC = 0x18; \
NVIC_ENABLE_IRQ(IRQ_FTM0);
#define CONFIG_MATCH_NORMAL() (FTM0_C6SC = 0)
#define CONFIG_MATCH_TOGGLE() (FTM0_C6SC = (FTM0_C6SC & 0xC3) | 0x14)
#define CONFIG_MATCH_CLEAR() (FTM0_C6SC = (FTM0_C6SC & 0xC3) | 0x18)
#define CONFIG_MATCH_SET() (FTM0_C6SC = (FTM0_C6SC & 0xC3) | 0x1C)
#define CONFIG_CAPTURE_FALLING_EDGE() (FTM0_C5SC = (FTM0_C5SC & 0xC3) | 0x08)
#define CONFIG_CAPTURE_RISING_EDGE() (FTM0_C5SC = (FTM0_C5SC & 0xC3) | 0x04)
#define ENABLE_INT_INPUT_CAPTURE() FTM0_C5SC = 0x48; \
CORE_PIN20_CONFIG = PORT_PCR_MUX(4)|PORT_PCR_PE|PORT_PCR_PS
#define ENABLE_INT_COMPARE_A() FTM0_C6SC |= 0x40; \
CORE_PIN21_CONFIG = PORT_PCR_MUX(4)|PORT_PCR_DSE|PORT_PCR_SRE
#define ENABLE_INT_COMPARE_B() (FTM0_C0SC = 0x58)
#define DISABLE_INT_INPUT_CAPTURE() FTM0_C5SC &= ~0x40; \
CORE_PIN20_CONFIG = PORT_PCR_MUX(1)|PORT_PCR_PE|PORT_PCR_PS
#define DISABLE_INT_COMPARE_A() FTM0_C6SC &= ~0x40; \
CORE_PIN21_CONFIG = PORT_PCR_MUX(1)|PORT_PCR_DSE|PORT_PCR_SRE; \
digitalWriteFast(21, HIGH)
#define DISABLE_INT_COMPARE_B() (FTM0_C0SC &= ~0x40)
#define GET_TIMER_COUNT() (FTM0_CNT)
#define GET_INPUT_CAPTURE() (FTM0_C5V)
#define GET_COMPARE_A() (FTM0_C6V)
#define GET_COMPARE_B() (FTM0_C0V)
#define SET_COMPARE_A(val) (FTM0_C6V = val)
#define SET_COMPARE_B(val) if (FTM0_C0SC & FTM_CSC_CHF) FTM0_C0SC = 0x18; \
do { FTM0_C0V = (val); } while (FTM0_C0V != (val));
#define CAPTURE_INTERRUPT altss_capture_interrupt
#define COMPARE_A_INTERRUPT altss_compare_a_interrupt
#define COMPARE_B_INTERRUPT altss_compare_b_interrupt
#ifdef ISR
#undef ISR
#endif
#define ISR(f) static void f (void)
#endif

View File

@@ -0,0 +1,64 @@
Please use this form only to report code defects or bugs.
For any question, even questions directly pertaining to this code, post your question on the forums related to the board you are using.
Arduino: forum.arduino.cc
Teensy: forum.pjrc.com
ESP8266: www.esp8266.com
ESP32: www.esp32.com
Adafruit Feather/Metro/Trinket: forums.adafruit.com
Particle Photon: community.particle.io
If you are experiencing trouble but not certain of the cause, or need help using this code, ask on the appropriate forum. This is not the place to ask for support or help, even directly related to this code. Only use this form you are certain you have discovered a defect in this code!
Please verify the problem occurs when using the very latest version, using the newest version of Arduino and any other related software.
----------------------------- Remove above -----------------------------
### Description
Describe your problem.
### Steps To Reproduce Problem
Please give detailed instructions needed for anyone to attempt to reproduce the problem.
### Hardware & Software
Board
Shields / modules used
Arduino IDE version
Teensyduino version (if using Teensy)
Version info & package name (from Tools > Boards > Board Manager)
Operating system & version
Any other software or hardware?
### Arduino Sketch
```cpp
// Change the code below by your sketch (please try to give the smallest code which demonstrates the problem)
#include <Arduino.h>
// libraries: give links/details so anyone can compile your code for the same result
void setup() {
}
void loop() {
}
```
### Errors or Incorrect Output
If you see any errors or incorrect output, please show it here. Please use copy & paste to give an exact copy of the message. Details matter, so please show (not merely describe) the actual message or error exactly as it appears.

View File

@@ -0,0 +1,40 @@
#include <AltSoftSerial.h>
// AltSoftSerial always uses these pins:
//
// Board Transmit Receive PWM Unusable
// ----- -------- ------- ------------
// Teensy 3.0 & 3.1 21 20 22
// Teensy 2.0 9 10 (none)
// Teensy++ 2.0 25 4 26, 27
// Arduino Uno 9 8 10
// Arduino Leonardo 5 13 (none)
// Arduino Mega 46 48 44, 45
// Wiring-S 5 6 4
// Sanguino 13 14 12
// This example code is in the public domain.
AltSoftSerial altSerial;
void setup() {
Serial.begin(9600);
while (!Serial) ; // wait for Arduino Serial Monitor to open
Serial.println("AltSoftSerial Test Begin");
altSerial.begin(9600);
altSerial.println("Hello World");
}
void loop() {
char c;
if (Serial.available()) {
c = Serial.read();
altSerial.print(c);
}
if (altSerial.available()) {
c = altSerial.read();
Serial.print(c);
}
}

View File

@@ -0,0 +1,56 @@
// AltSoftSerial Receive Test
//
// Transmit data with Serial1 and try to receive
// it with AltSoftSerial. You must connect a wire
// from Serial1 TX to AltSoftSerial RX.
#include <AltSoftSerial.h>
AltSoftSerial altser;
const int mybaud = 9600;
// Board Serial1 TX AltSoftSerial RX
// ----- ---------- ----------------
// Teensy 3.x 1 20
// Teensy 2.0 8 (D3) 10 (C7)
// Teensy++ 2.0 3 (D3) 4 (D4)
// Arduino Leonardo 1 13
// Arduino Mega 18 48
// Serial1 on AVR @ 16 MHz minimum baud is 245
// Serial1 on Teensy 3.2 @ 96 MHz minimum baud is 733
// This example code is in the public domain.
byte sentbyte;
unsigned long prevmillis;
byte testbyte=0xF0;
void setup()
{
delay(200);
Serial.begin(9600);
while (!Serial) ; // wait for Arduino Serial Monitor
Serial1.begin(mybaud); // connect a wire from TX1
altser.begin(mybaud); // to AltSoftSerial RX
Serial.println("AltSoftSerial Receive Test");
prevmillis = millis();
}
void loop()
{
// transmit a test byte on Serial 1
if (millis() - prevmillis > 250) {
sentbyte = testbyte++;
Serial1.write(sentbyte);
prevmillis = millis();
}
// attempt to receive it by AltSoftSerial
if (altser.available() > 0) {
byte b = altser.read();
Serial.println(b);
if (b != sentbyte) {
Serial.println("***** ERROR *****");
}
}
}

View File

@@ -0,0 +1,97 @@
#include <AltSoftSerial.h>
// AltSoftSerial show configuration example
// Will print library configuration to default serial port
// Open your serial monitor to see config for your board
// Printout would repeat every 10sec (just in case you missed it somehow)
// Print Configuration
// -----------------------
// Direct include AltSoftSerial internals to obtaion PIN setup (you do not need this in regular code)
// This example code is in the public domain.
#include <config/AltSoftSerial_Boards.h>
void printAltSoftSerialSetup(Stream &port)
{
#define PRINT_PFX "AltSoftSerial:"
#define PRINT_PIN_NAME(pin,name) { char buffer[128+1]; sprintf(buffer, PRINT_PFX "PIN:%2d %s", (int)pin, (const char*)name); port.println(buffer); }
port.println(PRINT_PFX "Setup info: begin");
#if defined(ALTSS_USE_FTM0)
port.println(PRINT_PFX "USE FTM0");
#endif
#if defined(ALTSS_USE_TIMER1)
port.println(PRINT_PFX "USE TIMER1");
#endif
#if defined(ALTSS_USE_TIMER2)
port.println(PRINT_PFX "USE TIMER2");
#endif
#if defined(ALTSS_USE_TIMER3)
port.println(PRINT_PFX "USE TIMER3");
#endif
#if defined(ALTSS_USE_TIMER4)
port.println(PRINT_PFX "USE TIMER4");
#endif
#if defined(ALTSS_USE_TIMER5)
port.println(PRINT_PFX "USE TIMER5");
#endif
#if defined(INPUT_CAPTURE_PIN)
PRINT_PIN_NAME(INPUT_CAPTURE_PIN,"RX");
#endif
#if defined(OUTPUT_COMPARE_A_PIN)
PRINT_PIN_NAME(OUTPUT_COMPARE_A_PIN,"TX");
#endif
#if defined(OUTPUT_COMPARE_B_PIN)
PRINT_PIN_NAME(OUTPUT_COMPARE_B_PIN,"(unused PWM)");
#endif
#if defined(OUTPUT_COMPARE_C_PIN)
PRINT_PIN_NAME(OUTPUT_COMPARE_C_PIN,"(unused PWM)");
#endif
#if defined(OUTPUT_COMPARE_D_PIN)
PRINT_PIN_NAME(OUTPUT_COMPARE_D_PIN,"(unused PWM)");
#endif
#if defined(OUTPUT_COMPARE_E_PIN)
PRINT_PIN_NAME(OUTPUT_COMPARE_E_PIN,"(unused PWM)");
#endif
#if defined(OUTPUT_COMPARE_F_PIN)
PRINT_PIN_NAME(OUTPUT_COMPARE_F_PIN,"(unused PWM)");
#endif
port.println(PRINT_PFX "Setup info: end");
#undef PRINT_PIN_NAME
#undef PRINT_PFX
}
void setup()
{
// Open default serial to dump config to
Serial.begin(9600);
while (!Serial) ; // wait for serial monitor
printAltSoftSerialSetup(Serial);
}
void loop()
{
// Repeat every 10 sec (just in case)
delay(10000);
Serial.println("");
printAltSoftSerialSetup(Serial);
}

View File

@@ -0,0 +1,4 @@
AltSoftSerial KEYWORD1
active KEYWORD2
overflow KEYWORD2
library_version KEYWORD2

View File

@@ -0,0 +1,197 @@
/*
CircularBuffer - An Arduino circular buffering library for arbitrary types.
Created by Ivo Pullens, Emmission, 2014-2016 -- www.emmission.nl
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file CircularBuffer.h
*
* Circular buffering for arbitrary types.
*/
#ifndef CircularBuffer_h
#define CircularBuffer_h
/**
* The circular buffer class.
* Pass the datatype to be stored in the buffer as template parameter.
*/
template <class T> class CircularBuffer
{
public:
/**
* Constructor
* @param buffer Preallocated buffer of at least size records.
* @param size Number of records available in the buffer.
*/
CircularBuffer(T* buffer, const uint8_t size )
: m_size(size), m_buff(buffer)
{
clear();
}
/**
* Clear all entries in the circular buffer.
*/
void clear(void)
{
MY_CRITICAL_SECTION {
m_front = 0;
m_fill = 0;
}
}
/**
* Test if the circular buffer is empty.
* @return True, when empty.
*/
inline bool empty(void) const
{
bool empty;
MY_CRITICAL_SECTION {
empty = !m_fill;
}
return empty;
}
/**
* Test if the circular buffer is full.
* @return True, when full.
*/
inline bool full(void) const
{
bool full;
MY_CRITICAL_SECTION {
full = m_fill == m_size;
}
return full;
}
/**
* Return the number of records stored in the buffer.
* @return number of records.
*/
inline uint8_t available(void) const
{
uint8_t ret_value;
MY_CRITICAL_SECTION {
ret_value = m_fill;
}
return ret_value;
}
/**
* Aquire unused record on front of the buffer, for writing.
* After filling the record, it has to be pushed to actually
* add it to the buffer.
* @return Pointer to record, or NULL when buffer is full.
*/
T* getFront(void) const
{
MY_CRITICAL_SECTION {
if (!full())
{
return get(m_front);
}
}
return static_cast<T*>(NULL);
}
/**
* Push record to front of the buffer.
* @param record Record to push. If record was aquired previously (using getFront) its
* data will not be copied as it is already present in the buffer.
* @return True, when record was pushed successfully.
*/
bool pushFront(T* record)
{
MY_CRITICAL_SECTION {
if (!full())
{
T* f = get(m_front);
if (f != record) {
*f = *record;
}
m_front = (m_front+1) % m_size;
m_fill++;
return true;
}
}
return false;
}
/**
* Aquire record on back of the buffer, for reading.
* After reading the record, it has to be pop'ed to actually
* remove it from the buffer.
* @return Pointer to record, or NULL when buffer is empty.
*/
T* getBack(void) const
{
MY_CRITICAL_SECTION {
if (!empty())
{
return get(back());
}
}
return static_cast<T*>(NULL);
}
/**
* Remove record from back of the buffer.
* @return True, when record was pop'ed successfully.
*/
bool popBack(void)
{
MY_CRITICAL_SECTION {
if (!empty())
{
m_fill--;
return true;
}
}
return false;
}
protected:
/**
* Internal getter for records.
* @param idx Record index in buffer.
* @return Ptr to record.
*/
inline T * get(const uint8_t idx) const
{
return &(m_buff[idx]);
}
/**
* Internal getter for index of last used record in buffer.
* @return Index of last record.
*/
inline uint8_t back(void) const
{
return (m_front - m_fill + m_size) % m_size;
}
const uint8_t m_size; //!< Total number of records that can be stored in the buffer.
T* const m_buff; //!< Ptr to buffer holding all records.
volatile uint8_t m_front; //!< Index of front element (not pushed yet).
volatile uint8_t m_fill; //!< Amount of records currently pushed.
};
#endif // CircularBuffer_h

View File

@@ -0,0 +1,81 @@
// Copyright (C) 2016 Krister W. <kisse66@hobbylabs.org>
//
// Original SPI flash driver this is based on:
// Copyright (c) 2013-2015 by Felix Rusu, LowPowerLab.com
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
#include <Wire.h>
#include "I2CEeprom.h"
I2CEeprom::I2CEeprom(uint8_t addr) : extEEPROM(I2CEEPROM_CHIP_SIZE, 1, I2CEEPROM_PAGE_SIZE)
{
m_addr = addr; // we only need this for busy()
}
/// setup
bool I2CEeprom::initialize()
{
return extEEPROM::begin(I2CEEPROM_TWI_CLK) ? false : true;
}
/// read 1 byte
uint8_t I2CEeprom::readByte(uint32_t addr)
{
uint8_t val;
readBytes(addr, &val, 1);
return val;
}
/// read multiple bytes
void I2CEeprom::readBytes(uint32_t addr, void* buf, uint16_t len)
{
extEEPROM::read((unsigned long)addr, (byte *)buf, (unsigned int) len);
}
/// check if the chip is busy
bool I2CEeprom::busy()
{
Wire.beginTransmission(m_addr);
Wire.write(0);
Wire.write(0);
if (Wire.endTransmission() == 0) {
return false;
} else {
return true; // busy
}
}
/// Write 1 byte
void I2CEeprom::writeByte(uint32_t addr, uint8_t byt)
{
writeBytes(addr, &byt, 1);
}
/// write multiple bytes
void I2CEeprom::writeBytes(uint32_t addr, const void* buf, uint16_t len)
{
extEEPROM::write((unsigned long) addr, (byte *)buf, (unsigned int) len);
}

View File

@@ -0,0 +1,116 @@
// Copyright (C) 2016 Krister W. <kisse66@hobbylabs.org>
//
// Original SPI flash driver this is based on:
// Copyright (c) 2013-2015 by Felix Rusu, LowPowerLab.com
//
// I2C EEPROM library for MySensors OTA. Based on SPI Flash memory library for
// arduino/moteino.
// This driver is made to look like the SPI flash driver so changes needed to
// MySensors OTA code is minimized.
// This works with 32 or 64kB I2C EEPROM like an 24(L)C256. AVR HW I2C is assumed
// and error handling is quite minimal. Uses extEEPROM as the underlying driver.
// DEPENDS ON: Arduino Wire library, extEEPROM
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
///
/// @file I2CEeprom.h
///
/// @brief I2CEeprom provides access to a I2C EEPROM IC for OTA update or storing data
///
/// This is a wrapper over extEEPROM to make it look like a flash chip.
///
#ifndef _I2CEeprom_H_
#define _I2CEeprom_H_
#include <Arduino.h>
#include <extEEPROM.h>
/// I2C speed
// 400kHz clock as default. Use extEEPROM type
#ifndef I2CEEPROM_TWI_CLK
#define I2CEEPROM_TWI_CLK twiClock400kHz
#endif
/// EEPROM page size
//Typically 64 (see data sheet for your EEPROM)
// Some 512kbit chips use 128 byte pages (e.g. Atmel AT24C512)
#ifndef I2CEEPROM_PAGE_SIZE
#define I2CEEPROM_PAGE_SIZE 64
#endif
/// EEPROM size
// 24C256 is 32kB, minimum that fits code for ATmega328
// Use extEEPROM type
#ifndef I2CEEPROM_CHIP_SIZE
#define I2CEEPROM_CHIP_SIZE kbits_256
#endif
/** I2CEeprom class */
class I2CEeprom : extEEPROM
{
public:
explicit I2CEeprom(uint8_t addr); //!< Constructor
bool initialize(); //!< setup
uint8_t readByte(uint32_t addr); //!< read 1 byte from flash memory
void readBytes(uint32_t addr, void* buf, uint16_t len); //!< read multiple bytes
void writeByte(uint32_t addr, uint8_t byt); //!< Write 1 byte to flash memory
void writeBytes(uint32_t addr, const void* buf,
uint16_t len); //!< write multiple bytes to flash memory (up to 64K), if define SPIFLASH_SST25TYPE is set AAI Word Programming will be used
bool busy(); //!< check if the chip is busy erasing/writing
// the rest not needed for EEPROMs, but kept so SPI flash code compiles as is (functions are NOP)
/// dummy function for SPI flash compatibility
uint16_t readDeviceId()
{
return 0xDEAD;
};
/// dummy function for SPI flash compatibility
void chipErase() {};
/// dummy function for SPI flash compatibility
void blockErase4K(uint32_t address)
{
(void)address;
};
/// dummy function for SPI flash compatibility
void blockErase32K(uint32_t address)
{
(void)address;
};
/// dummy function for SPI flash compatibility
void sleep() {};
/// dummy function for SPI flash compatibility
void wakeup() {};
/// dummy function for SPI flash compatibility
void end() {};
protected:
uint8_t m_addr; ///< I2C address for busy()
};
#endif

View File

@@ -0,0 +1,178 @@
/*
* Flash.h - Flash library
* Original Copyright (c) 2017 Frank Holtz. All right reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file Flash.h
* @brief Flash abstraction layer
*
* @ingroup NVM
* @details Nonvolatile Memory Class
* @{
*/
#pragma once
#include <Arduino.h>
#include <stdio.h> // for size_t
/*
* Define characteristics of Flash
*
* @def FLASH_ERASE_CYCLES
* @brief Specified number of erase cycles
*
* @def FLASH_PAGE_SIZE
* @brief Used/supported Flash page size
*
* @def FLASH_ERASE_PAGE_TIME
* @brief Time in ms to delete a page
*
* @def FLASH_WRITES_PER_WORD
* @brief How often a dataword (32 bit) can be written
*
* @def FLASH_WRITES_PER_PAGE
* @brief How many writes are allowed into a page
*
* @def FLASH_SUPPORTS_RANDOM_WRITE
* @brief Set this if it is allowed to write to a page in random order.
*/
#if defined(NRF51)
#define FLASH_ERASE_CYCLES 20000
#define FLASH_PAGE_SIZE 1024
#define FLASH_ERASE_PAGE_TIME 23
#define FLASH_SUPPORTS_RANDOM_WRITE true
#define FLASH_WRITES_PER_WORD 2
#define FLASH_WRITES_PER_PAGE 512
#elif defined(NRF52)
#define FLASH_ERASE_CYCLES 10000
#define FLASH_PAGE_SIZE 4096
#define FLASH_ERASE_PAGE_TIME 90
#define FLASH_SUPPORTS_RANDOM_WRITE true
#define FLASH_WRITES_PER_WORD 32
#define FLASH_WRITES_PER_PAGE 181
#elif defined(NRF52840)
#define FLASH_ERASE_CYCLES 10000
#define FLASH_PAGE_SIZE 4096
#define FLASH_ERASE_PAGE_TIME 90
#define FLASH_SUPPORTS_RANDOM_WRITE true
#define FLASH_WRITES_PER_WORD 2
#define FLASH_WRITES_PER_PAGE 403
#else
#define FLASH_ERASE_CYCLES 10000 //!< FLASH_ERASE_CYCLES
#define FLASH_PAGE_SIZE 4096 //!< FLASH_PAGE_SIZE
#define FLASH_ERASE_PAGE_TIME 100 //!< FLASH_ERASE_PAGE_TIME
//#define FLASH_SUPPORTS_RANDOM_WRITE true
#define FLASH_WRITES_PER_WORD 1 //!< FLASH_WRITES_PER_WORD
#warning "Unknown platform. Please check the code."
#endif
/**
* @class FlashClass
* @brief This class provides low-level access to internal Flash memory.
*/
class FlashClass
{
public:
//----------------------------------------------------------------------------
/** Constructor */
FlashClass() {};
//----------------------------------------------------------------------------
/** Initialize Flash */
void begin() {};
/** Deinitialize Flash */
void end() {};
//----------------------------------------------------------------------------
/*
* Physical flash geometry
*/
//----------------------------------------------------------------------------
/** Page size in bytes
* @return Number of bytes
*/
uint32_t page_size() const;
//----------------------------------------------------------------------------
/** Page address width in bits. Page size is 2^x
* @return Number of bits
*/
uint8_t page_size_bits() const;
//----------------------------------------------------------------------------
/** Number of managed flash pages
* @return Number of pages
*/
uint32_t page_count() const;
//----------------------------------------------------------------------------
/** Number of page erase cycles
* @return Number of page erase cycles
*/
uint32_t specified_erase_cycles() const;
//----------------------------------------------------------------------------
/** Get a address of a page
* @param[in] page Page number, starting at 0
* @return address of given page
*/
uint32_t *page_address(size_t page);
/** Get top of available flash for application data
* @return Last available address + 1
*/
uint32_t *top_app_page_address();
//----------------------------------------------------------------------------
/*
* Accessing flash memory
*/
//----------------------------------------------------------------------------
/** Erase a page of given size. Size must be page_size aligned!
* Take care about RADIO, WDT and Interrupt timing!
* @param[in] *address Pointer to page
* @param[in] size number of page aligned bytes to erase
*/
void erase(uint32_t *address, size_t size);
//----------------------------------------------------------------------------
/** Erase the complete MCU. This can brick your device!
*/
void erase_all();
//----------------------------------------------------------------------------
/** write a aligned 32 bit word to flash.
* @param[in] *address 32 bit aligned pointer to destination word
* @param[in] value Data word to write
*/
void write(uint32_t *address, uint32_t value);
//----------------------------------------------------------------------------
/** write a aligned block to flash.
* @param[in] *dst_address 32 bit aligned pointer to destination
* @param[in] *src_address 32 bit aligned pointer to source
* @param[in] word_count Number of words to write
*/
void write_block(uint32_t *dst_address, uint32_t *src_address,
uint16_t word_count);
private:
// Wait until flash is ready
void wait_for_ready();
};
extern FlashClass Flash; //!< extern FlashClass
/** Load Hardwarespecific files */
#ifdef NRF5
#include "hal/architecture/NRF5/drivers/Flash.cpp"
#else
#error "Unsupported platform."
#endif
/** @} */

View File

@@ -0,0 +1,342 @@
/*
NVRAM.cpp - Byte wise storage for Virtual Pages.
Original Copyright (c) 2017 Frank Holtz. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "NVRAM.h"
// VirtualPage magic
#define NVRAM_MAGIC (0x7710fdb9)
// Number of emulated cells
#define NVRAM_LENGTH 3072
// Log configuration: Address index in bits
#define NVRAM_ADDR_POS 20
// Log configuration: Mask for comparsion (4k space)
#define NVRAM_ADDR_MASK 0xfff00000
// Log configuration: Bit position of used address bitmap
#define NVRAM_BITMAP_POS 8
// Log configuration: used address bitmap calulation
#define NVRAM_BITMAP_ADDR_SHIFT 8
// Log configuration: Mask for bitmap extraction
#define NVRAM_BITMAP_MASK 0x000fff00
#define ADDR2BIT(index) \
((1 << (index >> NVRAM_BITMAP_ADDR_SHIFT)) << NVRAM_BITMAP_POS)
NVRAMClass NVRAM;
uint16_t NVRAMClass::length() const
{
return (NVRAM_LENGTH);
}
void NVRAMClass::read_block(uint8_t *dst, uint16_t idx, uint16_t n)
{
uint32_t *vpage;
uint16_t log_start, log_end;
// find correct page
vpage = get_page();
// copy 0xff to dst when no page is available
if (vpage == (uint32_t *)~0) {
for (uint32_t i = 0; i < n; i++) {
((uint8_t *)dst)[i] = 0xff;
}
return;
}
// calculate actual log position
log_end = get_log_position(vpage);
if (log_end == 0) {
log_start = 1;
} else {
log_start = vpage[0] + 1;
}
/*
Serial.print("\r\nread_block idx=");
Serial.print(idx);
Serial.print(" n=");
Serial.print(n);
Serial.print("("); */
while (n > 0) {
// Read cell
*dst = get_byte_from_page(vpage, log_start, log_end, idx);
// Serial.print(*dst, HEX);
// calculate next address
n--;
dst++;
idx++;
}
// Serial.println(")");
}
uint8_t NVRAMClass::read(const uint16_t idx)
{
uint8_t ret;
read_block(&ret, idx, 1);
return ret;
}
bool NVRAMClass::write_block(uint8_t *src, uint16_t idx, uint16_t n)
{
uint32_t *vpage;
uint32_t bitmap;
uint16_t log_start, log_end;
// find correct page
vpage = get_page();
// return on invalid page
if (vpage == (uint32_t *)~0) {
return false;
}
// calculate actual log position
log_start = vpage[0] + 1;
log_end = get_log_position(vpage);
if (log_end > log_start) {
bitmap = vpage[log_end - 1] & NVRAM_BITMAP_MASK;
} else {
bitmap = 0;
}
while (n > 0) {
// Read cell
uint8_t old_value = get_byte_from_page(vpage, log_start, log_end, idx);
uint8_t new_value = *src;
// Have to write into log?
if (new_value != old_value) {
// need to calculate a new page?
if (log_end >= VirtualPage.length()) {
vpage = switch_page(vpage, &log_start, &log_end);
if (vpage == (uint32_t *)~0) {
// do nothing if no page is available
return false;
}
bitmap = 0;
}
// Add Entry into log
Flash.write(&vpage[log_end], (idx << NVRAM_ADDR_POS) | bitmap |
ADDR2BIT(idx) | (uint32_t)new_value);
log_end++;
}
// calculate next address
n--;
src++;
idx++;
}
return true;
}
bool NVRAMClass::write(uint16_t idx, uint8_t value)
{
return (write_block(&value, idx, 1));
}
int NVRAMClass::write_prepare(uint16_t number)
{
// find correct page
uint32_t *vpage = get_page();
// Want to write to much or into an invalid page?
if ((vpage == (uint32_t *)~0) || (number > length())) {
return -1;
}
// calculate actual log position
uint16_t log_end = get_log_position(vpage);
// Calculate number of free bytes in log
int free_bytes = ((VirtualPage.length() - 1) - log_end);
// switch page when
if (free_bytes < number) {
uint16_t log_start = vpage[0] + 1;
vpage = switch_page(vpage, &log_start, &log_end);
if (vpage == (uint32_t *)~0) {
// do nothing if no page is available
return -1;
}
log_end = get_log_position(vpage);
free_bytes = ((VirtualPage.length() - 1) - log_end);
}
return free_bytes;
}
void NVRAMClass::clean_up(uint16_t write_preserve)
{
VirtualPage.clean_up();
if (write_preserve > 0) {
write_prepare(write_preserve);
}
}
uint32_t *NVRAMClass::switch_page(uint32_t *old_vpage, uint16_t *log_start,
uint16_t *log_end)
{
// Mark old page as in release
VirtualPage.release_prepare(old_vpage);
// Get a blank page
uint32_t *new_vpage = VirtualPage.allocate(NVRAM_MAGIC, VirtualPage.length());
if (new_vpage == (uint32_t *)~0) {
// failed
return new_vpage;
}
// Store four bytes for map creation
uint32_t value;
// Length of new map
uint16_t map_length = 0;
// Build map
#ifdef FLASH_SUPPORTS_RANDOM_WRITE
// Copy current values
for (uint16_t i = 0; i < (NVRAM_LENGTH >> 2); i++) {
read_block((uint8_t *)&value, i << 2, 4);
if (value != (uint32_t)~0) {
// Value found
map_length = i + 1;
Flash.write(&new_vpage[i + 1], value);
}
}
// Store map length
Flash.write(new_vpage, map_length);
#else
// find map length
for (uint16_t i = (NVRAM_LENGTH >> 2); i > 0; i--) {
read_block((uint8_t *)&value, i << 2, 4);
if (value != (uint32_t)~0) {
// Value found
map_length = i;
break;
}
}
map_length++;
// Store map length
Flash.write(new_vpage, map_length);
// Copy current values
for (uint16_t i = 0; i <= map_length; i++) {
read_block((uint8_t *)&value, i << 2, 4);
if (value != (uint32_t)~0) {
// Value found
map_length = i;
Flash.write(&new_vpage[i + 1], value);
}
}
#endif
// Release old page
VirtualPage.release(old_vpage);
// Set log position
*log_start = map_length + 1;
*log_end = *log_start;
return new_vpage;
}
uint32_t *NVRAMClass::get_page()
{
uint32_t *vpage = VirtualPage.get(NVRAM_MAGIC);
// Invalid page?
if (vpage == (uint32_t *)~0) {
// Allocate a new page
vpage = VirtualPage.allocate(NVRAM_MAGIC, VirtualPage.length());
// Set map length to 0
Flash.write(&vpage[0], 0x0);
}
return vpage;
}
uint16_t NVRAMClass::get_log_position(uint32_t *vpage)
{
uint16_t position_min = vpage[0] + 1;
uint16_t position_max = VirtualPage.length();
// Return if page is not filled
if ((vpage[0] == (uint32_t)~0) || (position_min >= position_max)) {
return 0;
}
// loop until postition_min != position_max-1
while (position_min != position_max - 1) {
// Calculate middle between min and max
uint16_t mid = position_min + ((position_max - position_min) >> 1);
// Set max or min to current position
if (vpage[mid] == (uint32_t)~0) {
position_max = mid;
} else {
position_min = mid;
}
}
return position_max;
}
uint8_t NVRAMClass::get_byte_from_page(uint32_t *vpage, uint16_t log_start,
uint16_t log_end, uint16_t idx)
{
// mask matching a bit signaling wich address range is in log
uint32_t address_mask = ADDR2BIT(idx);
// mask matching the index address
uint32_t address_match = idx << NVRAM_ADDR_POS;
// Check the log backwards
while (log_end > log_start) {
log_end--;
uint32_t value = vpage[log_end];
// end here if address map bit is not set
if ((value & address_mask) == 0) {
break;
}
// check address match -> update found -> return
if ((value & NVRAM_ADDR_MASK) == address_match) {
return (uint8_t)value;
}
}
// Calculate address in the eeprom map at the beginning of a vpage
uint16_t map_address = (idx >> 2);
map_address++; // jump over log offset field
// look at map if calculated addess before log start position
if (map_address < log_start) {
switch (idx % 4) {
case 3:
return (uint8_t)(vpage[map_address] >> 24);
break;
case 2:
return (uint8_t)(vpage[map_address] >> 16);
break;
case 1:
return (uint8_t)(vpage[map_address] >> 8);
break;
default:
return (uint8_t)(vpage[map_address]);
break;
}
}
// empty cell
return 0xff;
}

View File

@@ -0,0 +1,113 @@
/*
* NVRAM.h - Byte-wise storage for Virtual Pages.
* Original Copyright (c) 2017 Frank Holtz. All right reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file NVRAM.h
* @brief Byte-wise storage for Virtual Pages.
*
* @defgroup NVM Nonvolatile Memory
* @ingroup internals
* @details Nonvolatile Memory Class
* @{
*/
#pragma once
#include "Flash.h"
#include "VirtualPage.h"
#include <Arduino.h>
/**
* @class NVRAMClass
* @brief Nonvolatile Memory
*/
class NVRAMClass
{
public:
//----------------------------------------------------------------------------
/** Constructor. */
NVRAMClass() {};
//----------------------------------------------------------------------------
/** Initialize Class */
void begin() {};
//----------------------------------------------------------------------------
/** Deinitialize Class */
void end() {};
//----------------------------------------------------------------------------
/** NVM available space in bytes
* @return Number of bytes
*/
uint16_t length() const;
//----------------------------------------------------------------------------
/** Read a block of bytes
* @param[in] *dst Write bytes to given pointer
* @param[in] idx First NVM address to read
* @param[in] n Number of bytes to read
*/
void read_block(uint8_t *dst, uint16_t idx, uint16_t n);
//----------------------------------------------------------------------------
/** Read a byte
* @param[in] idx NVM Address to read
* @return Byte from given address
*/
uint8_t read(const uint16_t idx);
//----------------------------------------------------------------------------
/** Write a block
* @param[in] *src Read bytes from given pointer
* @param[in] idx First NVM address to write
* @param[in] n Number of bytes to write
* @return success
*/
bool write_block(uint8_t *src, uint16_t idx, uint16_t n);
//----------------------------------------------------------------------------
/** Write a byte
* @param[in] idx NVM Address to write
* @param[in] value Byte to write
* @return success
*/
bool write(const uint16_t idx, uint8_t value);
//----------------------------------------------------------------------------
/** Preserve the number of bytes in NVM log for time critical writes. Runtime
* up to 5000ms!
* @param[in] number Bytes to preserve, can 0 to find out free log space
* @return Bytes available for writing
*/
int write_prepare(uint16_t number);
//----------------------------------------------------------------------------
/** lear log if full and prepare released pages for faster reallocation.
* Runtime up to 5000ms!
* @param[in] write_preserve Byte to preserve
*/
void clean_up(uint16_t write_preserve);
private:
// Return a virtual page
uint32_t *get_page();
// Get actual log position
uint16_t get_log_position(uint32_t *vpage);
// Read a byte from page
uint8_t get_byte_from_page(uint32_t *vpage, uint16_t log_start,
uint16_t log_end, uint16_t idx);
// switch a page
uint32_t *switch_page(uint32_t *old_vpage, uint16_t *log_start,
uint16_t *log_end);
};
/** Variable to access the NVRAMClass */
extern NVRAMClass NVRAM;
/** @} */

View File

@@ -0,0 +1,51 @@
This code is based on (arduino-NVM)[https://github.com/d00616/arduino-NVM]. If you change code, please create a Pull Request for arduino-NVM to keep code synchronized.
# arduino-NVM
[![Build Status](https://travis-ci.org/d00616/arduino-NVM.svg?branch=master)](https://travis-ci.org/d00616/arduino-NVM)
This library allows the usage of internal Flash memory. To enhance the limited erase cycles a VirtualPage layer is available. On top of VirtualPage, there is an NVRAM class to allow a lot of writes by using a log-based storage.
For Arduino compatibility, a subset of avr/eeprom.h functionality and a complete port of EEPROM.h is provided.
Accessing bytes via NVRAM or EEPROM is faster than an AVR controller until the internal log is full. At this point, a new page must build. This process takes up to 3400ms (nRF51) or 1300ms (nRF52) depending on your hardware and the highest written address.
To find out more about timing, please run "test_all" example.
_This code is not compatible with any SoftDevice. You have to use the [radio notification](https://devzone.nordicsemi.com/tutorials/14/radio-notification/) and VirtualPage.clean_up()/NVRAM.write_prepare(NUMBER) to ensure that writes are only used in a time without radio activation._
## Flash.h
This class is the hardware abstraction to the Flash controller. Please look into Flash.h for a more detailed description.
Please read the documentation of your microcontroller to find out limitations about writing into flash. You can use the FLASH_... defines in your code to take care about quirks.
## VirtualPage.h
This class provides manages Flash pages. This helps you to reach more erase cycles and handle page faults. The underlying Flash page needs to hold some metadata so a VirtualPage is some bytes smaller than 4k. The size can differ between different hardware.
If you need to allocate VirtualPages in performance critical situations, call VirtualPage.clean_up(), when you have a time slot of more than 100ms.
For VirtualPages the last 16k(nRF51) or 32k(nRF52) are used. This allows the same number of erase cycles on both platforms.
## NVRAM.h
This class provides a 3072 bytes large memory. You can access this memory in a random order without needing to take care of the underlying flash architecture. This class is stateless, this means there is nothing cached in RAM. With every access, the data structure is parsed. This saves RAM and avoids conflicts when you have more than one instance of NVRAM class in your code.
To reach a maximum of write cycles and performance, place all your data at the beginning of the memory. This allows a maximum of write cycles.
When you only use the first 8 Bytes of the NVRAM, you have 5,100,000 write cycles per byte. If you use all 3072 bytes, you have only 3,300 write cycles per byte.
For your own calculation of write cycles, you can calculate the sum of available writes with: (VirtualPage.length()-4-HIGHEST_ADDRESS/4)*40,000
Reading or writing the NVRAM is fast until the internal log is full. On nRF51 you can calculate with 1.2ms and on nRF52 with 0,5ms. If the log is full a new VirtualPage is allocated and written. This procedure can take a time of 3400ms (nRF51) or 1300ms (nRF52).
If you use this code in performance critical situations. Use NVRAM.write_prepare(NUMBER) before to guarantee a fast write for the given number of bytes.
## EEPROM.h and avr_eeprom.h
Both libraries are for Arduino compatibility. Instead of avr/eeprom.h, you have to include avr_eeprom.h. This file maps a limited set of functions to NVRAM.
The EEPROM.h is fully compatible with the AVR version without committing changes.
If you use one of both files, please keep in mind that writing a single byte is fast until the log becomes full. In that case, a single write operation can take up to 3400ms (nRF51) or 1300ms (nRF52).

View File

@@ -0,0 +1,349 @@
/*
VirtualPage.cpp - Flash page management
Original Copyright (c) 2017 Frank Holtz. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "VirtualPage.h"
VirtualPageClass VirtualPage;
#ifndef NVM_VIRTUAL_PAGE_SIZE_BITS
#define NVM_VIRTUAL_PAGE_SIZE_BITS 12
#elif NVM_VIRTUAL_PAGE_SIZE_BITS < 12
#error "NVM_VIRTUAL_PAGE_SIZE_BITS must be >= 12"
#endif
// Calculate virtual page count, when mcuboot is present
#if defined(MCUBOOT_PRESENT) && !defined(NVM_VIRTUAL_PAGE_COUNT)
// mcuboot zephyr build via generated_dts_board.h
#include "generated_dts_board.h"
// Calculate number of free pages after scratch area
#define NVM_VIRTUAL_PAGE_COUNT (((CONFIG_FLASH_SIZE_0<<10)-(FLASH_AREA_IMAGE_SCRATCH_OFFSET_0+FLASH_AREA_IMAGE_SCRATCH_SIZE_0)) >> NVM_VIRTUAL_PAGE_SIZE_BITS)
#endif
// check page size
#ifndef NVM_VIRTUAL_PAGE_COUNT
#if FLASH_ERASE_CYCLES >= 20000
// use 16k of flash memory
#define NVM_VIRTUAL_PAGE_COUNT 4
#else
// use 32k of flash memory
#define NVM_VIRTUAL_PAGE_COUNT 8
#endif
#endif
/*
* How many virtual pages are skipped from top of flash
*/
#ifndef NVM_VIRTUAL_PAGE_SKIP_FROM_TOP
#define NVM_VIRTUAL_PAGE_SKIP_FROM_TOP 0
#endif
/*
* Calculate things around NVM_VIRTUAL_PAGE_SIZE
*/
#define NVM_VIRTUAL_PAGE_SIZE (1 << (NVM_VIRTUAL_PAGE_SIZE_BITS))
#define NVM_VIRTUAL_PAGE_ADDRESS_MASK (~(NVM_VIRTUAL_PAGE_SIZE - 1))
#define NVM_VIRTUAL_PAGE_ALIGN(address) \
{ address = (uint32_t *)((uint32_t)address & NVM_VIRTUAL_PAGE_ADDRESS_MASK); }
/*
* Defines the position of status words in a page.
* Offsets are defined in words!
*/
#ifdef FLASH_SUPPORTS_RANDOM_WRITE
// use first 8 byte for magic, erase counter and status
#define OFFSET_MAGIC 0
#define OFFSET_ERASE_COUNTER 1
#if FLASH_WRITES_PER_WORD > 2
// use first 12 bytes for magic, erase counter and status
#define MASK_ERASE_COUNTER 0x00FFFFFF
#define OFFSET_STATUS_RELEASE_PREPARE 1
#define OFFSET_STATUS_RELEASE_END 1
#define METADATA_SIZE 8
#define OFFSET_DATA 2
#elif FLASH_WRITES_PER_WORD == 2
#define MASK_ERASE_COUNTER 0x00FFFFFF
#define OFFSET_STATUS_RELEASE_PREPARE 2
#define OFFSET_STATUS_RELEASE_END 2
#define METADATA_SIZE 12
#define OFFSET_DATA 3
#else
// use first 12 bytes for erase counter, and magic
#define OFFSET_MAGIC 1
#define OFFSET_COUNTER 0
#define MASK_ERASE_COUNTER 0x00FFFFFF
#define OFFSET_STATUS_RELEASE_PREPARE NVM_VIRTUAL_PAGE_SIZE - 8
#define OFFSET_STATUS_RELEASE_END NVM_VIRTUAL_PAGE_SIZE - 4
#define METADATA_SIZE 16
#define OFFSET_DATA 4
#endif
#define BIT_STATUS_RELEASE_PREPARE (1 << 30)
#define BIT_STATUS_RELEASE_END (1 << 31)
#define NVM_VIRTUAL_PAGE_DATA_SIZE (NVM_VIRTUAL_PAGE_SIZE - METADATA_SIZE)
#else
// use first 8 byte for magic and erase counter and last 8 byte for page release
#define OFFSET_MAGIC 1
#define OFFSET_ERASE_COUNTER 0
#define OFFSET_DATA 2
#define OFFSET_STATUS_RELEASE_PREPARE \
((NVM_VIRTUAL_PAGE_SIZE - 8) / sizeof(uint32_t))
#define OFFSET_STATUS_RELEASE_END \
((NVM_VIRTUAL_PAGE_SIZE - 4) / sizeof(uint32_t))
#define MASK_ERASE_COUNTER 0xFFFFFFFF
#define BIT_STATUS_RELEASE_PREPARE 1
#define BIT_STATUS_RELEASE_END 1
#define NVM_VIRTUAL_PAGE_DATA_SIZE (NVM_VIRTUAL_PAGE_SIZE - 16)
#endif
uint16_t VirtualPageClass::size() const
{
return (NVM_VIRTUAL_PAGE_DATA_SIZE);
}
uint16_t VirtualPageClass::length() const
{
return (NVM_VIRTUAL_PAGE_DATA_SIZE / 4);
}
uint16_t VirtualPageClass::page_count() const
{
return (NVM_VIRTUAL_PAGE_COUNT - 1);
}
uint32_t VirtualPageClass::wear_level()
{
uint32_t max_erase_cycles = 0;
for (int i = 1; i <= NVM_VIRTUAL_PAGE_COUNT; i++) {
uint32_t erase_cycles = get_page_erase_cycles(get_page_address(i));
if (erase_cycles > max_erase_cycles) {
max_erase_cycles = erase_cycles;
}
}
return (uint32_t)((((uint64_t)max_erase_cycles * 10000)) /
Flash.specified_erase_cycles());
}
uint32_t *VirtualPageClass::get(uint32_t magic)
{
// Give back a page prepared for release and not closed
for (int i = 1; i <= NVM_VIRTUAL_PAGE_COUNT; i++) {
uint32_t *page = get_page_address(i);
if (
// correct magic is set
(page[OFFSET_MAGIC] == magic) &&
// page is in release_prepare mode
((page[OFFSET_STATUS_RELEASE_PREPARE] & BIT_STATUS_RELEASE_PREPARE) ==
0) &&
// page is not released
((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) > 0)) {
// Return page in release process with priority
return &page[OFFSET_DATA];
}
}
// check if a unreleased page is available
for (int i = 1; i <= NVM_VIRTUAL_PAGE_COUNT; i++) {
uint32_t *page = get_page_address(i);
if (
// correct magic is set
(page[OFFSET_MAGIC] == magic) &&
// page is not released
((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) > 0)) {
// return page in normal operation
return &page[OFFSET_DATA];
}
}
return (uint32_t *)(~0);
}
uint32_t *VirtualPageClass::allocate(uint32_t magic)
{
uint32_t *return_page = (uint32_t *)(~0);
uint32_t max_erase_cycles = (uint32_t)~0;
// Avoid duplicate allocation of pages, look for the less used page
for (int i = 1; i <= NVM_VIRTUAL_PAGE_COUNT; i++) {
uint32_t *page = get_page_address(i);
// Delete duplicated pages
if (
// same magic
(page[OFFSET_MAGIC] == magic) &&
// Not in release_end state
((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) > 0) &&
// Not in release_prepare state
(!release_started(page))) {
// clear the page
build_page(page, (uint32_t)~0);
}
uint32_t erase_cycles = get_page_erase_cycles(page);
// When the page has less erase cycles and is not marked as failed
if ((erase_cycles < max_erase_cycles) && (page[OFFSET_MAGIC] > 0) &&
(
// magic is empty
(page[OFFSET_MAGIC] == (uint32_t)~0) ||
// marked as released
((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) ==
0))) {
max_erase_cycles = erase_cycles;
return_page = page;
}
}
// return if no page was found
if (return_page == (uint32_t *)~0) {
return return_page;
}
build_page(return_page, magic);
return &return_page[OFFSET_DATA];
}
uint32_t *VirtualPageClass::allocate(uint32_t magic, uint32_t max_writes)
{
// max_writes is not implemented yet -> page is erased with every allocate
(void)max_writes;
return allocate(magic);
}
void VirtualPageClass::release_prepare(uint32_t *address)
{
// move pointer to beginning of the page
NVM_VIRTUAL_PAGE_ALIGN(address);
// Nothing to do at a empty page
if (address[OFFSET_MAGIC] == (uint32_t)~0) {
return;
}
if (release_started(address) == false) {
// Clear bit BIT_PAGE_RELEASED
Flash.write(&address[OFFSET_STATUS_RELEASE_PREPARE],
address[OFFSET_STATUS_RELEASE_PREPARE] &
~BIT_STATUS_RELEASE_PREPARE);
}
return;
}
void VirtualPageClass::release(uint32_t *address)
{
// move pointer to beginning of the page
NVM_VIRTUAL_PAGE_ALIGN(address);
// Nothing to do at a empty page
if (address[OFFSET_MAGIC] == (uint32_t)~0) {
return;
}
// Check if status bit already cleared
if ((address[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) > 0) {
// Clear bit BIT_PAGE_RELEASED
Flash.write(&address[OFFSET_STATUS_RELEASE_END],
address[OFFSET_STATUS_RELEASE_END] & ~BIT_STATUS_RELEASE_END);
}
return;
}
bool VirtualPageClass::release_started(uint32_t *address)
{
// move pointer to beginning of the page
NVM_VIRTUAL_PAGE_ALIGN(address);
return (address[OFFSET_STATUS_RELEASE_PREPARE] &
BIT_STATUS_RELEASE_PREPARE) == 0;
}
void VirtualPageClass::fail(uint32_t *address)
{
// move pointer to beginning of the page
NVM_VIRTUAL_PAGE_ALIGN(address);
build_page(address, 0x00000000);
return;
}
void VirtualPageClass::clean_up()
{
// No page found -> try to give back a page prepared for release
for (int i = 1; i <= NVM_VIRTUAL_PAGE_COUNT; i++) {
uint32_t *page = get_page_address(i);
if ((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) == 0) {
build_page(get_page_address(i), ~0);
return; // a maximum of a page is cleaned -> return
}
}
}
void VirtualPageClass::format()
{
for (int i = 1; i <= NVM_VIRTUAL_PAGE_COUNT; i++) {
uint32_t *address = get_page_address(i);
build_page(address, (uint32_t)~0);
}
}
uint32_t *VirtualPageClass::get_page_address(uint16_t page)
{
return (uint32_t *)(Flash.top_app_page_address() -
((page + NVM_VIRTUAL_PAGE_SKIP_FROM_TOP)
<< NVM_VIRTUAL_PAGE_SIZE_BITS));
}
void VirtualPageClass::build_page(uint32_t *address, uint32_t magic)
{
// move pointer to beginning of the page
NVM_VIRTUAL_PAGE_ALIGN(address);
// get erase counter
uint32_t erase_counter = get_page_erase_cycles(address);
// Check if a magic is set
if (address[OFFSET_MAGIC] != (uint32_t)~0) {
Flash.erase(address, NVM_VIRTUAL_PAGE_SIZE);
} else {
// check if page is empty
for (int i = OFFSET_DATA; i < (NVM_VIRTUAL_PAGE_SIZE / 4); i++) {
if (address[i] != (uint32_t)~0) {
Flash.erase(address, NVM_VIRTUAL_PAGE_SIZE);
break;
}
}
}
// write a new page
Flash.write(&address[OFFSET_MAGIC], magic);
if (address[OFFSET_ERASE_COUNTER] == (uint32_t)~0) {
Flash.write(&address[OFFSET_ERASE_COUNTER],
erase_counter | ~MASK_ERASE_COUNTER);
}
}
uint32_t VirtualPageClass::get_page_erase_cycles(uint32_t *address)
{
// Return number of cycles
return ((((uint32_t)address[OFFSET_ERASE_COUNTER])+1) &
(uint32_t)MASK_ERASE_COUNTER);
}

View File

@@ -0,0 +1,139 @@
/*
VirtualPage.h - Flash page management
Original Copyright (c) 2017 Frank Holtz. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file VirtualPage.h
* @brief Virtual page management on top of Flash
* The managed pages are organized into VirtualPage.size() sized pages.
* Some Bytes of a Flash page are reserved for a magic number and page management.
*
* @ingroup NVM
* @details Nonvolatile Memory Class
* @{
*/
#pragma once
#include "Flash.h"
#include <Arduino.h>
/**
* @class VirtualPageClass
* @brief Virtual page management on top of Flash
*/
class VirtualPageClass
{
public:
//----------------------------------------------------------------------------
/** Constructor */
VirtualPageClass() {};
//----------------------------------------------------------------------------
/** Initialize Virtual Pages */
void begin() {};
//----------------------------------------------------------------------------
/** Deinitilize Virtual Pages */
void end() {};
//----------------------------------------------------------------------------
/** Reports usable page size in bytes
* @return Number of bytes
*/
uint16_t size() const;
//----------------------------------------------------------------------------
/** Reports usable page size in 32 Bit words
* @return Number of words
*/
uint16_t length() const;
//----------------------------------------------------------------------------
/** Reports the maximum number of allocatable pages
* @return Number of pages
*/
uint16_t page_count() const;
//----------------------------------------------------------------------------
/** Calculates the rate of wear in percent*100.
* Values greater than 10000 indicates exceeding the chip specification.
* This value is only valid for controllers, never erased completely.
* @return calculated level
*/
uint32_t wear_level();
//----------------------------------------------------------------------------
/** Search for a page by given, unique magic number.
* Returns a pointer to (uint32_t *)~0 if there is no page. Don't write
* to this address.
* @param[in] magic A magic number to identify the page.
* @return Pointer to a page
*/
uint32_t *get(uint32_t magic);
//----------------------------------------------------------------------------
/** Returns an address to a blank page or (uint32_t *)~0 if no space
* available. Take care about RADIO, WDT and Interrupt timing! Calculate with
* 0-100ms until a page is available.
* @param[in] magic A magic number to identify the page.
* @return Pointer to a page
*/
uint32_t *allocate(uint32_t magic);
//----------------------------------------------------------------------------
/** Returns an address to a blank page or (uint32_t *)~0 if no space
* available. Take care about RADIO, WDT and Interrupt timing! Calculate with
* 0-100ms until a page is available.
* This function allows using a page multiple times without erasing on some platforms.
* @param[in] magic A magic number to identify the page.
* @param[in] max_writes The number of planned write operations in page lifecycle.
* @return Pointer to a page
*/
uint32_t *allocate(uint32_t magic, uint32_t max_writes);
//----------------------------------------------------------------------------
/** Start releasing a page. You have to allocate a new one and then release the
* old page.
* @param[in] *address A pointer to the page to release
*/
void release_prepare(uint32_t *address);
//----------------------------------------------------------------------------
/** End releasing a page.
* @param[in] *address A pointer to the page to release
*/
void release(uint32_t *address);
//----------------------------------------------------------------------------
/** Returns true if page is in release_prepare state
* @param[in] *address A pointer to a page
* @return Release state
*/
bool release_started(uint32_t *address);
//----------------------------------------------------------------------------
/** Mark a page as a defect page.
*/
void fail(uint32_t *address);
//----------------------------------------------------------------------------
/** Prepare released pages for faster reallocation. Plan with 0-100ms.
*/
void clean_up();
//----------------------------------------------------------------------------
/** Release all pages
*/
void format();
private:
// convert page number 1..max_pages into an address
uint32_t *get_page_address(uint16_t page);
// build a page
void build_page(uint32_t *address, uint32_t magic);
// return number of erase cycles
uint32_t get_page_erase_cycles(uint32_t *address);
};
extern VirtualPageClass VirtualPage; //!< extern VirtualPageClass
/** @} */

View File

@@ -0,0 +1,76 @@
2.7
* Fix remaining-length handling to prevent buffer overrun
* Add large-payload API - beginPublish/write/publish/endPublish
* Add yield call to improve reliability on ESP
* Add Clean Session flag to connect options
* Add ESP32 support for functional callback signature
* Various other fixes
2.4
* Add MQTT_SOCKET_TIMEOUT to prevent it blocking indefinitely
whilst waiting for inbound data
* Fixed return code when publishing >256 bytes
2.3
* Add publish(topic,payload,retained) function
2.2
* Change code layout to match Arduino Library reqs
2.1
* Add MAX_TRANSFER_SIZE def to chunk messages if needed
* Reject topic/payloads that exceed MQTT_MAX_PACKET_SIZE
2.0
* Add (and default to) MQTT 3.1.1 support
* Fix PROGMEM handling for Intel Galileo/ESP8266
* Add overloaded constructors for convenience
* Add chainable setters for server/callback/client/stream
* Add state function to return connack return code
1.9
* Do not split MQTT packets over multiple calls to _client->write()
* API change: All constructors now require an instance of Client
to be passed in.
* Fixed example to match 1.8 api changes - dpslwk
* Added username/password support - WilHall
* Added publish_P - publishes messages from PROGMEM - jobytaffey
1.8
* KeepAlive interval is configurable in PubSubClient.h
* Maximum packet size is configurable in PubSubClient.h
* API change: Return bool rather than int from various functions
* API change: Length parameter in message callback changed
from int to unsigned int
* Various internal tidy-ups around types
1.7
* Improved keepalive handling
* Updated to the Arduino-1.0 API
1.6
* Added the ability to publish a retained message
1.5
* Added default constructor
* Fixed compile error when used with arduino-0021 or later
1.4
* Fixed connection lost handling
1.3
* Fixed packet reading bug in PubSubClient.readPacket
1.2
* Fixed compile error when used with arduino-0016 or later
1.1
* Reduced size of library
* Added support for Will messages
* Clarified licensing - see LICENSE.txt
1.0
* Only Quality of Service (QOS) 0 messaging is supported
* The maximum message size, including header, is 128 bytes
* The keepalive interval is set to 30 seconds
* No support for Will messages

View File

@@ -0,0 +1,20 @@
Copyright (c) 2008-2015 Nicholas O'Leary
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,736 @@
/*
PubSubClient.cpp - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#include "PubSubClient.h"
#include "Arduino.h"
// Suppress uninitialized member variable in all constructors because some memory can be saved with
// on-demand initialization of these members
// cppcheck-suppress uninitMemberVar
PubSubClient::PubSubClient()
{
this->_state = MQTT_DISCONNECTED;
this->_client = NULL;
this->stream = NULL;
setCallback(NULL);
}
// cppcheck-suppress uninitMemberVar
PubSubClient::PubSubClient(Client& client)
{
this->_state = MQTT_DISCONNECTED;
setClient(client);
this->stream = NULL;
}
// cppcheck-suppress uninitMemberVar
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client)
{
this->_state = MQTT_DISCONNECTED;
setServer(addr, port);
setClient(client);
this->stream = NULL;
}
// cppcheck-suppress uninitMemberVar
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream)
{
this->_state = MQTT_DISCONNECTED;
setServer(addr,port);
setClient(client);
setStream(stream);
}
// cppcheck-suppress uninitMemberVar
// cppcheck-suppress passedByValue
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client)
{
this->_state = MQTT_DISCONNECTED;
setServer(addr, port);
setCallback(callback);
setClient(client);
this->stream = NULL;
}
// cppcheck-suppress uninitMemberVar
// cppcheck-suppress passedByValue
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client,
Stream& stream)
{
this->_state = MQTT_DISCONNECTED;
setServer(addr,port);
setCallback(callback);
setClient(client);
setStream(stream);
}
// cppcheck-suppress uninitMemberVar
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client)
{
this->_state = MQTT_DISCONNECTED;
setServer(ip, port);
setClient(client);
this->stream = NULL;
}
// cppcheck-suppress uninitMemberVar
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream)
{
this->_state = MQTT_DISCONNECTED;
setServer(ip,port);
setClient(client);
setStream(stream);
}
// cppcheck-suppress uninitMemberVar
// cppcheck-suppress passedByValue
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client)
{
this->_state = MQTT_DISCONNECTED;
setServer(ip, port);
setCallback(callback);
setClient(client);
this->stream = NULL;
}
// cppcheck-suppress uninitMemberVar
// cppcheck-suppress passedByValue
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client,
Stream& stream)
{
this->_state = MQTT_DISCONNECTED;
setServer(ip,port);
setCallback(callback);
setClient(client);
setStream(stream);
}
// cppcheck-suppress uninitMemberVar
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client)
{
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setClient(client);
this->stream = NULL;
}
// cppcheck-suppress uninitMemberVar
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream)
{
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setClient(client);
setStream(stream);
}
// cppcheck-suppress uninitMemberVar
// cppcheck-suppress passedByValue
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE,
Client& client)
{
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setCallback(callback);
setClient(client);
this->stream = NULL;
}
// cppcheck-suppress uninitMemberVar
// cppcheck-suppress passedByValue
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE,
Client& client, Stream& stream)
{
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setCallback(callback);
setClient(client);
setStream(stream);
}
bool PubSubClient::connect(const char *id)
{
return connect(id,NULL,NULL,0,0,0,0,1);
}
bool PubSubClient::connect(const char *id, const char *user, const char *pass)
{
return connect(id,user,pass,0,0,0,0,1);
}
bool PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos,
bool willRetain, const char* willMessage)
{
return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1);
}
bool PubSubClient::connect(const char *id, const char *user, const char *pass,
const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage)
{
return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1);
}
bool PubSubClient::connect(const char *id, const char *user, const char *pass,
const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage,
bool cleanSession)
{
if (!connected()) {
int result = 0;
if (domain != NULL) {
result = _client->connect(this->domain, this->port);
} else {
result = _client->connect(this->ip, this->port);
}
if (result == 1) {
nextMsgId = 1;
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
unsigned int j;
#if MQTT_VERSION == MQTT_VERSION_3_1
uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION};
#define MQTT_HEADER_VERSION_LENGTH 9
#elif MQTT_VERSION == MQTT_VERSION_3_1_1
uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION};
#define MQTT_HEADER_VERSION_LENGTH 7
#endif
for (j = 0; j<MQTT_HEADER_VERSION_LENGTH; j++) {
buffer[length++] = d[j];
}
uint8_t v;
if (willTopic) {
v = 0x04|(willQos<<3)|(willRetain<<5);
} else {
v = 0x00;
}
if (cleanSession) {
v = v|0x02;
}
if(user != NULL) {
v = v|0x80;
if(pass != NULL) {
v = v|(0x80>>1);
}
}
buffer[length++] = v;
buffer[length++] = ((MQTT_KEEPALIVE) >> 8);
buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF);
CHECK_STRING_LENGTH(length,id)
length = writeString(id,buffer,length);
if (willTopic) {
CHECK_STRING_LENGTH(length,willTopic)
length = writeString(willTopic,buffer,length);
CHECK_STRING_LENGTH(length,willMessage)
length = writeString(willMessage,buffer,length);
}
if(user != NULL) {
CHECK_STRING_LENGTH(length,user)
length = writeString(user,buffer,length);
if(pass != NULL) {
CHECK_STRING_LENGTH(length,pass)
length = writeString(pass,buffer,length);
}
}
write(MQTTCONNECT,buffer,length-MQTT_MAX_HEADER_SIZE);
lastInActivity = lastOutActivity = millis();
while (!_client->available()) {
unsigned long t = millis();
if (t-lastInActivity >= ((int32_t) MQTT_SOCKET_TIMEOUT*1000UL)) {
_state = MQTT_CONNECTION_TIMEOUT;
_client->stop();
return false;
}
}
uint8_t llen;
uint16_t len = readPacket(&llen);
if (len == 4) {
if (buffer[3] == 0) {
lastInActivity = millis();
pingOutstanding = false;
_state = MQTT_CONNECTED;
return true;
} else {
_state = buffer[3];
}
}
_client->stop();
} else {
_state = MQTT_CONNECT_FAILED;
}
return false;
}
return true;
}
// reads a byte into result
bool PubSubClient::readByte(uint8_t * result)
{
uint32_t previousMillis = millis();
while(!_client->available()) {
yield();
uint32_t currentMillis = millis();
if(currentMillis - previousMillis >= ((int32_t) MQTT_SOCKET_TIMEOUT * 1000)) {
return false;
}
}
*result = _client->read();
return true;
}
// reads a byte into result[*index] and increments index
bool PubSubClient::readByte(uint8_t * result, uint16_t * index)
{
uint16_t current_index = *index;
uint8_t * write_address = &(result[current_index]);
if(readByte(write_address)) {
*index = current_index + 1;
return true;
}
return false;
}
uint16_t PubSubClient::readPacket(uint8_t* lengthLength)
{
uint16_t len = 0;
if(!readByte(buffer, &len)) {
return 0;
}
bool isPublish = (buffer[0]&0xF0) == MQTTPUBLISH;
uint32_t multiplier = 1;
uint16_t length = 0;
uint8_t digit = 0;
uint16_t skip = 0;
uint8_t start = 0;
do {
if (len == 5) {
// Invalid remaining length encoding - kill the connection
_state = MQTT_DISCONNECTED;
_client->stop();
return 0;
}
if(!readByte(&digit)) {
return 0;
}
buffer[len++] = digit;
length += (digit & 127) * multiplier;
multiplier *= 128;
} while ((digit & 128) != 0);
*lengthLength = len-1;
if (isPublish) {
// Read in topic length to calculate bytes to skip over for Stream writing
if(!readByte(buffer, &len)) {
return 0;
}
if(!readByte(buffer, &len)) {
return 0;
}
skip = (buffer[*lengthLength+1]<<8)+buffer[*lengthLength+2];
start = 2;
if (buffer[0]&MQTTQOS1) {
// skip message id
skip += 2;
}
}
for (uint16_t i = start; i<length; i++) {
if(!readByte(&digit)) {
return 0;
}
if (this->stream) {
if (isPublish && len-*lengthLength-2>skip) {
this->stream->write(digit);
}
}
if (len < MQTT_MAX_PACKET_SIZE) {
buffer[len] = digit;
}
len++;
}
if (!this->stream && len > MQTT_MAX_PACKET_SIZE) {
len = 0; // This will cause the packet to be ignored.
}
return len;
}
bool PubSubClient::loop()
{
if (connected()) {
unsigned long t = millis();
if ((t - lastInActivity > MQTT_KEEPALIVE*1000UL) || (t - lastOutActivity > MQTT_KEEPALIVE*1000UL)) {
if (pingOutstanding) {
this->_state = MQTT_CONNECTION_TIMEOUT;
_client->stop();
return false;
} else {
buffer[0] = MQTTPINGREQ;
buffer[1] = 0;
_client->write(buffer,2);
lastOutActivity = t;
lastInActivity = t;
pingOutstanding = true;
}
}
if (_client->available()) {
uint8_t llen;
uint16_t len = readPacket(&llen);
if (len > 0) {
lastInActivity = t;
uint8_t type = buffer[0]&0xF0;
if (type == MQTTPUBLISH) {
if (callback) {
uint16_t tl = (buffer[llen+1]<<8)+buffer[llen+2]; /* topic length in bytes */
memmove(buffer+llen+2,buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */
buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */
char *topic = (char*) buffer+llen+2;
uint8_t *payload;
// msgId only present for QOS>0
if ((buffer[0]&0x06) == MQTTQOS1) {
uint16_t msgId = 0;
msgId = (buffer[llen+3+tl]<<8)+buffer[llen+3+tl+1];
payload = buffer+llen+3+tl+2;
callback(topic,payload,len-llen-3-tl-2);
buffer[0] = MQTTPUBACK;
buffer[1] = 2;
buffer[2] = (msgId >> 8);
buffer[3] = (msgId & 0xFF);
_client->write(buffer,4);
lastOutActivity = t;
} else {
payload = buffer+llen+3+tl;
callback(topic,payload,len-llen-3-tl);
}
}
} else if (type == MQTTPINGREQ) {
buffer[0] = MQTTPINGRESP;
buffer[1] = 0;
_client->write(buffer,2);
} else if (type == MQTTPINGRESP) {
pingOutstanding = false;
}
} else if (!connected()) {
// readPacket has closed the connection
return false;
}
}
return true;
}
return false;
}
bool PubSubClient::publish(const char* topic, const char* payload)
{
return publish(topic,(const uint8_t*)payload,strlen(payload),false);
}
bool PubSubClient::publish(const char* topic, const char* payload, bool retained)
{
return publish(topic,(const uint8_t*)payload,strlen(payload),retained);
}
bool PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength)
{
return publish(topic, payload, plength, false);
}
bool PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength,
bool retained)
{
if (connected()) {
if (MQTT_MAX_PACKET_SIZE < MQTT_MAX_HEADER_SIZE + 2+strlen(topic) + plength) {
// Too long
return false;
}
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
length = writeString(topic,buffer,length);
uint16_t i;
for (i=0; i<plength; i++) {
buffer[length++] = payload[i];
}
uint8_t header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
return write(header,buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false;
}
bool PubSubClient::publish_P(const char* topic, const char* payload, bool retained)
{
return publish_P(topic, (const uint8_t*)payload, strlen(payload), retained);
}
bool PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength,
bool retained)
{
unsigned int rc = 0;
uint16_t tlen;
unsigned int pos = 0;
unsigned int i;
uint8_t header;
unsigned int len;
if (!connected()) {
return false;
}
tlen = strlen(topic);
header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
buffer[pos++] = header;
len = plength + 2 + tlen;
do {
uint8_t digit;
digit = len % 128;
len = len / 128;
if (len > 0) {
digit |= 0x80;
}
buffer[pos++] = digit;
} while(len>0);
pos = writeString(topic,buffer,pos);
rc += _client->write(buffer,pos);
for (i=0; i<plength; i++) {
rc += _client->write((char)pgm_read_byte_near(payload + i));
}
lastOutActivity = millis();
return rc == tlen + 4 + plength;
}
bool PubSubClient::beginPublish(const char* topic, unsigned int plength, bool retained)
{
if (connected()) {
// Send the header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
length = writeString(topic,buffer,length);
uint8_t header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
size_t hlen = buildHeader(header, buffer, plength+length-MQTT_MAX_HEADER_SIZE);
uint16_t rc = _client->write(buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen));
lastOutActivity = millis();
return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen)));
}
return false;
}
int PubSubClient::endPublish()
{
return 1;
}
size_t PubSubClient::write(uint8_t data)
{
lastOutActivity = millis();
return _client->write(data);
}
size_t PubSubClient::write(const uint8_t *buffer, size_t size)
{
lastOutActivity = millis();
return _client->write(buffer,size);
}
size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length)
{
uint8_t lenBuf[4];
uint8_t llen = 0;
uint8_t pos = 0;
uint16_t len = length;
do {
uint8_t digit;
digit = len % 128;
len = len / 128;
if (len > 0) {
digit |= 0x80;
}
lenBuf[pos++] = digit;
llen++;
} while(len>0);
buf[4-llen] = header;
for (int i=0; i<llen; i++) {
buf[MQTT_MAX_HEADER_SIZE-llen+i] = lenBuf[i];
}
return llen+1; // Full header size is variable length bit plus the 1-byte fixed header
}
bool PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length)
{
uint16_t rc;
uint8_t hlen = buildHeader(header, buf, length);
#ifdef MQTT_MAX_TRANSFER_SIZE
uint8_t* writeBuf = buf+(MQTT_MAX_HEADER_SIZE-hlen);
uint16_t bytesRemaining = length+hlen; //Match the length type
uint8_t bytesToWrite;
bool result = true;
while((bytesRemaining > 0) && result) {
bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining;
rc = _client->write(writeBuf,bytesToWrite);
result = (rc == bytesToWrite);
bytesRemaining -= rc;
writeBuf += rc;
}
return result;
#else
rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen);
lastOutActivity = millis();
return (rc == hlen+length);
#endif
}
bool PubSubClient::subscribe(const char* topic)
{
return subscribe(topic, 0);
}
bool PubSubClient::subscribe(const char* topic, uint8_t qos)
{
if (qos > 1) {
return false;
}
if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
// Too long
return false;
}
if (connected()) {
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
buffer[length++] = (nextMsgId >> 8);
buffer[length++] = (nextMsgId & 0xFF);
length = writeString((char*)topic, buffer,length);
buffer[length++] = qos;
return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false;
}
bool PubSubClient::unsubscribe(const char* topic)
{
if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
// Too long
return false;
}
if (connected()) {
uint16_t length = MQTT_MAX_HEADER_SIZE;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
buffer[length++] = (nextMsgId >> 8);
buffer[length++] = (nextMsgId & 0xFF);
length = writeString(topic, buffer,length);
return write(MQTTUNSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false;
}
void PubSubClient::disconnect()
{
buffer[0] = MQTTDISCONNECT;
buffer[1] = 0;
_client->write(buffer,2);
_state = MQTT_DISCONNECTED;
_client->flush();
_client->stop();
lastInActivity = lastOutActivity = millis();
}
uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos)
{
const char* idp = string;
uint16_t i = 0;
pos += 2;
while (*idp) {
buf[pos++] = *idp++;
i++;
}
buf[pos-i-2] = (i >> 8);
buf[pos-i-1] = (i & 0xFF);
return pos;
}
bool PubSubClient::connected()
{
bool rc;
if (_client == NULL ) {
rc = false;
} else {
rc = (int)_client->connected();
if (!rc) {
if (this->_state == MQTT_CONNECTED) {
this->_state = MQTT_CONNECTION_LOST;
_client->flush();
_client->stop();
}
}
}
return rc;
}
PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port)
{
IPAddress addr(ip[0],ip[1],ip[2],ip[3]);
return setServer(addr,port);
}
PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port)
{
this->ip = ip;
this->port = port;
this->domain = NULL;
return *this;
}
PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port)
{
this->domain = domain;
this->port = port;
return *this;
}
// cppcheck-suppress passedByValue
PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE)
{
this->callback = callback;
return *this;
}
PubSubClient& PubSubClient::setClient(Client& client)
{
this->_client = &client;
return *this;
}
PubSubClient& PubSubClient::setStream(Stream& stream)
{
this->stream = &stream;
return *this;
}
int PubSubClient::state()
{
return this->_state;
}

View File

@@ -0,0 +1,183 @@
/*
PubSubClient.h - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#ifndef PubSubClient_h
#define PubSubClient_h
#include <Arduino.h>
#include "IPAddress.h"
#include "Client.h"
#include "Stream.h"
#define MQTT_VERSION_3_1 3
#define MQTT_VERSION_3_1_1 4
// MQTT_VERSION : Pick the version
//#define MQTT_VERSION MQTT_VERSION_3_1
#ifndef MQTT_VERSION
#define MQTT_VERSION MQTT_VERSION_3_1_1
#endif
// MQTT_MAX_PACKET_SIZE : Maximum packet size
#ifndef MQTT_MAX_PACKET_SIZE
#define MQTT_MAX_PACKET_SIZE 128
#endif
// MQTT_KEEPALIVE : keepAlive interval in Seconds
#ifndef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 15
#endif
// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds
#ifndef MQTT_SOCKET_TIMEOUT
#define MQTT_SOCKET_TIMEOUT 15
#endif
// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client
// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to
// pass the entire MQTT packet in each write call.
//#define MQTT_MAX_TRANSFER_SIZE 80
// Possible values for client.state()
#define MQTT_CONNECTION_TIMEOUT -4
#define MQTT_CONNECTION_LOST -3
#define MQTT_CONNECT_FAILED -2
#define MQTT_DISCONNECTED -1
#define MQTT_CONNECTED 0
#define MQTT_CONNECT_BAD_PROTOCOL 1
#define MQTT_CONNECT_BAD_CLIENT_ID 2
#define MQTT_CONNECT_UNAVAILABLE 3
#define MQTT_CONNECT_BAD_CREDENTIALS 4
#define MQTT_CONNECT_UNAUTHORIZED 5
#define MQTTCONNECT 1 << 4 // Client request to connect to Server
#define MQTTCONNACK 2 << 4 // Connect Acknowledgment
#define MQTTPUBLISH 3 << 4 // Publish message
#define MQTTPUBACK 4 << 4 // Publish Acknowledgment
#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1)
#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2)
#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3)
#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request
#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment
#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request
#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment
#define MQTTPINGREQ 12 << 4 // PING Request
#define MQTTPINGRESP 13 << 4 // PING Response
#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting
#define MQTTReserved 15 << 4 // Reserved
#define MQTTQOS0 (0 << 1)
#define MQTTQOS1 (1 << 1)
#define MQTTQOS2 (2 << 1)
// Maximum size of fixed header and variable length size header
#define MQTT_MAX_HEADER_SIZE 5
#if defined(ESP8266) || defined(ESP32)
#include <functional>
#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
#else
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
#endif
#define CHECK_STRING_LENGTH(l,s) if (l+2+strlen(s) > MQTT_MAX_PACKET_SIZE) {_client->stop();return false;}
/** PubSubClient class */
class PubSubClient : public Print
{
private:
Client* _client;
uint8_t buffer[MQTT_MAX_PACKET_SIZE];
uint16_t nextMsgId;
unsigned long lastOutActivity;
unsigned long lastInActivity;
bool pingOutstanding;
MQTT_CALLBACK_SIGNATURE;
uint16_t readPacket(uint8_t*);
bool readByte(uint8_t * result);
bool readByte(uint8_t * result, uint16_t * index);
bool write(uint8_t header, uint8_t* buf, uint16_t length);
uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos);
// Build up the header ready to send
// Returns the size of the header
// Note: the header is built at the end of the first MQTT_MAX_HEADER_SIZE bytes, so will start
// (MQTT_MAX_HEADER_SIZE - <returned size>) bytes into the buffer
size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length);
IPAddress ip;
const char* domain;
uint16_t port;
Stream* stream;
int _state;
public:
PubSubClient(); //!< PubSubClient
explicit PubSubClient(Client& client); //!< PubSubClient
PubSubClient(IPAddress, uint16_t, Client& client); //!< PubSubClient
PubSubClient(IPAddress, uint16_t, Client& client, Stream&); //!< PubSubClient
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); //!< PubSubClient
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client,
Stream&); //!< PubSubClient
PubSubClient(uint8_t *, uint16_t, Client& client); //!< PubSubClient
PubSubClient(uint8_t *, uint16_t, Client& client, Stream&); //!< PubSubClient
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); //!< PubSubClient
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client,
Stream&); //!< PubSubClient
PubSubClient(const char*, uint16_t, Client& client); //!< PubSubClient
PubSubClient(const char*, uint16_t, Client& client, Stream&); //!< PubSubClient
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); //!< PubSubClient
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client,
Stream&); //!< PubSubClient
PubSubClient& setServer(IPAddress ip, uint16_t port); //!< setServer
PubSubClient& setServer(uint8_t * ip, uint16_t port); //!< setServer
PubSubClient& setServer(const char * domain, uint16_t port); //!< setServer
PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE); //!< setCallback
PubSubClient& setClient(Client& client); //!< setClient
PubSubClient& setStream(Stream& stream); //!< setStream
bool connect(const char* id); //!< connect
bool connect(const char* id, const char* user, const char* pass); //!< connect
bool connect(const char* id, const char* willTopic, uint8_t willQos, bool willRetain,
const char* willMessage); //!< connect
bool connect(const char* id, const char* user, const char* pass, const char* willTopic,
uint8_t willQos, bool willRetain, const char* willMessage); //!< connect
bool connect(const char* id, const char* user, const char* pass, const char* willTopic,
uint8_t willQos, bool willRetain, const char* willMessage, bool cleanSession); //!< connect
void disconnect(); //!< disconnect
bool publish(const char* topic, const char* payload); //!< publish
bool publish(const char* topic, const char* payload, bool retained); //!< publish
bool publish(const char* topic, const uint8_t * payload, unsigned int plength); //!< publish
bool publish(const char* topic, const uint8_t * payload, unsigned int plength,
bool retained); //!< publish
bool publish_P(const char* topic, const char* payload, bool retained); //!< publish
bool publish_P(const char* topic, const uint8_t * payload, unsigned int plength,
bool retained); //!< publish
// Start to publish a message.
// This API:
// beginPublish(...)
// one or more calls to write(...)
// endPublish()
// Allows for arbitrarily large payloads to be sent without them having to be copied into
// a new buffer and held in memory at one time
// Returns 1 if the message was started successfully, 0 if there was an error
bool beginPublish(const char* topic, unsigned int plength, bool retained); //!< beginPublish
// Finish off this publish message (started with beginPublish)
// Returns 1 if the packet was sent successfully, 0 if there was an error
int endPublish(); //!< endPublish
// Write a single byte of payload (only to be used with beginPublish/endPublish)
virtual size_t write(uint8_t); //!< write
// Write size bytes from buffer into the payload (only to be used with beginPublish/endPublish)
// Returns the number of bytes written
virtual size_t write(const uint8_t *buffer, size_t size); //!< write
bool subscribe(const char* topic); //!< subscribe
bool subscribe(const char* topic, uint8_t qos); //!< subscribe
bool unsubscribe(const char* topic); //!< unsubscribe
bool loop(); //!< loop
bool connected(); //!< connected
int state(); //!< state
};
#endif

View File

@@ -0,0 +1,48 @@
# Arduino Client for MQTT
This library provides a client for doing simple publish/subscribe messaging with
a server that supports MQTT.
## Examples
The library comes with a number of example sketches. See File > Examples > PubSubClient
within the Arduino application.
Full API documentation is available here: https://pubsubclient.knolleary.net
## Limitations
- It can only publish QoS 0 messages. It can subscribe at QoS 0 or QoS 1.
- The maximum message size, including header, is **128 bytes** by default. This
is configurable via `MQTT_MAX_PACKET_SIZE` in `PubSubClient.h`.
- The keepalive interval is set to 15 seconds by default. This is configurable
via `MQTT_KEEPALIVE` in `PubSubClient.h`.
- The client uses MQTT 3.1.1 by default. It can be changed to use MQTT 3.1 by
changing value of `MQTT_VERSION` in `PubSubClient.h`.
## Compatible Hardware
The library uses the Arduino Ethernet Client api for interacting with the
underlying network hardware. This means it Just Works with a growing number of
boards and shields, including:
- Arduino Ethernet
- Arduino Ethernet Shield
- Arduino YUN use the included `YunClient` in place of `EthernetClient`, and
be sure to do a `Bridge.begin()` first
- Arduino WiFi Shield - if you want to send packets > 90 bytes with this shield,
enable the `MQTT_MAX_TRANSFER_SIZE` define in `PubSubClient.h`.
- Sparkfun WiFly Shield [library](https://github.com/dpslwk/WiFly)
- TI CC3000 WiFi - [library](https://github.com/sparkfun/SFE_CC3000_Library)
- Intel Galileo/Edison
- ESP8266
- ESP32
The library cannot currently be used with hardware based on the ENC28J60 chip
such as the Nanode or the Nuelectronics Ethernet Shield. For those, there is an
[alternative library](https://github.com/njh/NanodeMQTT) available.
## License
This code is released under the MIT License.

View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -0,0 +1,32 @@
SPIFlash
========
[![Build Status](https://travis-ci.org/LowPowerLab/SPIFlash.svg?branch=master)](https://travis-ci.org/LowPowerLab/SPIFlash)
[![GitHub release](https://img.shields.io/github/release/LowPowerLab/SPIFlash.svg)](https://github.com/LowPowerLab/SPIFlash)
[![GitHub issues](https://img.shields.io/github/issues/LowPowerLab/SPIFlash.svg)](https://github.com/LowPowerLab/SPIFlash/issues)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/LowPowerLab/SPIFlash.svg)](https://github.com/LowPowerLab/SPIFlash/pulls)
[![license](https://img.shields.io/github/license/LowPowerLab/SPIFlash.svg)](https://github.com/LowPowerLab/SPIFlash/blob/master/LICENSE.txt)
Arduino/Moteino library for read/write access to SPI flash memory chips.
This works with 256byte/page SPI flash memory such as the [4MBIT W25X40CLSNIG](https://lowpowerlab.com/shop/product/72) used on [Moteino](https://www.moteino.com) for data storage and wireless programming.
<br/>
For instance a 4MBit (512Kbyte) flash chip will have 2048 pages: 256*2048 = 524288 bytes (512Kbytes).
<br/>Minimal modifications should allow chips that have different page size to work.
<br/>DEPENDS ON: Arduino native *SPI library*.
<br/>
This library was primarily developed to enable **safe** wireless programming on Moteino nodes and Moteino based applications such as the [SwitchMote](https://lowpowerlab.com/guide/switchmote/). This has been documented at [lowpowerlab](https://lowpowerlab.com/guide/moteino/wireless-programming/). [Dualoptiboot](https://github.com/LowPowerLab/DualOptiboot) (all AVR based Moteinos come with it) and [RFM69_OTA WirelessProgramming library](https://github.com/LowPowerLab/RFM69) are required to be able to wirelessly re-flash a remote Moteino.
### Installation
Copy the content of this library in the "Arduino/libraries/SPIFlash" folder.
<br />
To find your Arduino folder go to File>Preferences in the Arduino IDE.
<br/>
See [this tutorial](https://www.arduino.cc/en/Guide/Libraries) on installing Arduino libraries.
### License
Copyright (c) 2013-2018 by Felix Rusu <felix@lowpowerlab.com>
<br/><br/>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
<br/><br/>
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
<br/><br/>
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@@ -0,0 +1,375 @@
// Copyright (c) 2013-2015 by Felix Rusu, LowPowerLab.com
// SPI Flash memory library for arduino/moteino.
// This works with 256byte/page SPI flash memory
// For instance a 4MBit (512Kbyte) flash chip will have 2048 pages: 256*2048 = 524288 bytes (512Kbytes)
// Minimal modifications should allow chips that have different page size but modifications
// DEPENDS ON: Arduino SPI library
// > Updated Jan. 5, 2015, TomWS1, modified writeBytes to allow blocks > 256 bytes and handle page misalignment.
// > Updated Feb. 26, 2015 TomWS1, added support for SPI Transactions (Arduino 1.5.8 and above)
// > Selective merge by Felix after testing in IDE 1.0.6, 1.6.4
// > Updated May 19, 2016 D-H-R, added support for SST25/Microchip Flash which does not support Page programming with OPCode 0x02,
// > use define MY_SPIFLASH_SST25TYPE for SST25 Type Flash Memory
// > Updated Sep 07, 2018 tekka, sync with https://github.com/LowPowerLab/SPIFlash
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
#include "SPIFlash.h"
uint8_t SPIFlash::UNIQUEID[8];
/// IMPORTANT: NAND FLASH memory requires erase before write, because
/// it can only transition from 1s to 0s and only the erase command can reset all 0s to 1s
/// See http://en.wikipedia.org/wiki/Flash_memory
/// The smallest range that can be erased is a sector (4K, 32K, 64K); there is also a chip erase command
/// Constructor. JedecID is optional but recommended, since this will ensure that the device is present and has a valid response
/// get this from the datasheet of your flash chip
/// Example for Atmel-Adesto 4Mbit AT25DF041A: 0x1F44 (page 27: http://www.adestotech.com/sites/default/files/datasheets/doc3668.pdf)
/// Example for Winbond 4Mbit W25X40CL: 0xEF30 (page 14: http://www.winbond.com/NR/rdonlyres/6E25084C-0BFE-4B25-903D-AE10221A0929/0/W25X40CL.pdf)
// Suppress uninitialized member variable in constructor because some memory can be saved with
// on-demand initialization of these members
// cppcheck-suppress uninitMemberVar
SPIFlash::SPIFlash(uint8_t slaveSelectPin, uint16_t jedecID)
{
_slaveSelectPin = slaveSelectPin;
_jedecID = jedecID;
}
/// Select the flash chip
void SPIFlash::select()
{
//save current SPI settings
#ifndef SPI_HAS_TRANSACTION
noInterrupts();
#endif
#if defined(SPCR) && defined(SPSR)
_SPCR = SPCR;
_SPSR = SPSR;
#endif
#ifdef SPI_HAS_TRANSACTION
SPI.beginTransaction(_settings);
#else
// set FLASH SPI settings
SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(MSBFIRST);
SPI.setClockDivider(
SPI_CLOCK_DIV4); //decided to slow down from DIV2 after SPI stalling in some instances, especially visible on mega1284p when RFM69 and FLASH chip both present
#endif
hwDigitalWrite(_slaveSelectPin, LOW);
}
/// UNselect the flash chip
void SPIFlash::unselect()
{
hwDigitalWrite(_slaveSelectPin, HIGH);
//restore SPI settings to what they were before talking to the FLASH chip
#ifdef SPI_HAS_TRANSACTION
SPI.endTransaction();
#else
interrupts();
#endif
#if defined(SPCR) && defined(SPSR)
SPCR = _SPCR;
SPSR = _SPSR;
#endif
}
/// setup SPI, read device ID etc...
bool SPIFlash::initialize()
{
#if defined(SPCR) && defined(SPSR)
_SPCR = SPCR;
_SPSR = SPSR;
#endif
hwPinMode(_slaveSelectPin, OUTPUT);
SPI.begin(); // see https://github.com/LowPowerLab/SPIFlash/issues/20
#ifdef SPI_HAS_TRANSACTION
_settings = SPISettings(4000000, MSBFIRST, SPI_MODE0);
#endif
unselect();
wakeup();
if (_jedecID == 0 || readDeviceId() == _jedecID) {
command(SPIFLASH_STATUSWRITE, true); // Write Status Register
SPI.transfer(0); // Global Unprotect
unselect();
return true;
}
return false;
}
/// Get the manufacturer and device ID bytes (as a short word)
uint16_t SPIFlash::readDeviceId()
{
#if defined(__AVR_ATmega32U4__) // Arduino Leonardo, MoteinoLeo
command(SPIFLASH_IDREAD); // Read JEDEC ID
#else
select();
SPI.transfer(SPIFLASH_IDREAD);
#endif
uint16_t jedecid = SPI.transfer(0) << 8;
jedecid |= SPI.transfer(0);
unselect();
return jedecid;
}
/// Get the 64 bit unique identifier, stores it in UNIQUEID[8]. Only needs to be called once, ie after initialize
/// Returns the byte pointer to the UNIQUEID byte array
/// Read UNIQUEID like this:
/// flash.readUniqueId(); for (uint8_t i=0;i<8;i++) { Serial.print(flash.UNIQUEID[i], HEX); Serial.print(' '); }
/// or like this:
/// flash.readUniqueId(); uint8_t* MAC = flash.readUniqueId(); for (uint8_t i=0;i<8;i++) { Serial.print(MAC[i], HEX); Serial.print(' '); }
uint8_t* SPIFlash::readUniqueId()
{
command(SPIFLASH_MACREAD);
SPI.transfer(0);
SPI.transfer(0);
SPI.transfer(0);
SPI.transfer(0);
for (uint8_t i=0; i<8; i++) {
UNIQUEID[i] = SPI.transfer(0);
}
unselect();
return UNIQUEID;
}
/// read 1 byte from flash memory
uint8_t SPIFlash::readByte(uint32_t addr)
{
command(SPIFLASH_ARRAYREADLOWFREQ);
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
uint8_t result = SPI.transfer(0);
unselect();
return result;
}
/// read unlimited # of bytes
void SPIFlash::readBytes(uint32_t addr, void* buf, uint16_t len)
{
command(SPIFLASH_ARRAYREAD);
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
SPI.transfer(0); //"dont care"
for (uint16_t i = 0; i < len; ++i) {
((uint8_t*) buf)[i] = SPI.transfer(0);
}
unselect();
}
/// Send a command to the flash chip, pass TRUE for isWrite when its a write command
void SPIFlash::command(uint8_t cmd, bool isWrite)
{
#if defined(__AVR_ATmega32U4__) // Arduino Leonardo, MoteinoLeo
DDRB |= B00000001; // Make sure the SS pin (PB0 - used by RFM12B on MoteinoLeo R1) is set as output HIGH!
PORTB |= B00000001;
#endif
if (isWrite) {
command(SPIFLASH_WRITEENABLE); // Write Enable
unselect();
}
//wait for any write/erase to complete
// a time limit cannot really be added here without it being a very large safe limit
// that is because some chips can take several seconds to carry out a chip erase or other similar multi block or entire-chip operations
// a recommended alternative to such situations where chip can be or not be present is to add a 10k or similar weak pulldown on the
// open drain MISO input which can read noise/static and hence return a non 0 status byte, causing the while() to hang when a flash chip is not present
if (cmd != SPIFLASH_WAKE) while(busy());
select();
SPI.transfer(cmd);
}
/// check if the chip is busy erasing/writing
bool SPIFlash::busy()
{
/*
select();
SPI.transfer(SPIFLASH_STATUSREAD);
uint8_t status = SPI.transfer(0);
unselect();
return status & 1;
*/
return readStatus() & 1;
}
/// return the STATUS register
uint8_t SPIFlash::readStatus()
{
select();
SPI.transfer(SPIFLASH_STATUSREAD);
uint8_t status = SPI.transfer(0);
unselect();
return status;
}
/// Write 1 byte to flash memory
/// WARNING: you can only write to previously erased memory locations (see datasheet)
/// use the block erase commands to first clear memory (write 0xFFs)
void SPIFlash::writeByte(uint32_t addr, uint8_t byt)
{
command(SPIFLASH_BYTEPAGEPROGRAM, true); // Byte/Page Program
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
SPI.transfer(byt);
unselect();
}
/// write multiple bytes to flash memory (up to 64K)
/// WARNING: you can only write to previously erased memory locations (see datasheet)
/// use the block erase commands to first clear memory (write 0xFFs)
/// This version handles both page alignment and data blocks larger than 256 bytes.
/// See documentation of #MY_SPIFLASH_SST25TYPE define for more information
void SPIFlash::writeBytes(uint32_t addr, const void* buf, uint16_t len)
{
#ifdef MY_SPIFLASH_SST25TYPE
//SST25 Type of Flash does not support Page Programming but AAI Word Programming
uint16_t i=0;
uint8_t oddAdr=0;
command(SPIFLASH_AAIWORDPROGRAM, true); // Byte/Page Program
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
if (addr%2) {
//start address is not even, i.e. first byte of word must be 0xff
SPI.transfer(0xff);
SPI.transfer(((uint8_t*) buf)[0]);
unselect();
oddAdr=1; //following addresses must all be shifted one off
len--;
if (len > 0) {
command(SPIFLASH_AAIWORDPROGRAM); //If for loop will run issue Wordprogram command
}
}
for (i=0; i<(len/2); i++) {
//AAI command must be set before every new word
if (i>0) {
command(SPIFLASH_AAIWORDPROGRAM); //Wordprogram command for first write has been issued before
}
SPI.transfer(((uint8_t*) buf)[i*2+oddAdr]);
SPI.transfer(((uint8_t*) buf)[i*2+1+oddAdr]);
unselect();
}
if (len-i*2 == 1) {
//There is one byte (i.e. half word) left. This happens if len was odd or (len was even and addr odd)
if (i>0) {
command(SPIFLASH_AAIWORDPROGRAM); //if for loop had not run wordprogram command from before is still valid
}
SPI.transfer(((uint8_t*) buf)[i*2+oddAdr]);
SPI.transfer(0xff);
unselect();
}
command(SPIFLASH_WRITEDISABLE); //end AAI programming
unselect();
#else
uint16_t maxBytes = 256-(addr%256); // force the first set of bytes to stay within the first page
uint16_t offset = 0;
while (len>0) {
uint16_t n = (len<=maxBytes) ? len : maxBytes;
command(SPIFLASH_BYTEPAGEPROGRAM, true); // Byte/Page Program
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
for (uint16_t i = 0; i < n; i++) {
SPI.transfer(((uint8_t*) buf)[offset + i]);
}
unselect();
addr+=n; // adjust the addresses and remaining bytes by what we've just transferred.
offset +=n;
len -= n;
maxBytes = 256; // now we can do up to 256 bytes per loop
}
#endif
}
/// erase entire flash memory array
/// may take several seconds depending on size, but is non blocking
/// so you may wait for this to complete using busy() or continue doing
/// other things and later check if the chip is done with busy()
/// note that any command will first wait for chip to become available using busy()
/// so no need to do that twice
void SPIFlash::chipErase()
{
command(SPIFLASH_CHIPERASE, true);
unselect();
}
/// erase a 4Kbyte block
void SPIFlash::blockErase4K(uint32_t addr)
{
command(SPIFLASH_BLOCKERASE_4K, true); // Block Erase
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
unselect();
}
/// erase a 32Kbyte block
void SPIFlash::blockErase32K(uint32_t addr)
{
command(SPIFLASH_BLOCKERASE_32K, true); // Block Erase
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
unselect();
}
/// erase a 64Kbyte block
void SPIFlash::blockErase64K(uint32_t addr)
{
command(SPIFLASH_BLOCKERASE_64K, true); // Block Erase
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
unselect();
}
void SPIFlash::sleep()
{
command(SPIFLASH_SLEEP);
unselect();
}
void SPIFlash::wakeup()
{
command(SPIFLASH_WAKE);
unselect();
}
/// cleanup
void SPIFlash::end()
{
SPI.end();
}

View File

@@ -0,0 +1,198 @@
// Copyright (c) 2013-2015 by Felix Rusu, LowPowerLab.com
// SPI Flash memory library for arduino/moteino.
// This works with 256byte/page SPI flash memory
// For instance a 4MBit (512Kbyte) flash chip will have 2048 pages: 256*2048 = 524288 bytes (512Kbytes)
// Minimal modifications should allow chips that have different page size but modifications
// DEPENDS ON: Arduino SPI library
// > Updated Jan. 5, 2015, TomWS1, modified writeBytes to allow blocks > 256 bytes and handle page misalignment.
// > Updated Feb. 26, 2015 TomWS1, added support for SPI Transactions (Arduino 1.5.8 and above)
// > Selective merge by Felix after testing in IDE 1.0.6, 1.6.4
// > Updated May 19, 2016 D-H-R, added support for SST25/Microchip Flash which does not support Page programming with OPCode 0x02,
// > use define MY_SPIFLASH_SST25TYPE for SST25 Type Flash Memory. Added / changed comments to better suit doxygen
// > Updated Sep 07, 2018 tekka, sync with https://github.com/LowPowerLab/SPIFlash
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
///
/// @file SPIFlash.h
///
/// @brief SPIFlash provides access to a SPI Flash IC for OTA update or storing data
///
/// IMPORTANT: NAND FLASH memory requires erase before write, because
/// it can only transition from 1s to 0s and only the erase command can reset all 0s to 1s
/// See http://en.wikipedia.org/wiki/Flash_memory
/// The smallest range that can be erased is a sector (4K, 32K, 64K); there is also a chip erase command
///
/// Standard SPI flash commands <BR>
/// Assuming the WP pin is pulled up (to disable hardware write protection).<BR>
/// To use any write commands the WEL bit in the status register must be set to 1.<BR>
/// This is accomplished by sending a 0x06 command before any such write/erase command.<BR>
/// The WEL bit in the status register resets to the logical ?0? state after a device power-up or reset.
/// In addition, the WEL bit will be reset to the logical ?0? state automatically under the following conditions:<BR>
/// - Write Disable operation completes successfully<BR>
/// - Write Status Register operation completes successfully or aborts<BR>
/// - Protect Sector operation completes successfully or aborts<BR>
/// - Unprotect Sector operation completes successfully or aborts<BR>
/// - Byte/Page Program operation completes successfully or aborts<BR>
/// - Sequential Program Mode reaches highest unprotected memory location<BR>
/// - Sequential Program Mode reaches the end of the memory array<BR>
/// - Sequential Program Mode aborts<BR>
/// - Block Erase operation completes successfully or aborts<BR>
/// - Chip Erase operation completes successfully or aborts<BR>
/// - Hold condition aborts
///
#ifndef _SPIFLASH_H_
#define _SPIFLASH_H_
#if ARDUINO >= 100
#include <Arduino.h>
#else
#include <wiring.h>
#include "pins_arduino.h"
#endif
#include <SPI.h>
#ifndef SPIFLASH_WRITEENABLE
#define SPIFLASH_WRITEENABLE 0x06 //!< write enable
#endif
#ifndef SPIFLASH_WRITEDISABLE
#define SPIFLASH_WRITEDISABLE 0x04 //!< write disable
#endif
#ifndef SPIFLASH_BLOCKERASE_4K
#define SPIFLASH_BLOCKERASE_4K 0x20 //!< erase one 4K block of flash memory
#endif
#ifndef SPIFLASH_BLOCKERASE_32K
#define SPIFLASH_BLOCKERASE_32K 0x52 //!< erase one 32K block of flash memory
#endif
#ifndef SPIFLASH_BLOCKERASE_64K
#define SPIFLASH_BLOCKERASE_64K 0xD8 //!< erase one 64K block of flash memory
#endif
#ifndef SPIFLASH_CHIPERASE
#define SPIFLASH_CHIPERASE 0x60 //!< @brief chip erase (may take several seconds depending on size)
#endif
//!< Chip is erased but not actually waited for completion (instead need to check the status register BUSY bit)
#ifndef SPIFLASH_STATUSREAD
#define SPIFLASH_STATUSREAD 0x05 //!< read status register
#endif
#ifndef SPIFLASH_STATUSWRITE
#define SPIFLASH_STATUSWRITE 0x01 //!< write status register
#endif
#ifndef SPIFLASH_ARRAYREAD
#define SPIFLASH_ARRAYREAD 0x0B //!< read array (fast, need to add 1 dummy byte after 3 address bytes)
#endif
#ifndef SPIFLASH_ARRAYREADLOWFREQ
#define SPIFLASH_ARRAYREADLOWFREQ 0x03 //!< read array (low frequency)
#endif
#ifndef SPIFLASH_SLEEP
#define SPIFLASH_SLEEP 0xB9 //!< deep power down
#endif
#ifndef SPIFLASH_WAKE
#define SPIFLASH_WAKE 0xAB //!< deep power wake up
#endif
#ifndef SPIFLASH_BYTEPAGEPROGRAM
#define SPIFLASH_BYTEPAGEPROGRAM 0x02 //!< write (1 to 256bytes). Writing more than one Byte is not supported on all devices (e.g. SST25 Series)
#endif
#ifndef SPIFLASH_AAIWORDPROGRAM
#define SPIFLASH_AAIWORDPROGRAM 0xAD //!< @brief Auto Address Increment Programming on Microchip SST Family Devices which do not support page program. <BR>
#endif
//!< Use define #MY_SPIFLASH_SST25TYPE to use AAI prog instead of Bytepageprogram which does not work on SST Family Chips
//!< tested with SST25PF020B80 http://ww1.microchip.com/downloads/en/DeviceDoc/20005135B.pdf
#ifndef SPIFLASH_IDREAD
#define SPIFLASH_IDREAD 0x9F //!< @brief read JEDEC manufacturer and device ID (2 bytes, specific bytes for each manufacturer and device)
#endif
//!< Example for Atmel-Adesto 4Mbit AT25DF041A: 0x1F44 (page 27: http://www.adestotech.com/sites/default/files/datasheets/doc3668.pdf)
//!< Example for Winbond 4Mbit W25X40CL: 0xEF30 (page 14: http://www.winbond.com/NR/rdonlyres/6E25084C-0BFE-4B25-903D-AE10221A0929/0/W25X40CL.pdf)
#ifndef SPIFLASH_MACREAD
#define SPIFLASH_MACREAD 0x4B //!< read unique ID number (MAC)
#endif
///
/// @def MY_SPIFLASH_SST25TYPE
/// @brief If set AAI Word Programming is used to support SST25 Family SPI Flash.
///
/// SST25 Family Flash does not support programming multiple Bytes with opcode 0x02 #SPIFLASH_BYTEPAGEPROGRAM. <BR>
/// If SPIFLASH_SST25TYPE is set and writeBytes is called, it will use opcode 0xAD #SPIFLASH_AAIWORDPROGRAM and care for Byte alignment.<BR>
/// Note: AAI Wordprogramming is independent of Pages, so pagebreaking is not an issue when using AAI Wordprogramming.
///
#ifdef DOXYGEN //needed to tell doxygen not to ignore the define which is actually made somewhere else
#define MY_SPIFLASH_SST25TYPE
#endif
/** SPIFlash class */
class SPIFlash
{
public:
static uint8_t UNIQUEID[8]; //!< Storage for unique identifier
explicit SPIFlash(uint8_t slaveSelectPin, uint16_t jedecID=0); //!< Constructor
bool initialize(); //!< setup SPI, read device ID etc...
void command(uint8_t cmd, bool isWrite=
false); //!< Send a command to the flash chip, pass TRUE for isWrite when its a write command
uint8_t readStatus(); //!< return the STATUS register
uint8_t readByte(uint32_t addr); //!< read 1 byte from flash memory
void readBytes(uint32_t addr, void* buf, uint16_t len); //!< read unlimited # of bytes
void writeByte(uint32_t addr, uint8_t byt); //!< Write 1 byte to flash memory
void writeBytes(uint32_t addr, const void* buf,
uint16_t len); //!< write multiple bytes to flash memory (up to 64K), if define SPIFLASH_SST25TYPE is set AAI Word Programming will be used
bool busy(); //!< check if the chip is busy erasing/writing
void chipErase(); //!< erase entire flash memory array
void blockErase4K(uint32_t address); //!< erase a 4Kbyte block
void blockErase32K(uint32_t address); //!< erase a 32Kbyte block
void blockErase64K(uint32_t addr); //!< erase a 64Kbyte block
uint16_t readDeviceId(); //!< Get the manufacturer and device ID bytes (as a short word)
uint8_t* readUniqueId(); //!< Get the 64 bit unique identifier, stores it in @ref UNIQUEID[8]
void sleep(); //!< Put device to sleep
void wakeup(); //!< Wake device
void end(); //!< end
protected:
void select(); //!< select
void unselect(); //!< unselect
uint8_t _slaveSelectPin; //!< Slave select pin
uint16_t _jedecID; //!< JEDEC ID
uint8_t _SPCR; //!< SPCR
uint8_t _SPSR; //!< SPSR
#ifdef SPI_HAS_TRANSACTION
SPISettings _settings;
#endif
};
#endif

View File

@@ -0,0 +1,127 @@
// **********************************************************************************
// This sketch is an example of using the SPIFlash library with a Moteino
// that has an onboard SPI Flash chip. This sketch listens to a few serial commands
// Hence type the following commands to interact with the SPI flash memory array:
// - 'd' dumps the first 256bytes of the flash chip to screen
// - 'e' erases the entire memory chip
// - 'i' print manufacturer/device ID
// - [0-9] writes a random byte to addresses [0-9] (either 0xAA or 0xBB)
// Get the SPIFlash library from here: https://github.com/LowPowerLab/SPIFlash
// **********************************************************************************
// Copyright Felix Rusu, LowPowerLab.com
// Library and code by Felix Rusu - felix@lowpowerlab.com
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// **********************************************************************************
#include <SPIFlash.h> //get it here: https://github.com/LowPowerLab/SPIFlash
#include <SPI.h>
#define SERIAL_BAUD 115200
char input = 0;
long lastPeriod = -1;
#ifdef __AVR_ATmega1284P__
#define LED 15 // Moteino MEGAs have LEDs on D15
#define FLASH_SS 23 // and FLASH SS on D23
#else
#define LED 9 // Moteinos have LEDs on D9
#define FLASH_SS 8 // and FLASH SS on D8
#endif
//////////////////////////////////////////
// flash(SPI_CS, MANUFACTURER_ID)
// SPI_CS - CS pin attached to SPI flash chip (8 in case of Moteino)
// MANUFACTURER_ID - OPTIONAL, 0x1F44 for adesto(ex atmel) 4mbit flash
// 0xEF30 for windbond 4mbit flash
//////////////////////////////////////////
SPIFlash flash(FLASH_SS, 0xEF30);
void setup()
{
Serial.begin(SERIAL_BAUD);
Serial.print("Start...");
if (flash.initialize()) {
Serial.println("Init OK!");
Blink(LED, 20, 10);
} else {
Serial.println("Init FAIL!");
}
delay(1000);
}
void loop()
{
// Handle serial input (to allow basic DEBUGGING of FLASH chip)
// ie: display first 256 bytes in FLASH, erase chip, write bytes at first 10 positions, etc
if (Serial.available() > 0) {
input = Serial.read();
if (input == 'd') { //d=dump flash area
Serial.println("Flash content:");
int counter = 0;
while(counter<=256) {
Serial.print(flash.readByte(counter++), HEX);
Serial.print('.');
}
Serial.println();
} else if (input == 'e') {
Serial.print("Erasing Flash chip ... ");
flash.chipErase();
while(flash.busy());
Serial.println("DONE");
} else if (input == 'i') {
Serial.print("DeviceID: ");
Serial.println(flash.readDeviceId(), HEX);
} else if (input >= 48 && input <= 57) { //0-9
Serial.print("\nWriteByte(");
Serial.print(input);
Serial.print(")");
flash.writeByte(input-48, (millis()%2) ? 0xaa : 0xbb);
}
}
// Periodically blink the onboard LED while listening for serial commands
if ((int)(millis()/500) > lastPeriod) {
lastPeriod++;
pinMode(LED, OUTPUT);
digitalWrite(LED, lastPeriod%2);
}
}
void Blink(byte PIN, int DELAY_MS, byte loops)
{
pinMode(PIN, OUTPUT);
while (loops--) {
digitalWrite(PIN,HIGH);
delay(DELAY_MS);
digitalWrite(PIN,LOW);
delay(DELAY_MS);
}
}

View File

@@ -0,0 +1,18 @@
SPIFlash KEYWORD1
initialize KEYWORD2
command KEYWORD2
readStatus KEYWORD2
readByte KEYWORD2
readBytes KEYWORD2
writeByte KEYWORD2
writeBytes KEYWORD2
flashBusy KEYWORD2
chipErase KEYWORD2
blockErase4K KEYWORD2
blockErase32K KEYWORD2
readDeviceId KEYWORD2
readUniqueId KEYWORD2
UNIQUEID KEYWORD2
sleep KEYWORD2
wakeup KEYWORD2
end KEYWORD2

View File

@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

Some files were not shown because too many files have changed in this diff Show More