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

626
lib/TelnetSpy/TelnetSpy.cpp Normal file
View File

@@ -0,0 +1,626 @@
/*
* TELNET SERVER FOR ESP8266 / ESP32
* Cloning the serial port via Telnet.
*
* Written by Wolfgang Mattis (arduino@yasheena.de).
* Version 1.1 / September 7, 2018.
* MIT license, all text above must be included in any redistribution.
*/
#ifdef ESP8266
extern "C" {
#include "user_interface.h"
}
#endif
#include "TelnetSpy.h"
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif
static TelnetSpy * actualObject = NULL;
static void TelnetSpy_putc(char c) {
if (actualObject) {
actualObject->write(c);
}
}
static void TelnetSpy_ignore_putc(char c) {
;
}
TelnetSpy::TelnetSpy() {
port = TELNETSPY_PORT;
telnetServer = NULL;
started = false;
listening = false;
firstMainLoop = true;
usedSer = &Serial;
storeOffline = true;
connected = false;
callbackConnect = NULL;
callbackDisconnect = NULL;
welcomeMsg = strdup(TELNETSPY_WELCOME_MSG);
rejectMsg = strdup(TELNETSPY_REJECT_MSG);
minBlockSize = TELNETSPY_MIN_BLOCK_SIZE;
collectingTime = TELNETSPY_COLLECTING_TIME;
maxBlockSize = TELNETSPY_MAX_BLOCK_SIZE;
pingTime = TELNETSPY_PING_TIME;
pingRef = 0xFFFFFFFF;
waitRef = 0xFFFFFFFF;
telnetBuf = NULL;
bufLen = 0;
uint16_t size = TELNETSPY_BUFFER_LEN;
while (!setBufferSize(size)) {
size = size >> 1;
if (size < minBlockSize) {
setBufferSize(minBlockSize);
break;
}
}
debugOutput = TELNETSPY_CAPTURE_OS_PRINT;
if (debugOutput) {
setDebugOutput(true);
}
}
TelnetSpy::~TelnetSpy() {
end();
}
// added by proddy
void TelnetSpy::disconnectClient() {
if (client.connected()) {
client.flush();
client.stop();
}
if (connected && (callbackDisconnect != NULL)) {
callbackDisconnect();
}
connected = false;
}
void TelnetSpy::setPort(uint16_t portToUse) {
port = portToUse;
if (listening) {
if (client.connected()) {
client.flush();
client.stop();
}
if (connected && (callbackDisconnect != NULL)) {
callbackDisconnect();
}
connected = false;
telnetServer->close();
delete telnetServer;
telnetServer = new WiFiServer(port);
if (started) {
telnetServer->begin();
telnetServer->setNoDelay(bufLen > 0);
}
}
}
void TelnetSpy::setWelcomeMsg(char * msg) {
if (welcomeMsg) {
free(welcomeMsg);
}
welcomeMsg = strdup(msg);
}
void TelnetSpy::setRejectMsg(char * msg) {
if (rejectMsg) {
free(rejectMsg);
}
rejectMsg = strdup(msg);
}
void TelnetSpy::setMinBlockSize(uint16_t minSize) {
minBlockSize = min(max((uint16_t)1, minSize), maxBlockSize);
}
void TelnetSpy::setCollectingTime(uint16_t colTime) {
collectingTime = colTime;
}
void TelnetSpy::setMaxBlockSize(uint16_t maxSize) {
maxBlockSize = max(maxSize, minBlockSize);
}
bool TelnetSpy::setBufferSize(uint16_t newSize) {
if (telnetBuf && (bufLen == newSize)) {
return true;
}
if (newSize == 0) {
bufLen = 0;
if (telnetBuf) {
free(telnetBuf);
telnetBuf = NULL;
}
if (telnetServer) {
telnetServer->setNoDelay(false);
}
return true;
}
newSize = max(newSize, minBlockSize);
uint16_t oldBufLen = bufLen;
bufLen = newSize;
uint16_t tmp;
if (!telnetBuf || (bufUsed == 0)) {
bufRdIdx = 0;
bufWrIdx = 0;
bufUsed = 0;
} else {
if (bufLen < oldBufLen) {
if (bufRdIdx < bufWrIdx) {
if (bufWrIdx > bufLen) {
tmp = min(bufLen, (uint16_t)(bufWrIdx - max(bufLen, bufRdIdx)));
memcpy(telnetBuf, &telnetBuf[bufWrIdx - tmp], tmp);
bufWrIdx = tmp;
if (bufWrIdx > bufRdIdx) {
bufRdIdx = bufWrIdx;
} else {
if (bufRdIdx > bufLen) {
bufRdIdx = 0;
}
}
if (bufRdIdx == bufWrIdx) {
bufUsed = bufLen;
} else {
bufUsed = bufWrIdx - bufRdIdx;
}
}
} else {
if (bufWrIdx > bufLen) {
memcpy(telnetBuf, &telnetBuf[bufWrIdx - bufLen], bufLen);
bufRdIdx = 0;
bufWrIdx = 0;
bufUsed = bufLen;
} else {
tmp = min(bufLen - bufWrIdx, oldBufLen - bufRdIdx);
memcpy(&telnetBuf[bufLen - tmp], &telnetBuf[oldBufLen - tmp], tmp);
bufRdIdx = bufLen - tmp;
bufUsed = bufWrIdx + tmp;
}
}
}
}
char * temp = (char *)realloc(telnetBuf, bufLen);
if (!temp) {
return false;
}
telnetBuf = temp;
if (telnetBuf && (bufLen > oldBufLen) && (bufRdIdx > bufWrIdx)) {
tmp = bufLen - (oldBufLen - bufRdIdx);
memcpy(&telnetBuf[tmp], &telnetBuf[bufRdIdx], oldBufLen - bufRdIdx);
bufRdIdx = tmp;
}
if (telnetServer) {
telnetServer->setNoDelay(true);
}
return true;
}
uint16_t TelnetSpy::getBufferSize() {
if (!telnetBuf) {
return 0;
}
return bufLen;
}
void TelnetSpy::setStoreOffline(bool store) {
storeOffline = store;
}
bool TelnetSpy::getStoreOffline() {
return storeOffline;
}
void TelnetSpy::setPingTime(uint16_t pngTime) {
pingTime = pngTime;
if (pingTime == 0) {
pingRef = 0xFFFFFFFF;
} else {
pingRef = (millis() & 0x7FFFFFF) + pingTime;
}
}
void TelnetSpy::setSerial(HardwareSerial * usedSerial) {
usedSer = usedSerial;
}
size_t TelnetSpy::write(uint8_t data) {
if (telnetBuf) {
if (storeOffline || client.connected()) {
if (bufUsed == bufLen) {
if (client.connected()) {
sendBlock();
}
if (bufUsed == bufLen) {
char c;
while (bufUsed > 0) {
c = pullTelnetBuf();
if (c == '\n') {
addTelnetBuf('\r');
break;
}
}
if (peekTelnetBuf() == '\r') {
pullTelnetBuf();
}
}
}
addTelnetBuf(data);
/*
if (data == '\n') {
addTelnetBuf('\r'); // added by proddy, fix for Windows
}
*/
}
} else {
if (client.connected()) {
client.write(data);
}
}
if (usedSer) {
return usedSer->write(data);
}
return 1;
}
int TelnetSpy::available(void) {
if (usedSer) {
int avail = usedSer->available();
if (avail > 0) {
return avail;
}
}
if (client.connected()) {
return telnetAvailable();
}
return 0;
}
int TelnetSpy::read(void) {
int val;
if (usedSer) {
val = usedSer->read();
if (val != -1) {
return val;
}
}
if (client.connected()) {
if (telnetAvailable()) {
val = client.read();
}
}
return val;
}
int TelnetSpy::peek(void) {
int val;
if (usedSer) {
val = usedSer->peek();
if (val != -1) {
return val;
}
}
if (client.connected()) {
if (telnetAvailable()) {
val = client.peek();
}
}
return val;
}
void TelnetSpy::flush(void) {
if (usedSer) {
usedSer->flush();
}
}
#ifdef ESP8266
void TelnetSpy::begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin) {
if (usedSer) {
usedSer->begin(baud, config, mode, tx_pin);
}
started = true;
}
#else // ESP32
void TelnetSpy::begin(unsigned long baud, uint32_t config, int8_t rxPin, int8_t txPin, bool invert) {
if (usedSer) {
usedSer->begin(baud, config, rxPin, txPin, invert);
}
started = true;
}
#endif
void TelnetSpy::end() {
if (debugOutput) {
setDebugOutput(false);
}
if (usedSer) {
usedSer->end();
}
if (client.connected()) {
client.flush();
client.stop();
}
if (connected && (callbackDisconnect != NULL)) {
callbackDisconnect();
}
connected = false;
telnetServer->close();
delete telnetServer;
telnetServer = NULL;
listening = false;
started = false;
}
#ifdef ESP8266
void TelnetSpy::swap(uint8_t tx_pin) {
if (usedSer) {
usedSer->swap(tx_pin);
}
}
void TelnetSpy::set_tx(uint8_t tx_pin) {
if (usedSer) {
usedSer->set_tx(tx_pin);
}
}
void TelnetSpy::pins(uint8_t tx, uint8_t rx) {
if (usedSer) {
usedSer->pins(tx, rx);
}
}
bool TelnetSpy::isTxEnabled(void) {
if (usedSer) {
return usedSer->isTxEnabled();
}
return true;
}
bool TelnetSpy::isRxEnabled(void) {
if (usedSer) {
return usedSer->isRxEnabled();
}
return true;
}
#endif
int TelnetSpy::availableForWrite(void) {
if (usedSer) {
return min(usedSer->availableForWrite(), bufLen - bufUsed);
}
return bufLen - bufUsed;
}
TelnetSpy::operator bool() const {
if (usedSer) {
return (bool)*usedSer;
}
return true;
}
void TelnetSpy::setDebugOutput(bool en) {
debugOutput = en;
if (debugOutput) {
actualObject = this;
#ifdef ESP8266
os_install_putc1((void *)TelnetSpy_putc); // Set system printing (os_printf) to TelnetSpy
system_set_os_print(true);
#else // ESP32 \
// ToDo: How can be done this for ESP32 ?
#endif
} else {
if (actualObject == this) {
#ifdef ESP8266
system_set_os_print(false);
os_install_putc1((void *)TelnetSpy_ignore_putc); // Ignore system printing
#else // ESP32 \
// ToDo: How can be done this for ESP32 ?
#endif
actualObject = NULL;
}
}
}
uint32_t TelnetSpy::baudRate(void) {
if (usedSer) {
return usedSer->baudRate();
}
return 115200;
}
void TelnetSpy::sendBlock() {
uint16_t len = bufUsed;
if (len > maxBlockSize) {
len = maxBlockSize;
}
len = min(len, (uint16_t)(bufLen - bufRdIdx));
client.write(&telnetBuf[bufRdIdx], len);
bufRdIdx += len;
if (bufRdIdx >= bufLen) {
bufRdIdx = 0;
}
bufUsed -= len;
if (bufUsed == 0) {
bufRdIdx = 0;
bufWrIdx = 0;
}
waitRef = 0xFFFFFFFF;
if (pingRef != 0xFFFFFFFF) {
pingRef = (millis() & 0x7FFFFFF) + pingTime;
if (pingRef > 0x7FFFFFFF) {
pingRef -= 0x80000000;
}
}
}
void TelnetSpy::addTelnetBuf(char c) {
telnetBuf[bufWrIdx] = c;
if (bufUsed == bufLen) {
bufRdIdx++;
if (bufRdIdx >= bufLen) {
bufRdIdx = 0;
}
} else {
bufUsed++;
}
bufWrIdx++;
if (bufWrIdx >= bufLen) {
bufWrIdx = 0;
}
}
char TelnetSpy::pullTelnetBuf() {
if (bufUsed == 0) {
return 0;
}
char c = telnetBuf[bufRdIdx++];
if (bufRdIdx >= bufLen) {
bufRdIdx = 0;
}
bufUsed--;
return c;
}
char TelnetSpy::peekTelnetBuf() {
if (bufUsed == 0) {
return 0;
}
return telnetBuf[bufRdIdx];
}
int TelnetSpy::telnetAvailable() {
int n = client.available();
while (n > 0) {
if (0xff == client.peek()) { // If esc char for telnet NVT protocol data remove that telegram:
client.read(); // Remove esc char
n--;
if (0xff == client.peek()) { // If esc sequence for 0xFF data byte...
return n; // ...return info about available data (just this 0xFF data byte)
}
client.read(); // Skip the rest of the telegram of the telnet NVT protocol data
client.read();
n--;
n--;
} else { // If next char is a normal data byte...
return n; // ...return info about available data
}
}
return 0;
}
bool TelnetSpy::isClientConnected() {
return connected;
}
void TelnetSpy::setCallbackOnConnect(telnetSpyCallback callback) {
callbackConnect = callback;
}
void TelnetSpy::setCallbackOnDisconnect(telnetSpyCallback callback) {
callbackDisconnect = callback;
}
void TelnetSpy::handle() {
if (firstMainLoop) {
firstMainLoop = false;
// Between setup() and loop() the configuration for os_print may be changed so it must be renewed
if (debugOutput && (actualObject == this)) {
setDebugOutput(true);
}
}
if (!started) {
return;
}
if (!listening) {
if (WiFi.status() != WL_CONNECTED) {
return;
}
telnetServer = new WiFiServer(port);
telnetServer->begin();
telnetServer->setNoDelay(bufLen > 0);
listening = true;
if (usedSer) {
usedSer->println("[TELNET] Telnet server started"); // added by Proddy
}
}
if (telnetServer->hasClient()) {
if (client.connected()) {
WiFiClient rejectClient = telnetServer->available();
if (strlen(rejectMsg) > 0) {
rejectClient.write((const uint8_t *)rejectMsg, strlen(rejectMsg));
}
rejectClient.flush();
rejectClient.stop();
} else {
client = telnetServer->available();
if (strlen(welcomeMsg) > 0) {
client.write((const uint8_t *)welcomeMsg, strlen(welcomeMsg));
}
}
}
if (client.connected()) {
if (!connected) {
connected = true;
if (pingTime != 0) {
pingRef = (millis() & 0x7FFFFFF) + pingTime;
}
if (callbackConnect != NULL) {
callbackConnect();
}
}
} else {
if (connected) {
connected = false;
client.flush();
client.stop();
pingRef = 0xFFFFFFFF;
waitRef = 0xFFFFFFFF;
if (callbackDisconnect != NULL) {
callbackDisconnect();
}
}
}
if (client.connected() && (bufUsed > 0)) {
if (bufUsed >= minBlockSize) {
sendBlock();
} else {
unsigned long m = millis() & 0x7FFFFFF;
if (waitRef == 0xFFFFFFFF) {
waitRef = m + collectingTime;
if (waitRef > 0x7FFFFFFF) {
waitRef -= 0x80000000;
}
} else {
if (!((waitRef < 0x20000000) && (m > 0x60000000)) && (m >= waitRef)) {
sendBlock();
}
}
}
}
if (client.connected() && (pingRef != 0xFFFFFFFF)) {
unsigned long m = millis() & 0x7FFFFFF;
if (!((pingRef < 0x20000000) && (m > 0x60000000)) && (m >= pingRef)) {
addTelnetBuf(0);
sendBlock();
}
}
}

