version 1.2.0. See ChangeLog

This commit is contained in:
proddy
2019-01-02 00:14:36 +01:00
parent aaa8505402
commit e14c2c658c
21 changed files with 3057 additions and 2009 deletions

View File

@@ -1,895 +0,0 @@
/*
Based off :
1) ESPHelper.cpp - Copyright (c) 2017 ItKindaWorks Inc All right reserved. github.com/ItKindaWorks
2) https://github.com/JoaoLopesF/ESP8266-RemoteDebug-Telnet
*/
#include "ESPHelper.h"
WiFiServer telnetServer(TELNET_PORT);
//initializer with single netInfo network
ESPHelper::ESPHelper(netInfo * startingNet) {
//disconnect from and previous wifi networks
WiFi.softAPdisconnect();
WiFi.disconnect();
//setup current network information
_currentNet = *startingNet;
//validate various bits of network/MQTT info
//network pass
if (_currentNet.pass[0] == '\0') {
_passSet = false;
} else {
_passSet = true;
}
//ssid
if (_currentNet.ssid[0] == '\0') {
_ssidSet = false;
} else {
_ssidSet = true;
}
//mqtt host
if (_currentNet.mqttHost[0] == '\0') {
_mqttSet = false;
} else {
_mqttSet = true;
}
//mqtt port
if (_currentNet.mqttPort == 0) {
_currentNet.mqttPort = 1883;
}
//mqtt username
if (_currentNet.mqttUser[0] == '\0') {
_mqttUserSet = false;
} else {
_mqttUserSet = true;
}
//mqtt password
if (_currentNet.mqttPass[0] == '\0') {
_mqttPassSet = false;
} else {
_mqttPassSet = true;
}
//disable hopping on single network
_hoppingAllowed = false;
//disable ota by default
_useOTA = false;
}
//start the wifi & mqtt systems and attempt connection (currently blocking)
//true on: parameter check validated
//false on: parameter check failed
bool ESPHelper::begin(const char * hostname, const char * app_name, const char * app_version) {
#ifdef USE_SERIAL1
Serial1.begin(115200);
Serial1.setDebugOutput(true);
#endif
#ifdef USE_SERIAL
Serial.begin(115200);
Serial.setDebugOutput(true);
#endif
// set hostname first
strcpy(_hostname, hostname);
OTA_enable();
strcpy(_app_name, app_name); // app name
strcpy(_app_version, app_version); // app version
setBoottime("<unknown>");
if (_ssidSet) {
strcpy(_clientName, hostname);
/*
// Generate client name based on MAC address and last 8 bits of microsecond counter
_clientName += "esp-";
uint8_t mac[6];
WiFi.macAddress(mac);
_clientName += macToStr(mac);
*/
// set hostname
// can ping by <hostname> or on Windows10 it's <hostname>.
#if defined(ESP8266)
WiFi.hostname(_hostname);
#elif defined(ESP32)
WiFi.setHostname(_hostname);
#endif
//set the wifi mode to station and begin the wifi (connect using either ssid or ssid/pass)
WiFi.mode(WIFI_STA);
if (_passSet) {
WiFi.begin(_currentNet.ssid, _currentNet.pass);
} else {
WiFi.begin(_currentNet.ssid);
}
//as long as an mqtt ip has been set create an instance of PubSub for client
if (_mqttSet) {
//make mqtt client use either the secure or non-secure wifi client depending on the setting
if (_useSecureClient) {
client = PubSubClient(_currentNet.mqttHost, _currentNet.mqttPort, wifiClientSecure);
} else {
client = PubSubClient(_currentNet.mqttHost, _currentNet.mqttPort, wifiClient);
}
//set the mqtt message callback if needed
if (_mqttCallbackSet) {
client.setCallback(_mqttCallback);
}
}
//define a dummy instance of mqtt so that it is instantiated if no mqtt ip is set
else {
//make mqtt client use either the secure or non-secure wifi client depending on the setting
//(this shouldnt be needed if making a dummy connection since the idea would be that there wont be mqtt in this case)
if (_useSecureClient) {
client = PubSubClient("192.168.1.255", _currentNet.mqttPort, wifiClientSecure);
} else {
client = PubSubClient("192.168.1.255", _currentNet.mqttPort, wifiClient);
}
}
//ota event handlers
ArduinoOTA.onStart([]() { /* ota start code */ });
ArduinoOTA.onEnd([]() {
//on ota end we disconnect from wifi cleanly before restarting.
WiFi.softAPdisconnect();
WiFi.disconnect();
uint8_t timeout = 0;
//max timeout of 2seconds before just dropping out and restarting
while (WiFi.status() != WL_DISCONNECTED && timeout < 200) {
timeout++;
}
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { /* ota progress code */ });
ArduinoOTA.onError([](ota_error_t error) { /* ota error code */ });
//initially attempt to connect to wifi when we begin (but only block for 2 seconds before timing out)
uint8_t timeout = 0; //counter for begin connection attempts
while (((!client.connected() && _mqttSet) || WiFi.status() != WL_CONNECTED)
&& timeout < 200) { //max 2 sec before timeout
reconnect();
timeout++;
}
//attempt to start ota if needed
OTA_begin();
// Initialize the telnet server
telnetServer.begin();
telnetServer.setNoDelay(true);
// init command buffer for console commands
memset(_command, 0, sizeof(_command));
consoleShowHelp(); // show this at bootup
// mark the system as started and return
_hasBegun = true;
return true;
}
//if no ssid was set even then dont try to begin and return false
return false;
}
//end the instance of ESPHelper (shutdown wifi, ota, mqtt)
void ESPHelper::end() {
// Stop telnet Client & Server
if (telnetClient && telnetClient.connected()) {
telnetClient.stop();
}
// Stop server
telnetServer.stop();
OTA_disable();
WiFi.softAPdisconnect();
WiFi.disconnect();
uint8_t timeout = 0;
while (WiFi.status() != WL_DISCONNECTED && timeout < 200) {
timeout++;
}
#ifdef USE_SERIAL
Serial.flush();
#endif
#ifdef USE_SERIAL1
Serial1.flush();
#endif
}
//main loop - should be called as often as possible - handles wifi/mqtt connection and mqtt handler
//true on: network/server connected
//false on: network or server disconnected
int ESPHelper::loop() {
if (_ssidSet) {
//check for good connections and attempt a reconnect if needed
if (((_mqttSet && !client.connected()) || setConnectionStatus() < WIFI_ONLY) && _connectionStatus != BROADCAST) {
reconnect();
}
//run the wifi loop as long as the connection status is at a minimum of BROADCAST
if (_connectionStatus >= BROADCAST) {
//run the MQTT loop if we have a full connection
if (_connectionStatus == FULL_CONNECTION) {
client.loop();
}
//check for whether we want to use OTA and whether the system is running
if (_useOTA && _OTArunning) {
ArduinoOTA.handle();
}
//if we want to use OTA but its not running yet, start it up.
else if (_useOTA && !_OTArunning) {
OTA_begin();
ArduinoOTA.handle();
}
// do the telnet stuff
consoleHandle();
return _connectionStatus;
}
}
//return -1 for no connection because of bad network info
return -1;
}
//subscribe to a specific topic (does not add to topic list)
//true on: subscription success
//false on: subscription failed (either from PubSub lib or network is disconnected)
bool ESPHelper::subscribe(const char * topic, uint8_t qos) {
if (_connectionStatus == FULL_CONNECTION) {
//set the return value to the output of subscribe
bool returnVal = client.subscribe(topic, qos);
//loop mqtt client
client.loop();
return returnVal;
}
//if not fully connected return false
else {
return false;
}
}
//add a topic to the list of subscriptions and attempt to subscribe to the topic on the spot
//true on: subscription added to list (does not guarantee that the topic was subscribed to, only that it was added to the list)
//false on: subscription not added to list
bool ESPHelper::addSubscription(const char * topic) {
//default return value is false
bool subscribed = false;
//loop through finding the next available slot for a subscription and add it
for (uint8_t i = 0; i < MAX_SUBSCRIPTIONS; i++) {
if (_subscriptions[i].isUsed == false) {
_subscriptions[i].topic = topic;
_subscriptions[i].isUsed = true;
subscribed = true;
break;
}
}
//if added to the list, subscribe to the topic
if (subscribed) {
subscribe(topic, _qos);
}
return subscribed;
}
//loops through list of subscriptions and attempts to subscribe to all topics
void ESPHelper::resubscribe() {
for (uint8_t i = 0; i < MAX_SUBSCRIPTIONS; i++) {
if (_subscriptions[i].isUsed) {
subscribe(_subscriptions[i].topic, _qos);
yield();
}
}
}
//manually unsubscribes from a topic (This is basically just a wrapper for the pubsubclient function)
bool ESPHelper::unsubscribe(const char * topic) {
return client.unsubscribe(topic);
}
//publish to a specified topic
void ESPHelper::publish(const char * topic, const char * payload) {
if (_mqttSet) {
publish(topic, payload, false);
}
}
//publish to a specified topic with a given retain level
void ESPHelper::publish(const char * topic, const char * payload, bool retain) {
client.publish(topic, payload, retain);
}
//set the callback function for MQTT
void ESPHelper::setMQTTCallback(MQTT_CALLBACK_SIGNATURE) {
_mqttCallback = callback;
//only set the callback if using mqtt AND the system has already been started. Otherwise just save it for later
if (_hasBegun && _mqttSet) {
client.setCallback(_mqttCallback);
}
_mqttCallbackSet = true;
}
//sets a custom function to run when connection to wifi is established
void ESPHelper::setWifiCallback(void (*callback)()) {
_wifiCallback = callback;
_wifiCallbackSet = true;
}
//sets a custom function to run when telnet is started
void ESPHelper::setInitCallback(void (*callback)()) {
_initCallback = callback;
_initCallbackSet = true;
}
//attempts to connect to wifi & mqtt server if not connected
void ESPHelper::reconnect() {
static uint8_t tryCount = 0;
if (_connectionStatus != BROADCAST && setConnectionStatus() != FULL_CONNECTION) {
logger(LOG_CONSOLE, "Attempting WiFi Connection...");
//attempt to connect to the wifi if connection is lost
if (WiFi.status() != WL_CONNECTED) {
_connectionStatus = NO_CONNECTION;
//increment try count each time it cannot connect (this is used to determine when to hop to a new network)
tryCount++;
if (tryCount == 20) {
//change networks (if possible) when we have tried to connect 20 times and failed
changeNetwork();
tryCount = 0;
return;
}
}
// make sure we are connected to WIFI before attempting to reconnect to MQTT
//----note---- maybe want to reset tryCount whenever we succeed at getting wifi connection?
if (WiFi.status() == WL_CONNECTED) {
//if the wifi previously wasnt connected but now is, run the callback
if (_connectionStatus < WIFI_ONLY && _wifiCallbackSet) {
_wifiCallback();
}
logger(LOG_CONSOLE, "---WiFi Connected!---");
_connectionStatus = WIFI_ONLY;
//attempt to connect to mqtt when we finally get connected to WiFi
if (_mqttSet) {
static uint8_t timeout = 0; //allow a max of 5 mqtt connection attempts before timing out
if (!client.connected() && timeout < 5) {
logger(LOG_CONSOLE, "Attempting MQTT connection...");
uint8_t connected = 0;
//connect to mqtt with user/pass
if (_mqttUserSet) {
connected = client.connect(_clientName, _currentNet.mqttUser, _currentNet.mqttPass);
}
//connect to mqtt without credentials
else {
connected = client.connect(_clientName);
}
//if connected, subscribe to the topic(s) we want to be notified about
if (connected) {
logger(LOG_CONSOLE, " -- Connected");
//if using https, verify the fingerprint of the server before setting full connection (return on fail)
// removing this as not supported with ESP32, see https://github.com/espressif/arduino-esp32/issues/278
/*
if (wifiClientSecure.verify(_fingerprint,
_currentNet.mqttHost)) {
logger(LOG_CONSOLE,
"Certificate Matches - SUCCESS\n");
} else {
logger(LOG_CONSOLE,
"Certificate Doesn't Match - FAIL\n");
return;
}
}
*/
_connectionStatus = FULL_CONNECTION;
resubscribe();
timeout = 0;
} else {
logger(LOG_CONSOLE, " -- Failed\n");
}
timeout++;
}
//if we still cant connect to mqtt after 10 attempts increment the try count
if (timeout >= 5 && !client.connected()) {
timeout = 0;
tryCount++;
if (tryCount == 20) {
changeNetwork();
tryCount = 0;
return;
}
}
}
}
//reset the reconnect metro
//reconnectMetro.reset();
}
}
uint8_t ESPHelper::setConnectionStatus() {
//assume no connection
uint8_t returnVal = NO_CONNECTION;
//make sure were not in broadcast mode
if (_connectionStatus != BROADCAST) {
//if connected to wifi set the mode to wifi only and run the callback if needed
if (WiFi.status() == WL_CONNECTED) {
if (_connectionStatus < WIFI_ONLY
&& _wifiCallbackSet) { //if the wifi previously wasn't connected but now is, run the callback
_wifiCallback();
}
returnVal = WIFI_ONLY;
//if mqtt is connected as well then set the status to full connection
if (client.connected()) {
returnVal = FULL_CONNECTION;
}
}
}
else {
returnVal = BROADCAST;
}
//set the connection status and return
_connectionStatus = returnVal;
return returnVal;
}
//changes the current network settings to the next listed network if network hopping is allowed
void ESPHelper::changeNetwork() {
//only attempt to change networks if hopping is allowed
if (_hoppingAllowed) {
//change the index/reset to 0 if we've hit the last network setting
_currentIndex++;
if (_currentIndex >= _netCount) {
_currentIndex = 0;
}
//set the current netlist to the new network
_currentNet = *_netList[_currentIndex];
//verify various bits of network info
//network password
if (_currentNet.pass[0] == '\0') {
_passSet = false;
} else {
_passSet = true;
}
//ssid
if (_currentNet.ssid[0] == '\0') {
_ssidSet = false;
} else {
_ssidSet = true;
}
//mqtt host
if (_currentNet.mqttHost[0] == '\0') {
_mqttSet = false;
} else {
_mqttSet = true;
}
//mqtt username
if (_currentNet.mqttUser[0] == '\0') {
_mqttUserSet = false;
} else {
_mqttUserSet = true;
}
//mqtt password
if (_currentNet.mqttPass[0] == '\0') {
_mqttPassSet = false;
} else {
_mqttPassSet = true;
}
printf("Trying next network: %s\n", _currentNet.ssid);
//update the network connection
updateNetwork();
}
}
void ESPHelper::updateNetwork() {
logger(LOG_CONSOLE, "\tDisconnecting from WiFi");
WiFi.disconnect();
logger(LOG_CONSOLE, "\tAttempting to begin on new network...");
//set the wifi mode
WiFi.mode(WIFI_STA);
//connect to the network
if (_passSet && _ssidSet) {
WiFi.begin(_currentNet.ssid, _currentNet.pass);
} else if (_ssidSet) {
WiFi.begin(_currentNet.ssid);
} else {
WiFi.begin("NO_SSID_SET");
}
logger(LOG_CONSOLE, "\tSetting new MQTT server");
//setup the mqtt broker info
if (_mqttSet) {
client.setServer(_currentNet.mqttHost, _currentNet.mqttPort);
} else {
client.setServer("192.168.1.3", 1883);
}
logger(LOG_CONSOLE, "\tDone - Ready for next reconnect attempt");
}
//enable use of OTA updates
void ESPHelper::OTA_enable() {
_useOTA = true;
ArduinoOTA.setHostname(_hostname);
OTA_begin();
}
//begin the OTA subsystem but with a check for connectivity and enabled use of OTA
void ESPHelper::OTA_begin() {
if (_connectionStatus >= BROADCAST && _useOTA) {
ArduinoOTA.begin();
_OTArunning = true;
}
}
//disable use of OTA updates
void ESPHelper::OTA_disable() {
_useOTA = false;
_OTArunning = false;
}
// Is CR or LF ?
bool ESPHelper::isCRLF(char character) {
return (character == '\r' || character == '\n');
}
// handler for Telnet
void ESPHelper::consoleHandle() {
// look for Client
if (telnetServer.hasClient()) {
if (telnetClient && telnetClient.connected()) {
// Verify if the IP is same than actual connection
WiFiClient newClient;
newClient = telnetServer.available();
String ip = newClient.remoteIP().toString();
if (ip == telnetClient.remoteIP().toString()) {
// Reconnect
telnetClient.stop();
telnetClient = newClient;
} else {
// Desconnect (not allow more than one connection)
newClient.stop();
return;
}
} else {
// New TCP client
telnetClient = telnetServer.available();
}
if (!telnetClient) { // No client yet ???
return;
}
// Set client
telnetClient.setNoDelay(true); // faster
telnetClient.flush(); // clear input buffer, to prevent strange characters
_lastTimeCommand = millis(); // To mark time for inactivity
// Show the initial message
consoleShowHelp();
_initCallback(); // call callback to set any custom things
// Empty buffer
while (telnetClient.available()) {
telnetClient.read();
}
}
// Is client connected ? (to reduce overhead in active)
_telnetConnected = (telnetClient && telnetClient.connected());
// Get command over telnet
if (_telnetConnected) {
char last = ' '; // To avoid processing double "\r\n"
while (telnetClient.available()) { // get data from Client
// Get character
char character = telnetClient.read();
// Newline (CR or LF) - once one time if (\r\n)
if (isCRLF(character) == true) {
if (isCRLF(last) == false) {
// Process the command
if (strlen(_command) > 0) {
consoleProcessCommand();
}
}
// reset for next command
memset(_command, 0, sizeof(_command));
} else if (isPrintable(character)) {
// Concat char to end of buffer
uint16_t len = strlen(_command);
_command[len] = character;
_command[len + 1] = '\0';
}
// Last char
last = character;
}
// Inactivity - close connection if not received commands from user in telnet to reduce overheads
if ((millis() - _lastTimeCommand) > MAX_TIME_INACTIVE) {
telnetClient.println("* Closing telnet session due to inactivity");
telnetClient.flush();
telnetClient.stop();
_telnetConnected = false;
}
}
}
// Set callback of sketch function to process project messages
void ESPHelper::consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)()) {
_helpProjectCmds = cmds; // command list
_helpProjectCmds_count = count; // number of commands
_consoleCallbackProjectCmds = callback; // external function to handle commands
}
// Set bootime received as a string from HA
void ESPHelper::setBoottime(const char * boottime) {
strcpy(_boottime, boottime);
}
// overrides the write call to print to the telnet connection
size_t ESPHelper::write(uint8_t character) {
if (!_verboseMessages)
return 0;
//static uint32_t elapsed = 0;
// If start of a new line, initiate a new string buffer with time counter as a prefix
if (_newLine) {
unsigned long upt = millis();
sprintf(bufferPrint,
"(%s%02d:%02d:%02d%s) ",
COLOR_CYAN,
(uint8_t)((upt / (1000 * 60 * 60)) % 24),
(uint8_t)((upt / (1000 * 60)) % 60),
(uint8_t)((upt / 1000) % 60),
COLOR_RESET);
_newLine = false;
}
// Print ?
bool doPrint = false;
// New line ?
if (character == '\n') {
_newLine = true;
doPrint = true;
} else if (strlen(bufferPrint) == BUFFER_PRINT - 1) { // Limit of buffer
doPrint = true;
}
// Add character to telnet buffer
uint16_t len = strlen(bufferPrint);
bufferPrint[len] = character;
if (_newLine) {
// add additional \r for windows
bufferPrint[++len] = '\r';
}
// terminate string
bufferPrint[++len] = '\0';
// Send the characters buffered by print.h
if (doPrint) {
if (_telnetConnected) {
telnetClient.print(bufferPrint);
}
// echo to Serial if enabled
#ifdef USE_SERIAL
Serial.print(bufferPrint);
#endif
#ifdef USE_SERIAL1
Serial1.print(bufferPrint);
#endif
// Empty the buffer
bufferPrint[0] = '\0';
}
return len + 1;
}
// Show help of commands
void ESPHelper::consoleShowHelp() {
String help = "**********************************************\n\r* Remote Telnet Command Center & Log Monitor "
"*\n\r**********************************************\n\r";
help += "* Device hostname: " + WiFi.hostname() + "\tIP: " + WiFi.localIP().toString()
+ "\tMAC address: " + WiFi.macAddress() + "\n\r";
help += "* Connected to WiFi AP: " + WiFi.SSID() + "\n\r";
help += "* Boot time: ";
help.concat(_boottime);
help += "\n\r* ";
help.concat(_app_name);
help += " Version ";
help.concat(_app_version);
help += "\n\r* Free RAM: ";
help.concat(ESP.getFreeHeap());
help += " bytes\n\r";
help += "*\n\r* Commands:\n\r* ?=this help, q=quit telnet, $=show free memory, !=reboot, &=suspend all "
"notifications\n\r";
char s[100];
// print custom commands if available
if (_consoleCallbackProjectCmds) {
for (uint8_t i = 0; i < _helpProjectCmds_count; i++) {
help += FPSTR("* ");
help += FPSTR(_helpProjectCmds[i].key);
for (int j = 0; j < (8 - strlen(_helpProjectCmds[i].key)); j++) { // padding
help += FPSTR(" ");
}
help += FPSTR(_helpProjectCmds[i].description);
help += FPSTR("\n\r");
}
}
telnetClient.print(help);
#ifdef USE_SERIAL
Serial.print(help);
#endif
#ifdef USE_SERIAL1
Serial1.print(help);
#endif
}
// reset / restart
void ESPHelper::resetESP() {
telnetClient.println("* Reboot ESP...");
telnetClient.flush();
telnetClient.stop();
// end();
// Reset
ESP.restart();
// ESP.reset(); // for ESP8266 only
}
// Get last command received
char * ESPHelper::consoleGetLastCommand() {
return _command;
}
// Process user command over telnet
void ESPHelper::consoleProcessCommand() {
// Set time of last command received
_lastTimeCommand = millis();
uint8_t cmd = _command[0];
if (!_verboseMessages) {
telnetClient.println("Warning, all messages are supsended. Use & to enable.");
}
// Process the command
if (cmd == '?') {
consoleShowHelp(); // Show help
} else if (cmd == 'q') { // quit
telnetClient.println("* Closing telnet connection...");
telnetClient.stop();
} else if (cmd == '$') {
telnetClient.print("* Free RAM (bytes): ");
telnetClient.println(ESP.getFreeHeap());
} else if (cmd == '!') {
resetESP();
} else if (cmd == '&') {
_verboseMessages = !_verboseMessages; // toggle
telnetClient.printf("Suspend all messages is %s\n\r", _verboseMessages ? "disabled" : "enabled");
} else {
// custom Project commands
if (_consoleCallbackProjectCmds) {
_consoleCallbackProjectCmds();
}
}
}
// Logger
// LOG_CONSOLE sends to the Telnet session
// LOG_HA sends to Telnet session plus a MQTT for Home Assistant
// LOG_NONE turns off all logging
void ESPHelper::logger(log_level_t level, const char * message) {
// do we log to the telnet window?
if ((level == LOG_CONSOLE) && (telnetClient && telnetClient.connected())) {
telnetClient.println(message);
telnetClient.flush();
} else if (level == LOG_HA) {
char s[100];
sprintf(s, "%s: %s\n", _hostname, message); // add new line, for the debug telnet printer
publish(MQTT_NOTIFICATION, s, false);
}
// print to Serial if set in platform.io (requires recompile)
#ifdef USE_SERIAL
Serial.println(message);
#endif
#ifdef USE_SERIAL1
Serial.println(message);
#endif
}
// send specific command to HA via MQTT
// format is: home/<hostname>/command/<cmd>
void ESPHelper::sendHACommand(const char * cmd) {
//logger(LOG_CONSOLE, "Sending command to HA...");
char s[100];
sprintf(s, "%s%s/%s", MQTT_BASE, _hostname, MQTT_TOPIC_COMMAND);
publish(s, cmd, false);
}
// send specific start command to HA via MQTT, which returns the boottime
// format is: home/<hostname>/start
void ESPHelper::sendStart() {
//logger(LOG_CONSOLE, "Sending Start command to HA...");
char s[100];
sprintf(s, "%s%s/%s", MQTT_BASE, _hostname, MQTT_TOPIC_START);
// send initial payload of "start" to kick things off
publish(s, MQTT_TOPIC_START, false);
}

