From 12c10a1d88ba9536da7f3cb2ff15b5a9410a7b66 Mon Sep 17 00:00:00 2001 From: Hypfer Date: Sun, 28 Jun 2020 14:36:37 +0200 Subject: [PATCH] Added WifiManager and Home Assistant Autodiscovery --- dep/SimpleTimer-schinken/README.md | 7 + dep/SimpleTimer-schinken/SimpleTimer.cpp | 241 +++++ dep/SimpleTimer-schinken/SimpleTimer.h | 132 +++ dep/WiFiManager-0.15.0/LICENSE | 22 + dep/WiFiManager-0.15.0/WiFiManager.cpp | 847 ++++++++++++++++++ dep/WiFiManager-0.15.0/WiFiManager.h | 203 +++++ dep/WiFiManager-0.15.0/library.json | 13 + dep/WiFiManager-0.15.0/library.properties | 9 + dep/pubsubclient-2.7/.gitignore | 1 + dep/pubsubclient-2.7/.travis.yml | 7 + dep/pubsubclient-2.7/CHANGES.txt | 76 ++ dep/pubsubclient-2.7/LICENSE.txt | 20 + .../examples/mqtt_auth/mqtt_auth.ino | 43 + .../examples/mqtt_basic/mqtt_basic.ino | 77 ++ .../examples/mqtt_esp8266/mqtt_esp8266.ino | 132 +++ .../mqtt_large_message/mqtt_large_message.ino | 179 ++++ .../mqtt_publish_in_callback.ino | 60 ++ .../mqtt_reconnect_nonblocking.ino | 67 ++ .../examples/mqtt_stream/mqtt_stream.ino | 57 ++ dep/pubsubclient-2.7/keywords.txt | 33 + dep/pubsubclient-2.7/src/PubSubClient.cpp | 653 ++++++++++++++ dep/pubsubclient-2.7/src/PubSubClient.h | 174 ++++ dep/pubsubclient-2.7/src/PubSubClient.h.bak | 173 ++++ dep/pubsubclient-2.7/tests/.gitignore | 4 + dep/pubsubclient-2.7/tests/Makefile | 25 + dep/pubsubclient-2.7/tests/README.md | 93 ++ .../tests/src/connect_spec.cpp | 302 +++++++ .../tests/src/keepalive_spec.cpp | 185 ++++ dep/pubsubclient-2.7/tests/src/lib/Arduino.h | 26 + .../tests/src/lib/BDDTest.cpp | 50 ++ dep/pubsubclient-2.7/tests/src/lib/BDDTest.h | 23 + dep/pubsubclient-2.7/tests/src/lib/Buffer.cpp | 34 + dep/pubsubclient-2.7/tests/src/lib/Buffer.h | 23 + dep/pubsubclient-2.7/tests/src/lib/Client.h | 21 + .../tests/src/lib/IPAddress.cpp | 44 + .../tests/src/lib/IPAddress.h | 72 ++ dep/pubsubclient-2.7/tests/src/lib/Print.h | 28 + .../tests/src/lib/ShimClient.cpp | 153 ++++ .../tests/src/lib/ShimClient.h | 51 ++ dep/pubsubclient-2.7/tests/src/lib/Stream.cpp | 39 + dep/pubsubclient-2.7/tests/src/lib/Stream.h | 22 + dep/pubsubclient-2.7/tests/src/lib/trace.h | 10 + .../tests/src/publish_spec.cpp | 190 ++++ .../tests/src/receive_spec.cpp | 279 ++++++ .../tests/src/subscribe_spec.cpp | 177 ++++ .../tests/testcases/__init__.py | 0 .../tests/testcases/mqtt_basic.py | 39 + .../testcases/mqtt_publish_in_callback.py | 59 ++ .../tests/testcases/settings.py | 2 + dep/pubsubclient-2.7/tests/testsuite.py | 181 ++++ esp8266-geigercounter.ino | 286 ++++-- settings.h.example | 36 +- 52 files changed, 5606 insertions(+), 74 deletions(-) create mode 100644 dep/SimpleTimer-schinken/README.md create mode 100644 dep/SimpleTimer-schinken/SimpleTimer.cpp create mode 100644 dep/SimpleTimer-schinken/SimpleTimer.h create mode 100644 dep/WiFiManager-0.15.0/LICENSE create mode 100644 dep/WiFiManager-0.15.0/WiFiManager.cpp create mode 100644 dep/WiFiManager-0.15.0/WiFiManager.h create mode 100644 dep/WiFiManager-0.15.0/library.json create mode 100644 dep/WiFiManager-0.15.0/library.properties create mode 100644 dep/pubsubclient-2.7/.gitignore create mode 100644 dep/pubsubclient-2.7/.travis.yml create mode 100644 dep/pubsubclient-2.7/CHANGES.txt create mode 100644 dep/pubsubclient-2.7/LICENSE.txt create mode 100644 dep/pubsubclient-2.7/examples/mqtt_auth/mqtt_auth.ino create mode 100644 dep/pubsubclient-2.7/examples/mqtt_basic/mqtt_basic.ino create mode 100644 dep/pubsubclient-2.7/examples/mqtt_esp8266/mqtt_esp8266.ino create mode 100644 dep/pubsubclient-2.7/examples/mqtt_large_message/mqtt_large_message.ino create mode 100644 dep/pubsubclient-2.7/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino create mode 100644 dep/pubsubclient-2.7/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino create mode 100644 dep/pubsubclient-2.7/examples/mqtt_stream/mqtt_stream.ino create mode 100644 dep/pubsubclient-2.7/keywords.txt create mode 100644 dep/pubsubclient-2.7/src/PubSubClient.cpp create mode 100644 dep/pubsubclient-2.7/src/PubSubClient.h create mode 100644 dep/pubsubclient-2.7/src/PubSubClient.h.bak create mode 100644 dep/pubsubclient-2.7/tests/.gitignore create mode 100644 dep/pubsubclient-2.7/tests/Makefile create mode 100644 dep/pubsubclient-2.7/tests/README.md create mode 100644 dep/pubsubclient-2.7/tests/src/connect_spec.cpp create mode 100644 dep/pubsubclient-2.7/tests/src/keepalive_spec.cpp create mode 100644 dep/pubsubclient-2.7/tests/src/lib/Arduino.h create mode 100644 dep/pubsubclient-2.7/tests/src/lib/BDDTest.cpp create mode 100644 dep/pubsubclient-2.7/tests/src/lib/BDDTest.h create mode 100644 dep/pubsubclient-2.7/tests/src/lib/Buffer.cpp create mode 100644 dep/pubsubclient-2.7/tests/src/lib/Buffer.h create mode 100644 dep/pubsubclient-2.7/tests/src/lib/Client.h create mode 100644 dep/pubsubclient-2.7/tests/src/lib/IPAddress.cpp create mode 100644 dep/pubsubclient-2.7/tests/src/lib/IPAddress.h create mode 100644 dep/pubsubclient-2.7/tests/src/lib/Print.h create mode 100644 dep/pubsubclient-2.7/tests/src/lib/ShimClient.cpp create mode 100644 dep/pubsubclient-2.7/tests/src/lib/ShimClient.h create mode 100644 dep/pubsubclient-2.7/tests/src/lib/Stream.cpp create mode 100644 dep/pubsubclient-2.7/tests/src/lib/Stream.h create mode 100644 dep/pubsubclient-2.7/tests/src/lib/trace.h create mode 100644 dep/pubsubclient-2.7/tests/src/publish_spec.cpp create mode 100644 dep/pubsubclient-2.7/tests/src/receive_spec.cpp create mode 100644 dep/pubsubclient-2.7/tests/src/subscribe_spec.cpp create mode 100644 dep/pubsubclient-2.7/tests/testcases/__init__.py create mode 100644 dep/pubsubclient-2.7/tests/testcases/mqtt_basic.py create mode 100644 dep/pubsubclient-2.7/tests/testcases/mqtt_publish_in_callback.py create mode 100644 dep/pubsubclient-2.7/tests/testcases/settings.py create mode 100644 dep/pubsubclient-2.7/tests/testsuite.py diff --git a/dep/SimpleTimer-schinken/README.md b/dep/SimpleTimer-schinken/README.md new file mode 100644 index 0000000..8b1a819 --- /dev/null +++ b/dep/SimpleTimer-schinken/README.md @@ -0,0 +1,7 @@ +# SimpleTimer for Arduino + +This is a fork of the SimpleTimer library from Marcello Romani (http://playground.arduino.cc/Code/SimpleTimer). Sadly there is no original repository to apply pull requests. + +## Additional Features + +* Support lambda-expressions / std::function callbacks diff --git a/dep/SimpleTimer-schinken/SimpleTimer.cpp b/dep/SimpleTimer-schinken/SimpleTimer.cpp new file mode 100644 index 0000000..693eccf --- /dev/null +++ b/dep/SimpleTimer-schinken/SimpleTimer.cpp @@ -0,0 +1,241 @@ +/* + * SimpleTimer.cpp + * + * SimpleTimer - A timer library for Arduino. + * Author: mromani@ottotecnica.com + * Copyright (c) 2010 OTTOTECNICA Italy + * + * 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 "SimpleTimer.h" + + +// Select time function: +//static inline unsigned long elapsed() { return micros(); } +static inline unsigned long elapsed() { return millis(); } + + +SimpleTimer::SimpleTimer() { + unsigned long current_millis = elapsed(); + + for (int i = 0; i < MAX_TIMERS; i++) { + enabled[i] = false; + callbacks[i] = 0; // if the callback pointer is zero, the slot is free, i.e. doesn't "contain" any timer + prev_millis[i] = current_millis; + numRuns[i] = 0; + } + + numTimers = 0; +} + + +void SimpleTimer::run() { + int i; + unsigned long current_millis; + + // get current time + current_millis = elapsed(); + + for (i = 0; i < MAX_TIMERS; i++) { + + toBeCalled[i] = DEFCALL_DONTRUN; + + // no callback == no timer, i.e. jump over empty slots + if (callbacks[i]) { + + // is it time to process this timer ? + // see http://arduino.cc/forum/index.php/topic,124048.msg932592.html#msg932592 + + if (current_millis - prev_millis[i] >= delays[i]) { + + // update time + //prev_millis[i] = current_millis; + prev_millis[i] += delays[i]; + + // check if the timer callback has to be executed + if (enabled[i]) { + + // "run forever" timers must always be executed + if (maxNumRuns[i] == RUN_FOREVER) { + toBeCalled[i] = DEFCALL_RUNONLY; + } + // other timers get executed the specified number of times + else if (numRuns[i] < maxNumRuns[i]) { + toBeCalled[i] = DEFCALL_RUNONLY; + numRuns[i]++; + + // after the last run, delete the timer + if (numRuns[i] >= maxNumRuns[i]) { + toBeCalled[i] = DEFCALL_RUNANDDEL; + } + } + } + } + } + } + + for (i = 0; i < MAX_TIMERS; i++) { + switch(toBeCalled[i]) { + case DEFCALL_DONTRUN: + break; + + case DEFCALL_RUNONLY: + callbacks[i](); + break; + + case DEFCALL_RUNANDDEL: + callbacks[i](); + deleteTimer(i); + break; + } + } +} + + +// find the first available slot +// return -1 if none found +int SimpleTimer::findFirstFreeSlot() { + int i; + + // all slots are used + if (numTimers >= MAX_TIMERS) { + return -1; + } + + // return the first slot with no callback (i.e. free) + for (i = 0; i < MAX_TIMERS; i++) { + if (callbacks[i] == 0) { + return i; + } + } + + // no free slots found + return -1; +} + + +int SimpleTimer::setTimer(unsigned long d, timer_callback f, int n) { + int freeTimer; + + freeTimer = findFirstFreeSlot(); + if (freeTimer < 0) { + return -1; + } + + if (f == NULL) { + return -1; + } + + delays[freeTimer] = d; + callbacks[freeTimer] = f; + maxNumRuns[freeTimer] = n; + enabled[freeTimer] = true; + prev_millis[freeTimer] = elapsed(); + + numTimers++; + + return freeTimer; +} + + +int SimpleTimer::setInterval(unsigned long d, timer_callback f) { + return setTimer(d, f, RUN_FOREVER); +} + + +int SimpleTimer::setTimeout(unsigned long d, timer_callback f) { + return setTimer(d, f, RUN_ONCE); +} + + +void SimpleTimer::deleteTimer(int timerId) { + if (timerId >= MAX_TIMERS) { + return; + } + + // nothing to delete if no timers are in use + if (numTimers == 0) { + return; + } + + // don't decrease the number of timers if the + // specified slot is already empty + if (callbacks[timerId] != NULL) { + callbacks[timerId] = 0; + enabled[timerId] = false; + toBeCalled[timerId] = DEFCALL_DONTRUN; + delays[timerId] = 0; + numRuns[timerId] = 0; + + // update number of timers + numTimers--; + } +} + + +// function contributed by code@rowansimms.com +void SimpleTimer::restartTimer(int numTimer) { + if (numTimer >= MAX_TIMERS) { + return; + } + + prev_millis[numTimer] = elapsed(); +} + + +boolean SimpleTimer::isEnabled(int numTimer) { + if (numTimer >= MAX_TIMERS) { + return false; + } + + return enabled[numTimer]; +} + + +void SimpleTimer::enable(int numTimer) { + if (numTimer >= MAX_TIMERS) { + return; + } + + enabled[numTimer] = true; +} + + +void SimpleTimer::disable(int numTimer) { + if (numTimer >= MAX_TIMERS) { + return; + } + + enabled[numTimer] = false; +} + + +void SimpleTimer::toggle(int numTimer) { + if (numTimer >= MAX_TIMERS) { + return; + } + + enabled[numTimer] = !enabled[numTimer]; +} + + +int SimpleTimer::getNumTimers() { + return numTimers; +} diff --git a/dep/SimpleTimer-schinken/SimpleTimer.h b/dep/SimpleTimer-schinken/SimpleTimer.h new file mode 100644 index 0000000..176f9b8 --- /dev/null +++ b/dep/SimpleTimer-schinken/SimpleTimer.h @@ -0,0 +1,132 @@ +/* + * SimpleTimer.h + * + * SimpleTimer - A timer library for Arduino. + * Author: mromani@ottotecnica.com + * Copyright (c) 2010 OTTOTECNICA Italy + * + * 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 + * + */ + + +#ifndef SIMPLETIMER_H +#define SIMPLETIMER_H + +#ifndef __AVR__ +#include +#endif // __AVR__ + +#if defined(ARDUINO) && ARDUINO >= 100 +#include +#else +#include +#endif + +#ifndef __AVR__ +typedef std::function timer_callback; +#else +typedef void (*timer_callback)(); +#endif // __AVR__ + +class SimpleTimer { + +public: + // maximum number of timers + const static int MAX_TIMERS = 10; + + // setTimer() constants + const static int RUN_FOREVER = 0; + const static int RUN_ONCE = 1; + + // constructor + SimpleTimer(); + + // this function must be called inside loop() + void run(); + + // call function f every d milliseconds + int setInterval(unsigned long d, timer_callback f); + + // call function f once after d milliseconds + int setTimeout(unsigned long d, timer_callback f); + + // call function f every d milliseconds for n times + int setTimer(unsigned long d, timer_callback f, int n); + + // destroy the specified timer + void deleteTimer(int numTimer); + + // restart the specified timer + void restartTimer(int numTimer); + + // returns true if the specified timer is enabled + boolean isEnabled(int numTimer); + + // enables the specified timer + void enable(int numTimer); + + // disables the specified timer + void disable(int numTimer); + + // enables the specified timer if it's currently disabled, + // and vice-versa + void toggle(int numTimer); + + // returns the number of used timers + int getNumTimers(); + + // returns the number of available timers + int getNumAvailableTimers() { return MAX_TIMERS - numTimers; }; + +private: + // deferred call constants + const static int DEFCALL_DONTRUN = 0; // don't call the callback function + const static int DEFCALL_RUNONLY = 1; // call the callback function but don't delete the timer + const static int DEFCALL_RUNANDDEL = 2; // call the callback function and delete the timer + + // find the first available slot + int findFirstFreeSlot(); + + // value returned by the millis() function + // in the previous run() call + unsigned long prev_millis[MAX_TIMERS]; + + // pointers to the callback functions + timer_callback callbacks[MAX_TIMERS]; + + // delay values + unsigned long delays[MAX_TIMERS]; + + // number of runs to be executed for each timer + int maxNumRuns[MAX_TIMERS]; + + // number of executed runs for each timer + int numRuns[MAX_TIMERS]; + + // which timers are enabled + boolean enabled[MAX_TIMERS]; + + // deferred function call (sort of) - N.B.: this array is only used in run() + int toBeCalled[MAX_TIMERS]; + + // actual number of timers in use + int numTimers; +}; + +#endif diff --git a/dep/WiFiManager-0.15.0/LICENSE b/dep/WiFiManager-0.15.0/LICENSE new file mode 100644 index 0000000..1dabff5 --- /dev/null +++ b/dep/WiFiManager-0.15.0/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 tzapu + +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. + diff --git a/dep/WiFiManager-0.15.0/WiFiManager.cpp b/dep/WiFiManager-0.15.0/WiFiManager.cpp new file mode 100644 index 0000000..17358ce --- /dev/null +++ b/dep/WiFiManager-0.15.0/WiFiManager.cpp @@ -0,0 +1,847 @@ +/************************************************************** + WiFiManager is a library for the ESP8266/Arduino platform + (https://github.com/esp8266/Arduino) to enable easy + configuration and reconfiguration of WiFi credentials using a Captive Portal + inspired by: + http://www.esp8266.com/viewtopic.php?f=29&t=2520 + https://github.com/chriscook8/esp-arduino-apboot + https://github.com/esp8266/Arduino/tree/master/libraries/DNSServer/examples/CaptivePortalAdvanced + Built by AlexT https://github.com/tzapu + Licensed under MIT license + **************************************************************/ + +#include "WiFiManager.h" + +WiFiManagerParameter::WiFiManagerParameter(const char *custom) { + _id = NULL; + _placeholder = NULL; + _length = 0; + _value = NULL; + + _customHTML = custom; +} + +WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length) { + init(id, placeholder, defaultValue, length, ""); +} + +WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom) { + init(id, placeholder, defaultValue, length, custom); +} + +void WiFiManagerParameter::init(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom) { + _id = id; + _placeholder = placeholder; + _length = length; + _value = new char[length + 1]; + for (int i = 0; i < length + 1; i++) { + _value[i] = 0; + } + if (defaultValue != NULL) { + strncpy(_value, defaultValue, length); + } + + _customHTML = custom; +} + +WiFiManagerParameter::~WiFiManagerParameter() { + if (_value != NULL) { + delete[] _value; + } +} + +const char* WiFiManagerParameter::getValue() { + return _value; +} +const char* WiFiManagerParameter::getID() { + return _id; +} +const char* WiFiManagerParameter::getPlaceholder() { + return _placeholder; +} +int WiFiManagerParameter::getValueLength() { + return _length; +} +const char* WiFiManagerParameter::getCustomHTML() { + return _customHTML; +} + + +WiFiManager::WiFiManager() { + _max_params = WIFI_MANAGER_MAX_PARAMS; + _params = (WiFiManagerParameter**)malloc(_max_params * sizeof(WiFiManagerParameter*)); +} + +WiFiManager::~WiFiManager() +{ + if (_params != NULL) + { + DEBUG_WM(F("freeing allocated params!")); + free(_params); + } +} + +bool WiFiManager::addParameter(WiFiManagerParameter *p) { + if(_paramsCount + 1 > _max_params) + { + // rezise the params array + _max_params += WIFI_MANAGER_MAX_PARAMS; + DEBUG_WM(F("Increasing _max_params to:")); + DEBUG_WM(_max_params); + WiFiManagerParameter** new_params = (WiFiManagerParameter**)realloc(_params, _max_params * sizeof(WiFiManagerParameter*)); + if (new_params != NULL) { + _params = new_params; + } else { + DEBUG_WM(F("ERROR: failed to realloc params, size not increased!")); + return false; + } + } + + _params[_paramsCount] = p; + _paramsCount++; + DEBUG_WM(F("Adding parameter")); + DEBUG_WM(p->getID()); + return true; +} + +void WiFiManager::setupConfigPortal() { + dnsServer.reset(new DNSServer()); + server.reset(new ESP8266WebServer(80)); + + DEBUG_WM(F("")); + _configPortalStart = millis(); + + DEBUG_WM(F("Configuring access point... ")); + DEBUG_WM(_apName); + if (_apPassword != NULL) { + if (strlen(_apPassword) < 8 || strlen(_apPassword) > 63) { + // fail passphrase to short or long! + DEBUG_WM(F("Invalid AccessPoint password. Ignoring")); + _apPassword = NULL; + } + DEBUG_WM(_apPassword); + } + + //optional soft ip config + if (_ap_static_ip) { + DEBUG_WM(F("Custom AP IP/GW/Subnet")); + WiFi.softAPConfig(_ap_static_ip, _ap_static_gw, _ap_static_sn); + } + + if (_apPassword != NULL) { + WiFi.softAP(_apName, _apPassword);//password option + } else { + WiFi.softAP(_apName); + } + + delay(500); // Without delay I've seen the IP address blank + DEBUG_WM(F("AP IP address: ")); + DEBUG_WM(WiFi.softAPIP()); + + /* Setup the DNS server redirecting all the domains to the apIP */ + dnsServer->setErrorReplyCode(DNSReplyCode::NoError); + dnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); + + /* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */ + server->on(String(F("/")).c_str(), std::bind(&WiFiManager::handleRoot, this)); + server->on(String(F("/wifi")).c_str(), std::bind(&WiFiManager::handleWifi, this, true)); + server->on(String(F("/0wifi")).c_str(), std::bind(&WiFiManager::handleWifi, this, false)); + server->on(String(F("/wifisave")).c_str(), std::bind(&WiFiManager::handleWifiSave, this)); + server->on(String(F("/i")).c_str(), std::bind(&WiFiManager::handleInfo, this)); + server->on(String(F("/r")).c_str(), std::bind(&WiFiManager::handleReset, this)); + //server->on("/generate_204", std::bind(&WiFiManager::handle204, this)); //Android/Chrome OS captive portal check. + server->on(String(F("/fwlink")).c_str(), std::bind(&WiFiManager::handleRoot, this)); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler. + server->onNotFound (std::bind(&WiFiManager::handleNotFound, this)); + server->begin(); // Web server start + DEBUG_WM(F("HTTP server started")); +} + +boolean WiFiManager::autoConnect() { + String ssid = "ESP" + String(ESP.getChipId()); + return autoConnect(ssid.c_str(), NULL); +} + +boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) { + DEBUG_WM(F("")); + DEBUG_WM(F("AutoConnect")); + + // read eeprom for ssid and pass + //String ssid = getSSID(); + //String pass = getPassword(); + + // attempt to connect; should it fail, fall back to AP + WiFi.mode(WIFI_STA); + + if (connectWifi("", "") == WL_CONNECTED) { + DEBUG_WM(F("IP Address:")); + DEBUG_WM(WiFi.localIP()); + //connected + return true; + } + + return startConfigPortal(apName, apPassword); +} + +boolean WiFiManager::configPortalHasTimeout(){ + if(_configPortalTimeout == 0 || wifi_softap_get_station_num() > 0){ + _configPortalStart = millis(); // kludge, bump configportal start time to skew timeouts + return false; + } + return (millis() > _configPortalStart + _configPortalTimeout); +} + +boolean WiFiManager::startConfigPortal() { + String ssid = "ESP" + String(ESP.getChipId()); + return startConfigPortal(ssid.c_str(), NULL); +} + +boolean WiFiManager::startConfigPortal(char const *apName, char const *apPassword) { + + if(!WiFi.isConnected()){ + WiFi.persistent(false); + // disconnect sta, start ap + WiFi.disconnect(); // this alone is not enough to stop the autoconnecter + WiFi.mode(WIFI_AP); + WiFi.persistent(true); + } + else { + //setup AP + WiFi.mode(WIFI_AP_STA); + DEBUG_WM(F("SET AP STA")); + } + + + _apName = apName; + _apPassword = apPassword; + + //notify we entered AP mode + if ( _apcallback != NULL) { + _apcallback(this); + } + + connect = false; + setupConfigPortal(); + + while(1){ + + // check if timeout + if(configPortalHasTimeout()) break; + + //DNS + dnsServer->processNextRequest(); + //HTTP + server->handleClient(); + + if (connect) { + delay(1000); + connect = false; + + // if saving with no ssid filled in, reconnect to ssid + // will not exit cp + if(_ssid == ""){ + DEBUG_WM(F("No ssid, skipping wifi")); + } + else{ + DEBUG_WM(F("Connecting to new AP")); + if (connectWifi(_ssid, _pass) != WL_CONNECTED) { + delay(2000); + // using user-provided _ssid, _pass in place of system-stored ssid and pass + DEBUG_WM(F("Failed to connect.")); + } + else { + //connected + WiFi.mode(WIFI_STA); + //notify that configuration has changed and any optional parameters should be saved + if ( _savecallback != NULL) { + //todo: check if any custom parameters actually exist, and check if they really changed maybe + _savecallback(); + } + break; + } + } + if (_shouldBreakAfterConfig) { + //flag set to exit after config after trying to connect + //notify that configuration has changed and any optional parameters should be saved + if ( _savecallback != NULL) { + //todo: check if any custom parameters actually exist, and check if they really changed maybe + _savecallback(); + } + WiFi.mode(WIFI_STA); // turn off ap + // reconnect to ssid + // int res = WiFi.begin(); + // attempt connect for 10 seconds + DEBUG_WM(F("Waiting for sta (10 secs) .......")); + for(size_t i = 0 ; i<100;i++){ + if(WiFi.status() == WL_CONNECTED) break; + DEBUG_WM("."); + // Serial.println(WiFi.status()); + delay(100); + } + delay(1000); + break; + } + } + yield(); + } + + server.reset(); + dnsServer.reset(); + + return WiFi.status() == WL_CONNECTED; +} + + +int WiFiManager::connectWifi(String ssid, String pass) { + DEBUG_WM(F("Connecting as wifi client...")); + + // check if we've got static_ip settings, if we do, use those. + if (_sta_static_ip) { + DEBUG_WM(F("Custom STA IP/GW/Subnet")); + WiFi.config(_sta_static_ip, _sta_static_gw, _sta_static_sn); + DEBUG_WM(WiFi.localIP()); + } + //fix for auto connect racing issue + if (WiFi.status() == WL_CONNECTED && (WiFi.SSID() == ssid)) { + DEBUG_WM(F("Already connected. Bailing out.")); + return WL_CONNECTED; + } + + DEBUG_WM(F("Status:")); + DEBUG_WM(WiFi.status()); + + wl_status_t res; + //check if we have ssid and pass and force those, if not, try with last saved values + if (ssid != "") { + //trying to fix connection in progress hanging + ETS_UART_INTR_DISABLE(); + wifi_station_disconnect(); + ETS_UART_INTR_ENABLE(); + res = WiFi.begin(ssid.c_str(), pass.c_str(),0,NULL,true); + if(res != WL_CONNECTED){ + DEBUG_WM(F("[ERROR] WiFi.begin res:")); + DEBUG_WM(res); + } + } else { + if (WiFi.SSID() != "") { + DEBUG_WM(F("Using last saved values, should be faster")); + //trying to fix connection in progress hanging + ETS_UART_INTR_DISABLE(); + wifi_station_disconnect(); + ETS_UART_INTR_ENABLE(); + res = WiFi.begin(); + } else { + DEBUG_WM(F("No saved credentials")); + } + } + + int connRes = waitForConnectResult(); + DEBUG_WM ("Connection result: "); + DEBUG_WM ( connRes ); + //not connected, WPS enabled, no pass - first attempt + #ifdef NO_EXTRA_4K_HEAP + if (_tryWPS && connRes != WL_CONNECTED && pass == "") { + startWPS(); + //should be connected at the end of WPS + connRes = waitForConnectResult(); + } + #endif + return connRes; +} + +uint8_t WiFiManager::waitForConnectResult() { + if (_connectTimeout == 0) { + return WiFi.waitForConnectResult(); + } else { + DEBUG_WM (F("Waiting for connection result with time out")); + unsigned long start = millis(); + boolean keepConnecting = true; + uint8_t status; + while (keepConnecting) { + status = WiFi.status(); + if (millis() > start + _connectTimeout) { + keepConnecting = false; + DEBUG_WM (F("Connection timed out")); + } + if (status == WL_CONNECTED) { + keepConnecting = false; + } + delay(100); + } + return status; + } +} + +void WiFiManager::startWPS() { + DEBUG_WM(F("START WPS")); + WiFi.beginWPSConfig(); + DEBUG_WM(F("END WPS")); +} +/* + String WiFiManager::getSSID() { + if (_ssid == "") { + DEBUG_WM(F("Reading SSID")); + _ssid = WiFi.SSID(); + DEBUG_WM(F("SSID: ")); + DEBUG_WM(_ssid); + } + return _ssid; + } + + String WiFiManager::getPassword() { + if (_pass == "") { + DEBUG_WM(F("Reading Password")); + _pass = WiFi.psk(); + DEBUG_WM("Password: " + _pass); + //DEBUG_WM(_pass); + } + return _pass; + } +*/ +String WiFiManager::getConfigPortalSSID() { + return _apName; +} + +void WiFiManager::resetSettings() { + DEBUG_WM(F("settings invalidated")); + DEBUG_WM(F("THIS MAY CAUSE AP NOT TO START UP PROPERLY. YOU NEED TO COMMENT IT OUT AFTER ERASING THE DATA.")); + WiFi.disconnect(true); + //delay(200); +} +void WiFiManager::setTimeout(unsigned long seconds) { + setConfigPortalTimeout(seconds); +} + +void WiFiManager::setConfigPortalTimeout(unsigned long seconds) { + _configPortalTimeout = seconds * 1000; +} + +void WiFiManager::setConnectTimeout(unsigned long seconds) { + _connectTimeout = seconds * 1000; +} + +void WiFiManager::setDebugOutput(boolean debug) { + _debug = debug; +} + +void WiFiManager::setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) { + _ap_static_ip = ip; + _ap_static_gw = gw; + _ap_static_sn = sn; +} + +void WiFiManager::setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) { + _sta_static_ip = ip; + _sta_static_gw = gw; + _sta_static_sn = sn; +} + +void WiFiManager::setMinimumSignalQuality(int quality) { + _minimumQuality = quality; +} + +void WiFiManager::setBreakAfterConfig(boolean shouldBreak) { + _shouldBreakAfterConfig = shouldBreak; +} + +/** Handle root or redirect to captive portal */ +void WiFiManager::handleRoot() { + DEBUG_WM(F("Handle root")); + if (captivePortal()) { // If caprive portal redirect instead of displaying the page. + return; + } + + String page = FPSTR(HTTP_HEADER); + page.replace("{v}", "Options"); + page += FPSTR(HTTP_SCRIPT); + page += FPSTR(HTTP_STYLE); + page += _customHeadElement; + page += FPSTR(HTTP_HEADER_END); + page += String(F("

