diff --git a/code/espurna/config/arduino.h b/code/espurna/config/arduino.h index 1e6a80b7..856456ac 100644 --- a/code/espurna/config/arduino.h +++ b/code/espurna/config/arduino.h @@ -48,6 +48,7 @@ //#define ANALOG_SUPPORT 1 //#define COUNTER_SUPPORT 1 //#define DEBUG_SERIAL_SUPPORT 0 +//#define DEBUG_TELNET_SUPPORT 0 //#define DEBUG_UDP_SUPPORT 1 //#define DHT_SUPPORT 1 //#define DOMOTICZ_SUPPORT 0 @@ -61,5 +62,6 @@ //#define NTP_SUPPORT 0 //#define RF_SUPPORT 1 //#define SPIFFS_SUPPORT 1 +//#define TELNET_SUPPORT 0 //#define TERMINAL_SUPPORT 0 //#define WEB_SUPPORT 0 diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 357db0f2..a791c8c3 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -9,6 +9,18 @@ #define ADMIN_PASS "fibonacci" // Default password (WEB, OTA, WIFI) +//------------------------------------------------------------------------------ +// TELNET +//------------------------------------------------------------------------------ + +#ifndef TELNET_SUPPORT +#define TELNET_SUPPORT 1 // Enable telnet support by default +#endif + +#define TELNET_ONLY_AP 1 // By default, allow only connections via AP interface +#define TELNET_PORT 23 // Port to listen to telnet clients +#define TELNET_MAX_CLIENTS 1 // Max number of concurrent telnet clients + //------------------------------------------------------------------------------ // DEBUG //------------------------------------------------------------------------------ @@ -36,10 +48,18 @@ //------------------------------------------------------------------------------ +#ifndef DEBUG_TELNET_SUPPORT +#define DEBUG_TELNET_SUPPORT TELNET_SUPPORT // Enable telnet debug log if telnet is enabled too +#endif + +//------------------------------------------------------------------------------ + // General debug options and macros #define DEBUG_MESSAGE_MAX_LENGTH 80 +#define DEBUG_SUPPORT DEBUG_SERIAL_SUPPORT || DEBUG_UDP_SUPPORT || DEBUG_TELNET_SUPPORT + -#if (DEBUG_SERIAL_SUPPORT==1) || (DEBUG_UDP_SUPPORT==1) +#if DEBUG_SUPPORT #define DEBUG_MSG(...) debugSend(__VA_ARGS__) #define DEBUG_MSG_P(...) debugSend_P(__VA_ARGS__) #endif diff --git a/code/espurna/debug.ino b/code/espurna/debug.ino index 128c357c..f3b16154 100644 --- a/code/espurna/debug.ino +++ b/code/espurna/debug.ino @@ -6,7 +6,7 @@ Copyright (C) 2016-2017 by Xose Pérez */ -#if DEBUG_SERIAL_SUPPORT || DEBUG_UDP_SUPPORT +#if DEBUG_SUPPORT #include #include @@ -44,6 +44,10 @@ void debugSend(const char * format, ...) { } #endif + #if DEBUG_TELNET_SUPPORT + _telnetWrite(buffer, strlen(buffer)); + #endif + } void debugSend_P(PGM_P format, ...) { @@ -77,6 +81,10 @@ void debugSend_P(PGM_P format, ...) { } #endif + #if DEBUG_TELNET_SUPPORT + _telnetWrite(buffer, strlen(buffer)); + #endif + } -#endif // DEBUG_SERIAL_SUPPORT || DEBUG_UDP_SUPPORT +#endif // DEBUG_SUPPORT diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index 0609d56c..a226e790 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -225,6 +225,9 @@ void setup() { delay(500); wifiSetup(); otaSetup(); + #if TELNET_SUPPORT + telnetSetup(); + #endif // Do not run the next services if system is flagged stable if (!systemCheck()) return; diff --git a/code/espurna/ota.ino b/code/espurna/ota.ino index 17116b0e..15764081 100644 --- a/code/espurna/ota.ino +++ b/code/espurna/ota.ino @@ -43,7 +43,7 @@ void otaSetup() { }); ArduinoOTA.onError([](ota_error_t error) { - #if DEBUG_SERIAL_SUPPORT || DEBUG_UDP_SUPPORT + #if DEBUG_SUPPORT DEBUG_MSG_P(PSTR("\n[OTA] Error #%u: "), error); if (error == OTA_AUTH_ERROR) DEBUG_MSG_P(PSTR("Auth Failed\n")); else if (error == OTA_BEGIN_ERROR) DEBUG_MSG_P(PSTR("Begin Failed\n")); diff --git a/code/espurna/settings.h b/code/espurna/settings.h new file mode 100644 index 00000000..24568b6a --- /dev/null +++ b/code/espurna/settings.h @@ -0,0 +1,78 @@ +// ----------------------------------------------------------------------------- +// Stream Injector +// ----------------------------------------------------------------------------- + +#pragma once + +#define STREAM_INJECTOR_BUFFER_SIZE 32 + +class StreamInjector : public Stream { + + public: + + typedef std::function writeCallback; + + StreamInjector(Stream& serial) : _stream(serial) {} + + virtual void callback(writeCallback c) { + _callback = c; + } + + virtual size_t write(uint8_t ch) { + if (_callback) _callback(ch); + return _stream.write(ch); + } + + virtual int read() { + int ch = _stream.read(); + if (ch == -1) { + if (_buffer_read != _buffer_write) { + ch = _buffer[_buffer_read]; + _buffer_read = (_buffer_read + 1) % STREAM_INJECTOR_BUFFER_SIZE; + } + } + return ch; + } + + virtual int available() { + unsigned int bytes = _stream.available(); + if (_buffer_read > _buffer_write) { + bytes += (_buffer_write - _buffer_read + STREAM_INJECTOR_BUFFER_SIZE); + } else if (_buffer_read < _buffer_write) { + bytes += (_buffer_write - _buffer_read); + } + return bytes; + } + + virtual int peek() { + int ch = _stream.peek(); + if (ch == -1) { + if (_buffer_read != _buffer_write) { + ch = _buffer[_buffer_read]; + } + } + return ch; + } + + virtual void flush() { + _stream.flush(); + _buffer_read = _buffer_write; + } + + virtual void inject(char *data, size_t len) { + for (int i=0; i #include "spi_flash.h" #include -#ifdef DEBUG_PORT -Embedis embedis(DEBUG_PORT); +#if TELNET_SUPPORT + #include "settings.h" + #ifdef DEBUG_PORT + StreamInjector _serial = StreamInjector(DEBUG_PORT); + #else + StreamInjector _serial = StreamInjector(Serial); + #endif + Embedis embedis(_serial); #else -Embedis embedis(Serial); + #ifdef DEBUG_PORT + Embedis embedis(DEBUG_PORT); + #else + Embedis embedis(_serial); + #endif #endif bool _settings_save = false; @@ -23,6 +33,12 @@ bool _settings_save = false; // Settings // ----------------------------------------------------------------------------- +#if TELNET_SUPPORT + void settingsInject(void *data, size_t len) { + _serial.inject((char *) data, len); + } +#endif + size_t settingsMaxSize() { size_t size = EEPROM_SIZE; if (size > SPI_FLASH_SEC_SIZE) size = SPI_FLASH_SEC_SIZE; @@ -82,6 +98,12 @@ void settingsSetup() { EEPROM.begin(SPI_FLASH_SEC_SIZE); + #if TELNET_SUPPORT + _serial.callback([](uint8_t ch) { + telnetWrite(ch); + }); + #endif + Embedis::dictionary( F("EEPROM"), SPI_FLASH_SEC_SIZE, [](size_t pos) -> char { return EEPROM.read(pos); }, diff --git a/code/espurna/telnet.ino b/code/espurna/telnet.ino new file mode 100644 index 00000000..1c28244b --- /dev/null +++ b/code/espurna/telnet.ino @@ -0,0 +1,128 @@ +/* + +TELNET MODULE + +Copyright (C) 2017 by Xose Pérez +Parts of the code have been borrowed from Thomas Sarlandie's NetServer +(https://github.com/sarfata/kbox-firmware/tree/master/src/esp) + +*/ + +#if TELNET_SUPPORT + +#include + +AsyncServer * _telnetServer; +AsyncClient * _telnetClients[TELNET_MAX_CLIENTS]; + +// ----------------------------------------------------------------------------- +// Private methods +// ----------------------------------------------------------------------------- + +void _telnetDisconnect(unsigned char clientId) { + _telnetClients[clientId]->free(); + delete(_telnetClients[clientId]); + _telnetClients[clientId] = 0; + DEBUG_MSG_P(PSTR("[TELNET] Client #%d disconnected\n"), clientId); +} + +bool _telnetWrite(unsigned char clientId, void *data, size_t len) { + if (_telnetClients[clientId] && _telnetClients[clientId]->connected()) { + return (_telnetClients[clientId]->write((const char*) data, len) > 0); + } + return false; +} + +unsigned char _telnetWrite(void *data, size_t len) { + unsigned char count = 0; + for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) { + if (_telnetWrite(i, data, len)) ++count; + } + return count; +} + +void _telnetData(unsigned char clientId, void *data, size_t len) { + + // Capture close connection + char * p = (char *) data; + if (strncmp(p, "close", 5) == 0) { + _telnetClients[clientId]->close(); + return; + } + + // Inject into Embedis stream + settingsInject(data, len); + +} + +void _telnetNewClient(AsyncClient *client) { + + client->onDisconnect([](void *s, AsyncClient *c) { + delete(c); + }); + + #if TELNET_ONLY_AP + if (client->localIP() != WiFi.softAPIP()) { + DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Only local connections\n")); + client->stop(); + } + #endif + + for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) { + if (!_telnetClients[i] || !_telnetClients[i]->connected()) { + + _telnetClients[i] = client; + + client->onAck([i](void *s, AsyncClient *c, size_t len, uint32_t time) { + }, 0); + + client->onData([i](void *s, AsyncClient *c, void *data, size_t len) { + _telnetData(i, data, len); + }, 0); + + client->onDisconnect([i](void *s, AsyncClient *c) { + _telnetDisconnect(i); + }, 0); + + client->onError([i](void *s, AsyncClient *c, int8_t error) { + DEBUG_MSG_P(PSTR("[TELNET] Error %s (%d) on client #%d\n"), c->errorToString(error), error, i); + }, 0); + + client->onTimeout([i](void *s, AsyncClient *c, uint32_t time) { + DEBUG_MSG_P(PSTR("[TELNET] Timeout on client #%d at %i\n"), i, time); + c->close(); + }, 0); + + DEBUG_MSG_P(PSTR("[TELNET] Client #%d connected\n"), i); + return; + + } + } + + DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Too many connections\n")); + client->stop(); + +} + +// ----------------------------------------------------------------------------- +// Public API +// ----------------------------------------------------------------------------- + +unsigned char telnetWrite(unsigned char ch) { + char data[1] = {ch}; + return _telnetWrite(data, 1); +} + +void telnetSetup() { + + _telnetServer = new AsyncServer(TELNET_PORT); + _telnetServer->onClient([](void *s, AsyncClient* c) { + _telnetNewClient(c); + }, 0); + _telnetServer->begin(); + + DEBUG_MSG_P(PSTR("[TELNET] Listening on port %d\n"), TELNET_PORT); + +} + +#endif // TELNET_SUPPORT diff --git a/code/espurna/wifi.ino b/code/espurna/wifi.ino index 931baa10..09daf1da 100644 --- a/code/espurna/wifi.ino +++ b/code/espurna/wifi.ino @@ -172,7 +172,7 @@ void wifiSetup() { // Message callbacks jw.onMessage([](justwifi_messages_t code, char * parameter) { - #if DEBUG_SERIAL_SUPPORT || DEBUG_UDP_SUPPORT + #if DEBUG_SUPPORT if (code == MESSAGE_SCANNING) { DEBUG_MSG_P(PSTR("[WIFI] Scanning\n")); @@ -226,19 +226,34 @@ void wifiSetup() { DEBUG_MSG_P(PSTR("[WIFI] Could not create access point\n")); } - #endif // DEBUG_SERIAL_SUPPORT || DEBUG_UDP_SUPPORT + #endif // DEBUG_SUPPORT // Configure mDNS #if MDNS_SUPPORT + if (code == MESSAGE_CONNECTED || code == MESSAGE_ACCESSPOINT_CREATED) { + if (MDNS.begin(WiFi.getMode() == WIFI_AP ? APP_NAME : (char *) WiFi.hostname().c_str())) { - MDNS.addService("http", "tcp", getSetting("webPort", WEB_PORT).toInt()); - DEBUG_MSG_P(PSTR("[MDNS] OK\n")); + + DEBUG_MSG_P(PSTR("[MDNS] OK\n")); + + #if WEB_SUPPORT + MDNS.addService("http", "tcp", getSetting("webPort", WEB_PORT).toInt()); + #endif + #if TELNET_SUPPORT + MDNS.addService("telnet", "tcp", TELNET_PORT); + #endif + if (code == MESSAGE_CONNECTED) mqttDiscover(); + } else { + DEBUG_MSG_P(PSTR("[MDNS] FAIL\n")); + } + } + #endif // NTP connection reset