View File

@@ -1,219 +0,0 @@
/*
ESPHelper.h
Copyright (c) 2017 ItKindaWorks Inc All right reserved.
github.com/ItKindaWorks
This file is part of ESPHelper
ESPHelper is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ESPHelper is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ESPHelper. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <ArduinoOTA.h>
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
#include <ESP8266mDNS.h>
#include <Print.h>
#include <PubSubClient.h>
#include <WiFiClientSecure.h>
#include <WiFiUdp.h>
#include <pgmspace.h>
// MQTT stuff
#define DEFAULT_QOS 1 //at least once - devices are guarantee to get a message.
#define MQTT_BASE "home/"
#define MQTT_NOTIFICATION MQTT_BASE "notification"
#define MQTT_TOPIC_COMMAND "command"
#define MQTT_TOPIC_START "start"
#define MQTT_HA MQTT_BASE "ha"
#define MAX_SUBSCRIPTIONS 25 // max # of subscriptions
#define MAX_TIME_INACTIVE 600000 // Max time for inactivity (ms) - 10 mins
#define TELNET_PORT 23 // telnet port
#define BUFFER_PRINT 500 // length of telnet buffer (default was 150)
#define COMMAND_LENGTH 20 // length of a command
// ANSI Colors
#define COLOR_RESET "\x1B[0m"
#define COLOR_BLACK "\x1B[0;30m"
#define COLOR_RED "\x1B[0;31m"
#define COLOR_GREEN "\x1B[0;32m"
#define COLOR_YELLOW "\x1B[0;33m"
#define COLOR_BLUE "\x1B[0;34m"
#define COLOR_MAGENTA "\x1B[0;35m"
#define COLOR_CYAN "\x1B[0;36m"
#define COLOR_WHITE "\x1B[0;37m"
// Logger
typedef enum { LOG_NONE, LOG_CONSOLE, LOG_HA } log_level_t;
enum connStatus { NO_CONNECTION, BROADCAST, WIFI_ONLY, FULL_CONNECTION };
typedef struct {
const char * mqttHost;
const char * mqttUser;
const char * mqttPass;
uint16_t mqttPort;
const char * ssid;
const char * pass;
} netInfo;
typedef struct {
bool isUsed = false;
const char * topic;
} subscription;
typedef struct {
char key[10];
char description[400];
} command_t;
// class ESPHelper {
class ESPHelper : public Print {
public:
void consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)());
char * consoleGetLastCommand();
void resetESP();
void logger(log_level_t level, const char * message);
virtual size_t write(uint8_t);
ESPHelper(netInfo * startingNet);
bool begin(const char * hostname, const char * app_name, const char * app_version);
void end();
void useSecureClient(const char * fingerprint);
int loop();
bool subscribe(const char * topic, uint8_t qos);
bool addSubscription(const char * topic);
bool removeSubscription(const char * topic);
bool unsubscribe(const char * topic);
bool addHASubscription(const char * topic);
void publish(const char * topic, const char * payload);
void publish(const char * topic, const char * payload, bool retain);
bool setCallback(MQTT_CALLBACK_SIGNATURE);
void setMQTTCallback(MQTT_CALLBACK_SIGNATURE);
void setWifiCallback(void (*callback)());
void setInitCallback(void (*callback)());
void sendHACommand(const char * s);
void sendStart();
void reconnect();
void updateNetwork();
const char * getSSID();
void setSSID(const char * ssid);
const char * getPASS();
void setPASS(const char * pass);
const char * getMQTTIP();
void setMQTTIP(const char * mqttIP);
void setMQTTIP(const char * mqttIP, const char * mqttUser, const char * mqttPass);
uint8_t getMQTTQOS();
void setMQTTQOS(uint8_t qos);
String getIP();
IPAddress getIPAddress();
uint8_t getStatus();
void setNetInfo(netInfo newNetwork);
void setNetInfo(netInfo * newNetwork);
netInfo * getNetInfo();
void setHopping(bool canHop);
void listSubscriptions();
void OTA_enable();
void OTA_disable();
void OTA_begin();
void setBoottime(const char * boottime);
void consoleHandle();
private:
netInfo _currentNet;
PubSubClient client;
WiFiClient wifiClient;
WiFiClientSecure wifiClientSecure;
const char * _fingerprint;
bool _useSecureClient = false;
char _clientName[40];
void (*_wifiCallback)();
bool _wifiCallbackSet = false;
void (*_initCallback)();
bool _initCallbackSet = false;
std::function<void(char *, uint8_t *, uint8_t)> _mqttCallback;
bool _mqttCallbackSet = false;
uint8_t _connectionStatus = NO_CONNECTION;
uint8_t _netCount = 0;
uint8_t _currentIndex = 0;
bool _ssidSet = false;
bool _passSet = false;
bool _mqttSet = false;
bool _mqttUserSet = false;
bool _mqttPassSet = false;
bool _useOTA = false;
bool _OTArunning = false;
bool _hoppingAllowed = false;
bool _hasBegun = false;
netInfo ** _netList;
bool _verboseMessages = true;
subscription _subscriptions[MAX_SUBSCRIPTIONS];
char _hostname[24];
uint8_t _qos = DEFAULT_QOS;
IPAddress _apIP = IPAddress(192, 168, 1, 254);
void changeNetwork();
String macToStr(const uint8_t * mac);
bool checkParams();
void resubscribe();
uint8_t setConnectionStatus();
char _boottime[24];
char _app_name[24];
char _app_version[10];
// console/telnet specific
WiFiClient telnetClient;
bool _telnetConnected = false; // Client is connected ?
bool _newLine = true; // New line write ?
char _command[COMMAND_LENGTH]; // Command received, includes options seperated by a space
uint32_t _lastTimeCommand = millis(); // Last time command received
command_t * _helpProjectCmds; // Help of commands setted by project
uint8_t _helpProjectCmds_count; // # available commands
void (*_consoleCallbackProjectCmds)(); // Callable for projects commands
void consoleShowHelp();
void consoleProcessCommand();
bool isCRLF(char character);
char bufferPrint[BUFFER_PRINT];
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

190
src/ems.h
View File

@@ -1,26 +1,28 @@
/*
* Header file for EMS.cpp
*
* You shouldn't need to change much in this file
*/
#pragma once
#include "emsuart.h"
#include "my_config.h" // include custom configuration settings
#include <Arduino.h>
#include <CircularBuffer.h> // https://github.com/rlogiacco/CircularBuffer
#include <MyESP.h>
// EMS IDs
#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs
#define EMS_ID_BOILER 0x08 // Fixed - also known as MC10.
#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as "Service Key"
#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs
#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as "Service Key"
#define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC
#define EMS_MAX_TELEGRAM_LENGTH 99 // max length of a telegram, including CRC
//#define EMS_ID_THERMOSTAT 0xFF // Fixed - to recognize a Thermostat
//#define EMS_ID_BOILER 0x08 // Fixed - also known as MC10.
#define EMS_TX_MAXBUFFERSIZE 128 // max size of the buffer. packets are 32 bits
#define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC
#define EMS_ID_THERMOSTAT_RC20 0x17 // RC20 (e.g. Moduline 300)
#define EMS_ID_THERMOSTAT_RC30 0x10 // RC30 (e.g. Moduline 400)
#define EMS_ID_THERMOSTAT_EASY 0x18 // TC100 (Nefit Easy)
// max length of a telegram, including CRC, for Rx and Tx.
// This can differs per firmware version and typically 32 is the max
#define EMS_MAX_TELEGRAM_LENGTH 99
// define here the EMS telegram types you need
@@ -41,8 +43,11 @@
#define EMS_TYPE_UBASetPoints 0x1A
#define EMS_TYPE_UBAFunctionTest 0x1D
#define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature
#define EMS_OFFSET_UBAParameterWW_wwactivated 1 // WW Activated
#define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature
#define EMS_OFFSET_UBAParameterWW_wwactivated 1 // WW Activated
#define EMS_OFFSET_UBAParameterWW_wwComfort 9 // WW is in comfort or eco mode
#define EMS_VALUE_UBAParameterWW_wwComfort_Comfort 0x00 // the value for comfort
#define EMS_VALUE_UBAParameterWW_wwComfort_Eco 0xD8 // the value for eco
/*
* Thermostat...
@@ -64,14 +69,21 @@
#define EMS_OFFSET_RC30Set_mode 23 // position of thermostat mode
#define EMS_OFFSET_RC30Set_temp 28 // position of thermostat setpoint temperature
// RC35 specific - not implemented yet
#define EMS_TYPE_RC35StatusMessage 0x3E // is an automatic thermostat broadcast giving us temps
#define EMS_TYPE_RC35Set 0x3D // for setting values like temp and mode
#define EMS_OFFSET_RC35Set_mode 7 // position of thermostat mode
#define EMS_OFFSET_RC35Set_temp 2 // position of thermostat setpoint temperature
// Easy specific
#define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat
// default values
#define EMS_VALUE_INT_ON 1 // boolean true
#define EMS_VALUE_INT_OFF 0 // boolean false
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit ints
#define EMS_VALUE_FLOAT_NOTSET -255 // float unset
#define EMS_VALUE_INT_ON 1 // boolean true
#define EMS_VALUE_INT_OFF 0 // boolean false
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit ints
#define EMS_VALUE_LONG_NOTSET 0xFFFFFF // for 3-byte longs
#define EMS_VALUE_FLOAT_NOTSET -255 // float unset
/* EMS UART transfer status */
typedef enum {
@@ -116,6 +128,7 @@ typedef struct {
bool emsBoilerEnabled; // is the boiler online
_EMS_SYS_LOGGING emsLogging; // logging
bool emsRefreshed; // fresh data, needs to be pushed out to MQTT
bool emsBusConnected; // is there an active bus
} _EMS_Sys_Status;
// The Tx send package
@@ -130,11 +143,13 @@ typedef struct {
uint8_t comparisonValue; // value to compare against during a validate
uint8_t comparisonOffset; // offset of where the byte is we want to compare too later
uint8_t comparisonPostRead; // after a successful write call this to read
bool hasSent; // has been sent, just pending ack
bool forceRefresh; // should we send to MQTT after a successful Tx?
uint8_t data[EMS_TX_MAXBUFFERSIZE];
unsigned long timestamp; // when created
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
} _EMS_TxTelegram;
#define EMS_TX_TELEGRAM_QUEUE_MAX 20 // max size of Tx FIFO queue
// default empty Tx
const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
EMS_TX_TELEGRAM_INIT, // action
@@ -147,11 +162,42 @@ const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
0, // comparisonValue
0, // comparisonOffset
EMS_ID_NONE, // comparisonPostRead
false, // hasSent
false, // forceRefresh
0, // timestamp
{0x00} // data
};
// Known Buderus non-Thermostat types
typedef enum {
EMS_MODEL_NONE,
EMS_MODEL_ALL, // common for all devices
// service key
EMS_MODEL_SERVICEKEY, // this is us
// main buderus boiler type devices
EMS_MODEL_BK15,
EMS_MODEL_UBA,
EMS_MODEL_BC10,
EMS_MODEL_MM10,
EMS_MODEL_WM10,
// thermostats
EMS_MODEL_ES73,
EMS_MODEL_RC20,
EMS_MODEL_RC30,
EMS_MODEL_RC35,
EMS_MODEL_EASY
} _EMS_MODEL_ID;
typedef struct {
_EMS_MODEL_ID model_id;
uint8_t product_id;
uint8_t type_id;
char model_string[50];
} _Model_Type;
/*
* Telegram package defintions
*/
@@ -160,54 +206,81 @@ typedef struct { // UBAParameterWW
uint8_t wWSelTemp; // Warm Water selected temperature
uint8_t wWCircPump; // Warm Water circulation pump Available
uint8_t wWDesiredTemp; // Warm Water desired temperature
uint8_t wWComfort; // Warm water comfort or ECO mode
// UBAMonitorFast
uint8_t selFlowTemp; // Selected flow temperature
float curFlowTemp; // Current flow temperature
float retTemp; // Return temperature
uint8_t burnGas; // Gas on/off
uint8_t fanWork; // Fan on/off
uint8_t ignWork; // Ignition on/off
uint8_t heatPmp; // Circulating pump on/off
uint8_t wWHeat; // 3-way valve on WW
uint8_t wWCirc; // Circulation on/off
uint8_t selBurnPow; // Burner max power
uint8_t curBurnPow; // Burner current power
float flameCurr; // Flame current in micro amps
float sysPress; // System pressure
uint8_t selFlowTemp; // Selected flow temperature
float curFlowTemp; // Current flow temperature
float retTemp; // Return temperature
uint8_t burnGas; // Gas on/off
uint8_t fanWork; // Fan on/off
uint8_t ignWork; // Ignition on/off
uint8_t heatPmp; // Circulating pump on/off
uint8_t wWHeat; // 3-way valve on WW
uint8_t wWCirc; // Circulation on/off
uint8_t selBurnPow; // Burner max power
uint8_t curBurnPow; // Burner current power
float flameCurr; // Flame current in micro amps
float sysPress; // System pressure
uint8_t serviceCodeChar1; // First Character in status/service code
uint8_t serviceCodeChar2; // Second Character in status/service code
// UBAMonitorSlow
float extTemp; // Outside temperature
float boilTemp; // Boiler temperature
uint8_t pumpMod; // Pump modulation
uint16_t burnStarts; // # burner restarts
uint16_t burnWorkMin; // Total burner operating time
uint16_t heatWorkMin; // Total heat operating time
uint32_t burnStarts; // # burner restarts
uint32_t burnWorkMin; // Total burner operating time
uint32_t heatWorkMin; // Total heat operating time
// UBAMonitorWWMessage
float wWCurTmp; // Warm Water current temperature:
uint32_t wWStarts; // Warm Water # starts
uint32_t wWWorkM; // Warm Water # minutes
uint8_t wWOneTime; // Warm Water one time function on/off
uint8_t wWCurFlow; // Warm Water current flow in l/min
// UBATotalUptimeMessage
uint32_t UBAuptime; // Total UBA working hours
// calculated values
uint8_t tapwaterActive; // Hot tap water is on/off
uint8_t heatingActive; // Central heating is on/off
// settings
char version[10];
uint8_t type_id;
_EMS_MODEL_ID model_id;
} _EMS_Boiler;
// Definition for thermostat type
typedef struct {
_EMS_MODEL_ID model_id;
bool read_supported;
bool write_supported;
} _Thermostat_Type;
#define EMS_THERMOSTAT_READ_YES true
#define EMS_THERMOSTAT_READ_NO false
#define EMS_THERMOSTAT_WRITE_YES true
#define EMS_THERMOSTAT_WRITE_NO false
// Thermostat data
typedef struct {
uint8_t type; // thermostat type (RC30, Easy etc)
float setpoint_roomTemp; // current set temp
float curr_roomTemp; // current room temp
uint8_t mode; // 0=low, 1=manual, 2=auto
uint8_t hour;
uint8_t minute;
uint8_t second;
uint8_t day;
uint8_t month;
uint8_t year;
uint8_t type_id; // the type ID of the thermostat
_EMS_MODEL_ID model_id; // which Thermostat type
bool read_supported;
bool write_supported;
char version[10];
float setpoint_roomTemp; // current set temp
float curr_roomTemp; // current room temp
uint8_t mode; // 0=low, 1=manual, 2=auto
uint8_t hour;
uint8_t minute;
uint8_t second;
uint8_t day;
uint8_t month;
uint8_t year;
} _EMS_Thermostat;
// call back function signature
@@ -215,17 +288,11 @@ typedef void (*EMS_processType_cb)(uint8_t * data, uint8_t length);
// Definition for each EMS type, including the relative callback function
typedef struct {
uint8_t src;
_EMS_MODEL_ID model_id;
uint8_t type;
const char typeString[50];
EMS_processType_cb processType_cb;
} _EMS_Types;
// Definition for thermostat type
typedef struct {
uint8_t id;
const char typeString[50];
} _Thermostat_Types;
} _EMS_Type;
// ANSI Colors
#define COLOR_RESET "\x1B[0m"
@@ -255,28 +322,37 @@ void ems_setExperimental(uint8_t value);
void ems_setPoll(bool b);
void ems_setTxEnabled(bool b);
void ems_setThermostatEnabled(bool b);
void ems_setBoilerEnabled(bool b);
void ems_setLogging(_EMS_SYS_LOGGING loglevel);
void ems_setEmsRefreshed(bool b);
void ems_setBusConnected(bool b);
void ems_setWarmWaterModeComfort(bool comfort);
void ems_getThermostatValues();
void ems_getBoilerValues();
bool ems_getPoll();
bool ems_getTxEnabled();
bool ems_getThermostatEnabled();
bool ems_getBoilerEnabled();
bool ems_getBusConnected();
_EMS_SYS_LOGGING ems_getLogging();
uint8_t ems_getEmsTypesCount();
uint8_t ems_getThermostatTypesCount();
bool ems_getEmsRefreshed();
void ems_getVersions();
_EMS_MODEL_ID ems_getThermostatModel();
void ems_printAllTypes();
void ems_printThermostatType();
void ems_printTxQueue();
void ems_printAllTypes();
char * ems_getThermostatType(char * buffer);
void ems_printTxQueue();
char * ems_getBoilerType(char * buffer);
// private functions
uint8_t _crcCalculator(uint8_t * data, uint8_t len);
void _processType(uint8_t * telegram, uint8_t length);
void _debugPrintPackage(const char * prefix, uint8_t * data, uint8_t len, const char * color);
void _ems_clearTxData();
int _ems_findModel(_EMS_MODEL_ID model_id);
char * _ems_buildModelString(char * buffer, uint8_t size, _EMS_MODEL_ID model_id);
// global so can referenced in other classes
extern _EMS_Sys_Status EMS_Sys_Status;