")); + page += _apName; + page += String(F("

")); + page += String(F("

WiFiManager

")); + page += FPSTR(HTTP_PORTAL_OPTIONS); + page += FPSTR(HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); + +} + +/** Wifi config page handler */ +void WiFiManager::handleWifi(boolean scan) { + + String page = FPSTR(HTTP_HEADER); + page.replace("{v}", "Config ESP"); + page += FPSTR(HTTP_SCRIPT); + page += FPSTR(HTTP_STYLE); + page += _customHeadElement; + page += FPSTR(HTTP_HEADER_END); + + if (scan) { + int n = WiFi.scanNetworks(); + DEBUG_WM(F("Scan done")); + if (n == 0) { + DEBUG_WM(F("No networks found")); + page += F("No networks found. Refresh to scan again."); + } else { + + //sort networks + int indices[n]; + for (int i = 0; i < n; i++) { + indices[i] = i; + } + + // RSSI SORT + + // old sort + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) { + std::swap(indices[i], indices[j]); + } + } + } + + /*std::sort(indices, indices + n, [](const int & a, const int & b) -> bool + { + return WiFi.RSSI(a) > WiFi.RSSI(b); + });*/ + + // remove duplicates ( must be RSSI sorted ) + if (_removeDuplicateAPs) { + String cssid; + for (int i = 0; i < n; i++) { + if (indices[i] == -1) continue; + cssid = WiFi.SSID(indices[i]); + for (int j = i + 1; j < n; j++) { + if (cssid == WiFi.SSID(indices[j])) { + DEBUG_WM("DUP AP: " + WiFi.SSID(indices[j])); + indices[j] = -1; // set dup aps to index -1 + } + } + } + } + + //display networks in page + for (int i = 0; i < n; i++) { + if (indices[i] == -1) continue; // skip dups + DEBUG_WM(WiFi.SSID(indices[i])); + DEBUG_WM(WiFi.RSSI(indices[i])); + int quality = getRSSIasQuality(WiFi.RSSI(indices[i])); + + if (_minimumQuality == -1 || _minimumQuality < quality) { + String item = FPSTR(HTTP_ITEM); + String rssiQ; + rssiQ += quality; + item.replace("{v}", WiFi.SSID(indices[i])); + item.replace("{r}", rssiQ); + if (WiFi.encryptionType(indices[i]) != ENC_TYPE_NONE) { + item.replace("{i}", "l"); + } else { + item.replace("{i}", ""); + } + //DEBUG_WM(item); + page += item; + delay(0); + } else { + DEBUG_WM(F("Skipping due to quality")); + } + + } + page += "
"; + } + } + + page += FPSTR(HTTP_FORM_START); + char parLength[5]; + // add the extra parameters to the form + for (int i = 0; i < _paramsCount; i++) { + if (_params[i] == NULL) { + break; + } + + String pitem = FPSTR(HTTP_FORM_PARAM); + if (_params[i]->getID() != NULL) { + pitem.replace("{i}", _params[i]->getID()); + pitem.replace("{n}", _params[i]->getID()); + pitem.replace("{p}", _params[i]->getPlaceholder()); + snprintf(parLength, 5, "%d", _params[i]->getValueLength()); + pitem.replace("{l}", parLength); + pitem.replace("{v}", _params[i]->getValue()); + pitem.replace("{c}", _params[i]->getCustomHTML()); + } else { + pitem = _params[i]->getCustomHTML(); + } + + page += pitem; + } + if (_params[0] != NULL) { + page += "
"; + } + + if (_sta_static_ip) { + + String item = FPSTR(HTTP_FORM_PARAM); + item.replace("{i}", "ip"); + item.replace("{n}", "ip"); + item.replace("{p}", "Static IP"); + item.replace("{l}", "15"); + item.replace("{v}", _sta_static_ip.toString()); + + page += item; + + item = FPSTR(HTTP_FORM_PARAM); + item.replace("{i}", "gw"); + item.replace("{n}", "gw"); + item.replace("{p}", "Static Gateway"); + item.replace("{l}", "15"); + item.replace("{v}", _sta_static_gw.toString()); + + page += item; + + item = FPSTR(HTTP_FORM_PARAM); + item.replace("{i}", "sn"); + item.replace("{n}", "sn"); + item.replace("{p}", "Subnet"); + item.replace("{l}", "15"); + item.replace("{v}", _sta_static_sn.toString()); + + page += item; + + page += "
"; + } + + page += FPSTR(HTTP_FORM_END); + page += FPSTR(HTTP_SCAN_LINK); + + page += FPSTR(HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); + + + DEBUG_WM(F("Sent config page")); +} + +/** Handle the WLAN save form and redirect to WLAN config page again */ +void WiFiManager::handleWifiSave() { + DEBUG_WM(F("WiFi save")); + + //SAVE/connect here + _ssid = server->arg("s").c_str(); + _pass = server->arg("p").c_str(); + + //parameters + for (int i = 0; i < _paramsCount; i++) { + if (_params[i] == NULL) { + break; + } + //read parameter + String value = server->arg(_params[i]->getID()).c_str(); + //store it in array + value.toCharArray(_params[i]->_value, _params[i]->_length + 1); + DEBUG_WM(F("Parameter")); + DEBUG_WM(_params[i]->getID()); + DEBUG_WM(value); + } + + if (server->arg("ip") != "") { + DEBUG_WM(F("static ip")); + DEBUG_WM(server->arg("ip")); + //_sta_static_ip.fromString(server->arg("ip")); + String ip = server->arg("ip"); + optionalIPFromString(&_sta_static_ip, ip.c_str()); + } + if (server->arg("gw") != "") { + DEBUG_WM(F("static gateway")); + DEBUG_WM(server->arg("gw")); + String gw = server->arg("gw"); + optionalIPFromString(&_sta_static_gw, gw.c_str()); + } + if (server->arg("sn") != "") { + DEBUG_WM(F("static netmask")); + DEBUG_WM(server->arg("sn")); + String sn = server->arg("sn"); + optionalIPFromString(&_sta_static_sn, sn.c_str()); + } + + String page = FPSTR(HTTP_HEADER); + page.replace("{v}", "Credentials Saved"); + page += FPSTR(HTTP_SCRIPT); + page += FPSTR(HTTP_STYLE); + page += _customHeadElement; + page += FPSTR(HTTP_HEADER_END); + page += FPSTR(HTTP_SAVED); + page += FPSTR(HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); + + DEBUG_WM(F("Sent wifi save page")); + + connect = true; //signal ready to connect/reset +} + +/** Handle the info page */ +void WiFiManager::handleInfo() { + DEBUG_WM(F("Info")); + + String page = FPSTR(HTTP_HEADER); + page.replace("{v}", "Info"); + page += FPSTR(HTTP_SCRIPT); + page += FPSTR(HTTP_STYLE); + page += _customHeadElement; + page += FPSTR(HTTP_HEADER_END); + page += F("
"); + page += F("
Chip ID
"); + page += ESP.getChipId(); + page += F("
"); + page += F("
Flash Chip ID
"); + page += ESP.getFlashChipId(); + page += F("
"); + page += F("
IDE Flash Size
"); + page += ESP.getFlashChipSize(); + page += F(" bytes
"); + page += F("
Real Flash Size
"); + page += ESP.getFlashChipRealSize(); + page += F(" bytes
"); + page += F("
Soft AP IP
"); + page += WiFi.softAPIP().toString(); + page += F("
"); + page += F("
Soft AP MAC
"); + page += WiFi.softAPmacAddress(); + page += F("
"); + page += F("
Station MAC
"); + page += WiFi.macAddress(); + page += F("
"); + page += F("
"); + page += FPSTR(HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); + + DEBUG_WM(F("Sent info page")); +} + +/** Handle the reset page */ +void WiFiManager::handleReset() { + DEBUG_WM(F("Reset")); + + String page = FPSTR(HTTP_HEADER); + page.replace("{v}", "Info"); + page += FPSTR(HTTP_SCRIPT); + page += FPSTR(HTTP_STYLE); + page += _customHeadElement; + page += FPSTR(HTTP_HEADER_END); + page += F("Module will reset in a few seconds."); + page += FPSTR(HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); + + DEBUG_WM(F("Sent reset page")); + delay(5000); + ESP.reset(); + delay(2000); +} + +void WiFiManager::handleNotFound() { + if (captivePortal()) { // If captive portal redirect instead of displaying the error page. + return; + } + String message = "File Not Found\n\n"; + message += "URI: "; + message += server->uri(); + message += "\nMethod: "; + message += ( server->method() == HTTP_GET ) ? "GET" : "POST"; + message += "\nArguments: "; + message += server->args(); + message += "\n"; + + for ( uint8_t i = 0; i < server->args(); i++ ) { + message += " " + server->argName ( i ) + ": " + server->arg ( i ) + "\n"; + } + server->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + server->sendHeader("Pragma", "no-cache"); + server->sendHeader("Expires", "-1"); + server->sendHeader("Content-Length", String(message.length())); + server->send ( 404, "text/plain", message ); +} + + +/** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */ +boolean WiFiManager::captivePortal() { + if (!isIp(server->hostHeader()) ) { + DEBUG_WM(F("Request redirected to captive portal")); + server->sendHeader("Location", String("http://") + toStringIp(server->client().localIP()), true); + server->send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. + server->client().stop(); // Stop is needed because we sent no content length + return true; + } + return false; +} + +//start up config portal callback +void WiFiManager::setAPCallback( void (*func)(WiFiManager* myWiFiManager) ) { + _apcallback = func; +} + +//start up save config callback +void WiFiManager::setSaveConfigCallback( void (*func)(void) ) { + _savecallback = func; +} + +//sets a custom element to add to head, like a new style tag +void WiFiManager::setCustomHeadElement(const char* element) { + _customHeadElement = element; +} + +//if this is true, remove duplicated Access Points - defaut true +void WiFiManager::setRemoveDuplicateAPs(boolean removeDuplicates) { + _removeDuplicateAPs = removeDuplicates; +} + + + +template +void WiFiManager::DEBUG_WM(Generic text) { + if (_debug) { + Serial.print("*WM: "); + Serial.println(text); + } +} + +int WiFiManager::getRSSIasQuality(int RSSI) { + int quality = 0; + + if (RSSI <= -100) { + quality = 0; + } else if (RSSI >= -50) { + quality = 100; + } else { + quality = 2 * (RSSI + 100); + } + return quality; +} + +/** Is this an IP? */ +boolean WiFiManager::isIp(String str) { + for (size_t i = 0; i < str.length(); i++) { + int c = str.charAt(i); + if (c != '.' && (c < '0' || c > '9')) { + return false; + } + } + return true; +} + +/** IP to String? */ +String WiFiManager::toStringIp(IPAddress ip) { + String res = ""; + for (int i = 0; i < 3; i++) { + res += String((ip >> (8 * i)) & 0xFF) + "."; + } + res += String(((ip >> 8 * 3)) & 0xFF); + return res; +} diff --git a/dep/WiFiManager-0.15.0/WiFiManager.h b/dep/WiFiManager-0.15.0/WiFiManager.h new file mode 100644 index 0000000..136dde0 --- /dev/null +++ b/dep/WiFiManager-0.15.0/WiFiManager.h @@ -0,0 +1,203 @@ +/************************************************************** + WiFiManager is a library for the ESP8266/Arduino platform + (https://github.com/esp8266/Arduino) to enable easy + configuration and reconfiguration of WiFi credentials using a Captive Portal + inspired by: + http://www.esp8266.com/viewtopic.php?f=29&t=2520 + https://github.com/chriscook8/esp-arduino-apboot + https://github.com/esp8266/Arduino/tree/master/libraries/DNSServer/examples/CaptivePortalAdvanced + Built by AlexT https://github.com/tzapu + Licensed under MIT license + **************************************************************/ + +#ifndef WiFiManager_h +#define WiFiManager_h + +#include +#include +#include +#include + +extern "C" { + #include "user_interface.h" +} + +const char HTTP_HEADER[] PROGMEM = "{v}"; +const char HTTP_STYLE[] PROGMEM = ""; +const char HTTP_SCRIPT[] PROGMEM = ""; +const char HTTP_HEADER_END[] PROGMEM = "
"; +const char HTTP_PORTAL_OPTIONS[] PROGMEM = "



