Major Rewrite

This commit is contained in:
Hypfer 2020-09-26 17:34:24 +02:00
parent 0c10ef4ba9
commit 8a47d2a644
56 changed files with 286 additions and 5678 deletions

View file

@ -8,11 +8,27 @@ Here's some code and schematic to connect your mightyohm.com geiger counter to t
## compile
To compile this code, just clone or download the master branch as a zip and ramp up your arduino library. For this code to work, you need the SimpleTimer-library (http://playground.arduino.cc/Code/SimpleTimer#Download) and the ESP8266 added to your Arduino-IDE as a board (https://github.com/esp8266/Arduino, see install instructions)
The firmware can be built and flashed using the Arduino IDE.
For this, you will need to add ESP8266 support to it by [using the Boards Manager](https://github.com/esp8266/Arduino#installing-with-boards-manager).
Furthermore, you will also need to install the following libraries using the Library Manager:
* ArduinoJSON 6.10.1
* PubSubClient 2.8.0
* WiFiManager 0.15.0
## settings
You need to adjust the settings in the settings.h file.
Since this project is using the WifiManager library, the ESP8266 will open up a WiFi Access Point for its initial configuration
or if it is unable to connect to the previously configured WiFi.
The library pretends that said WiFi AP requires a captive portal which triggers a notification on recent android phones.
Simply connect to the AP with your phone, tap the "Login required"-notification and you should be able to configure everything.
## usage
Since we're using the Home Assistant Autodiscovery feature, everything should just work™.
## license and credits

37
configFileHandling.ino Normal file
View file

@ -0,0 +1,37 @@
void saveConfig() {
DynamicJsonDocument json(512);
json["mqtt_server"] = mqtt_server;
json["username"] = username;
json["password"] = password;
File configFile = SPIFFS.open("/config.json", "w");
if (!configFile) {
return;
}
serializeJson(json, configFile);
configFile.close();
}
void loadConfig() {
if (SPIFFS.begin()) {
if (SPIFFS.exists("/config.json")) {
File configFile = SPIFFS.open("/config.json", "r");
if (configFile) {
const size_t size = configFile.size();
std::unique_ptr<char[]> buf(new char[size]);
configFile.readBytes(buf.get(), size);
DynamicJsonDocument json(512);
if (DeserializationError::Ok == deserializeJson(json, buf.get())) {
strcpy(mqtt_server, json["mqtt_server"]);
strcpy(username, json["username"]);
strcpy(password, json["password"]);
}
}
}
}
}

View file

@ -1,7 +0,0 @@
# 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

View file

@ -1,241 +0,0 @@
/*
* 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;
}

View file

@ -1,132 +0,0 @@
/*
* 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 <functional>
#endif // __AVR__
#if defined(ARDUINO) && ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
#ifndef __AVR__
typedef std::function<void(void)> 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

View file

@ -1,22 +0,0 @@
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.

View file

@ -1,847 +0,0 @@
/**************************************************************
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("<h1>"));
page += _apName;
page += String(F("</h1>"));
page += String(F("<h3>WiFiManager</h3>"));
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 += "<br/>";
}
}
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 += "<br/>";
}
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 += "<br/>";
}
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("<dl>");
page += F("<dt>Chip ID</dt><dd>");
page += ESP.getChipId();
page += F("</dd>");
page += F("<dt>Flash Chip ID</dt><dd>");
page += ESP.getFlashChipId();
page += F("</dd>");
page += F("<dt>IDE Flash Size</dt><dd>");
page += ESP.getFlashChipSize();
page += F(" bytes</dd>");
page += F("<dt>Real Flash Size</dt><dd>");
page += ESP.getFlashChipRealSize();
page += F(" bytes</dd>");
page += F("<dt>Soft AP IP</dt><dd>");
page += WiFi.softAPIP().toString();
page += F("</dd>");
page += F("<dt>Soft AP MAC</dt><dd>");
page += WiFi.softAPmacAddress();
page += F("</dd>");
page += F("<dt>Station MAC</dt><dd>");
page += WiFi.macAddress();
page += F("</dd>");
page += F("</dl>");
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 <typename Generic>
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;
}

View file

@ -1,203 +0,0 @@
/**************************************************************
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 <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <memory>
extern "C" {
#include "user_interface.h"
}
const char HTTP_HEADER[] PROGMEM = "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/><title>{v}</title>";
const char HTTP_STYLE[] PROGMEM = "<style>.c{text-align: center;} div,input{padding:5px;font-size:1em;} input{width:95%;} body{text-align: center;font-family:verdana;} button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;} .q{float: right;width: 64px;text-align: right;} .l{background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==\") no-repeat left center;background-size: 1em;}</style>";
const char HTTP_SCRIPT[] PROGMEM = "<script>function c(l){document.getElementById('s').value=l.innerText||l.textContent;document.getElementById('p').focus();}</script>";
const char HTTP_HEADER_END[] PROGMEM = "</head><body><div style='text-align:left;display:inline-block;min-width:260px;'>";
const char HTTP_PORTAL_OPTIONS[] PROGMEM = "<form action=\"/wifi\" method=\"get\"><button>Configure WiFi</button></form><br/><form action=\"/0wifi\" method=\"get\"><button>Configure WiFi (No Scan)</button></form><br/><form action=\"/i\" method=\"get\"><button>Info</button></form><br/><form action=\"/r\" method=\"post\"><button>Reset</button></form>";
const char HTTP_ITEM[] PROGMEM = "<div><a href='#p' onclick='c(this)'>{v}</a>&nbsp;<span class='q {i}'>{r}%</span></div>";
const char HTTP_FORM_START[] PROGMEM = "<form method='get' action='wifisave'><input id='s' name='s' length=32 placeholder='SSID'><br/><input id='p' name='p' length=64 type='password' placeholder='password'><br/>";
const char HTTP_FORM_PARAM[] PROGMEM = "<br/><input id='{i}' name='{n}' maxlength={l} placeholder='{p}' value='{v}' {c}>";
const char HTTP_FORM_END[] PROGMEM = "<br/><button type='submit'>save</button></form>";
const char HTTP_SCAN_LINK[] PROGMEM = "<br/><div class=\"c\"><a href=\"/wifi\">Scan</a></div>";
const char HTTP_SAVED[] PROGMEM = "<div>Credentials Saved<br />Trying to connect ESP to network.<br />If it fails reconnect to AP to try again</div>";
const char HTTP_END[] PROGMEM = "</div></body></html>";
#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> dnsServer;
std::unique_ptr<ESP8266WebServer> server;
//const int WM_DONE = 0;
//const int WM_WAIT = 10;
//const String HTTP_HEADER = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/><title>{v}</title>";
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 <typename Generic>
void DEBUG_WM(Generic text);
template <class T>
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

View file

@ -1,13 +0,0 @@
{
"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"
}

View file

@ -1,9 +0,0 @@
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

View file

@ -1 +0,0 @@
tests/bin

View file

@ -1,7 +0,0 @@
sudo: false
language: cpp
compiler:
- g++
script: cd tests && make && make test
os:
- linux

View file

@ -1,76 +0,0 @@
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

View file

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

View file

@ -1,43 +0,0 @@
/*
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 <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// 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();
}

View file

@ -1,77 +0,0 @@
/*
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 <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// 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<length;i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
EthernetClient ethClient;
PubSubClient client(ethClient);
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("arduinoClient")) {
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()
{
Serial.begin(57600);
client.setServer(server, 1883);
client.setCallback(callback);
Ethernet.begin(mac, ip);
// Allow the hardware to sort itself out
delay(1500);
}
void loop()
{
if (!client.connected()) {
reconnect();
}
client.loop();
}

View file

@ -1,132 +0,0 @@
/*
Basic ESP8266 MQTT example
This sketch demonstrates the capabilities of the pubsub library in combination
with the ESP8266 board/library.
It connects to an MQTT server then:
- publishes "hello world" to the topic "outTopic" every two seconds
- subscribes to the topic "inTopic", printing out any messages
it receives. NB - it assumes the received payloads are strings not binary
- If the first character of the topic "inTopic" is an 1, switch ON the ESP Led,
else switch it off
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 <ESP8266WiFi.h>
#include <PubSubClient.h>
// 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);
}
}

View file

@ -1,179 +0,0 @@
/*
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 <ESP8266WiFi.h>
#include <PubSubClient.h>
// 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();
}

View file

@ -1,60 +0,0 @@
/*
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 <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// 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();
}

View file

@ -1,67 +0,0 @@
/*
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 <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// 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();
}
}

View file

@ -1,57 +0,0 @@
/*
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 <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
#include <SRAM.h>
// 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<length; i++) {
Serial.write(sram.read());
}
Serial.println();
// Reset position for the next message to be stored
sram.seek(1);
}
EthernetClient ethClient;
PubSubClient client(server, 1883, callback, ethClient, sram);
void setup()
{
Ethernet.begin(mac, ip);
if (client.connect("arduinoClient")) {
client.publish("outTopic","hello world");
client.subscribe("inTopic");
}
sram.begin();
sram.seek(1);
Serial.begin(9600);
}
void loop()
{
client.loop();
}

View file

@ -1,33 +0,0 @@
#######################################
# Syntax Coloring Map For PubSubClient
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
PubSubClient KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
connect KEYWORD2
disconnect KEYWORD2
publish KEYWORD2
publish_P KEYWORD2
beginPublish KEYWORD2
endPublish KEYWORD2
write KEYWORD2
subscribe KEYWORD2
unsubscribe KEYWORD2
loop KEYWORD2
connected KEYWORD2
setServer KEYWORD2
setCallback KEYWORD2
setClient KEYWORD2
setStream KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View file

@ -1,653 +0,0 @@
/*
PubSubClient.cpp - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#include "PubSubClient.h"
#include "Arduino.h"
PubSubClient::PubSubClient() {
this->_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<MQTT_HEADER_VERSION_LENGTH;j++) {
buffer[length++] = d[j];
}
uint8_t v;
if (willTopic) {
v = 0x04|(willQos<<3)|(willRetain<<5);
} else {
v = 0x00;
}
if (cleanSession) {
v = v|0x02;
}
if(user != NULL) {
v = v|0x80;
if(pass != NULL) {
v = v|(0x80>>1);
}
}
buffer[length++] = v;
buffer[length++] = ((MQTT_KEEPALIVE) >> 8);
buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF);
CHECK_STRING_LENGTH(length,id)
length = writeString(id,buffer,length);
if (willTopic) {
CHECK_STRING_LENGTH(length,willTopic)
length = writeString(willTopic,buffer,length);
CHECK_STRING_LENGTH(length,willMessage)
length = writeString(willMessage,buffer,length);
}
if(user != NULL) {
CHECK_STRING_LENGTH(length,user)
length = writeString(user,buffer,length);
if(pass != NULL) {
CHECK_STRING_LENGTH(length,pass)
length = writeString(pass,buffer,length);
}
}
write(MQTTCONNECT,buffer,length-MQTT_MAX_HEADER_SIZE);
lastInActivity = lastOutActivity = millis();
while (!_client->available()) {
unsigned long t = millis();
if (t-lastInActivity >= ((int32_t) MQTT_SOCKET_TIMEOUT*1000UL)) {
_state = MQTT_CONNECTION_TIMEOUT;
_client->stop();
return false;
}
}
uint8_t llen;
uint16_t len = readPacket(&llen);
if (len == 4) {
if (buffer[3] == 0) {
lastInActivity = millis();
pingOutstanding = false;
_state = MQTT_CONNECTED;
return true;
} else {
_state = buffer[3];
}
}
_client->stop();
} else {
_state = MQTT_CONNECT_FAILED;
}
return false;
}
return true;
}
// reads a byte into result
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;i<length;i++) {
if(!readByte(&digit)) return 0;
if (this->stream) {
if (isPublish && len-*lengthLength-2>skip) {
this->stream->write(digit);
}
}
if (len < MQTT_MAX_PACKET_SIZE) {
buffer[len] = digit;
}
len++;
}
if (!this->stream && len > MQTT_MAX_PACKET_SIZE) {
len = 0; // This will cause the packet to be ignored.
}
return len;
}
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<plength;i++) {
buffer[length++] = payload[i];
}
uint8_t header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
return write(header,buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false;
}
boolean PubSubClient::publish_P(const char* topic, const char* payload, boolean retained) {
return publish_P(topic, (const uint8_t*)payload, strlen(payload), retained);
}
boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
uint8_t llen = 0;
uint8_t digit;
unsigned int rc = 0;
uint16_t tlen;
unsigned int pos = 0;
unsigned int i;
uint8_t header;
unsigned int len;
if (!connected()) {
return false;
}
tlen = strlen(topic);
header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
buffer[pos++] = header;
len = plength + 2 + tlen;
do {
digit = len % 128;
len = len / 128;
if (len > 0) {
digit |= 0x80;
}
buffer[pos++] = digit;
llen++;
} while(len>0);
pos = writeString(topic,buffer,pos);
rc += _client->write(buffer,pos);
for (i=0;i<plength;i++) {
rc += _client->write((char)pgm_read_byte_near(payload + i));
}
lastOutActivity = millis();
return rc == tlen + 4 + plength;
}
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<llen;i++) {
buf[MQTT_MAX_HEADER_SIZE-llen+i] = lenBuf[i];
}
return llen+1; // Full header size is variable length bit plus the 1-byte fixed header
}
boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
uint16_t rc;
uint8_t hlen = buildHeader(header, buf, length);
#ifdef MQTT_MAX_TRANSFER_SIZE
uint8_t* writeBuf = buf+(MQTT_MAX_HEADER_SIZE-hlen);
uint16_t bytesRemaining = length+hlen; //Match the length type
uint8_t bytesToWrite;
boolean result = true;
while((bytesRemaining > 0) && result) {
bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining;
rc = _client->write(writeBuf,bytesToWrite);
result = (rc == bytesToWrite);
bytesRemaining -= rc;
writeBuf += rc;
}
return result;
#else
rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen);
lastOutActivity = millis();
return (rc == hlen+length);
#endif
}
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;
}

View file

@ -1,174 +0,0 @@
/*
PubSubClient.h - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#ifndef PubSubClient_h
#define PubSubClient_h
#include <Arduino.h>
#include "IPAddress.h"
#include "Client.h"
#include "Stream.h"
#define MQTT_VERSION_3_1 3
#define MQTT_VERSION_3_1_1 4
// MQTT_VERSION : Pick the version
//#define MQTT_VERSION MQTT_VERSION_3_1
#ifndef MQTT_VERSION
#define MQTT_VERSION MQTT_VERSION_3_1_1
#endif
// MQTT_MAX_PACKET_SIZE : Maximum packet size
#ifndef MQTT_MAX_PACKET_SIZE
//#define MQTT_MAX_PACKET_SIZE 128
#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 <functional>
#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
#else
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
#endif
#define CHECK_STRING_LENGTH(l,s) if (l+2+strlen(s) > MQTT_MAX_PACKET_SIZE) {_client->stop();return false;}
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 - <returned size>) bytes into the buffer
size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length);
IPAddress ip;
const char* domain;
uint16_t port;
Stream* stream;
int _state;
public:
PubSubClient();
PubSubClient(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

View file

@ -1,173 +0,0 @@
/*
PubSubClient.h - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#ifndef PubSubClient_h
#define PubSubClient_h
#include <Arduino.h>
#include "IPAddress.h"
#include "Client.h"
#include "Stream.h"
#define MQTT_VERSION_3_1 3
#define MQTT_VERSION_3_1_1 4
// MQTT_VERSION : Pick the version
//#define MQTT_VERSION MQTT_VERSION_3_1
#ifndef MQTT_VERSION
#define MQTT_VERSION MQTT_VERSION_3_1_1
#endif
// MQTT_MAX_PACKET_SIZE : Maximum packet size
#ifndef MQTT_MAX_PACKET_SIZE
#define MQTT_MAX_PACKET_SIZE 128
#endif
// MQTT_KEEPALIVE : keepAlive interval in Seconds
#ifndef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 15
#endif
// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds
#ifndef MQTT_SOCKET_TIMEOUT
#define MQTT_SOCKET_TIMEOUT 15
#endif
// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client
// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to
// pass the entire MQTT packet in each write call.
//#define MQTT_MAX_TRANSFER_SIZE 80
// Possible values for client.state()
#define MQTT_CONNECTION_TIMEOUT -4
#define MQTT_CONNECTION_LOST -3
#define MQTT_CONNECT_FAILED -2
#define MQTT_DISCONNECTED -1
#define MQTT_CONNECTED 0
#define MQTT_CONNECT_BAD_PROTOCOL 1
#define MQTT_CONNECT_BAD_CLIENT_ID 2
#define MQTT_CONNECT_UNAVAILABLE 3
#define MQTT_CONNECT_BAD_CREDENTIALS 4
#define MQTT_CONNECT_UNAUTHORIZED 5
#define MQTTCONNECT 1 << 4 // Client request to connect to Server
#define MQTTCONNACK 2 << 4 // Connect Acknowledgment
#define MQTTPUBLISH 3 << 4 // Publish message
#define MQTTPUBACK 4 << 4 // Publish Acknowledgment
#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1)
#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2)
#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3)
#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request
#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment
#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request
#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment
#define MQTTPINGREQ 12 << 4 // PING Request
#define MQTTPINGRESP 13 << 4 // PING Response
#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting
#define MQTTReserved 15 << 4 // Reserved
#define MQTTQOS0 (0 << 1)
#define MQTTQOS1 (1 << 1)
#define MQTTQOS2 (2 << 1)
// Maximum size of fixed header and variable length size header
#define MQTT_MAX_HEADER_SIZE 5
#if defined(ESP8266) || defined(ESP32)
#include <functional>
#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
#else
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
#endif
#define CHECK_STRING_LENGTH(l,s) if (l+2+strlen(s) > MQTT_MAX_PACKET_SIZE) {_client->stop();return false;}
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 - <returned size>) bytes into the buffer
size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length);
IPAddress ip;
const char* domain;
uint16_t port;
Stream* stream;
int _state;
public:
PubSubClient();
PubSubClient(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

View file

@ -1,4 +0,0 @@
.build
tmpbin
logs
*.pyc

View file

@ -1,25 +0,0 @@
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

View file

@ -1,93 +0,0 @@
# 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.

View file

@ -1,302 +0,0 @@
#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
}

View file

@ -1,185 +0,0 @@
#include "PubSubClient.h"
#include "ShimClient.h"
#include "Buffer.h"
#include "BDDTest.h"
#include "trace.h"
#include <unistd.h>
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
}

View file

@ -1,26 +0,0 @@
#ifndef Arduino_h
#define Arduino_h
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#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

View file

@ -1,50 +0,0 @@
#include "BDDTest.h"
#include "trace.h"
#include <sstream>
#include <iostream>
#include <string>
#include <list>
int testCount = 0;
int testPasses = 0;
const char* testDescription;
std::list<std::string> 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 << " ! "<<testDescription<<"\n " <<file << ":" <<line<<" : "<<assertion<<" ["<<result<<"]";
failureList.push_back(os.str());
}
return result;
}
void bddtest_start(const char* description) {
LOG(" - "<<description<<" ");
testDescription = description;
testCount ++;
}
void bddtest_end() {
LOG("\n");
testPasses ++;
}
int bddtest_summary() {
for (std::list<std::string>::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;
}

View file

@ -1,23 +0,0 @@
#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

View file

@ -1,34 +0,0 @@
#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 (;i<size;i++) {
this->buffer[this->length++] = buf[i];
}
}

View file

@ -1,23 +0,0 @@
#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

View file

@ -1,21 +0,0 @@
#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

View file

@ -1,44 +0,0 @@
#include <Arduino.h>
#include <IPAddress.h>
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;
}

View file

@ -1,72 +0,0 @@
/*
*
* 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

View file

@ -1,28 +0,0 @@
/*
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

View file

@ -1,153 +0,0 @@
#include "ShimClient.h"
#include "trace.h"
#include <iostream>
#include <Arduino.h>
#include <ctime>
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 (;i<size;i++) {
if (i>0) {
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"<<std::dec);
return size;
}
int ShimClient::available() {
return this->responseBuffer->available();
}
int ShimClient::read() { return this->responseBuffer->next(); }
int ShimClient::read(uint8_t *buf, size_t size) {
uint16_t i = 0;
for (;i<size;i++) {
buf[i] = this->read();
}
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;
}

View file

@ -1,51 +0,0 @@
#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

View file

@ -1,39 +0,0 @@
#include "Stream.h"
#include "trace.h"
#include <iostream>
#include <Arduino.h>
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;
}

View file

@ -1,22 +0,0 @@
#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

View file

@ -1,10 +0,0 @@
#ifndef trace_h
#define trace_h
#include <iostream>
#include <stdlib.h>
#define LOG(x) {std::cout << x << std::flush; }
#define TRACE(x) {if (getenv("TRACE")) { std::cout << x << std::flush; }}
#endif

View file

@ -1,190 +0,0 @@
#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
}

View file

@ -1,279 +0,0 @@
#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
}

View file

@ -1,177 +0,0 @@
#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
}

View file

@ -1,39 +0,0 @@
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")

View file

@ -1,59 +0,0 @@
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")

View file

@ -1,2 +0,0 @@
server_ip = "172.16.0.2"
arduino_ip = "172.16.0.100"

View file

@ -1,181 +0,0 @@
#!/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()

View file

@ -1,40 +1,55 @@
#include "wifi.h"
#include <ESP8266WiFi.h>
#include <ArduinoOTA.h>
#include <SoftwareSerial.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include "dep/pubsubclient-2.7/src/PubSubClient.cpp"
#include "dep/WiFiManager-0.15.0/WiFiManager.cpp"
#include "dep/SimpleTimer-schinken/SimpleTimer.cpp"
#include <ArduinoJson.h>
#include <WiFiManager.h>
#include <PubSubClient.h>
#include <SoftwareSerial.h>
#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
uint8_t mqttRetryCounter = 0;
WiFiManager wifiManager;
WiFiClient wifiClient;
PubSubClient mqttClient;
WiFiManagerParameter custom_mqtt_server("server", "mqtt server", mqtt_server, sizeof(mqtt_server));
WiFiManagerParameter custom_mqtt_user("user", "MQTT username", username, sizeof(username));
WiFiManagerParameter custom_mqtt_pass("pass", "MQTT password", password, sizeof(password));
unsigned long lastMqttConnectionAttempt = millis();
const long mqttConnectionInterval = 60000;
unsigned long statusPublishPreviousMillis = millis();
const long statusPublishInterval = 30000;
char identifier[24];
#define FIRMWARE_PREFIX "esp8266-geigercounter"
#define AVAILABILITY_ONLINE "online"
#define AVAILABILITY_OFFLINE "offline"
char MQTT_TOPIC_AVAILABILITY[128];
char MQTT_TOPIC_CPM[128];
char MQTT_TOPIC_USV[128];
char MQTT_TOPIC_AUTOCONF_CPM[128];
char MQTT_TOPIC_AUTOCONF_USV[128];
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;
bool shouldSaveConfig = false;
char hostname[24];
void saveConfigCallback () {
shouldSaveConfig = true;
}
void setup() {
delay(3000);
Serial.begin(115200);
Serial.begin(9600);
delay(2000);
Serial.println("\n");
Serial.println("Hello from esp8266-geigercounter");
@ -43,290 +58,150 @@ void setup() {
Serial.printf("Boot Mode: %u\n", ESP.getBootMode());
Serial.printf("CPU Frequency: %u MHz\n", ESP.getCpuFreqMHz());
Serial.printf("Reset reason: %s\n", ESP.getResetReason().c_str());
delay(3000);
geigerCounterSerial.begin(BAUD_GEIGERCOUNTER);
geigerCounterSerial.begin(9600);
snprintf(identifier, sizeof(identifier), "GEIGERCTR-%X", ESP.getChipId());
snprintf(MQTT_TOPIC_AVAILABILITY, 127, "%s/%s/status", FIRMWARE_PREFIX, identifier);
snprintf(MQTT_TOPIC_CPM, 127, "%s/%s_cpm/state", FIRMWARE_PREFIX, identifier);
snprintf(MQTT_TOPIC_USV, 127, "%s/%s_usv/state", FIRMWARE_PREFIX, identifier);
snprintf(MQTT_TOPIC_AUTOCONF_CPM, 127, "homeassistant/sensor/%s/%s_cpm/config", FIRMWARE_PREFIX, identifier);
snprintf(MQTT_TOPIC_AUTOCONF_USV, 127, "homeassistant/sensor/%s/%s_usv/config", FIRMWARE_PREFIX, identifier);
int32_t chipid = ESP.getChipId();
Serial.print("MQTT_MAX_PACKET_SIZE: ");
Serial.println(MQTT_MAX_PACKET_SIZE);
WiFi.hostname(identifier);
#ifdef HOSTNAME
strncpy(hostname, HOSTNAME, sizeof(hostname));
#else
snprintf(hostname, sizeof(hostname), "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
Serial.print("Connecting to WiFi");
WiFi.mode(WIFI_STA);
unsigned long wifiConnectStart = millis();
while (WiFi.status() != WL_CONNECTED) {
if (WiFi.status() == WL_CONNECT_FAILED) {
return;
}
if (millis() - wifiConnectStart > 10000) {
WiFiManager wifiManager;
#ifdef CONF_WIFI_PASSWORD
wifiManager.autoConnect(hostname, CONF_WIFI_PASSWORD);
#else
wifiManager.autoConnect(hostname);
#endif
}
delay(100);
Serial.print(".");
}
Serial.print("\nIP: ");
Serial.println(WiFi.localIP());
WiFi.hostname(hostname);
mqttClient.setClient(wifiClient);
mqttClient.setServer(MQTT_HOST, 1883);
ArduinoOTA.setHostname(hostname);
ArduinoOTA.setPassword(OTA_PASSWORD);
ArduinoOTA.begin();
loadConfig();
setupWifi();
mqttClient.setServer(mqtt_server, 1883);
mqttClient.setKeepAlive(10);
mqttClient.setBufferSize(2048);
Serial.print("Hostname: ");
Serial.println(hostname);
Serial.println(identifier);
Serial.print("\nIP: ");
Serial.println(WiFi.localIP());
Serial.println("-- Current GPIO Configuration --");
Serial.print("PIN_UART_RX: ");
Serial.println(PIN_UART_RX);
mqttConnect();
timer.setInterval(UPDATE_INTERVAL_SECONDS * 1000L, updateRadiationValues);
//Disable blue LED
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
mqttReconnect();
}
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();
ESP.wdtFeed();
if (geigerCounterSerial.available()) {
char in = (char) geigerCounterSerial.read();
handleUart();
serialInput += in;
if (in == '\n') {
serialInput.toCharArray(serialInputHelper, RECV_LINE_SIZE);
parseReceivedLine(serialInputHelper);
serialInput = "";
if (statusPublishInterval <= (millis() - statusPublishPreviousMillis)) {
statusPublishPreviousMillis = millis();
updateRadiationValues();
}
// Just in case the buffer gets to big, start from scratch
if (serialInput.length() > RECV_LINE_SIZE + 10) {
serialInput = "";
if (!mqttClient.connected() && (mqttConnectionInterval <= (millis() - lastMqttConnectionAttempt)) ) {
lastMqttConnectionAttempt = millis();
mqttReconnect();
}
Serial.write(in);
}
ArduinoOTA.handle();
}
void updateRadiationValues() {
char tmp[8];
void setupWifi() {
wifiManager.setDebugOutput(false);
wifiManager.setSaveConfigCallback(saveConfigCallback);
if (currentCPM != lastCPM) {
String(currentCPM).toCharArray(tmp, 8);
Serial.print("Sending CPM: ");
Serial.println(tmp);
mqttClient.publish(MQTT_TOPIC_CPM_MEASUREMENT, tmp, true);
wifiManager.addParameter(&custom_mqtt_server);
wifiManager.addParameter(&custom_mqtt_user);
wifiManager.addParameter(&custom_mqtt_pass);
WiFi.hostname(identifier);
wifiManager.autoConnect(identifier);
mqttClient.setClient(wifiClient);
strcpy(mqtt_server, custom_mqtt_server.getValue());
strcpy(username, custom_mqtt_user.getValue());
strcpy(password, custom_mqtt_pass.getValue());
if (shouldSaveConfig) {
saveConfig();
} else {
//For some reason, the read values get overwritten in this function
//To combat this, we just reload the config
//This is most likely a logic error which could be fixed otherwise
loadConfig();
}
if (currentuSv != lastuSv) {
String(currentuSv).toCharArray(tmp, 8);
Serial.print("Sending uSv: ");
Serial.println(tmp);
mqttClient.publish(MQTT_TOPIC_USV_MEASUREMENT, tmp, true);
}
lastCPM = currentCPM;
lastuSv = currentuSv;
}
void parseReceivedLine(char* input) {
void resetWifiSettingsAndReboot() {
wifiManager.resetSettings();
delay(3000);
ESP.restart();
}
char segment = 0;
char *token;
void mqttReconnect() {
for (int attempt = 0; attempt < 3; ++attempt) {
if (mqttClient.connect(identifier, username, password, MQTT_TOPIC_AVAILABILITY, 1, true, AVAILABILITY_OFFLINE)) {
mqttClient.publish(MQTT_TOPIC_AVAILABILITY, AVAILABILITY_ONLINE, true);
float uSv = 0;
float cpm = 0;
Serial.println("Connected to MQTT Server");
token = strtok(input, delimiter);
while (token != NULL) {
switch (segment) {
// This is just for validation
case IDX_CPS_KEY: if (strcmp(token, "CPS") != 0) return; break;
case IDX_CPM_KEY: if (strcmp(token, "CPM") != 0) return; break;
case IDX_uSv_KEY: if (strcmp(token, "uSv/hr") != 0) return; break;
case IDX_CPM:
Serial.printf("\nCurrent CPM: %s\n", token);
cpm = String(token).toInt();
break;
case IDX_uSv:
Serial.printf("Current uSv/hr: %s\n", token);
uSv = String(token).toFloat();
publishAutoConfig();
break;
} else {
Serial.println("Failed to connect to MQTT Server :(");
delay(5000);
}
if (segment > 7) {
// Invalid! There should be no more than 7 segments
return;
}
token = strtok(NULL, delimiter);
segment++;
}
currentuSv = uSv;
currentCPM = cpm;
}
boolean isMqttConnected() {
return mqttClient.connected();
}
void publishAutoConfig() {
char mqttPayload[2048];
DynamicJsonDocument device(256);
StaticJsonDocument<64> identifiersDoc;
JsonArray identifiers = identifiersDoc.to<JsonArray>();
identifiers.add(identifier);
device["identifiers"] = identifiers;
device["manufacturer"] = "MightyOhm LLC";
device["model"] = "Geiger Counter";
device["name"] = identifier;
device["sw_version"] = "0.0.1";
DynamicJsonDocument cpmSensorPayload(512);
cpmSensorPayload["device"] = device.as<JsonObject>();
cpmSensorPayload["availability_topic"] = MQTT_TOPIC_AVAILABILITY;
cpmSensorPayload["state_topic"] = MQTT_TOPIC_CPM;
cpmSensorPayload["name"] = identifier + String(" CPM");
cpmSensorPayload["unit_of_measurement"] = "CPM";
cpmSensorPayload["unique_id"] = identifier + String("_cpm");
serializeJson(cpmSensorPayload, mqttPayload);
mqttClient.publish(MQTT_TOPIC_AUTOCONF_CPM, mqttPayload, true);
DynamicJsonDocument usvSensorPayload(512);
usvSensorPayload["device"] = device.as<JsonObject>();
usvSensorPayload["availability_topic"] = MQTT_TOPIC_AVAILABILITY;
usvSensorPayload["state_topic"] = MQTT_TOPIC_USV;
usvSensorPayload["name"] = identifier + String(" uSv");
usvSensorPayload["unit_of_measurement"] = "µSv/h";
usvSensorPayload["unique_id"] = identifier + String("_uSv");
serializeJson(usvSensorPayload, mqttPayload);
mqttClient.publish(MQTT_TOPIC_AUTOCONF_USV, mqttPayload, true);
Serial.println("Published MQTT Autoconf");
}

81
serialCommunication.ino Normal file
View file

@ -0,0 +1,81 @@
const char* delimiter = ", ";
int lastCPM = 0, currentCPM = 0;
float lastuSv = 0, currentuSv = 0;
byte serialRxBuf[255];
void handleUart() {
if (geigerCounterSerial.available()) {
geigerCounterSerial.readBytesUntil('\n', serialRxBuf, 250);
parseReceivedLine((char*)serialRxBuf);
}
}
void parseReceivedLine(char* input) {
Serial.println(input);
char segment = 0;
char *token;
float uSv = 0;
float cpm = 0;
token = strtok(input, delimiter);
while (token != NULL) {
switch (segment) {
// This is just for validation
case IDX_CPS_KEY: if (strcmp(token, "CPS") != 0) return; break;
case IDX_CPM_KEY: if (strcmp(token, "CPM") != 0) return; break;
case IDX_uSv_KEY: if (strcmp(token, "uSv/hr") != 0) return; break;
case IDX_CPM:
//Serial.printf("\nCurrent CPM: %s\n", token);
cpm = String(token).toInt();
break;
case IDX_uSv:
//Serial.printf("Current uSv/hr: %s\n", token);
uSv = String(token).toFloat();
break;
}
if (segment > 7) {
// Invalid! There should be no more than 7 segments
return;
}
token = strtok(NULL, delimiter);
segment++;
}
currentuSv = uSv;
currentCPM = cpm;
}
void updateRadiationValues() {
char tmp[8];
if (currentCPM != lastCPM) {
String(currentCPM).toCharArray(tmp, 8);
Serial.print("Sending CPM: ");
Serial.println(tmp);
mqttClient.publish(MQTT_TOPIC_CPM, tmp, true);
}
if (currentuSv != lastuSv) {
String(currentuSv).toCharArray(tmp, 8);
Serial.print("Sending uSv: ");
Serial.println(tmp);
mqttClient.publish(MQTT_TOPIC_USV, tmp, true);
}
lastCPM = currentCPM;
lastuSv = currentuSv;
}

View file

@ -1,47 +0,0 @@
#define IDX_CPS_KEY 0
#define IDX_CPM_KEY 2
#define IDX_uSv_KEY 4
#define IDX_CPM 3
#define IDX_uSv 5
#define IDX_MODE 6
#define RECV_LINE_SIZE 37
#define PIN_UART_RX 0 // 4
#define PIN_UART_TX 13 // UNUSED
#define UPDATE_INTERVAL_SECONDS 60L
#define BAUD_GEIGERCOUNTER 9600
//#define HOSTNAME "ESP-GeigerCounter" //Optional
const char* OTA_PASSWORD = "foobar";
#define MQTT_HOST "mqtt.core.bckspc.de"
//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 = ", ";

4
wifi.h Normal file
View file

@ -0,0 +1,4 @@
char mqtt_server[80] = "example.tld";
char username[24] = "";
char password[24] = "";