View File

@@ -9,31 +9,25 @@
// these are set as -D build flags during compilation
// they can be set in platformio.ini or alternatively hard coded here
/*
#define WIFI_SSID "<my_ssid>"
#define WIFI_PASSWORD "<my_password>"
#define MQTT_IP "<broker_ip>"
#define MQTT_USER "<broker_username>"
#define MQTT_PASS "<broker_password>"
*/
// WIFI settings
//#define WIFI_SSID "<my_ssid>"
//#define WIFI_PASSWORD "<my_password>"
// MQTT settings
// Note port is the default 1883
//#define MQTT_IP "<broker_ip>"
//#define MQTT_USER "<broker_username>"
//#define MQTT_PASS "<broker_password>"
// default values
#define BOILER_THERMOSTAT_ENABLED 1 // thermostat support is enabled (1)
#define BOILER_SHOWER_TIMER 1 // monitors how long a shower has taken
#define BOILER_SHOWER_ALERT 0 // send alert if showetime exceeded
// define here the Thermostat type. see ems.h for the supported types
#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_RC20
//#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_RC30
//#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_EASY
// default values for shower logic on/off
#define BOILER_SHOWER_TIMER 1 // monitors how long a shower has taken
#define BOILER_SHOWER_ALERT 0 // send alert if showetime exceeded
// trigger settings to determine if hot tap water or the heating is active
#define EMS_BOILER_BURNPOWER_TAPWATER 100
#define EMS_BOILER_SELFLOWTEMP_HEATING 70
//define maximum settable tapwater temperature, not every installation supports 90 degrees
#define EMS_BOILER_TAPWATER_TEMPERATURE_MAX 60
// if using the shower timer, change these settings
#define SHOWER_PAUSE_TIME 15000 // in ms. 15 seconds, max time if water is switched off & on during a shower
#define SHOWER_MIN_DURATION 120000 // in ms. 2 minutes, before recognizing its a shower
@@ -41,8 +35,10 @@
#define SHOWER_OFFSET_TIME 5000 // in ms. 5 seconds grace time, to calibrate actual time under the shower
#define SHOWER_COLDSHOT_DURATION 10 // in seconds. 10 seconds for cold water before turning back hot water
// if using LEDs to show traffic, configure the GPIOs here
// only works if -DUSE_LED is set in platformio.ini
#define LED_RX D1 // GPIO5
#define LED_TX D2 // GPIO4
#define LED_ERR D3 // GPIO0
// The LED for showing connection errors, either onboard or via an external pull-up LED
// can be disabled using -DNO_LED build flag in platformio.ini
#define BOILER_LED LED_BUILTIN // LED_BULLETIN for onboard LED
//#define BOILER_LED D1 // for external LED like on the latest bbqkees boards
// set this if using an external temperature sensor like a DS18B20
#define TEMPERATURE_SENSOR_PIN D7

View File

@@ -1,2 +1,5 @@
#pragma once
#define APP_NAME "EMS-ESP-Boiler"
#define APP_VERSION "1.1.0"
#define APP_VERSION "1.2.0"
#define APP_HOSTNAME "boiler"