"; +const char HTTP_ITEM[] PROGMEM = "
{v} {r}%
"; +const char HTTP_FORM_START[] PROGMEM = "


"; +const char HTTP_FORM_PARAM[] PROGMEM = "
"; +const char HTTP_FORM_END[] PROGMEM = "
"; +const char HTTP_SCAN_LINK[] PROGMEM = "
"; +const char HTTP_SAVED[] PROGMEM = "
Credentials Saved
Trying to connect ESP to network.
If it fails reconnect to AP to try again
"; +const char HTTP_END[] PROGMEM = "
"; + +#ifndef WIFI_MANAGER_MAX_PARAMS +#define WIFI_MANAGER_MAX_PARAMS 10 +#endif + +class WiFiManagerParameter { + public: + /** + Create custom parameters that can be added to the WiFiManager setup web page + @id is used for HTTP queries and must not contain spaces nor other special characters + */ + WiFiManagerParameter(const char *custom); + WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length); + WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom); + ~WiFiManagerParameter(); + + const char *getID(); + const char *getValue(); + const char *getPlaceholder(); + int getValueLength(); + const char *getCustomHTML(); + private: + const char *_id; + const char *_placeholder; + char *_value; + int _length; + const char *_customHTML; + + void init(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom); + + friend class WiFiManager; +}; + + +class WiFiManager +{ + public: + WiFiManager(); + ~WiFiManager(); + + boolean autoConnect(); + boolean autoConnect(char const *apName, char const *apPassword = NULL); + + //if you want to always start the config portal, without trying to connect first + boolean startConfigPortal(); + boolean startConfigPortal(char const *apName, char const *apPassword = NULL); + + // get the AP name of the config portal, so it can be used in the callback + String getConfigPortalSSID(); + + void resetSettings(); + + //sets timeout before webserver loop ends and exits even if there has been no setup. + //useful for devices that failed to connect at some point and got stuck in a webserver loop + //in seconds setConfigPortalTimeout is a new name for setTimeout + void setConfigPortalTimeout(unsigned long seconds); + void setTimeout(unsigned long seconds); + + //sets timeout for which to attempt connecting, useful if you get a lot of failed connects + void setConnectTimeout(unsigned long seconds); + + + void setDebugOutput(boolean debug); + //defaults to not showing anything under 8% signal quality if called + void setMinimumSignalQuality(int quality = 8); + //sets a custom ip /gateway /subnet configuration + void setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn); + //sets config for a static IP + void setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn); + //called when AP mode and config portal is started + void setAPCallback( void (*func)(WiFiManager*) ); + //called when settings have been changed and connection was successful + void setSaveConfigCallback( void (*func)(void) ); + //adds a custom parameter, returns false on failure + bool addParameter(WiFiManagerParameter *p); + //if this is set, it will exit after config, even if connection is unsuccessful. + void setBreakAfterConfig(boolean shouldBreak); + //if this is set, try WPS setup when starting (this will delay config portal for up to 2 mins) + //TODO + //if this is set, customise style + void setCustomHeadElement(const char* element); + //if this is true, remove duplicated Access Points - defaut true + void setRemoveDuplicateAPs(boolean removeDuplicates); + + private: + std::unique_ptr dnsServer; + std::unique_ptr server; + + //const int WM_DONE = 0; + //const int WM_WAIT = 10; + + //const String HTTP_HEADER = "{v}"; + + void setupConfigPortal(); + void startWPS(); + + const char* _apName = "no-net"; + const char* _apPassword = NULL; + String _ssid = ""; + String _pass = ""; + unsigned long _configPortalTimeout = 0; + unsigned long _connectTimeout = 0; + unsigned long _configPortalStart = 0; + + IPAddress _ap_static_ip; + IPAddress _ap_static_gw; + IPAddress _ap_static_sn; + IPAddress _sta_static_ip; + IPAddress _sta_static_gw; + IPAddress _sta_static_sn; + + int _paramsCount = 0; + int _minimumQuality = -1; + boolean _removeDuplicateAPs = true; + boolean _shouldBreakAfterConfig = false; + boolean _tryWPS = false; + + const char* _customHeadElement = ""; + + //String getEEPROMString(int start, int len); + //void setEEPROMString(int start, int len, String string); + + int status = WL_IDLE_STATUS; + int connectWifi(String ssid, String pass); + uint8_t waitForConnectResult(); + + void handleRoot(); + void handleWifi(boolean scan); + void handleWifiSave(); + void handleInfo(); + void handleReset(); + void handleNotFound(); + void handle204(); + boolean captivePortal(); + boolean configPortalHasTimeout(); + + // DNS server + const byte DNS_PORT = 53; + + //helpers + int getRSSIasQuality(int RSSI); + boolean isIp(String str); + String toStringIp(IPAddress ip); + + boolean connect; + boolean _debug = true; + + void (*_apcallback)(WiFiManager*) = NULL; + void (*_savecallback)(void) = NULL; + + int _max_params; + WiFiManagerParameter** _params; + + template + void DEBUG_WM(Generic text); + + template + auto optionalIPFromString(T *obj, const char *s) -> decltype( obj->fromString(s) ) { + return obj->fromString(s); + } + auto optionalIPFromString(...) -> bool { + DEBUG_WM("NO fromString METHOD ON IPAddress, you need ESP8266 core 2.1.0 or newer for Custom IP configuration to work."); + return false; + } +}; + +#endif diff --git a/dep/WiFiManager-0.15.0/library.json b/dep/WiFiManager-0.15.0/library.json new file mode 100644 index 0000000..c7b0002 --- /dev/null +++ b/dep/WiFiManager-0.15.0/library.json @@ -0,0 +1,13 @@ +{ + "name": "WifiManager", + "keywords": "wifi, wi-fi", + "description": "ESP8266 WiFi Connection manager with fallback web configuration portal", + "repository": + { + "type": "git", + "url": "https://github.com/tzapu/WiFiManager.git" + }, + "frameworks": "arduino", + "platforms": "espressif8266", + "version": "0.15.0" +} diff --git a/dep/WiFiManager-0.15.0/library.properties b/dep/WiFiManager-0.15.0/library.properties new file mode 100644 index 0000000..0fb265c --- /dev/null +++ b/dep/WiFiManager-0.15.0/library.properties @@ -0,0 +1,9 @@ +name=WiFiManager +version=0.15.0 +author=tzapu +maintainer=tzapu +sentence=ESP8266 WiFi Connection manager with fallback web configuration portal +paragraph=Library for configuring ESP8266 modules WiFi credentials at runtime. +category=Communication +url=https://github.com/tzapu/WiFiManager.git +architectures=esp8266 diff --git a/dep/pubsubclient-2.7/.gitignore b/dep/pubsubclient-2.7/.gitignore new file mode 100644 index 0000000..1c3ba18 --- /dev/null +++ b/dep/pubsubclient-2.7/.gitignore @@ -0,0 +1 @@ +tests/bin diff --git a/dep/pubsubclient-2.7/.travis.yml b/dep/pubsubclient-2.7/.travis.yml new file mode 100644 index 0000000..e7b28cb --- /dev/null +++ b/dep/pubsubclient-2.7/.travis.yml @@ -0,0 +1,7 @@ +sudo: false +language: cpp +compiler: + - g++ +script: cd tests && make && make test +os: + - linux diff --git a/dep/pubsubclient-2.7/CHANGES.txt b/dep/pubsubclient-2.7/CHANGES.txt new file mode 100644 index 0000000..ff4da62 --- /dev/null +++ b/dep/pubsubclient-2.7/CHANGES.txt @@ -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 boolean 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 diff --git a/dep/pubsubclient-2.7/LICENSE.txt b/dep/pubsubclient-2.7/LICENSE.txt new file mode 100644 index 0000000..217df35 --- /dev/null +++ b/dep/pubsubclient-2.7/LICENSE.txt @@ -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. diff --git a/dep/pubsubclient-2.7/examples/mqtt_auth/mqtt_auth.ino b/dep/pubsubclient-2.7/examples/mqtt_auth/mqtt_auth.ino new file mode 100644 index 0000000..e9f7b18 --- /dev/null +++ b/dep/pubsubclient-2.7/examples/mqtt_auth/mqtt_auth.ino @@ -0,0 +1,43 @@ +/* + Basic MQTT example with Authentication + + - connects to an MQTT server, providing username + and password + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "inTopic" +*/ + +#include +#include +#include + +// Update these with values suitable for your network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + +EthernetClient ethClient; +PubSubClient client(server, 1883, callback, ethClient); + +void setup() +{ + Ethernet.begin(mac, ip); + // Note - the default maximum packet size is 128 bytes. If the + // combined length of clientId, username and password exceed this, + // you will need to increase the value of MQTT_MAX_PACKET_SIZE in + // PubSubClient.h + + if (client.connect("arduinoClient", "testuser", "testpass")) { + client.publish("outTopic","hello world"); + client.subscribe("inTopic"); + } +} + +void loop() +{ + client.loop(); +} diff --git a/dep/pubsubclient-2.7/examples/mqtt_basic/mqtt_basic.ino b/dep/pubsubclient-2.7/examples/mqtt_basic/mqtt_basic.ino new file mode 100644 index 0000000..f545ade --- /dev/null +++ b/dep/pubsubclient-2.7/examples/mqtt_basic/mqtt_basic.ino @@ -0,0 +1,77 @@ +/* + Basic MQTT example + + This sketch demonstrates the basic capabilities of the library. + It connects to an MQTT server then: + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "inTopic", printing out any messages + it receives. NB - it assumes the received payloads are strings not binary + + It will reconnect to the server if the connection is lost using a blocking + reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to + achieve the same result without blocking the main loop. + +*/ + +#include +#include +#include + +// Update these with values suitable for your network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +void callback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message arrived ["); + Serial.print(topic); + Serial.print("] "); + for (int i=0;i Preferences -> Additional Boards Manager URLs": + http://arduino.esp8266.com/stable/package_esp8266com_index.json + - Open the "Tools -> Board -> Board Manager" and click install for the ESP8266" + - Select your ESP8266 in "Tools -> Board" + +*/ + +#include +#include + +// Update these with values suitable for your network. + +const char* ssid = "........"; +const char* password = "........"; +const char* mqtt_server = "broker.mqtt-dashboard.com"; + +WiFiClient espClient; +PubSubClient client(espClient); +long lastMsg = 0; +char msg[50]; +int value = 0; + +void setup_wifi() { + + delay(10); + // We start by connecting to a WiFi network + Serial.println(); + Serial.print("Connecting to "); + Serial.println(ssid); + + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + randomSeed(micros()); + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); +} + +void callback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message arrived ["); + Serial.print(topic); + Serial.print("] "); + for (int i = 0; i < length; i++) { + Serial.print((char)payload[i]); + } + Serial.println(); + + // Switch on the LED if an 1 was received as first character + if ((char)payload[0] == '1') { + digitalWrite(BUILTIN_LED, LOW); // Turn the LED on (Note that LOW is the voltage level + // but actually the LED is on; this is because + // it is active low on the ESP-01) + } else { + digitalWrite(BUILTIN_LED, HIGH); // Turn the LED off by making the voltage HIGH + } + +} + +void reconnect() { + // Loop until we're reconnected + while (!client.connected()) { + Serial.print("Attempting MQTT connection..."); + // Create a random client ID + String clientId = "ESP8266Client-"; + clientId += String(random(0xffff), HEX); + // Attempt to connect + if (client.connect(clientId.c_str())) { + Serial.println("connected"); + // Once connected, publish an announcement... + client.publish("outTopic", "hello world"); + // ... and resubscribe + client.subscribe("inTopic"); + } else { + Serial.print("failed, rc="); + Serial.print(client.state()); + Serial.println(" try again in 5 seconds"); + // Wait 5 seconds before retrying + delay(5000); + } + } +} + +void setup() { + pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output + Serial.begin(115200); + setup_wifi(); + client.setServer(mqtt_server, 1883); + client.setCallback(callback); +} + +void loop() { + + if (!client.connected()) { + reconnect(); + } + client.loop(); + + long now = millis(); + if (now - lastMsg > 2000) { + lastMsg = now; + ++value; + snprintf (msg, 50, "hello world #%ld", value); + Serial.print("Publish message: "); + Serial.println(msg); + client.publish("outTopic", msg); + } +} diff --git a/dep/pubsubclient-2.7/examples/mqtt_large_message/mqtt_large_message.ino b/dep/pubsubclient-2.7/examples/mqtt_large_message/mqtt_large_message.ino new file mode 100644 index 0000000..e048c3e --- /dev/null +++ b/dep/pubsubclient-2.7/examples/mqtt_large_message/mqtt_large_message.ino @@ -0,0 +1,179 @@ +/* + Long message ESP8266 MQTT example + + This sketch demonstrates sending arbitrarily large messages in combination + with the ESP8266 board/library. + + It connects to an MQTT server then: + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "greenBottles/#", printing out any messages + it receives. NB - it assumes the received payloads are strings not binary + - If the sub-topic is a number, it publishes a "greenBottles/lyrics" message + with a payload consisting of the lyrics to "10 green bottles", replacing + 10 with the number given in the sub-topic. + + It will reconnect to the server if the connection is lost using a blocking + reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to + achieve the same result without blocking the main loop. + + To install the ESP8266 board, (using Arduino 1.6.4+): + - Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs": + http://arduino.esp8266.com/stable/package_esp8266com_index.json + - Open the "Tools -> Board -> Board Manager" and click install for the ESP8266" + - Select your ESP8266 in "Tools -> Board" + +*/ + +#include +#include + +// Update these with values suitable for your network. + +const char* ssid = "........"; +const char* password = "........"; +const char* mqtt_server = "broker.mqtt-dashboard.com"; + +WiFiClient espClient; +PubSubClient client(espClient); +long lastMsg = 0; +char msg[50]; +int value = 0; + +void setup_wifi() { + + delay(10); + // We start by connecting to a WiFi network + Serial.println(); + Serial.print("Connecting to "); + Serial.println(ssid); + + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + randomSeed(micros()); + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); +} + +void callback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message arrived ["); + Serial.print(topic); + Serial.print("] "); + for (int i = 0; i < length; i++) { + Serial.print((char)payload[i]); + } + Serial.println(); + + // Find out how many bottles we should generate lyrics for + String topicStr(topic); + int bottleCount = 0; // assume no bottles unless we correctly parse a value from the topic + if (topicStr.indexOf('/') >= 0) { + // The topic includes a '/', we'll try to read the number of bottles from just after that + topicStr.remove(0, topicStr.indexOf('/')+1); + // Now see if there's a number of bottles after the '/' + bottleCount = topicStr.toInt(); + } + + if (bottleCount > 0) { + // Work out how big our resulting message will be + int msgLen = 0; + for (int i = bottleCount; i > 0; i--) { + String numBottles(i); + msgLen += 2*numBottles.length(); + if (i == 1) { + msgLen += 2*String(" green bottle, standing on the wall\n").length(); + } else { + msgLen += 2*String(" green bottles, standing on the wall\n").length(); + } + msgLen += String("And if one green bottle should accidentally fall\nThere'll be ").length(); + switch (i) { + case 1: + msgLen += String("no green bottles, standing on the wall\n\n").length(); + break; + case 2: + msgLen += String("1 green bottle, standing on the wall\n\n").length(); + break; + default: + numBottles = i-1; + msgLen += numBottles.length(); + msgLen += String(" green bottles, standing on the wall\n\n").length(); + break; + }; + } + + // Now we can start to publish the message + client.beginPublish("greenBottles/lyrics", msgLen, false); + for (int i = bottleCount; i > 0; i--) { + for (int j = 0; j < 2; j++) { + client.print(i); + if (i == 1) { + client.print(" green bottle, standing on the wall\n"); + } else { + client.print(" green bottles, standing on the wall\n"); + } + } + client.print("And if one green bottle should accidentally fall\nThere'll be "); + switch (i) { + case 1: + client.print("no green bottles, standing on the wall\n\n"); + break; + case 2: + client.print("1 green bottle, standing on the wall\n\n"); + break; + default: + client.print(i-1); + client.print(" green bottles, standing on the wall\n\n"); + break; + }; + } + // Now we're done! + client.endPublish(); + } +} + +void reconnect() { + // Loop until we're reconnected + while (!client.connected()) { + Serial.print("Attempting MQTT connection..."); + // Create a random client ID + String clientId = "ESP8266Client-"; + clientId += String(random(0xffff), HEX); + // Attempt to connect + if (client.connect(clientId.c_str())) { + Serial.println("connected"); + // Once connected, publish an announcement... + client.publish("outTopic", "hello world"); + // ... and resubscribe + client.subscribe("greenBottles/#"); + } else { + Serial.print("failed, rc="); + Serial.print(client.state()); + Serial.println(" try again in 5 seconds"); + // Wait 5 seconds before retrying + delay(5000); + } + } +} + +void setup() { + pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output + Serial.begin(115200); + setup_wifi(); + client.setServer(mqtt_server, 1883); + client.setCallback(callback); +} + +void loop() { + + if (!client.connected()) { + reconnect(); + } + client.loop(); +} diff --git a/dep/pubsubclient-2.7/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino b/dep/pubsubclient-2.7/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino new file mode 100644 index 0000000..42afb2a --- /dev/null +++ b/dep/pubsubclient-2.7/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino @@ -0,0 +1,60 @@ +/* + Publishing in the callback + + - connects to an MQTT server + - subscribes to the topic "inTopic" + - when a message is received, republishes it to "outTopic" + + This example shows how to publish messages within the + callback function. The callback function header needs to + be declared before the PubSubClient constructor and the + actual callback defined afterwards. + This ensures the client reference in the callback function + is valid. + +*/ + +#include +#include +#include + +// Update these with values suitable for your network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +// Callback function header +void callback(char* topic, byte* payload, unsigned int length); + +EthernetClient ethClient; +PubSubClient client(server, 1883, callback, ethClient); + +// Callback function +void callback(char* topic, byte* payload, unsigned int length) { + // In order to republish this payload, a copy must be made + // as the orignal payload buffer will be overwritten whilst + // constructing the PUBLISH packet. + + // Allocate the correct amount of memory for the payload copy + byte* p = (byte*)malloc(length); + // Copy the payload to the new buffer + memcpy(p,payload,length); + client.publish("outTopic", p, length); + // Free the memory + free(p); +} + +void setup() +{ + + Ethernet.begin(mac, ip); + if (client.connect("arduinoClient")) { + client.publish("outTopic","hello world"); + client.subscribe("inTopic"); + } +} + +void loop() +{ + client.loop(); +} diff --git a/dep/pubsubclient-2.7/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino b/dep/pubsubclient-2.7/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino new file mode 100644 index 0000000..080b739 --- /dev/null +++ b/dep/pubsubclient-2.7/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino @@ -0,0 +1,67 @@ +/* + Reconnecting MQTT example - non-blocking + + This sketch demonstrates how to keep the client connected + using a non-blocking reconnect function. If the client loses + its connection, it attempts to reconnect every 5 seconds + without blocking the main loop. + +*/ + +#include +#include +#include + +// Update these with values suitable for your hardware/network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + +EthernetClient ethClient; +PubSubClient client(ethClient); + +long lastReconnectAttempt = 0; + +boolean reconnect() { + if (client.connect("arduinoClient")) { + // Once connected, publish an announcement... + client.publish("outTopic","hello world"); + // ... and resubscribe + client.subscribe("inTopic"); + } + return client.connected(); +} + +void setup() +{ + client.setServer(server, 1883); + client.setCallback(callback); + + Ethernet.begin(mac, ip); + delay(1500); + lastReconnectAttempt = 0; +} + + +void loop() +{ + if (!client.connected()) { + long now = millis(); + if (now - lastReconnectAttempt > 5000) { + lastReconnectAttempt = now; + // Attempt to reconnect + if (reconnect()) { + lastReconnectAttempt = 0; + } + } + } else { + // Client connected + + client.loop(); + } + +} diff --git a/dep/pubsubclient-2.7/examples/mqtt_stream/mqtt_stream.ino b/dep/pubsubclient-2.7/examples/mqtt_stream/mqtt_stream.ino new file mode 100644 index 0000000..67c2287 --- /dev/null +++ b/dep/pubsubclient-2.7/examples/mqtt_stream/mqtt_stream.ino @@ -0,0 +1,57 @@ +/* + Example of using a Stream object to store the message payload + + Uses SRAM library: https://github.com/ennui2342/arduino-sram + but could use any Stream based class such as SD + + - connects to an MQTT server + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "inTopic" +*/ + +#include +#include +#include +#include + +// Update these with values suitable for your network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +SRAM sram(4, SRAM_1024); + +void callback(char* topic, byte* payload, unsigned int length) { + sram.seek(1); + + // do something with the message + for(uint8_t i=0; i_state = MQTT_DISCONNECTED; + this->_client = NULL; + this->stream = NULL; + setCallback(NULL); +} + +PubSubClient::PubSubClient(Client& client) { + this->_state = MQTT_DISCONNECTED; + setClient(client); + this->stream = NULL; +} + +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setClient(client); + this->stream = NULL; +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr,port); + setClient(client); + setStream(stream); +} +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; +} +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); +} + +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setClient(client); + this->stream = NULL; +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip,port); + setClient(client); + setStream(stream); +} +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; +} +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); +} + +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + this->stream = NULL; +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + setStream(stream); +} +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; +} +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); +} + +boolean PubSubClient::connect(const char *id) { + return connect(id,NULL,NULL,0,0,0,0,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass) { + return connect(id,user,pass,0,0,0,0,1); +} + +boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean 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>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 +boolean 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 +boolean 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;istream) { + 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; +} + +boolean 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); + uint16_t msgId = 0; + uint8_t *payload; + 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; + // msgId only present for QOS>0 + if ((buffer[0]&0x06) == MQTTQOS1) { + 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; +} + +boolean PubSubClient::publish(const char* topic, const char* payload) { + return publish(topic,(const uint8_t*)payload,strlen(payload),false); +} + +boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) { + return publish(topic,(const uint8_t*)payload,strlen(payload),retained); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { + return publish(topic, payload, plength, false); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean 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 0) { + digit |= 0x80; + } + buffer[pos++] = digit; + llen++; + } while(len>0); + + pos = writeString(topic,buffer,pos); + + rc += _client->write(buffer,pos); + + for (i=0;iwrite((char)pgm_read_byte_near(payload + i)); + } + + lastOutActivity = millis(); + + return rc == tlen + 4 + plength; +} + +boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, boolean retained) { + if (connected()) { + // Send the header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + length = writeString(topic,buffer,length); + uint16_t i; + 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 digit; + uint8_t pos = 0; + uint16_t len = length; + do { + 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 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 +} + +boolean PubSubClient::subscribe(const char* topic) { + return subscribe(topic, 0); +} + +boolean 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; +} + +boolean 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; +} + + +boolean PubSubClient::connected() { + boolean 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; +} + +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; +} diff --git a/dep/pubsubclient-2.7/src/PubSubClient.h b/dep/pubsubclient-2.7/src/PubSubClient.h new file mode 100644 index 0000000..63da49c --- /dev/null +++ b/dep/pubsubclient-2.7/src/PubSubClient.h @@ -0,0 +1,174 @@ +/* + PubSubClient.h - A simple client for MQTT. + Nick O'Leary + http://knolleary.net +*/ + +#ifndef PubSubClient_h +#define PubSubClient_h + +#include +#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 +#define MQTT_MAX_PACKET_SIZE 1200 +#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 +#define MQTT_CALLBACK_SIGNATURE std::function 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;} + +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*); + boolean readByte(uint8_t * result); + boolean readByte(uint8_t * result, uint16_t * index); + boolean 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 - ) 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(Client& client); + PubSubClient(IPAddress, uint16_t, Client& client); + PubSubClient(IPAddress, uint16_t, Client& client, Stream&); + PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + PubSubClient(uint8_t *, uint16_t, Client& client); + PubSubClient(uint8_t *, uint16_t, Client& client, Stream&); + PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + PubSubClient(const char*, uint16_t, Client& client); + PubSubClient(const char*, uint16_t, Client& client, Stream&); + PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + + PubSubClient& setServer(IPAddress ip, uint16_t port); + PubSubClient& setServer(uint8_t * ip, uint16_t port); + PubSubClient& setServer(const char * domain, uint16_t port); + PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE); + PubSubClient& setClient(Client& client); + PubSubClient& setStream(Stream& stream); + + boolean connect(const char* id); + boolean connect(const char* id, const char* user, const char* pass); + boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); + boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); + boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession); + void disconnect(); + boolean publish(const char* topic, const char* payload); + boolean publish(const char* topic, const char* payload, boolean retained); + boolean publish(const char* topic, const uint8_t * payload, unsigned int plength); + boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); + boolean publish_P(const char* topic, const char* payload, boolean retained); + boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); + // 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 + boolean beginPublish(const char* topic, unsigned int plength, boolean retained); + // Finish off this publish message (started with beginPublish) + // Returns 1 if the packet was sent successfully, 0 if there was an error + int endPublish(); + // Write a single byte of payload (only to be used with beginPublish/endPublish) + virtual size_t write(uint8_t); + // 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); + boolean subscribe(const char* topic); + boolean subscribe(const char* topic, uint8_t qos); + boolean unsubscribe(const char* topic); + boolean loop(); + boolean connected(); + int state(); +}; + + +#endif diff --git a/dep/pubsubclient-2.7/src/PubSubClient.h.bak b/dep/pubsubclient-2.7/src/PubSubClient.h.bak new file mode 100644 index 0000000..2fd6f1d --- /dev/null +++ b/dep/pubsubclient-2.7/src/PubSubClient.h.bak @@ -0,0 +1,173 @@ +/* + PubSubClient.h - A simple client for MQTT. + Nick O'Leary + http://knolleary.net +*/ + +#ifndef PubSubClient_h +#define PubSubClient_h + +#include +#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 +#define MQTT_CALLBACK_SIGNATURE std::function 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;} + +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*); + boolean readByte(uint8_t * result); + boolean readByte(uint8_t * result, uint16_t * index); + boolean 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 - ) 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(Client& client); + PubSubClient(IPAddress, uint16_t, Client& client); + PubSubClient(IPAddress, uint16_t, Client& client, Stream&); + PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + PubSubClient(uint8_t *, uint16_t, Client& client); + PubSubClient(uint8_t *, uint16_t, Client& client, Stream&); + PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + PubSubClient(const char*, uint16_t, Client& client); + PubSubClient(const char*, uint16_t, Client& client, Stream&); + PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + + PubSubClient& setServer(IPAddress ip, uint16_t port); + PubSubClient& setServer(uint8_t * ip, uint16_t port); + PubSubClient& setServer(const char * domain, uint16_t port); + PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE); + PubSubClient& setClient(Client& client); + PubSubClient& setStream(Stream& stream); + + boolean connect(const char* id); + boolean connect(const char* id, const char* user, const char* pass); + boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); + boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); + boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession); + void disconnect(); + boolean publish(const char* topic, const char* payload); + boolean publish(const char* topic, const char* payload, boolean retained); + boolean publish(const char* topic, const uint8_t * payload, unsigned int plength); + boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); + boolean publish_P(const char* topic, const char* payload, boolean retained); + boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); + // 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 + boolean beginPublish(const char* topic, unsigned int plength, boolean retained); + // Finish off this publish message (started with beginPublish) + // Returns 1 if the packet was sent successfully, 0 if there was an error + int endPublish(); + // Write a single byte of payload (only to be used with beginPublish/endPublish) + virtual size_t write(uint8_t); + // 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); + boolean subscribe(const char* topic); + boolean subscribe(const char* topic, uint8_t qos); + boolean unsubscribe(const char* topic); + boolean loop(); + boolean connected(); + int state(); +}; + + +#endif diff --git a/dep/pubsubclient-2.7/tests/.gitignore b/dep/pubsubclient-2.7/tests/.gitignore new file mode 100644 index 0000000..215de78 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/.gitignore @@ -0,0 +1,4 @@ +.build +tmpbin +logs +*.pyc diff --git a/dep/pubsubclient-2.7/tests/Makefile b/dep/pubsubclient-2.7/tests/Makefile new file mode 100644 index 0000000..1f71636 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/Makefile @@ -0,0 +1,25 @@ +SRC_PATH=./src +OUT_PATH=./bin +TEST_SRC=$(wildcard ${SRC_PATH}/*_spec.cpp) +TEST_BIN= $(TEST_SRC:${SRC_PATH}/%.cpp=${OUT_PATH}/%) +VPATH=${SRC_PATH} +SHIM_FILES=${SRC_PATH}/lib/*.cpp +PSC_FILE=../src/PubSubClient.cpp +CC=g++ +CFLAGS=-I${SRC_PATH}/lib -I../src + +all: $(TEST_BIN) + +${OUT_PATH}/%: ${SRC_PATH}/%.cpp ${PSC_FILE} ${SHIM_FILES} + mkdir -p ${OUT_PATH} + ${CC} ${CFLAGS} $^ -o $@ + +clean: + @rm -rf ${OUT_PATH} + +test: + @bin/connect_spec + @bin/publish_spec + @bin/receive_spec + @bin/subscribe_spec + @bin/keepalive_spec diff --git a/dep/pubsubclient-2.7/tests/README.md b/dep/pubsubclient-2.7/tests/README.md new file mode 100644 index 0000000..e5700a6 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/README.md @@ -0,0 +1,93 @@ +# Arduino Client for MQTT Test Suite + +This is a regression test suite for the `PubSubClient` library. + +There are two parts: + + - Tests that can be compiled and run on any machine + - Tests that build the example sketches using the Arduino IDE + + +It is a work-in-progress and is subject to complete refactoring as the whim takes +me. + + +## Local tests + +These are a set of executables that can be run to test specific areas of functionality. +They do not require a real Arduino to be attached, nor the use of the Arduino IDE. + +The tests include a set of mock files to stub out the parts of the Arduino environment the library +depends on. + +### Dependencies + + - g++ + +### Running + +Build the tests using the provided `Makefile`: + + $ make + +This will create a set of executables in `./bin/`. Run each of these executables to test the corresponding functionality. + +*Note:* the `connect_spec` and `keepalive_spec` tests involve testing keepalive timers so naturally take a few minutes to run through. + +## Arduino tests + +*Note:* INO Tool doesn't currently play nicely with Arduino 1.5. This has broken this test suite. + +Without a suitable arduino plugged in, the test suite will only check the +example sketches compile cleanly against the library. + +With an arduino plugged in, each sketch that has a corresponding python +test case is built, uploaded and then the tests run. + +### Dependencies + + - Python 2.7+ + - [INO Tool](http://inotool.org/) - this provides command-line build/upload of Arduino sketches + +### Running + +The test suite _does not_ run an MQTT server - it is assumed to be running already. + + $ python testsuite.py + +A summary of activity is printed to the console. More comprehensive logs are written +to the `logs` directory. + +### What it does + +For each sketch in the library's `examples` directory, e.g. `mqtt_basic.ino`, the suite looks for a matching test case +`testcases/mqtt_basic.py`. + +The test case must follow these conventions: + - sub-class `unittest.TestCase` + - provide the class methods `setUpClass` and `tearDownClass` (TODO: make this optional) + - all test method names begin with `test_` + +The suite will call the `setUpClass` method _before_ uploading the sketch. This +allows any test setup to be performed before the sketch runs - such as connecting +a client and subscribing to topics. + + +### Settings + +The file `testcases/settings.py` is used to config the test environment. + + - `server_ip` - the IP address of the broker the client should connect to (the broker port is assumed to be 1883). + - `arduino_ip` - the IP address the arduino should use (when not testing DHCP). + +Before each sketch is compiled, these values are automatically substituted in. To +do this, the suite looks for lines that _start_ with the following: + + byte server[] = { + byte ip[] = { + +and replaces them with the appropriate values. + + + + diff --git a/dep/pubsubclient-2.7/tests/src/connect_spec.cpp b/dep/pubsubclient-2.7/tests/src/connect_spec.cpp new file mode 100644 index 0000000..e27a1f5 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/connect_spec.cpp @@ -0,0 +1,302 @@ +#include "PubSubClient.h" +#include "ShimClient.h" +#include "Buffer.h" +#include "BDDTest.h" +#include "trace.h" + + +byte server[] = { 172, 16, 0, 2 }; + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + + +int test_connect_fails_no_network() { + IT("fails to connect if underlying client doesn't connect"); + ShimClient shimClient; + shimClient.setAllowConnect(false); + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_FALSE(rc); + int state = client.state(); + IS_TRUE(state == MQTT_CONNECT_FAILED); + END_IT +} + +int test_connect_fails_on_no_response() { + IT("fails to connect if no response received after 15 seconds"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_FALSE(rc); + int state = client.state(); + IS_TRUE(state == MQTT_CONNECTION_TIMEOUT); + END_IT +} + +int test_connect_properly_formatted() { + IT("sends a properly formatted connect packet and succeeds"); + ShimClient shimClient; + + shimClient.setAllowConnect(true); + byte expectServer[] = { 172, 16, 0, 2 }; + shimClient.expectConnect(expectServer,1883); + byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + + shimClient.expect(connect,26); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int state = client.state(); + IS_TRUE(state == MQTT_DISCONNECTED); + + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + state = client.state(); + IS_TRUE(state == MQTT_CONNECTED); + + END_IT +} + +int test_connect_properly_formatted_hostname() { + IT("accepts a hostname"); + ShimClient shimClient; + + shimClient.setAllowConnect(true); + shimClient.expectConnect((char* const)"localhost",1883); + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client((char* const)"localhost", 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} + + +int test_connect_fails_on_bad_rc() { + IT("fails to connect if a bad return code is received"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + byte connack[] = { 0x20, 0x02, 0x00, 0x01 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_FALSE(rc); + + int state = client.state(); + IS_TRUE(state == 0x01); + + END_IT +} + +int test_connect_non_clean_session() { + IT("sends a properly formatted non-clean session connect packet and succeeds"); + ShimClient shimClient; + + shimClient.setAllowConnect(true); + byte expectServer[] = { 172, 16, 0, 2 }; + shimClient.expectConnect(expectServer,1883); + byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x0,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + + shimClient.expect(connect,26); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int state = client.state(); + IS_TRUE(state == MQTT_DISCONNECTED); + + int rc = client.connect((char*)"client_test1",0,0,0,0,0,0,0); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + state = client.state(); + IS_TRUE(state == MQTT_CONNECTED); + + END_IT +} + +int test_connect_accepts_username_password() { + IT("accepts a username and password"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connect[] = { 0x10,0x24,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xc2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x4,0x70,0x61,0x73,0x73}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.expect(connect,0x26); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"pass"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_connect_accepts_username_no_password() { + IT("accepts a username but no password"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connect[] = { 0x10,0x1e,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x82,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.expect(connect,0x20); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1",(char*)"user",0); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} +int test_connect_accepts_username_blank_password() { + IT("accepts a username and blank password"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connect[] = { 0x10,0x20,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xc2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x0}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.expect(connect,0x26); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"pass"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_connect_ignores_password_no_username() { + IT("ignores a password but no username"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.expect(connect,26); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1",0,(char*)"pass"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_connect_with_will() { + IT("accepts a will"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connect[] = {0x10,0x30,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xe,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.expect(connect,0x32); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1",(char*)"willTopic",1,0,(char*)"willMessage"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_connect_with_will_username_password() { + IT("accepts a will, username and password"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connect[] = {0x10,0x40,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xce,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x8,0x70,0x61,0x73,0x73,0x77,0x6f,0x72,0x64}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.expect(connect,0x42); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"password",(char*)"willTopic",1,0,(char*)"willMessage"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_connect_disconnect_connect() { + IT("connects, disconnects and connects again"); + ShimClient shimClient; + + shimClient.setAllowConnect(true); + byte expectServer[] = { 172, 16, 0, 2 }; + shimClient.expectConnect(expectServer,1883); + byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + + shimClient.expect(connect,26); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + + int state = client.state(); + IS_TRUE(state == MQTT_DISCONNECTED); + + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + state = client.state(); + IS_TRUE(state == MQTT_CONNECTED); + + byte disconnect[] = {0xE0,0x00}; + shimClient.expect(disconnect,2); + + client.disconnect(); + + IS_FALSE(client.connected()); + IS_FALSE(shimClient.connected()); + IS_FALSE(shimClient.error()); + + state = client.state(); + IS_TRUE(state == MQTT_DISCONNECTED); + + shimClient.expect(connect,28); + shimClient.respond(connack,4); + rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + state = client.state(); + IS_TRUE(state == MQTT_CONNECTED); + + END_IT +} + +int main() +{ + SUITE("Connect"); + + test_connect_fails_no_network(); + test_connect_fails_on_no_response(); + + test_connect_properly_formatted(); + test_connect_non_clean_session(); + test_connect_accepts_username_password(); + test_connect_fails_on_bad_rc(); + test_connect_properly_formatted_hostname(); + + test_connect_accepts_username_no_password(); + test_connect_ignores_password_no_username(); + test_connect_with_will(); + test_connect_with_will_username_password(); + test_connect_disconnect_connect(); + FINISH +} diff --git a/dep/pubsubclient-2.7/tests/src/keepalive_spec.cpp b/dep/pubsubclient-2.7/tests/src/keepalive_spec.cpp new file mode 100644 index 0000000..ea643cf --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/keepalive_spec.cpp @@ -0,0 +1,185 @@ +#include "PubSubClient.h" +#include "ShimClient.h" +#include "Buffer.h" +#include "BDDTest.h" +#include "trace.h" +#include + +byte server[] = { 172, 16, 0, 2 }; + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + + +int test_keepalive_pings_idle() { + IT("keeps an idle connection alive (takes 1 minute)"); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte pingreq[] = { 0xC0,0x0 }; + shimClient.expect(pingreq,2); + byte pingresp[] = { 0xD0,0x0 }; + shimClient.respond(pingresp,2); + + for (int i = 0; i < 50; i++) { + sleep(1); + if ( i == 15 || i == 31 || i == 47) { + shimClient.expect(pingreq,2); + shimClient.respond(pingresp,2); + } + rc = client.loop(); + IS_TRUE(rc); + } + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_keepalive_pings_with_outbound_qos0() { + IT("keeps a connection alive that only sends qos0 (takes 1 minute)"); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + + for (int i = 0; i < 50; i++) { + TRACE(i<<":"); + shimClient.expect(publish,16); + rc = client.publish((char*)"topic",(char*)"payload"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + sleep(1); + if ( i == 15 || i == 31 || i == 47) { + byte pingreq[] = { 0xC0,0x0 }; + shimClient.expect(pingreq,2); + byte pingresp[] = { 0xD0,0x0 }; + shimClient.respond(pingresp,2); + } + rc = client.loop(); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + } + + END_IT +} + +int test_keepalive_pings_with_inbound_qos0() { + IT("keeps a connection alive that only receives qos0 (takes 1 minute)"); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + + for (int i = 0; i < 50; i++) { + TRACE(i<<":"); + sleep(1); + if ( i == 15 || i == 31 || i == 47) { + byte pingreq[] = { 0xC0,0x0 }; + shimClient.expect(pingreq,2); + byte pingresp[] = { 0xD0,0x0 }; + shimClient.respond(pingresp,2); + } + shimClient.respond(publish,16); + rc = client.loop(); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + } + + END_IT +} + +int test_keepalive_no_pings_inbound_qos1() { + IT("does not send pings for connections with inbound qos1 (takes 1 minute)"); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x32,0x10,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x12,0x34,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + byte puback[] = {0x40,0x2,0x12,0x34}; + + for (int i = 0; i < 50; i++) { + shimClient.respond(publish,18); + shimClient.expect(puback,4); + sleep(1); + rc = client.loop(); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + } + + END_IT +} + +int test_keepalive_disconnects_hung() { + IT("disconnects a hung connection (takes 30 seconds)"); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte pingreq[] = { 0xC0,0x0 }; + shimClient.expect(pingreq,2); + + for (int i = 0; i < 32; i++) { + sleep(1); + rc = client.loop(); + } + IS_FALSE(rc); + + int state = client.state(); + IS_TRUE(state == MQTT_CONNECTION_TIMEOUT); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int main() +{ + SUITE("Keep-alive"); + test_keepalive_pings_idle(); + test_keepalive_pings_with_outbound_qos0(); + test_keepalive_pings_with_inbound_qos0(); + test_keepalive_no_pings_inbound_qos1(); + test_keepalive_disconnects_hung(); + + FINISH +} diff --git a/dep/pubsubclient-2.7/tests/src/lib/Arduino.h b/dep/pubsubclient-2.7/tests/src/lib/Arduino.h new file mode 100644 index 0000000..2a00f24 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/Arduino.h @@ -0,0 +1,26 @@ +#ifndef Arduino_h +#define Arduino_h + +#include +#include +#include +#include +#include "Print.h" + + +extern "C"{ + typedef uint8_t byte ; + typedef uint8_t boolean ; + + /* sketch */ + extern void setup( void ) ; + extern void loop( void ) ; + uint32_t millis( void ); +} + +#define PROGMEM +#define pgm_read_byte_near(x) *(x) + +#define yield(x) {} + +#endif // Arduino_h diff --git a/dep/pubsubclient-2.7/tests/src/lib/BDDTest.cpp b/dep/pubsubclient-2.7/tests/src/lib/BDDTest.cpp new file mode 100644 index 0000000..a72bf65 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/BDDTest.cpp @@ -0,0 +1,50 @@ +#include "BDDTest.h" +#include "trace.h" +#include +#include +#include +#include + +int testCount = 0; +int testPasses = 0; +const char* testDescription; + +std::list failureList; + +void bddtest_suite(const char* name) { + LOG(name << "\n"); +} + +int bddtest_test(const char* file, int line, const char* assertion, int result) { + if (!result) { + LOG("✗\n"); + std::ostringstream os; + os << " ! "<::iterator it = failureList.begin(); it != failureList.end(); it++) { + LOG("\n"); + LOG(*it); + LOG("\n"); + } + + LOG(std::dec << testPasses << "/" << testCount << " tests passed\n\n"); + if (testPasses == testCount) { + return 0; + } + return 1; +} diff --git a/dep/pubsubclient-2.7/tests/src/lib/BDDTest.h b/dep/pubsubclient-2.7/tests/src/lib/BDDTest.h new file mode 100644 index 0000000..1197fdd --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/BDDTest.h @@ -0,0 +1,23 @@ +#ifndef bddtest_h +#define bddtest_h + +void bddtest_suite(const char* name); +int bddtest_test(const char*, int, const char*, int); +void bddtest_start(const char*); +void bddtest_end(); +int bddtest_summary(); + +#define SUITE(x) { bddtest_suite(x); } +#define TEST(x) { if (!bddtest_test(__FILE__, __LINE__, #x, (x))) return false; } + +#define IT(x) { bddtest_start(x); } +#define END_IT { bddtest_end();return true;} + +#define FINISH { return bddtest_summary(); } + +#define IS_TRUE(x) TEST(x) +#define IS_FALSE(x) TEST(!(x)) +#define IS_EQUAL(x,y) TEST(x==y) +#define IS_NOT_EQUAL(x,y) TEST(x!=y) + +#endif diff --git a/dep/pubsubclient-2.7/tests/src/lib/Buffer.cpp b/dep/pubsubclient-2.7/tests/src/lib/Buffer.cpp new file mode 100644 index 0000000..f07759a --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/Buffer.cpp @@ -0,0 +1,34 @@ +#include "Buffer.h" +#include "Arduino.h" + +Buffer::Buffer() { + this->pos = 0; + this->length = 0; +} + +Buffer::Buffer(uint8_t* buf, size_t size) { + this->pos = 0; + this->length = 0; + this->add(buf,size); +} +bool Buffer::available() { + return this->pos < this->length; +} + +uint8_t Buffer::next() { + if (this->available()) { + return this->buffer[this->pos++]; + } + return 0; +} + +void Buffer::reset() { + this->pos = 0; +} + +void Buffer::add(uint8_t* buf, size_t size) { + uint16_t i = 0; + for (;ibuffer[this->length++] = buf[i]; + } +} diff --git a/dep/pubsubclient-2.7/tests/src/lib/Buffer.h b/dep/pubsubclient-2.7/tests/src/lib/Buffer.h new file mode 100644 index 0000000..f448cad --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/Buffer.h @@ -0,0 +1,23 @@ +#ifndef buffer_h +#define buffer_h + +#include "Arduino.h" + +class Buffer { +private: + uint8_t buffer[1024]; + uint16_t pos; + uint16_t length; + +public: + Buffer(); + Buffer(uint8_t* buf, size_t size); + + virtual bool available(); + virtual uint8_t next(); + virtual void reset(); + + virtual void add(uint8_t* buf, size_t size); +}; + +#endif diff --git a/dep/pubsubclient-2.7/tests/src/lib/Client.h b/dep/pubsubclient-2.7/tests/src/lib/Client.h new file mode 100644 index 0000000..9e18c07 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/Client.h @@ -0,0 +1,21 @@ +#ifndef client_h +#define client_h +#include "IPAddress.h" + +class Client { +public: + virtual int connect(IPAddress ip, uint16_t port) =0; + virtual int connect(const char *host, uint16_t port) =0; + virtual size_t write(uint8_t) =0; + virtual size_t write(const uint8_t *buf, size_t size) =0; + virtual int available() = 0; + virtual int read() = 0; + virtual int read(uint8_t *buf, size_t size) = 0; + virtual int peek() = 0; + virtual void flush() = 0; + virtual void stop() = 0; + virtual uint8_t connected() = 0; + virtual operator bool() = 0; +}; + +#endif diff --git a/dep/pubsubclient-2.7/tests/src/lib/IPAddress.cpp b/dep/pubsubclient-2.7/tests/src/lib/IPAddress.cpp new file mode 100644 index 0000000..610ff4c --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/IPAddress.cpp @@ -0,0 +1,44 @@ + +#include +#include + +IPAddress::IPAddress() +{ + memset(_address, 0, sizeof(_address)); +} + +IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) +{ + _address[0] = first_octet; + _address[1] = second_octet; + _address[2] = third_octet; + _address[3] = fourth_octet; +} + +IPAddress::IPAddress(uint32_t address) +{ + memcpy(_address, &address, sizeof(_address)); +} + +IPAddress::IPAddress(const uint8_t *address) +{ + memcpy(_address, address, sizeof(_address)); +} + +IPAddress& IPAddress::operator=(const uint8_t *address) +{ + memcpy(_address, address, sizeof(_address)); + return *this; +} + +IPAddress& IPAddress::operator=(uint32_t address) +{ + memcpy(_address, (const uint8_t *)&address, sizeof(_address)); + return *this; +} + +bool IPAddress::operator==(const uint8_t* addr) +{ + return memcmp(addr, _address, sizeof(_address)) == 0; +} + diff --git a/dep/pubsubclient-2.7/tests/src/lib/IPAddress.h b/dep/pubsubclient-2.7/tests/src/lib/IPAddress.h new file mode 100644 index 0000000..e75a8fe --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/IPAddress.h @@ -0,0 +1,72 @@ +/* + * + * MIT License: + * Copyright (c) 2011 Adrian McEwen + * 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. + * + * adrianm@mcqn.com 1/1/2011 + */ + +#ifndef IPAddress_h +#define IPAddress_h + + +// A class to make it easier to handle and pass around IP addresses + +class IPAddress { +private: + uint8_t _address[4]; // IPv4 address + // Access the raw byte array containing the address. Because this returns a pointer + // to the internal structure rather than a copy of the address this function should only + // be used when you know that the usage of the returned uint8_t* will be transient and not + // stored. + uint8_t* raw_address() { return _address; }; + +public: + // Constructors + IPAddress(); + IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet); + IPAddress(uint32_t address); + IPAddress(const uint8_t *address); + + // Overloaded cast operator to allow IPAddress objects to be used where a pointer + // to a four-byte uint8_t array is expected + operator uint32_t() { return *((uint32_t*)_address); }; + bool operator==(const IPAddress& addr) { return (*((uint32_t*)_address)) == (*((uint32_t*)addr._address)); }; + bool operator==(const uint8_t* addr); + + // Overloaded index operator to allow getting and setting individual octets of the address + uint8_t operator[](int index) const { return _address[index]; }; + uint8_t& operator[](int index) { return _address[index]; }; + + // Overloaded copy operators to allow initialisation of IPAddress objects from other types + IPAddress& operator=(const uint8_t *address); + IPAddress& operator=(uint32_t address); + + + friend class EthernetClass; + friend class UDP; + friend class Client; + friend class Server; + friend class DhcpClass; + friend class DNSClient; +}; + + +#endif diff --git a/dep/pubsubclient-2.7/tests/src/lib/Print.h b/dep/pubsubclient-2.7/tests/src/lib/Print.h new file mode 100644 index 0000000..02ef77c --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/Print.h @@ -0,0 +1,28 @@ +/* + Print.h - Base class that provides print() and println() + Copyright (c) 2008 David A. Mellis. 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 + */ + +#ifndef Print_h +#define Print_h + +class Print { + public: + virtual size_t write(uint8_t) = 0; +}; + +#endif diff --git a/dep/pubsubclient-2.7/tests/src/lib/ShimClient.cpp b/dep/pubsubclient-2.7/tests/src/lib/ShimClient.cpp new file mode 100644 index 0000000..f70115f --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/ShimClient.cpp @@ -0,0 +1,153 @@ +#include "ShimClient.h" +#include "trace.h" +#include +#include +#include + +extern "C" { + uint32_t millis(void) { + return time(0)*1000; + } +} + +ShimClient::ShimClient() { + this->responseBuffer = new Buffer(); + this->expectBuffer = new Buffer(); + this->_allowConnect = true; + this->_connected = false; + this->_error = false; + this->expectAnything = true; + this->_received = 0; + this->_expectedPort = 0; +} + +int ShimClient::connect(IPAddress ip, uint16_t port) { + if (this->_allowConnect) { + this->_connected = true; + } + if (this->_expectedPort !=0) { + // if (memcmp(ip,this->_expectedIP,4) != 0) { + // TRACE( "ip mismatch\n"); + // this->_error = true; + // } + if (port != this->_expectedPort) { + TRACE( "port mismatch\n"); + this->_error = true; + } + } + return this->_connected; +} +int ShimClient::connect(const char *host, uint16_t port) { + if (this->_allowConnect) { + this->_connected = true; + } + if (this->_expectedPort !=0) { + if (strcmp(host,this->_expectedHost) != 0) { + TRACE( "host mismatch\n"); + this->_error = true; + } + if (port != this->_expectedPort) { + TRACE( "port mismatch\n"); + this->_error = true; + } + + } + return this->_connected; +} +size_t ShimClient::write(uint8_t b) { + this->_received += 1; + TRACE(std::hex << (unsigned int)b); + if (!this->expectAnything) { + if (this->expectBuffer->available()) { + uint8_t expected = this->expectBuffer->next(); + if (expected != b) { + this->_error = true; + TRACE("!=" << (unsigned int)expected); + } + } else { + this->_error = true; + } + } + TRACE("\n"<< std::dec); + return 1; +} +size_t ShimClient::write(const uint8_t *buf, size_t size) { + this->_received += size; + TRACE( "[" << std::dec << (unsigned int)(size) << "] "); + uint16_t i=0; + for (;i0) { + TRACE(":"); + } + TRACE(std::hex << (unsigned int)(buf[i])); + + if (!this->expectAnything) { + if (this->expectBuffer->available()) { + uint8_t expected = this->expectBuffer->next(); + if (expected != buf[i]) { + this->_error = true; + TRACE("!=" << (unsigned int)expected); + } + } else { + this->_error = true; + } + } + } + TRACE("\n"<responseBuffer->available(); +} +int ShimClient::read() { return this->responseBuffer->next(); } +int ShimClient::read(uint8_t *buf, size_t size) { + uint16_t i = 0; + for (;iread(); + } + return size; +} +int ShimClient::peek() { return 0; } +void ShimClient::flush() {} +void ShimClient::stop() { + this->setConnected(false); +} +uint8_t ShimClient::connected() { return this->_connected; } +ShimClient::operator bool() { return true; } + + +ShimClient* ShimClient::respond(uint8_t *buf, size_t size) { + this->responseBuffer->add(buf,size); + return this; +} + +ShimClient* ShimClient::expect(uint8_t *buf, size_t size) { + this->expectAnything = false; + this->expectBuffer->add(buf,size); + return this; +} + +void ShimClient::setConnected(bool b) { + this->_connected = b; +} +void ShimClient::setAllowConnect(bool b) { + this->_allowConnect = b; +} + +bool ShimClient::error() { + return this->_error; +} + +uint16_t ShimClient::received() { + return this->_received; +} + +void ShimClient::expectConnect(IPAddress ip, uint16_t port) { + this->_expectedIP = ip; + this->_expectedPort = port; +} + +void ShimClient::expectConnect(const char *host, uint16_t port) { + this->_expectedHost = host; + this->_expectedPort = port; +} diff --git a/dep/pubsubclient-2.7/tests/src/lib/ShimClient.h b/dep/pubsubclient-2.7/tests/src/lib/ShimClient.h new file mode 100644 index 0000000..2e3f874 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/ShimClient.h @@ -0,0 +1,51 @@ +#ifndef shimclient_h +#define shimclient_h + +#include "Arduino.h" +#include "Client.h" +#include "IPAddress.h" +#include "Buffer.h" + + +class ShimClient : public Client { +private: + Buffer* responseBuffer; + Buffer* expectBuffer; + bool _allowConnect; + bool _connected; + bool expectAnything; + bool _error; + uint16_t _received; + IPAddress _expectedIP; + uint16_t _expectedPort; + const char* _expectedHost; + +public: + ShimClient(); + virtual int connect(IPAddress ip, uint16_t port); + virtual int connect(const char *host, uint16_t port); + virtual size_t write(uint8_t); + virtual size_t write(const uint8_t *buf, size_t size); + virtual int available(); + virtual int read(); + virtual int read(uint8_t *buf, size_t size); + virtual int peek(); + virtual void flush(); + virtual void stop(); + virtual uint8_t connected(); + virtual operator bool(); + + virtual ShimClient* respond(uint8_t *buf, size_t size); + virtual ShimClient* expect(uint8_t *buf, size_t size); + + virtual void expectConnect(IPAddress ip, uint16_t port); + virtual void expectConnect(const char *host, uint16_t port); + + virtual uint16_t received(); + virtual bool error(); + + virtual void setAllowConnect(bool b); + virtual void setConnected(bool b); +}; + +#endif diff --git a/dep/pubsubclient-2.7/tests/src/lib/Stream.cpp b/dep/pubsubclient-2.7/tests/src/lib/Stream.cpp new file mode 100644 index 0000000..b0ecbb4 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/Stream.cpp @@ -0,0 +1,39 @@ +#include "Stream.h" +#include "trace.h" +#include +#include + +Stream::Stream() { + this->expectBuffer = new Buffer(); + this->_error = false; + this->_written = 0; +} + +size_t Stream::write(uint8_t b) { + this->_written++; + TRACE(std::hex << (unsigned int)b); + if (this->expectBuffer->available()) { + uint8_t expected = this->expectBuffer->next(); + if (expected != b) { + this->_error = true; + TRACE("!=" << (unsigned int)expected); + } + } else { + this->_error = true; + } + TRACE("\n"<< std::dec); + return 1; +} + + +bool Stream::error() { + return this->_error; +} + +void Stream::expect(uint8_t *buf, size_t size) { + this->expectBuffer->add(buf,size); +} + +uint16_t Stream::length() { + return this->_written; +} diff --git a/dep/pubsubclient-2.7/tests/src/lib/Stream.h b/dep/pubsubclient-2.7/tests/src/lib/Stream.h new file mode 100644 index 0000000..4e41f86 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/Stream.h @@ -0,0 +1,22 @@ +#ifndef Stream_h +#define Stream_h + +#include "Arduino.h" +#include "Buffer.h" + +class Stream { +private: + Buffer* expectBuffer; + bool _error; + uint16_t _written; + +public: + Stream(); + virtual size_t write(uint8_t); + + virtual bool error(); + virtual void expect(uint8_t *buf, size_t size); + virtual uint16_t length(); +}; + +#endif diff --git a/dep/pubsubclient-2.7/tests/src/lib/trace.h b/dep/pubsubclient-2.7/tests/src/lib/trace.h new file mode 100644 index 0000000..42eb991 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/lib/trace.h @@ -0,0 +1,10 @@ +#ifndef trace_h +#define trace_h +#include + +#include + +#define LOG(x) {std::cout << x << std::flush; } +#define TRACE(x) {if (getenv("TRACE")) { std::cout << x << std::flush; }} + +#endif diff --git a/dep/pubsubclient-2.7/tests/src/publish_spec.cpp b/dep/pubsubclient-2.7/tests/src/publish_spec.cpp new file mode 100644 index 0000000..232df0d --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/publish_spec.cpp @@ -0,0 +1,190 @@ +#include "PubSubClient.h" +#include "ShimClient.h" +#include "Buffer.h" +#include "BDDTest.h" +#include "trace.h" + + +byte server[] = { 172, 16, 0, 2 }; + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + +int test_publish() { + IT("publishes a null-terminated string"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + shimClient.expect(publish,16); + + rc = client.publish((char*)"topic",(char*)"payload"); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + + +int test_publish_bytes() { + IT("publishes a byte array"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte payload[] = { 0x01,0x02,0x03,0x0,0x05 }; + int length = 5; + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5}; + shimClient.expect(publish,14); + + rc = client.publish((char*)"topic",payload,length); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + + +int test_publish_retained() { + IT("publishes retained - 1"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte payload[] = { 0x01,0x02,0x03,0x0,0x05 }; + int length = 5; + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5}; + shimClient.expect(publish,14); + + rc = client.publish((char*)"topic",payload,length,true); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_retained_2() { + IT("publishes retained - 2"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,'A','B','C','D','E'}; + shimClient.expect(publish,14); + + rc = client.publish((char*)"topic",(char*)"ABCDE",true); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_not_connected() { + IT("publish fails when not connected"); + ShimClient shimClient; + + PubSubClient client(server, 1883, callback, shimClient); + + int rc = client.publish((char*)"topic",(char*)"payload"); + IS_FALSE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_too_long() { + IT("publish fails when topic/payload are too long"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + // 0 1 2 3 4 5 6 7 8 9 0 1 2 + rc = client.publish((char*)"topic",(char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); + IS_FALSE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_P() { + IT("publishes using PROGMEM"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte payload[] = { 0x01,0x02,0x03,0x0,0x05 }; + int length = 5; + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5}; + shimClient.expect(publish,14); + + rc = client.publish_P((char*)"topic",payload,length,true); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + + + + +int main() +{ + SUITE("Publish"); + test_publish(); + test_publish_bytes(); + test_publish_retained(); + test_publish_retained_2(); + test_publish_not_connected(); + test_publish_too_long(); + test_publish_P(); + + FINISH +} diff --git a/dep/pubsubclient-2.7/tests/src/receive_spec.cpp b/dep/pubsubclient-2.7/tests/src/receive_spec.cpp new file mode 100644 index 0000000..9a18af0 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/receive_spec.cpp @@ -0,0 +1,279 @@ +#include "PubSubClient.h" +#include "ShimClient.h" +#include "Buffer.h" +#include "BDDTest.h" +#include "trace.h" + + +byte server[] = { 172, 16, 0, 2 }; + +bool callback_called = false; +char lastTopic[1024]; +char lastPayload[1024]; +unsigned int lastLength; + +void reset_callback() { + callback_called = false; + lastTopic[0] = '\0'; + lastPayload[0] = '\0'; + lastLength = 0; +} + +void callback(char* topic, byte* payload, unsigned int length) { + callback_called = true; + strcpy(lastTopic,topic); + memcpy(lastPayload,payload,length); + lastLength = length; +} + +int test_receive_callback() { + IT("receives a callback message"); + reset_callback(); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + shimClient.respond(publish,16); + + rc = client.loop(); + + IS_TRUE(rc); + + IS_TRUE(callback_called); + IS_TRUE(strcmp(lastTopic,"topic")==0); + IS_TRUE(memcmp(lastPayload,"payload",7)==0); + IS_TRUE(lastLength == 7); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_receive_stream() { + IT("receives a streamed callback message"); + reset_callback(); + + Stream stream; + stream.expect((uint8_t*)"payload",7); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient, stream); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + shimClient.respond(publish,16); + + rc = client.loop(); + + IS_TRUE(rc); + + IS_TRUE(callback_called); + IS_TRUE(strcmp(lastTopic,"topic")==0); + IS_TRUE(lastLength == 7); + + IS_FALSE(stream.error()); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_receive_max_sized_message() { + IT("receives an max-sized message"); + reset_callback(); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + int length = MQTT_MAX_PACKET_SIZE; + byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + byte bigPublish[length]; + memset(bigPublish,'A',length); + bigPublish[length] = 'B'; + memcpy(bigPublish,publish,16); + shimClient.respond(bigPublish,length); + + rc = client.loop(); + + IS_TRUE(rc); + + IS_TRUE(callback_called); + IS_TRUE(strcmp(lastTopic,"topic")==0); + IS_TRUE(lastLength == length-9); + IS_TRUE(memcmp(lastPayload,bigPublish+9,lastLength)==0); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_receive_oversized_message() { + IT("drops an oversized message"); + reset_callback(); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + int length = MQTT_MAX_PACKET_SIZE+1; + byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + byte bigPublish[length]; + memset(bigPublish,'A',length); + bigPublish[length] = 'B'; + memcpy(bigPublish,publish,16); + shimClient.respond(bigPublish,length); + + rc = client.loop(); + + IS_TRUE(rc); + + IS_FALSE(callback_called); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_drop_invalid_remaining_length_message() { + IT("drops invalid remaining length message"); + reset_callback(); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0x92,0x92,0x92,0x92,0x01,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + shimClient.respond(publish,20); + + rc = client.loop(); + + IS_FALSE(rc); + + IS_FALSE(callback_called); + + IS_FALSE(shimClient.error()); + + END_IT +} + + +int test_receive_oversized_stream_message() { + IT("drops an oversized message"); + reset_callback(); + + Stream stream; + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient, stream); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + int length = MQTT_MAX_PACKET_SIZE+1; + byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + + byte bigPublish[length]; + memset(bigPublish,'A',length); + bigPublish[length] = 'B'; + memcpy(bigPublish,publish,16); + + shimClient.respond(bigPublish,length); + stream.expect(bigPublish+9,length-9); + + rc = client.loop(); + + IS_TRUE(rc); + + IS_TRUE(callback_called); + IS_TRUE(strcmp(lastTopic,"topic")==0); + IS_TRUE(lastLength == length-9); + + IS_FALSE(stream.error()); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_receive_qos1() { + IT("receives a qos1 message"); + reset_callback(); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x32,0x10,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x12,0x34,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + shimClient.respond(publish,18); + + byte puback[] = {0x40,0x2,0x12,0x34}; + shimClient.expect(puback,4); + + rc = client.loop(); + + IS_TRUE(rc); + + IS_TRUE(callback_called); + IS_TRUE(strcmp(lastTopic,"topic")==0); + IS_TRUE(memcmp(lastPayload,"payload",7)==0); + IS_TRUE(lastLength == 7); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int main() +{ + SUITE("Receive"); + test_receive_callback(); + test_receive_stream(); + test_receive_max_sized_message(); + test_drop_invalid_remaining_length_message(); + test_receive_oversized_message(); + test_receive_oversized_stream_message(); + test_receive_qos1(); + + FINISH +} diff --git a/dep/pubsubclient-2.7/tests/src/subscribe_spec.cpp b/dep/pubsubclient-2.7/tests/src/subscribe_spec.cpp new file mode 100644 index 0000000..a419823 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/src/subscribe_spec.cpp @@ -0,0 +1,177 @@ +#include "PubSubClient.h" +#include "ShimClient.h" +#include "Buffer.h" +#include "BDDTest.h" +#include "trace.h" + + +byte server[] = { 172, 16, 0, 2 }; + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + +int test_subscribe_no_qos() { + IT("subscribe without qos defaults to 0"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte subscribe[] = { 0x82,0xa,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x0 }; + shimClient.expect(subscribe,12); + byte suback[] = { 0x90,0x3,0x0,0x2,0x0 }; + shimClient.respond(suback,5); + + rc = client.subscribe((char*)"topic"); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_subscribe_qos_1() { + IT("subscribes qos 1"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte subscribe[] = { 0x82,0xa,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1 }; + shimClient.expect(subscribe,12); + byte suback[] = { 0x90,0x3,0x0,0x2,0x1 }; + shimClient.respond(suback,5); + + rc = client.subscribe((char*)"topic",1); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_subscribe_not_connected() { + IT("subscribe fails when not connected"); + ShimClient shimClient; + + PubSubClient client(server, 1883, callback, shimClient); + + int rc = client.subscribe((char*)"topic"); + IS_FALSE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_subscribe_invalid_qos() { + IT("subscribe fails with invalid qos values"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + rc = client.subscribe((char*)"topic",2); + IS_FALSE(rc); + rc = client.subscribe((char*)"topic",254); + IS_FALSE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_subscribe_too_long() { + IT("subscribe fails with too long topic"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + // max length should be allowed + // 0 1 2 3 4 5 6 7 8 9 0 1 2 + rc = client.subscribe((char*)"12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); + IS_TRUE(rc); + + // 0 1 2 3 4 5 6 7 8 9 0 1 2 + rc = client.subscribe((char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); + IS_FALSE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + + +int test_unsubscribe() { + IT("unsubscribes"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte unsubscribe[] = { 0xA2,0x9,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63 }; + shimClient.expect(unsubscribe,12); + byte unsuback[] = { 0xB0,0x2,0x0,0x2 }; + shimClient.respond(unsuback,4); + + rc = client.unsubscribe((char*)"topic"); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_unsubscribe_not_connected() { + IT("unsubscribe fails when not connected"); + ShimClient shimClient; + + PubSubClient client(server, 1883, callback, shimClient); + + int rc = client.unsubscribe((char*)"topic"); + IS_FALSE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int main() +{ + SUITE("Subscribe"); + test_subscribe_no_qos(); + test_subscribe_qos_1(); + test_subscribe_not_connected(); + test_subscribe_invalid_qos(); + test_subscribe_too_long(); + test_unsubscribe(); + test_unsubscribe_not_connected(); + FINISH +} diff --git a/dep/pubsubclient-2.7/tests/testcases/__init__.py b/dep/pubsubclient-2.7/tests/testcases/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dep/pubsubclient-2.7/tests/testcases/mqtt_basic.py b/dep/pubsubclient-2.7/tests/testcases/mqtt_basic.py new file mode 100644 index 0000000..f23ef71 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/testcases/mqtt_basic.py @@ -0,0 +1,39 @@ +import unittest +import settings +import time +import mosquitto + + +def on_message(mosq, obj, msg): + obj.message_queue.append(msg) + + +class mqtt_basic(unittest.TestCase): + + message_queue = [] + + @classmethod + def setUpClass(self): + self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True, obj=self) + self.client.connect(settings.server_ip) + self.client.on_message = on_message + self.client.subscribe("outTopic", 0) + + @classmethod + def tearDownClass(self): + self.client.disconnect() + + def test_one(self): + i = 30 + while len(self.message_queue) == 0 and i > 0: + self.client.loop() + time.sleep(0.5) + i -= 1 + self.assertTrue(i > 0, "message receive timed-out") + self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received") + msg = self.message_queue[0] + self.assertEqual(msg.mid, 0, "message id not 0") + self.assertEqual(msg.topic, "outTopic", "message topic incorrect") + self.assertEqual(msg.payload, "hello world") + self.assertEqual(msg.qos, 0, "message qos not 0") + self.assertEqual(msg.retain, False, "message retain flag incorrect") diff --git a/dep/pubsubclient-2.7/tests/testcases/mqtt_publish_in_callback.py b/dep/pubsubclient-2.7/tests/testcases/mqtt_publish_in_callback.py new file mode 100644 index 0000000..45b0a85 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/testcases/mqtt_publish_in_callback.py @@ -0,0 +1,59 @@ +import unittest +import settings +import time +import mosquitto + + +def on_message(mosq, obj, msg): + obj.message_queue.append(msg) + + +class mqtt_publish_in_callback(unittest.TestCase): + + message_queue = [] + + @classmethod + def setUpClass(self): + self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True, obj=self) + self.client.connect(settings.server_ip) + self.client.on_message = on_message + self.client.subscribe("outTopic", 0) + + @classmethod + def tearDownClass(self): + self.client.disconnect() + + def test_connect(self): + i = 30 + while len(self.message_queue) == 0 and i > 0: + self.client.loop() + time.sleep(0.5) + i -= 1 + self.assertTrue(i > 0, "message receive timed-out") + self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received") + msg = self.message_queue.pop(0) + self.assertEqual(msg.mid, 0, "message id not 0") + self.assertEqual(msg.topic, "outTopic", "message topic incorrect") + self.assertEqual(msg.payload, "hello world") + self.assertEqual(msg.qos, 0, "message qos not 0") + self.assertEqual(msg.retain, False, "message retain flag incorrect") + + def test_publish(self): + self.assertEqual(len(self.message_queue), 0, "message queue not empty") + payload = "abcdefghij" + self.client.publish("inTopic", payload) + + i = 30 + while len(self.message_queue) == 0 and i > 0: + self.client.loop() + time.sleep(0.5) + i -= 1 + + self.assertTrue(i > 0, "message receive timed-out") + self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received") + msg = self.message_queue.pop(0) + self.assertEqual(msg.mid, 0, "message id not 0") + self.assertEqual(msg.topic, "outTopic", "message topic incorrect") + self.assertEqual(msg.payload, payload) + self.assertEqual(msg.qos, 0, "message qos not 0") + self.assertEqual(msg.retain, False, "message retain flag incorrect") diff --git a/dep/pubsubclient-2.7/tests/testcases/settings.py b/dep/pubsubclient-2.7/tests/testcases/settings.py new file mode 100644 index 0000000..4ad8719 --- /dev/null +++ b/dep/pubsubclient-2.7/tests/testcases/settings.py @@ -0,0 +1,2 @@ +server_ip = "172.16.0.2" +arduino_ip = "172.16.0.100" diff --git a/dep/pubsubclient-2.7/tests/testsuite.py b/dep/pubsubclient-2.7/tests/testsuite.py new file mode 100644 index 0000000..788fc5d --- /dev/null +++ b/dep/pubsubclient-2.7/tests/testsuite.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python +import os +import os.path +import sys +import shutil +from subprocess import call +import importlib +import unittest +import re + +from testcases import settings + + +class Workspace(object): + + def __init__(self): + self.root_dir = os.getcwd() + self.build_dir = os.path.join(self.root_dir, "tmpbin") + self.log_dir = os.path.join(self.root_dir, "logs") + self.tests_dir = os.path.join(self.root_dir, "testcases") + self.examples_dir = os.path.join(self.root_dir, "../PubSubClient/examples") + self.examples = [] + self.tests = [] + if not os.path.isdir("../PubSubClient"): + raise Exception("Cannot find PubSubClient library") + try: + return __import__('ino') + except ImportError: + raise Exception("ino tool not installed") + + def init(self): + if os.path.isdir(self.build_dir): + shutil.rmtree(self.build_dir) + os.mkdir(self.build_dir) + if os.path.isdir(self.log_dir): + shutil.rmtree(self.log_dir) + os.mkdir(self.log_dir) + + os.chdir(self.build_dir) + call(["ino", "init"]) + + shutil.copytree("../../PubSubClient", "lib/PubSubClient") + + filenames = [] + for root, dirs, files in os.walk(self.examples_dir): + filenames += [os.path.join(root, f) for f in files if f.endswith(".ino")] + filenames.sort() + for e in filenames: + self.examples.append(Sketch(self, e)) + + filenames = [] + for root, dirs, files in os.walk(self.tests_dir): + filenames += [os.path.join(root, f) for f in files if f.endswith(".ino")] + filenames.sort() + for e in filenames: + self.tests.append(Sketch(self, e)) + + def clean(self): + shutil.rmtree(self.build_dir) + + +class Sketch(object): + def __init__(self, wksp, fn): + self.w = wksp + self.filename = fn + self.basename = os.path.basename(self.filename) + self.build_log = os.path.join(self.w.log_dir, "%s.log" % (os.path.basename(self.filename),)) + self.build_err_log = os.path.join(self.w.log_dir, "%s.err.log" % (os.path.basename(self.filename),)) + self.build_upload_log = os.path.join(self.w.log_dir, "%s.upload.log" % (os.path.basename(self.filename),)) + + def build(self): + sys.stdout.write(" Build: ") + sys.stdout.flush() + + # Copy sketch over, replacing IP addresses as necessary + fin = open(self.filename, "r") + lines = fin.readlines() + fin.close() + fout = open(os.path.join(self.w.build_dir, "src", "sketch.ino"), "w") + for l in lines: + if re.match(r"^byte server\[\] = {", l): + fout.write("byte server[] = { %s };\n" % (settings.server_ip.replace(".", ", "),)) + elif re.match(r"^byte ip\[\] = {", l): + fout.write("byte ip[] = { %s };\n" % (settings.arduino_ip.replace(".", ", "),)) + else: + fout.write(l) + fout.flush() + fout.close() + + # Run build + fout = open(self.build_log, "w") + ferr = open(self.build_err_log, "w") + rc = call(["ino", "build"], stdout=fout, stderr=ferr) + fout.close() + ferr.close() + if rc == 0: + sys.stdout.write("pass") + sys.stdout.write("\n") + return True + else: + sys.stdout.write("fail") + sys.stdout.write("\n") + with open(self.build_err_log) as f: + for line in f: + print(" " + line) + return False + + def upload(self): + sys.stdout.write(" Upload: ") + sys.stdout.flush() + fout = open(self.build_upload_log, "w") + rc = call(["ino", "upload"], stdout=fout, stderr=fout) + fout.close() + if rc == 0: + sys.stdout.write("pass") + sys.stdout.write("\n") + return True + else: + sys.stdout.write("fail") + sys.stdout.write("\n") + with open(self.build_upload_log) as f: + for line in f: + print(" " + line) + return False + + def test(self): + # import the matching test case, if it exists + try: + basename = os.path.basename(self.filename)[:-4] + i = importlib.import_module("testcases." + basename) + except: + sys.stdout.write(" Test: no tests found") + sys.stdout.write("\n") + return + c = getattr(i, basename) + + testmethods = [m for m in dir(c) if m.startswith("test_")] + testmethods.sort() + tests = [] + for m in testmethods: + tests.append(c(m)) + + result = unittest.TestResult() + c.setUpClass() + if self.upload(): + sys.stdout.write(" Test: ") + sys.stdout.flush() + for t in tests: + t.run(result) + print(str(result.testsRun - len(result.failures) - len(result.errors)) + "/" + str(result.testsRun)) + if not result.wasSuccessful(): + if len(result.failures) > 0: + for f in result.failures: + print("-- " + str(f[0])) + print(f[1]) + if len(result.errors) > 0: + print(" Errors:") + for f in result.errors: + print("-- " + str(f[0])) + print(f[1]) + c.tearDownClass() + + +if __name__ == '__main__': + run_tests = True + + w = Workspace() + w.init() + + for e in w.examples: + print("--------------------------------------") + print("[" + e.basename + "]") + if e.build() and run_tests: + e.test() + for e in w.tests: + print("--------------------------------------") + print("[" + e.basename + "]") + if e.build() and run_tests: + e.test() + + w.clean() diff --git a/esp8266-geigercounter.ino b/esp8266-geigercounter.ino index ee2e021..8ba54a5 100644 --- a/esp8266-geigercounter.ino +++ b/esp8266-geigercounter.ino @@ -1,85 +1,270 @@ #include -#include -#include #include -#include +#include +#include +#include + +#include "dep/pubsubclient-2.7/src/PubSubClient.cpp" +#include "dep/WiFiManager-0.15.0/WiFiManager.cpp" +#include "dep/SimpleTimer-schinken/SimpleTimer.cpp" + #include "settings.h" +#ifdef USE_HA_AUTODISCOVERY + #define FIRMWARE_PREFIX "esp8266-geigercounter" + char MQTT_TOPIC_LAST_WILL[128]; + char MQTT_TOPIC_CPM_MEASUREMENT[128]; + char MQTT_TOPIC_USV_MEASUREMENT[128]; +#endif + WiFiClient wifiClient; PubSubClient mqttClient; SoftwareSerial geigerCounterSerial(PIN_UART_RX, PIN_UART_TX); SimpleTimer timer; +uint8_t mqttRetryCounter = 0; String serialInput = ""; char serialInputHelper[RECV_LINE_SIZE]; int lastCPM = 0, currentCPM = 0; float lastuSv = 0, currentuSv = 0; +char hostname[16]; + void setup() { - -// power up wait - delay(3000); - Serial.begin(115200); + Serial.println("\n"); + Serial.println("Hello from esp8266-geigercounter"); + geigerCounterSerial.begin(BAUD_GEIGERCOUNTER); - delay(10); - WiFi.hostname(WIFI_HOSTNAME); - WiFi.mode(WIFI_STA); - WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + // Power up wait + delay(2000); - while (WiFi.status() != WL_CONNECTED) { - Serial.print("."); - delay(500); - } + WiFiManager wifiManager; + int32_t chipid = ESP.getChipId(); + Serial.print("MQTT_MAX_PACKET_SIZE: "); + Serial.println(MQTT_MAX_PACKET_SIZE); + + +#ifdef HOSTNAME + hostname = HOSTNAME; +#else + snprintf(hostname, 24, "GEIGERCTR-%X", chipid); +#endif + +#ifdef USE_HA_AUTODISCOVERY + snprintf(MQTT_TOPIC_LAST_WILL, 127, "%s/%s/presence", FIRMWARE_PREFIX, hostname); + snprintf(MQTT_TOPIC_CPM_MEASUREMENT, 127, "%s/%s/%s_%s/state", FIRMWARE_PREFIX, hostname, hostname, "cpm"); + snprintf(MQTT_TOPIC_USV_MEASUREMENT, 127, "%s/%s/%s_%s/state", FIRMWARE_PREFIX, hostname, hostname, "uSv"); +#endif + +#ifdef CONF_WIFI_PASSWORD + wifiManager.autoConnect(hostname, CONF_WIFI_PASSWORD); +#else + wifiManager.autoConnect(hostname); +#endif + + WiFi.hostname(hostname); mqttClient.setClient(wifiClient); mqttClient.setServer(MQTT_HOST, 1883); - - ArduinoOTA.setHostname(WIFI_HOSTNAME); + + ArduinoOTA.setHostname(hostname); ArduinoOTA.setPassword(OTA_PASSWORD); ArduinoOTA.begin(); + Serial.print("Hostname: "); + Serial.println(hostname); + + Serial.println("-- Current GPIO Configuration --"); + Serial.print("PIN_UART_RX: "); + Serial.println(PIN_UART_RX); + + mqttConnect(); timer.setInterval(UPDATE_INTERVAL_SECONDS * 1000L, updateRadiationValues); } +void mqttConnect() { + while (!mqttClient.connected()) { + + bool mqttConnected = false; + if (MQTT_USERNAME && MQTT_PASSWORD) { + mqttConnected = mqttClient.connect(hostname, MQTT_USERNAME, MQTT_PASSWORD, MQTT_TOPIC_LAST_WILL, 1, true, MQTT_LAST_WILL_PAYLOAD_DISCONNECTED); + } else { + mqttConnected = mqttClient.connect(hostname, MQTT_TOPIC_LAST_WILL, 1, true, MQTT_LAST_WILL_PAYLOAD_DISCONNECTED); + } + + if (mqttConnected) { + Serial.println("Connected to MQTT Broker"); + mqttClient.publish(MQTT_TOPIC_LAST_WILL, MQTT_LAST_WILL_PAYLOAD_CONNECTED, true); + mqttRetryCounter = 0; + + #ifdef USE_HA_AUTODISCOVERY + setupHAAutodiscovery(); + #endif + + } else { + Serial.println("Failed to connect to MQTT Broker"); + + if (mqttRetryCounter++ > MQTT_MAX_CONNECT_RETRY) { + Serial.println("Restarting uC"); + ESP.restart(); + } + + delay(2000); + } + } +} + + +#ifdef USE_HA_AUTODISCOVERY +#define AUTOCONFIG_PAYLOAD_TPL_USV "{\ +\"stat_t\":\"%s/%s/%s_uSv/state\",\ +\"avty_t\":\"%s/%s/presence\",\ +\"unique_id\":\"%s_uSv\",\ +\"name\":\"%s uSv\",\ +\"unit_of_meas\":\"µSv/h\",\ +\"dev\": {\ +\"identifiers\":\"%s\",\ +\"name\":\"%s\",\ +\"manufacturer\":\"MightyOhm LLC\",\ +\"model\":\"Geiger Counter\"\ +}\ +}" +#define AUTOCONFIG_PAYLOAD_TPL_CPM "{\ +\"stat_t\":\"%s/%s/%s_cpm/state\",\ +\"avty_t\":\"%s/%s/presence\",\ +\"unique_id\":\"%s_cpm\",\ +\"name\":\"%s CPM\",\ +\"unit_of_meas\":\"CPM\",\ +\"dev\": {\ +\"identifiers\":\"%s\",\ +\"name\":\"%s\",\ +\"manufacturer\":\"MightyOhm LLC\",\ +\"model\":\"Geiger Counter\"\ +}\ +}" + +void setupHAAutodiscovery() { + char autoconfig_topic_cpm[128]; + char autoconfig_payload_cpm[1024]; + char autoconfig_topic_usv[128]; + char autoconfig_payload_usv[1024]; + + snprintf( + autoconfig_topic_cpm, + 127, + "%s/sensor/%s/%s_cpm/config", + HA_DISCOVERY_PREFIX, + hostname, + hostname + ); + + snprintf( + autoconfig_topic_usv, + 127, + "%s/sensor/%s/%s_usv/config", + HA_DISCOVERY_PREFIX, + hostname, + hostname + ); + + snprintf( + autoconfig_payload_cpm, + 1023, + + AUTOCONFIG_PAYLOAD_TPL_CPM, + + FIRMWARE_PREFIX, + hostname, + hostname, + FIRMWARE_PREFIX, + hostname, + hostname, + hostname, + hostname, + hostname + ); + + snprintf( + autoconfig_payload_usv, + 1023, + + AUTOCONFIG_PAYLOAD_TPL_USV, + + FIRMWARE_PREFIX, + hostname, + hostname, + FIRMWARE_PREFIX, + hostname, + hostname, + hostname, + hostname, + hostname + ); + + if( + mqttClient.publish(autoconfig_topic_cpm, autoconfig_payload_cpm, true) && + mqttClient.publish(autoconfig_topic_usv, autoconfig_payload_usv, true) + ){ + Serial.println("Autoconf publish successful"); + } else { + Serial.println("Autoconf publish failed. Is MQTT_MAX_PACKET_SIZE large enough?"); + } +} +#endif + +void loop() { + timer.run(); + mqttConnect(); + mqttClient.loop(); + + if (geigerCounterSerial.available()) { + char in = (char) geigerCounterSerial.read(); + + serialInput += in; + + if (in == '\n') { + serialInput.toCharArray(serialInputHelper, RECV_LINE_SIZE); + parseReceivedLine(serialInputHelper); + + serialInput = ""; + } + + // Just in case the buffer gets to big, start from scratch + if (serialInput.length() > RECV_LINE_SIZE + 10) { + serialInput = ""; + } + + Serial.write(in); + } + + ArduinoOTA.handle(); +} + void updateRadiationValues() { - - char tmp[8]; if (currentCPM != lastCPM) { String(currentCPM).toCharArray(tmp, 8); - Serial.print("Sending CPM"); + Serial.print("Sending CPM: "); Serial.println(tmp); - mqttClient.publish(MQTT_TOPIC_CPM, tmp, true); + mqttClient.publish(MQTT_TOPIC_CPM_MEASUREMENT, tmp, true); } if (currentuSv != lastuSv) { String(currentuSv).toCharArray(tmp, 8); - Serial.print("Sending uSv"); + Serial.print("Sending uSv: "); Serial.println(tmp); - mqttClient.publish(MQTT_TOPIC_USV, tmp, true); + mqttClient.publish(MQTT_TOPIC_USV_MEASUREMENT, tmp, true); } lastCPM = currentCPM; lastuSv = currentuSv; } -void connectMqtt() { - - while (!mqttClient.connected()) { - if (mqttClient.connect(WIFI_HOSTNAME, MQTT_TOPIC_LAST_WILL, 1, true, "disconnected")) { - mqttClient.publish(MQTT_TOPIC_LAST_WILL, "connected", true); - } else { - delay(1000); - } - } - -} - void parseReceivedLine(char* input) { char segment = 0; @@ -121,33 +306,4 @@ void parseReceivedLine(char* input) { currentuSv = uSv; currentCPM = cpm; -} - -void loop() { - - connectMqtt(); - timer.run(); - mqttClient.loop(); - - if (geigerCounterSerial.available()) { - char in = (char) geigerCounterSerial.read(); - - serialInput += in; - - if (in == '\n') { - serialInput.toCharArray(serialInputHelper, RECV_LINE_SIZE); - parseReceivedLine(serialInputHelper); - - serialInput = ""; - } - - // Just in case the buffer gets to big, start from scratch - if (serialInput.length() > RECV_LINE_SIZE + 10) { - serialInput = ""; - } - - Serial.write(in); - } - - ArduinoOTA.handle(); -} +} \ No newline at end of file diff --git a/settings.h.example b/settings.h.example index 5ea0d47..4c70919 100644 --- a/settings.h.example +++ b/settings.h.example @@ -11,19 +11,37 @@ #define PIN_UART_RX 0 // 4 #define PIN_UART_TX 13 // UNUSED -#define UPDATE_INTERVAL_SECONDS 300L +#define UPDATE_INTERVAL_SECONDS 60L #define BAUD_GEIGERCOUNTER 9600 -const char* HOSTNAME = "ESP-GeigerCounter"; -const char* WIFI_SSID = "----"; -const char* WIFI_PASSWORD = "----"; +//const char* HOSTNAME = "ESP-GeigerCounter"; //Optional const char* OTA_PASSWORD = "foobar"; -#define MQTT_TOPIC_CPM "sensor/radiation/cpm" -#define MQTT_TOPIC_USV "sensor/radiation/uSv" -#define MQTT_TOPIC_LAST_WILL "state/sensor/geigercounter" +#define MQTT_HOST "mqtt.core.bckspc.de" -const char* mqttHost = "mqtt.core.bckspc.de"; -const char* delimiter = ", "; +//If you don't want to use home-assistant autodiscovery comment this out +#define USE_HA_AUTODISCOVERY + +#ifdef USE_HA_AUTODISCOVERY + #define HA_DISCOVERY_PREFIX "homeassistant" + const char* MQTT_LAST_WILL_PAYLOAD_CONNECTED = "online"; + const char* MQTT_LAST_WILL_PAYLOAD_DISCONNECTED = "offline"; +#else + //If you're not using HA Autodiscovery, you can specify your topics here + + const char* MQTT_TOPIC_CPM_MEASUREMENT = "sensor/radiation/cpm"; + const char* MQTT_TOPIC_USV_MEASUREMENT = "sensor/radiation/uSv"; + const char* MQTT_TOPIC_LAST_WILL = "sensor/radiation/will"; + const char* MQTT_LAST_WILL_PAYLOAD_CONNECTED = "connected"; + const char* MQTT_LAST_WILL_PAYLOAD_DISCONNECTED = "disconnected"; +#endif + + +const char* MQTT_USERNAME = NULL; +const char* MQTT_PASSWORD = NULL; +const uint8_t MQTT_MAX_CONNECT_RETRY = 42; + + +const char* delimiter = ", "; \ No newline at end of file