278
lib/TelnetSpy/TelnetSpy.h Normal file
View File

@@ -0,0 +1,278 @@
/*
* TELNET SERVER FOR ESP8266 / ESP32
* Cloning the serial port via Telnet.
*
* Written by Wolfgang Mattis (arduino@yasheena.de).
* Version 1.1 / September 7, 2018.
* MIT license, all text above must be included in any redistribution.
*/
/*
* DESCRIPTION
*
* This module allows you "Debugging over the air". So if you already use
* ArduinoOTA this is a helpful extension for wireless development. Use
* "TelnetSpy" instead of "Serial" to send data to the serial port and a copy
* to a telnet connection. There is a circular buffer which allows to store the
* data while the telnet connection is not established. So its possible to
* collect data even when the Wifi and Telnet connections are still not
* established. Its also possible to create a telnet session only if it is
* neccessary: then you will get the already collected data as far as it is
* still stored in the circular buffer. Data send from telnet terminal to
* ESP8266 / ESP32 will be handled as data received by serial port. It is also
* possible to use more than one instance of TelnetSpy, for example to send
* control information on the first instance and data dumps on the second
* instance.
*
* USAGE
*
* Add the following line to your sketch:
* #include <TelnetSpy.h>
* TelnetSpy LOG;
*
* Add the following line to your initialisation block ( void setup() ):
* LOG.begin();
*
* Add the following line at the beginning of your main loop ( void loop() ):
* LOG.handle();
*
* Use the following functions of the TelnetSpy object to modify behavior
*
* Change the port number of this telnet server. If a client is already
* connected it will be disconnected.
* Default: 23
* void setPort(uint16_t portToUse);
*
* Change the message which will be send to the telnet client after a session
* is established.
* Default: "Connection established via TelnetSpy.\n"
* void setWelcomeMsg(char* msg);
*
* Change the message which will be send to the telnet client if another
* session is already established.
* Default: "TelnetSpy: Only one connection possible.\n"
* void setRejectMsg(char* msg);
*
* Change the amount of characters to collect before sending a telnet block.
* Default: 64
* void setMinBlockSize(uint16_t minSize);
*
* Change the time (in ms) to wait before sending a telnet block if its size is
* less than <minSize> (defined by setMinBlockSize).
* Default: 100
* void setCollectingTime(uint16_t colTime);
*
* Change the maximum size of the telnet packets to send.
* Default: 512
* void setMaxBlockSize(uint16_t maxSize);
*
* Change the size of the ring buffer. Set it to 0 to disable buffering.
* Changing size tries to preserve the already collected data. If the new
* buffer size is too small the youngest data will be preserved only. Returns
* false if the requested buffer size cannot be set.
* Default: 3000
* bool setBufferSize(uint16_t newSize);
*
* This function returns the actual size of the ring buffer.
* uint16_t getBufferSize();
*
* Enable / disable storing new data in the ring buffer if no telnet connection
* is established. This function allows you to store important data only. You
* can do this by disabling "storeOffline" for sending less important data.
* Default: true
* void setStoreOffline(bool store);
*
* Get actual state of storing data when offline.
* bool getStoreOffline();
*
* If no data is sent via TelnetSpy the detection of a disconnected client has
* a long timeout. Use setPingTime to define the time (in ms) without traffic
* after which a ping (chr(0)) is sent to the telnet client to detect a
* disconnect earlier. Use 0 as parameter to disable pings.
* Default: 1500
* void setPingTime(uint16_t pngTime);
*
* Set the serial port you want to use with this object (especially for ESP32)
* or NULL if no serial port should be used (telnet only).
* Default: Serial
* void setSerial(HardwareSerial* usedSerial);
*
* This function returns true, if a telnet client is connected.
* bool isClientConnected();
*
* This function installs a callback function which will be called on every
* telnet connect of this object (except rejected connect tries). Use NULL to
* remove the callback.
* Default: NULL
* void setCallbackOnConnect(void (*callback)());
*
* This function installs a callback function which will be called on every
* telnet disconnect of this object (except rejected connect tries). Use NULL
* to remove the callback.
* Default: NULL
* void setCallbackOnDisconnect(void (*callback)());
*
* HINT
*
* Add the following lines to your sketch:
* #define SERIAL TelnetSpy
* //#define SERIAL Serial
*
* Replace "Serial" with "SERIAL" in your sketch. Now you can switch between
* serial only and serial with telnet by changing the comments of the defines
* only.
*
* IMPORTANT
*
* To connect to the telnet server you have to:
* - establish the Wifi connection
* - execute "TelnetSpy.begin(WhatEverYouWant);"
*
* The order is not important.
*
* All you do with "Serial" you can also do with "TelnetSpy", but remember:
* Transfering data also via telnet will need more performance than the serial
* port only. So time critical things may be influenced.
*
* It is not possible to establish more than one telnet connection at the same
* time. But its possible to use more than one instance of TelnetSpy.
*
* If you have problems with low memory you may reduce the value of the define
* TELNETSPY_BUFFER_LEN for a smaller ring buffer on initialisation.
*
* Usage of void setDebugOutput(bool) to enable / disable of capturing of
* os_print calls when you have more than one TelnetSpy instance: That
* TelnetSpy object will handle this functionality where you used
* setDebugOutput at last. On default TelnetSpy has the capturing of OS_print
* calls enabled. So if you have more instances the last created instance will
* handle the capturing.
*/
#ifndef TelnetSpy_h
#define TelnetSpy_h
#define TELNETSPY_BUFFER_LEN 3000
#define TELNETSPY_MIN_BLOCK_SIZE 64
#define TELNETSPY_COLLECTING_TIME 100
#define TELNETSPY_MAX_BLOCK_SIZE 512
#define TELNETSPY_PING_TIME 1500
#define TELNETSPY_PORT 23
#define TELNETSPY_CAPTURE_OS_PRINT true
#define TELNETSPY_WELCOME_MSG "Connection established via TelnetSpy2.\n"
#define TELNETSPY_REJECT_MSG "TelnetSpy: Only one connection possible.\n"
#ifdef ESP8266
#include <ESP8266WiFi.h>
#else // ESP32
#include <WiFi.h>
#endif
#include <WiFiClient.h>
class TelnetSpy : public Stream {
public:
TelnetSpy();
~TelnetSpy();
void handle(void);
void setPort(uint16_t portToUse);
void setWelcomeMsg(char * msg);
void setRejectMsg(char * msg);
void setMinBlockSize(uint16_t minSize);
void setCollectingTime(uint16_t colTime);
void setMaxBlockSize(uint16_t maxSize);
bool setBufferSize(uint16_t newSize);
uint16_t getBufferSize();
void setStoreOffline(bool store);
bool getStoreOffline();
void setPingTime(uint16_t pngTime);
void setSerial(HardwareSerial * usedSerial);
bool isClientConnected();
void disconnectClient(); // added by Proddy
typedef std::function<void()> telnetSpyCallback; // added by Proddy
void setCallbackOnConnect(telnetSpyCallback callback); // changed by proddy
void setCallbackOnDisconnect(telnetSpyCallback callback); // changed by proddy
// Functions offered by HardwareSerial class:
#ifdef ESP8266
void begin(unsigned long baud) {
begin(baud, SERIAL_8N1, SERIAL_FULL, 1);
}
void begin(unsigned long baud, SerialConfig config) {
begin(baud, config, SERIAL_FULL, 1);
}
void begin(unsigned long baud, SerialConfig config, SerialMode mode) {
begin(baud, config, mode, 1);
}
void begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin);
#else // ESP32
void begin(unsigned long baud, uint32_t config = SERIAL_8N1, int8_t rxPin = -1, int8_t txPin = -1, bool invert = false);
#endif
void end();
#ifdef ESP8266
void swap() {
swap(1);
}
void swap(uint8_t tx_pin);
void set_tx(uint8_t tx_pin);
void pins(uint8_t tx, uint8_t rx);
bool isTxEnabled(void);
bool isRxEnabled(void);
#endif
int available(void) override;
int peek(void) override;
int read(void) override;
int availableForWrite(void);
void flush(void) override;
size_t write(uint8_t) override;
inline size_t write(unsigned long n) {
return write((uint8_t)n);
}
inline size_t write(long n) {
return write((uint8_t)n);
}
inline size_t write(unsigned int n) {
return write((uint8_t)n);
}
inline size_t write(int n) {
return write((uint8_t)n);
}
using Print::write;
operator bool() const;
void setDebugOutput(bool);
uint32_t baudRate(void);
protected:
void sendBlock(void);
void addTelnetBuf(char c);
char pullTelnetBuf();
char peekTelnetBuf();
int telnetAvailable();
WiFiServer * telnetServer;
WiFiClient client;
uint16_t port;
HardwareSerial * usedSer;
bool storeOffline;
bool started;
bool listening;
bool firstMainLoop;
unsigned long waitRef;
unsigned long pingRef;
uint16_t pingTime;
char * welcomeMsg;
char * rejectMsg;
uint16_t minBlockSize;
uint16_t collectingTime;
uint16_t maxBlockSize;
bool debugOutput;
char * telnetBuf;
uint16_t bufLen;
uint16_t bufUsed;
uint16_t bufRdIdx;
uint16_t bufWrIdx;
bool connected;
telnetSpyCallback callbackConnect; // added by proddy
telnetSpyCallback callbackDisconnect; // added by proddy
};
#endif

617
lib/myESP/MyESP.cpp Normal file
View File

@@ -0,0 +1,617 @@
/*
* MyESP - my ESP helper class to handle Wifi, MDNS, MQTT and Telnet
*
* Paul Derbyshire - December 2018
*
* Some ideas from https://github.com/JoaoLopesF/ESP8266-RemoteDebug-Telnet
* Ideas from Espurna https://github.com/xoseperez/espurna
*/
#include "MyESP.h"
// constructor
MyESP::MyESP() {
_app_hostname = strdup("MyESP");
_app_name = strdup("MyESP");
_app_version = strdup("1.0.0");
_boottime = strdup("unknown");
_extern_WIFICallback = NULL;
_extern_WIFICallbackSet = false;
_consoleCallbackProjectCmds = NULL;
_helpProjectCmds = NULL;
_helpProjectCmds_count = 0;
_mqtt_host = NULL;
_mqtt_password = NULL;
_mqtt_username = NULL;
_wifi_password = NULL;
_wifi_ssid = NULL;
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
_verboseMessages = true;
_command = (char *)malloc(TELNET_MAX_COMMAND_LENGTH); // reserve buffer for Serial/Telnet commands
}
MyESP::~MyESP() {
end();
}
// end
void MyESP::end() {
free(_command);
SerialAndTelnet.end();
jw.disconnect();
}
// general debug to the telnet or serial channels
void MyESP::myDebug(const char * format, ...) {
va_list args;
va_start(args, format);
char test[1];
int len = ets_vsnprintf(test, 1, format, args) + 1;
char * buffer = new char[len];
ets_vsnprintf(buffer, len, format, args);
va_end(args);
SerialAndTelnet.println(buffer);
delete[] buffer;
}
// for flashmemory. Must use PSTR()
void MyESP::myDebug_P(PGM_P format_P, ...) {
char format[strlen_P(format_P) + 1];
memcpy_P(format, format_P, sizeof(format));
va_list args;
va_start(args, format_P);
char test[1];
int len = ets_vsnprintf(test, 1, format, args) + 1;
char * buffer = new char[len];
ets_vsnprintf(buffer, len, format, args);
va_end(args);
SerialAndTelnet.println(buffer);
delete[] buffer;
}
// called when WiFi is connected, and used to start MDNS
void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
if ((code == MESSAGE_CONNECTED) || (code == MESSAGE_ACCESSPOINT_CREATED)) {
#if defined(ARDUINO_ARCH_ESP32)
String hostname = String(WiFi.getHostname());
#else
String hostname = WiFi.hostname();
#endif
myDebug_P(PSTR("[WIFI] ----------------------------------------------"));
myDebug_P(PSTR("[WIFI] SSID %s"), WiFi.SSID().c_str());
myDebug_P(PSTR("[WIFI] CH %d"), WiFi.channel());
myDebug_P(PSTR("[WIFI] RSSI %d"), WiFi.RSSI());
myDebug_P(PSTR("[WIFI] IP %s"), WiFi.localIP().toString().c_str());
myDebug_P(PSTR("[WIFI] MAC %s"), WiFi.macAddress().c_str());
myDebug_P(PSTR("[WIFI] GW %s"), WiFi.gatewayIP().toString().c_str());
myDebug_P(PSTR("[WIFI] MASK %s"), WiFi.subnetMask().toString().c_str());
myDebug_P(PSTR("[WIFI] DNS %s"), WiFi.dnsIP().toString().c_str());
myDebug_P(PSTR("[WIFI] HOST %s"), hostname.c_str());
myDebug_P(PSTR("[WIFI] ----------------------------------------------"));
if (WiFi.getMode() & WIFI_AP) {
myDebug_P(PSTR("[WIFI] MODE AP --------------------------------------"));
myDebug_P(PSTR("[WIFI] SSID %s"), jw.getAPSSID().c_str());
myDebug_P(PSTR("[WIFI] IP %s"), WiFi.softAPIP().toString().c_str());
myDebug_P(PSTR("[WIFI] MAC %s"), WiFi.softAPmacAddress().c_str());
myDebug_P(PSTR("[WIFI] ----------------------------------------------"));
}
// start MDNS
if (MDNS.begin((char *)hostname.c_str())) {
myDebug_P(PSTR("[MDNS] OK"));
} else {
myDebug_P(PSTR("[MDNS] FAIL"));
}
// call any final custom settings
if (_extern_WIFICallbackSet) {
myDebug_P(PSTR("[WIFI] calling custom wifi settings function"));
_extern_WIFICallback(); // call callback to set any custom things
}
}
if (code == MESSAGE_CONNECTING) {
myDebug_P(PSTR("[WIFI] Connecting to %s"), parameter);
}
if (code == MESSAGE_CONNECT_FAILED) {
myDebug_P(PSTR("[WIFI] Could not connect to %s"), parameter);
}
if (code == MESSAGE_DISCONNECTED) {
myDebug_P(PSTR("[WIFI] Disconnected"));
}
}
// received MQTT message
void MyESP::_mqttOnMessage(char * topic, char * payload, size_t len) {
if (len == 0)
return;
char message[len + 1];
strlcpy(message, (char *)payload, len + 1);
// myDebug_P(PSTR("[MQTT] Received %s => %s"), topic, message);
// check for our default ones
if ((strcmp(topic, MQTT_HA) == 0) && (strcmp(message, MQTT_TOPIC_START_PAYLOAD) == 0)) {
myDebug_P(PSTR("[MQTT] HA rebooted - restarting device"));
resetESP();
return;
}
char s[100];
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, MQTT_TOPIC_START);
if (strcmp(topic, s) == 0) {
myDebug_P(PSTR("[MQTT] boottime: %s"), message);
setBoottime(message);
return;
}
// Send message event to custom service
(_mqtt_callback)(MQTT_MESSAGE_EVENT, topic, message);
}
// MQTT subscribe
void MyESP::mqttSubscribe(const char * topic) {
if (mqttClient.connected() && (strlen(topic) > 0)) {
char s[100];
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, topic);
unsigned int packetId = mqttClient.subscribe(s, MQTT_QOS);
myDebug_P(PSTR("[MQTT] Subscribing to %s (PID %d)"), s, packetId);
}
}
// MQTT unsubscribe
void MyESP::mqttUnsubscribe(const char * topic) {
if (mqttClient.connected() && (strlen(topic) > 0)) {
char s[100];
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, topic);
unsigned int packetId = mqttClient.unsubscribe(s);
myDebug_P(PSTR("[MQTT] Unsubscribing to %s (PID %d)"), s, packetId);
}
}
// MQTT Publish
void MyESP::mqttPublish(const char * topic, const char * payload) {
char s[500];
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, topic);
// myDebug_P(PSTR("[MQTT] Sending pubish to %s with payload %s"), s, payload);
mqttClient.publish(s, MQTT_QOS, false, payload);
}
// MQTT onConnect - when a connect is established automatically subscribe to my HA topics
void MyESP::_mqttOnConnect() {
myDebug_P(PSTR("[MQTT] Connected"));
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
#ifndef NO_HA
// standard subscribes for HA (Home Assistant)
mqttClient.subscribe(MQTT_HA, MQTT_QOS); // to "ha"
mqttSubscribe(MQTT_TOPIC_START); // to home/<hostname>/start
// send specific start command to HA via MQTT, which returns the boottime
char s[48];
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, MQTT_TOPIC_START);
mqttClient.publish(s, MQTT_QOS, false, MQTT_TOPIC_START_PAYLOAD);
#endif
// call custom
(_mqtt_callback)(MQTT_CONNECT_EVENT, NULL, NULL);
}
// MQTT setup
void MyESP::_mqtt_setup() {
if (!_mqtt_host) {
myDebug_P(PSTR("[MQTT] disabled"));
}
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
mqttClient.onConnect([this](bool sessionPresent) { _mqttOnConnect(); });
mqttClient.onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {
myDebug_P(PSTR("[MQTT] TCP Disconnected"));
(_mqtt_callback)(MQTT_DISCONNECT_EVENT, NULL, NULL); // call callback with disconnect
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) {
myDebug_P(PSTR("[MQTT] Identifier Rejected"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) {
myDebug_P(PSTR("[MQTT] Server unavailable"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) {
myDebug_P(PSTR("[MQTT] Malformed credentials"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
myDebug_P(PSTR("[MQTT] Not authorized"));
}
});
//mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { myDebug_P(PSTR("[MQTT] Subscribe ACK for PID %d"), packetId); });
//mqttClient.onPublish([this](uint16_t packetId) { myDebug_P(PSTR("[MQTT] Publish ACK for PID %d"), packetId); });
mqttClient.onMessage(
[this](char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
_mqttOnMessage(topic, payload, len);
});
}
// WiFI setup
void MyESP::_wifi_setup() {
jw.setHostname(_app_hostname); // Set WIFI hostname (otherwise it would be ESP-XXXXXX)
jw.subscribe([this](justwifi_messages_t code, char * parameter) { _wifiCallback(code, parameter); });
jw.enableAP(false);
jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
jw.setReconnectTimeout(WIFI_RECONNECT_INTERVAL);
jw.enableAPFallback(true); // AP mode only as fallback, but disabled
jw.enableSTA(true); // Enable STA mode (connecting to a router)
jw.enableScan(false); // Configure it to scan available networks and connect in order of dBm
jw.cleanNetworks(); // Clean existing network configuration
jw.addNetwork(_wifi_ssid, _wifi_password); // Add a network
}
// MDNS setup
void MyESP::_mdns_setup() {
MDNS.addService("telnet", "tcp", TELNETSPY_PORT);
// for OTA discovery
MDNS.addServiceTxt("arduino", "tcp", "app_name", (const char *)_app_name);
MDNS.addServiceTxt("arduino", "tcp", "app_version", (const char *)_app_version);
MDNS.addServiceTxt("arduino", "tcp", "mac", WiFi.macAddress());
{
char buffer[6] = {0};
itoa(ESP.getFlashChipRealSize() / 1024, buffer, 10);
MDNS.addServiceTxt("arduino", "tcp", "mem_size", (const char *)buffer);
}
{
char buffer[6] = {0};
itoa(ESP.getFlashChipSize() / 1024, buffer, 10);
MDNS.addServiceTxt("arduino", "tcp", "sdk_size", (const char *)buffer);
}
{
char buffer[6] = {0};
itoa(ESP.getFreeSketchSpace(), buffer, 10);
MDNS.addServiceTxt("arduino", "tcp", "free_space", (const char *)buffer);
}
}
// OTA Setup
void MyESP::_ota_setup() {
ArduinoOTA.setPort(OTA_PORT);
ArduinoOTA.setHostname(_app_hostname);
ArduinoOTA.onStart([this]() { myDebug_P(PSTR("[OTA] Start")); });
ArduinoOTA.onEnd([this]() { myDebug_P(PSTR("[OTA] Done, restarting...")); });
ArduinoOTA.onProgress([this](unsigned int progress, unsigned int total) {
static unsigned int _progOld;
unsigned int _prog = (progress / (total / 100));
if (_prog != _progOld) {
myDebug_P(PSTR("[OTA] Progress: %u%%\r"), _prog);
_progOld = _prog;
}
});
ArduinoOTA.onError([this](ota_error_t error) {
if (error == OTA_AUTH_ERROR)
myDebug_P(PSTR("Auth Failed"));
else if (error == OTA_BEGIN_ERROR)
myDebug_P(PSTR("Begin Failed"));
else if (error == OTA_CONNECT_ERROR)
myDebug_P(PSTR("Connect Failed"));
else if (error == OTA_RECEIVE_ERROR)
myDebug_P(PSTR("Receive Failed"));
else if (error == OTA_END_ERROR)
myDebug_P(PSTR("End Failed"));
});
ArduinoOTA.begin();
}
// sets boottime
void MyESP::setBoottime(char * boottime) {
if (_boottime) {
free(_boottime);
}
_boottime = strdup(boottime);
}
// returns boottime
char * MyESP::getBoottime() {
return _boottime;
}
// Set callback of sketch function to process project messages
void MyESP::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
}
void MyESP::_telnetConnected() {
myDebug_P(PSTR("[TELNET] Telnet connection established"));
_consoleShowHelp(); // Show the initial message
}
void MyESP::_telnetDisconnected() {
myDebug_P(PSTR("[TELNET] Telnet connection closed"));
}
// Initialize the telnet server
void MyESP::_telnet_setup() {
SerialAndTelnet.setWelcomeMsg("");
SerialAndTelnet.setCallbackOnConnect([this]() { _telnetConnected(); });
SerialAndTelnet.setCallbackOnDisconnect([this]() { _telnetDisconnected(); });
SerialAndTelnet.begin(115200);
SerialAndTelnet.setDebugOutput(false);
#ifndef DEBUG_SUPPORT
SerialAndTelnet.setSerial(NULL);
#endif
// init command buffer for console commands
memset(_command, 0, TELNET_MAX_COMMAND_LENGTH);
}
// Show help of commands
void MyESP::_consoleShowHelp() {
String help = "\n\r**********************************************\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";
#ifdef DEBUG_SUPPORT
help += "* !! in DEBUG_SUPPORT mode !!\n\r";
#endif
help += "*\n\r* Commands:\n\r* ?=this help, CTRL-D=quit, $=show free memory, !=reboot ESP, &=suspend all messages\n\r";
// 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");
}
}
SerialAndTelnet.println(help.c_str());
}
// reset / restart
void MyESP::resetESP() {
myDebug_P(PSTR("* Reboot ESP..."));
end();
#if defined(ARDUINO_ARCH_ESP32)
ESP.reset(); // for ESP8266 only
#else
ESP.restart();
#endif
}
// Get last command received
char * MyESP::consoleGetLastCommand() {
return _command;
}
// Process user command over telnet
void MyESP::consoleProcessCommand() {
uint8_t cmd = _command[0];
// Process the command
if (cmd == '?') {
_consoleShowHelp(); // Show help
} else if (cmd == '$') {
myDebug("* Free RAM (bytes): %d", ESP.getFreeHeap());
} else if (cmd == '!') {
resetESP();
} else if (cmd == '&') {
_verboseMessages = !_verboseMessages; // toggle
myDebug("Suspend all messages is %s", _verboseMessages ? "disabled" : "enabled");
} else {
// custom Project commands
if (_consoleCallbackProjectCmds) {
_consoleCallbackProjectCmds();
}
}
if (!_verboseMessages) {
myDebug("Warning, all log messages have been supsended. Use & to re-enable.");
}
}
// sends a MQTT notification message to Home Assistant
void MyESP::sendHANotification(const char * message) {
char payload[48];
snprintf(payload, sizeof(payload), "%s : %s", _app_hostname, message);
myDebug_P(PSTR("[MQTT] Sending HA notification %s"), payload);
mqttClient.publish(MQTT_NOTIFICATION, MQTT_QOS, false, payload);
}
// send specific command to HA via MQTT
// format is: home/<hostname>/command with payload <cmd>
void MyESP::sendHACommand(const char * cmd) {
myDebug_P(PSTR("[MQTT] Sending HA command %s"), cmd);
char topic[48];
snprintf(topic, sizeof(topic), "%s%s/%s", MQTT_BASE, _app_hostname, MQTT_TOPIC_COMMAND);
mqttClient.publish(topic, MQTT_QOS, false, cmd);
}
// handler for Telnet
void MyESP::_telnetHandle() {
SerialAndTelnet.handle();
char last = ' '; // To avoid processing double "\r\n"
while (SerialAndTelnet.available()) {
char character = SerialAndTelnet.read(); // Get character
// check for ctrl-D
if ((character == 0xEC) || (character == 0x04)) {
SerialAndTelnet.disconnectClient();
}
// if we reached our buffer limit, send what we have
if (strlen(_command) >= TELNET_MAX_COMMAND_LENGTH) {
consoleProcessCommand(); // Process the command
memset(_command, 0, TELNET_MAX_COMMAND_LENGTH); // reset for next command
}
// Check for newline (CR or LF)
if (_isCRLF(character) == true) {
if (_isCRLF(last) == false) {
if (strlen(_command) > 0) {
consoleProcessCommand(); // Process the command
}
}
memset(_command, 0, TELNET_MAX_COMMAND_LENGTH); // reset for next command
} else if (isPrintable(character)) {
// Concat char to end of buffer
uint16_t len = strlen(_command);
_command[len] = character;
_command[len + 1] = '\0';
}
last = character; // remember last char
}
}
// sets a custom function to run when wifi is started
void MyESP::setWIFICallback(void (*callback)()) {
_extern_WIFICallback = callback;
_extern_WIFICallbackSet = true;
}
// the mqtt callback for after connecting to subscribe and process incoming messages
void MyESP::setMQTTCallback(mqtt_callback_f callback) {
_mqtt_callback = callback;
}
// Is CR or LF ?
bool MyESP::_isCRLF(char character) {
return (character == '\r' || character == '\n');
}
// ensure we have a connection to MQTT broker
void MyESP::_mqttConnect() {
if (!_mqtt_host || mqttClient.connected() || (WiFi.status() != WL_CONNECTED)) {
return;
}
// Check reconnect interval
static unsigned long last = 0;
if (millis() - last < _mqtt_reconnect_delay)
return;
last = millis();
// Increase the reconnect delay
_mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP;
if (_mqtt_reconnect_delay > MQTT_RECONNECT_DELAY_MAX) {
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX;
}
mqttClient.setServer(_mqtt_host, MQTT_PORT);
mqttClient.setClientId(_app_hostname);
if (_mqtt_username && _mqtt_password) {
myDebug_P(PSTR("[MQTT] Connecting to MQTT using user %s & its password"), _mqtt_username);
mqttClient.setCredentials(_mqtt_username, _mqtt_password);
} else {
myDebug_P(PSTR("[MQTT] Connecting to MQTT..."));
}
// Connect to the MQTT broker
mqttClient.connect();
}
// Setup everything we need
void MyESP::setup(char * app_hostname,
char * app_name,
char * app_version,
char * wifi_ssid,
char * wifi_password,
char * mqtt_host,
char * mqtt_username,
char * mqtt_password) {
// get general params first
_app_hostname = strdup(app_hostname);
_app_name = strdup(app_name);
_app_version = strdup(app_version);
// Check SSID too long or missing
if (!wifi_ssid || *wifi_ssid == 0x00 || strlen(wifi_ssid) > 31) {
_wifi_ssid = NULL;
} else {
_wifi_ssid = strdup(wifi_ssid);
}
// Check PASS too long
if (wifi_password && strlen(wifi_password) > 63) {
_wifi_password = NULL;
} else {
_wifi_password = strdup(wifi_password);
}
// can be empty
if (!mqtt_host || *mqtt_host == 0x00) {
_mqtt_host = NULL;
} else {
_mqtt_host = strdup(mqtt_host);
}
// mqtt username and password can be empty
if (!mqtt_username || *mqtt_username == 0x00) {
_mqtt_username = NULL;
} else {
_mqtt_username = strdup(mqtt_username);
}
// can be empty
if (!mqtt_password || *mqtt_password == 0x00) {
_mqtt_password = NULL;
} else {
_mqtt_password = strdup(mqtt_password);
}
// call setup of the services...
_telnet_setup(); // Telnet setup
_wifi_setup(); // WIFI setup
_mqtt_setup(); // MQTT Setup
_mdns_setup(); // MDNS setup
_ota_setup(); // OTA setup
}
/*
* Loop. This is called as often as possible and it handles wifi, telnet, mqtt etc
*/
void MyESP::loop() {
jw.loop(); // WiFi
_telnetHandle(); // Telnet/Debugger
ArduinoOTA.handle(); // OTA
_mqttConnect(); // MQTT
yield(); // ...and breath
}
MyESP myESP;

169
lib/myESP/MyESP.h Normal file
View File

@@ -0,0 +1,169 @@
/*
* MyEsp.h
*
* Paul Derbyshire - December 2018
*/
#pragma once
#ifndef MyEMS_h
#define MyEMS_h
#include <ArduinoJson.h>
#include <ArduinoOTA.h>
#include <AsyncMqttClient.h> // https://github.com/marvinroger/async-mqtt-client
#include <DNSServer.h>
#include <ESPAsyncTCP.h> // https://github.com/me-no-dev/ESPAsyncTCP
#include <JustWifi.h> // https://github.com/xoseperez/justwifi
#include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy
#if defined(ARDUINO_ARCH_ESP32)
#include <ESPmDNS.h>
#else
#include <ESP8266mDNS.h>
#endif
// WIFI
#define WIFI_CONNECT_TIMEOUT 10000 // Connecting timeout for WIFI in ms
#define WIFI_RECONNECT_INTERVAL 60000 // If could not connect to WIFI, retry after this time in ms
// OTA
#define OTA_PORT 8266 // OTA port
// MQTT
#define MQTT_BASE "home/"
#define MQTT_NOTIFICATION MQTT_BASE "notification"
#define MQTT_TOPIC_COMMAND "command"
#define MQTT_TOPIC_START "start"
#define MQTT_TOPIC_START_PAYLOAD "start"
#define MQTT_HA MQTT_BASE "ha"
#define MQTT_PORT 1883 // MQTT port
#define MQTT_QOS 1
#define MQTT_RECONNECT_DELAY_MIN 5000 // Try to reconnect in 5 seconds upon disconnection
#define MQTT_RECONNECT_DELAY_STEP 5000 // Increase the reconnect delay in 5 seconds after each failed attempt
#define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most
// Internal MQTT events
#define MQTT_CONNECT_EVENT 0
#define MQTT_DISCONNECT_EVENT 1
#define MQTT_MESSAGE_EVENT 2
// Telnet
#define TELNET_MAX_COMMAND_LENGTH 80 // length of a command
#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"
typedef struct {
char key[10];
char description[400];
} command_t;
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
// calculates size of an 2d array at compile time
template <typename T, size_t N>
constexpr size_t ArraySize(T (&)[N]) {
return N;
}
// class definition
class MyESP {
public:
MyESP();
~MyESP();
// wifi
void setWIFICallback(void (*callback)());
void setMQTTCallback(mqtt_callback_f callback);
// ha
void sendHACommand(const char * cmd);
void sendHANotification(const char * message);
// mqtt
void mqttSubscribe(const char * topic);
void mqttUnsubscribe(const char * topic);
void mqttPublish(const char * topic, const char * payload);
// debug & telnet
void myDebug(const char * format, ...);
void myDebug_P(PGM_P format_P, ...);
void consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)());
char * consoleGetLastCommand();
void consoleProcessCommand();
void end();
void loop();
void setup(char * app_hostname,
char * app_name,
char * app_version,
char * wifi_ssid,
char * wifi_password,
char * mqtt_host,
char * mqtt_username,
char * mqtt_password);
char * getBoottime();
void setBoottime(char * boottime);
void resetESP();
private:
// mqtt
AsyncMqttClient mqttClient;
unsigned long _mqtt_reconnect_delay;
void _mqttOnMessage(char * topic, char * payload, size_t len);
void _mqttConnect();
void _mqtt_setup();
mqtt_callback_f _mqtt_callback;
void _mqttOnConnect();
void _sendStart();
char * _mqtt_host;
char * _mqtt_username;
char * _mqtt_password;
char * _boottime;
// wifi
DNSServer dnsServer; // For Access Point (AP) support
void _wifiCallback(justwifi_messages_t code, char * parameter);
void _wifi_setup();
void (*_extern_WIFICallback)();
bool _extern_WIFICallbackSet;
char * _wifi_ssid;
char * _wifi_password;
// mdns
void _mdns_setup();
// ota
void _ota_setup();
// telnet & debug
TelnetSpy SerialAndTelnet;
void _telnetConnected();
void _telnetDisconnected();
void _telnetHandle();
void _telnet_setup();
char * _command; // the input command from either Serial or Telnet
command_t * _helpProjectCmds; // Help of commands setted by project
uint8_t _helpProjectCmds_count; // # available commands
void _consoleShowHelp();
void (*_consoleCallbackProjectCmds)(); // Callable for projects commands
void _consoleProcessCommand();
bool _isCRLF(char character);
bool _verboseMessages;
// general
char * _app_hostname;
char * _app_name;
char * _app_version;
};
extern MyESP myESP;
#endif