Browse Source

Merge branch 'dev' into ssl

Conflicts:
	code/espurna/mqtt.ino
fastled
Xose Pérez 7 years ago
parent
commit
33a7ed4e46
28 changed files with 4492 additions and 3719 deletions
  1. +19
    -0
      CHANGELOG.md
  2. +13
    -2
      README.md
  3. +15
    -2
      code/espurna/alexa.ino
  4. +2
    -0
      code/espurna/analog.ino
  5. +3
    -0
      code/espurna/config/arduino.h
  6. +52
    -5
      code/espurna/config/general.h
  7. +7
    -9
      code/espurna/config/hardware.h
  8. +17
    -0
      code/espurna/config/sensors.h
  9. +2
    -2
      code/espurna/config/version.h
  10. +90
    -0
      code/espurna/counter.ino
  11. BIN
      code/espurna/data/index.html.gz
  12. +33
    -17
      code/espurna/debug.ino
  13. +141
    -41
      code/espurna/espurna.ino
  14. +6
    -0
      code/espurna/light.ino
  15. +9
    -8
      code/espurna/mqtt.ino
  16. +96
    -45
      code/espurna/nofuss.ino
  17. +1
    -1
      code/espurna/ota.ino
  18. +39
    -2
      code/espurna/relay.ino
  19. +78
    -0
      code/espurna/settings.h
  20. +55
    -11
      code/espurna/settings.ino
  21. +3533
    -3528
      code/espurna/static/index.html.gz.h
  22. +134
    -0
      code/espurna/telnet.ino
  23. +35
    -0
      code/espurna/utils.ino
  24. +34
    -11
      code/espurna/web.ino
  25. +22
    -4
      code/espurna/wifi.ino
  26. +20
    -25
      code/html/custom.js
  27. +21
    -4
      code/html/index.html
  28. +15
    -2
      code/platformio.ini

+ 19
- 0
CHANGELOG.md View File

@ -3,6 +3,25 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.9.2] 2017-08-31
### Added
- System stability check (turns off everything except WIFI AP, OTA and telnet if there is a boot crash loop) (#196)
- Telnet support (enabled by default only on AP interface)
- Option to set WiFi gain from web UI
- Option to disable MQTT from web UI
- MQTT autodiscover, with the option to autoconnect if no broker defined
- Home Assistant MQTT autodiscover feature
- List enabled modules in INIT debug info
- Counter module (counts and reports transitions in a digital pin)
### Changed
- Updated NoFUSS support
- Web UI documentation changes
- Changes in terminal commands
### Fixed
- Crash in settings saving (#190) and fixed UDP debug conditional build clauses
## [1.9.1] 2017-08-27
### Added
- Support to build without NTP support


+ 13
- 2
README.md View File

@ -4,7 +4,9 @@ ESPurna ("spark" in Catalan) is a custom firmware for ESP8266 based smart switch
It was originally developed with the **[IteadStudio Sonoff](https://www.itead.cc/sonoff-wifi-wireless-switch.html)** in mind but now it supports a growing number of ESP8266-based boards.
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
**Current Release Version is 1.9.1**, read the [changelog](https://bitbucket.org/xoseperez/espurna/src/master/CHANGELOG.md).
**Current Release Version is 1.9.2**, read the [changelog](https://bitbucket.org/xoseperez/espurna/src/master/CHANGELOG.md).
**NOTE**: since version 1.9.0 the default **MQTT topics for commands have changed**. They all now end with "/set". This means you will have to change your controller software (Node-RED or alike) to send messages to -for instance- "/home/living/light/relay/0/set". The device will publish its state in "/home/living/light/relay/0" like before.
## Features
@ -30,6 +32,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* **Alexa** integration using the [FauxmoESP Library](https://bitbucket.org/xoseperez/fauxmoesp)
* [**Domoticz**](https://domoticz.com/) integration via MQTT
* [**Home Assistant**](https://home-assistant.io/) integration via MQTT
* Supports MQTT auto-discover feature
* [**InfluxDB**](https://www.influxdata.com/) integration via HTTP API
* Support for different **sensors**
* DHT11 / DHT22 / DHT21 / AM2301 (supports celsius & fahrenheit reporting)
@ -57,6 +60,14 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Automatic updates through the [NoFUSS Library](https://bitbucket.org/xoseperez/nofuss)
* Update from web interface using pre-built images
* **Command line configuration**
* Change configuration
* Run special commands
* **Telnet support**
* Available only if connected to the AP interface
* Show debug info and allows to run terminal commands
* **Unstable system check**
* Detects unstable system (crashes on boot continuously) and defaults to a stable system
* Only WiFi AP, OTA and Telnet available if system is flagged as unstable
* Button interface
* Click to toggle relays
* Double click to enter AP mode (only main button)
@ -72,7 +83,7 @@ For more information please refer to the [ESPurna Wiki](https://bitbucket.org/xo
Here is the list of supported hardware. For more information please refer to the [ESPurna Wiki Hardware page](https://bitbucket.org/xoseperez/espurna/wiki/Hardware).
||||
|-|-|-|
|---|---|---|
|![Tinkerman Espurna H](images/devices/tinkerman-espurna-h.jpg)|![IteadStudio Sonoff RF Bridge](images/devices/itead-sonoff-rfbridge.jpg)||
|**Tinkerman ESPurna H**|**IteadStudio Sonoff RF Bridge**||
|![IteadStudio Sonoff Basic](images/devices/itead-sonoff-basic.jpg)|![IteadStudio Sonoff RF](images/devices/itead-sonoff-rf.jpg)|![Electrodragon WiFi IOT](images/devices/electrodragon-wifi-iot.jpg)|


+ 15
- 2
code/espurna/alexa.ino View File

@ -16,6 +16,10 @@ fauxmoESP alexa;
// ALEXA
// -----------------------------------------------------------------------------
bool _alexa_change = false;
unsigned int _alexa_device_id = 0;
bool _alexa_state = false;
void alexaConfigure() {
alexa.enable(getSetting("alexaEnabled", ALEXA_ENABLED).toInt() == 1);
}
@ -36,13 +40,22 @@ void alexaSetup() {
}
}
alexa.onMessage([relays](unsigned char device_id, const char * name, bool state) {
DEBUG_MSG_P(PSTR("[ALEXA] %s state: %s\n"), name, state ? "ON" : "OFF");
relayStatus(device_id, state);
_alexa_change = true;
_alexa_device_id = device_id;
_alexa_state = state;
});
}
void alexaLoop() {
alexa.handle();
if (_alexa_change) {
DEBUG_MSG_P(PSTR("[ALEXA] Device #%d state: %s\n"), _alexa_device_id, _alexa_state ? "ON" : "OFF");
_alexa_change = false;
relayStatus(_alexa_device_id, _alexa_state);
}
}
#endif

+ 2
- 0
code/espurna/analog.ino View File

@ -26,6 +26,8 @@ void analogSetup() {
});
#endif
DEBUG_MSG_P(PSTR("[ANALOG] Monitoring analog values\n"));
}
void analogLoop() {


+ 3
- 0
code/espurna/config/arduino.h View File

@ -46,7 +46,9 @@
//#define ALEXA_SUPPORT 0
//#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
@ -60,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

+ 52
- 5
code/espurna/config/general.h View File

@ -9,6 +9,21 @@
#define ADMIN_PASS "fibonacci" // Default password (WEB, OTA, WIFI)
//------------------------------------------------------------------------------
// TELNET
//------------------------------------------------------------------------------
#ifndef TELNET_SUPPORT
#define TELNET_SUPPORT 1 // Enable telnet support by default
#endif
#ifndef TELNET_ONLY_AP
#define TELNET_ONLY_AP 1 // By default, allow only connections via AP interface
#endif
#define TELNET_PORT 23 // Port to listen to telnet clients
#define TELNET_MAX_CLIENTS 1 // Max number of concurrent telnet clients
//------------------------------------------------------------------------------
// DEBUG
//------------------------------------------------------------------------------
@ -36,10 +51,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
@ -57,6 +80,14 @@
#define TERMINAL_SUPPORT 1 // Enable terminal commands
#endif
//------------------------------------------------------------------------------
// CRASH
//------------------------------------------------------------------------------
#define CRASH_SAFE_TIME 60000 // The system is considered stable after these many millis
#define CRASH_COUNT_MAX 5 // After this many crashes on boot
// the system is flagged as unstable
//------------------------------------------------------------------------------
// EEPROM
//------------------------------------------------------------------------------
@ -65,7 +96,8 @@
#define EEPROM_RELAY_STATUS 0 // Address for the relay status (1 byte)
#define EEPROM_ENERGY_COUNT 1 // Address for the energy counter (4 bytes)
#define EEPROM_CUSTOM_RESET 5 // Address for the reset reason (1 byte)
#define EEPROM_DATA_END 6 // End of custom EEPROM data block
#define EEPROM_CRASH_COUNTER 6 // Address for the crash counter (1 byte)
#define EEPROM_DATA_END 7 // End of custom EEPROM data block
//------------------------------------------------------------------------------
// HEARTBEAT
@ -296,9 +328,8 @@ PROGMEM const char* const custom_reset_string[] = {
// SPIFFS
// -----------------------------------------------------------------------------
// Do not add support for SPIFFS by default
#ifndef SPIFFS_SUPPORT
#define SPIFFS_SUPPORT 0
#define SPIFFS_SUPPORT 0 // Do not add support for SPIFFS by default
#endif
// -----------------------------------------------------------------------------
@ -311,6 +342,11 @@ PROGMEM const char* const custom_reset_string[] = {
// NOFUSS
// -----------------------------------------------------------------------------
#ifndef NOFUSS_SUPPORT
#define NOFUSS_SUPPORT 0 // Do not enable support for NoFuss by default
#endif
#define NOFUSS_ENABLED 0 // Do not perform NoFUSS updates by default
#define NOFUSS_SERVER "" // Default NoFuss Server
#define NOFUSS_INTERVAL 3600000 // Check for updates every hour
@ -337,6 +373,8 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_AUTOCONNECT 1 // If enabled and MDNS_SUPPORT=1 will perform an autodiscover and
// autoconnect to the first MQTT broker found if none defined
#define MQTT_SERVER "" // Default MQTT broker address
#define MQTT_USER "" // Default MQTT broker usename
#define MQTT_PASS "" // Default MQTT broker password
#define MQTT_PORT 1883 // MQTT broker port
#define MQTT_TOPIC "/test/switch/{identifier}" // Default MQTT base topic
#define MQTT_RETAIN true // MQTT retain flag
@ -370,6 +408,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_TOPIC_HOSTNAME "host"
#define MQTT_TOPIC_TIME "time"
#define MQTT_TOPIC_ANALOG "analog"
#define MQTT_TOPIC_COUNTER "counter"
#define MQTT_TOPIC_RFOUT "rfout"
#define MQTT_TOPIC_RFIN "rfin"
#define MQTT_TOPIC_RFLEARN "rflearn"
@ -397,12 +436,20 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_USE_GETTER ""
#define MQTT_USE_SETTER "/set"
// -----------------------------------------------------------------------------
// SETTINGS
// -----------------------------------------------------------------------------
#ifndef SETTINGS_AUTOSAVE
#define SETTINGS_AUTOSAVE 1 // Autosave settings o force manual commit
#endif
// -----------------------------------------------------------------------------
// I2C
// -----------------------------------------------------------------------------
#ifndef I2C_SUPPORT
#define I2C_SUPPORT 0 // I2C enabled
#define I2C_SUPPORT 0 // I2C enabled
#endif
#define I2C_SDA_PIN 4 // SDA GPIO


+ 7
- 9
code/espurna/config/hardware.h View File

@ -12,7 +12,7 @@
// - BUTTON_DEFAULT_HIGH: there is a pull up in place
// - BUTTON_SET_PULLUP: set pullup by software
// RELAY#_PIN: GPIO for the n-th relay (1-based, up to 4 relays)
// RELAY#_PIN_INVERSE: Relay has inversed logic (closed or ON when pulled down)
// RELAY#_TYPE: Relay can be RELAY_TYPE_NORMAL, RELAY_TYPE_INVERSE or RELAY_TYPE_LATCHED
// RELAY#_LED: LED number that will be bind to the n-th relay (1-based)
// LED#_PIN: GPIO for the n-th LED (1-based, up to 4 LEDs)
// LED#_PIN_INVERSE: LED has inversed logic (lit when pulled down)
@ -409,16 +409,14 @@
#elif defined(ITEAD_SONOFF_RFBRIDGE)
// Info
#define MANUFACTURER "ITEAD_STUDIO"
#define DEVICE "SONOFF_RFBRIDGE"
#define SERIAL_BAUDRATE 19200
#define RELAY_PROVIDER RELAY_PROVIDER_RFBRIDGE
#define MANUFACTURER "ITEAD_STUDIO"
#define DEVICE "SONOFF_RFBRIDGE"
#define SERIAL_BAUDRATE 19200
#define RELAY_PROVIDER RELAY_PROVIDER_RFBRIDGE
#ifndef DUMMY_RELAY_COUNT
#define DUMMY_RELAY_COUNT 6
#define DUMMY_RELAY_COUNT 6
#endif
#define TRACK_RELAY_STATUS 0
#define DEBUG_SERIAL_SUPPORT 0
#define TERMINAL_SUPPORT 0
#define TRACK_RELAY_STATUS 0
// Buttons
#define BUTTON1_PIN 0


+ 17
- 0
code/espurna/config/sensors.h View File

@ -51,6 +51,23 @@
#define ADC_VCC_ENABLED 0
#endif
//--------------------------------------------------------------------------------
// Counter sensor
// Enable support by passing COUNTER_SUPPORT=1 build flag
//--------------------------------------------------------------------------------
#ifndef COUNTER_SUPPORT
#define COUNTER_SUPPORT 0 // Do not build with counter support by default
#endif
#define COUNTER_PIN 2 // GPIO to monitor
#define COUNTER_PIN_MODE INPUT // INPUT, INPUT_PULLUP
#define COUNTER_INTERRUPT_MODE RISING // RISING, FALLING, BOTH
#define COUNTER_UPDATE_INTERVAL 5000 // Update counter every this millis
#define COUNTER_REPORT_EVERY 12 // Report counter every this updates (1 minute)
#define COUNTER_DEBOUNCE 10 // Do not register events within less than 10 millis
#define COUNTER_TOPIC "counter" // Default topic for MQTT, API and InfluxDB
//--------------------------------------------------------------------------------
// DS18B20 temperature sensor
// Enable support by passing DS18B20_SUPPORT=1 build flag


+ 2
- 2
code/espurna/config/version.h View File

@ -1,4 +1,4 @@
#define APP_NAME "ESPurna"
#define APP_VERSION "1.9.1"
#define APP_NAME "ESPURNA"
#define APP_VERSION "1.9.3b"
#define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat"

+ 90
- 0
code/espurna/counter.ino View File

@ -0,0 +1,90 @@
/*
COUNTER MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if COUNTER_SUPPORT
volatile unsigned long _counterCurrent = 0;
volatile unsigned long _counterLast = 0;
unsigned long _counterBuffer[COUNTER_REPORT_EVERY] = {0};
unsigned char _counterBufferPointer = 0;
unsigned long _counterValue = 0;
// -----------------------------------------------------------------------------
// COUNTER
// -----------------------------------------------------------------------------
void ICACHE_RAM_ATTR _counterISR() {
if (millis() - _counterLast > COUNTER_DEBOUNCE) {
++_counterCurrent;
_counterLast = millis();
}
}
unsigned long getCounter() {
return _counterValue;
}
void counterSetup() {
pinMode(COUNTER_PIN, COUNTER_PIN_MODE);
attachInterrupt(COUNTER_PIN, _counterISR, COUNTER_INTERRUPT_MODE);
#if WEB_SUPPORT
apiRegister(COUNTER_TOPIC, COUNTER_TOPIC, [](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), getCounter());
});
#endif
DEBUG_MSG_P(PSTR("[COUNTER] Counter on GPIO %d\n"), COUNTER_PIN);
}
void counterLoop() {
// Check if we should read new data
static unsigned long last_update = 0;
if ((millis() - last_update) < COUNTER_UPDATE_INTERVAL) return;
last_update = millis();
// Update buffer counts
_counterValue = _counterValue - _counterBuffer[_counterBufferPointer] + _counterCurrent;
_counterBuffer[_counterBufferPointer] = _counterCurrent;
_counterCurrent = 0;
_counterBufferPointer = (_counterBufferPointer + 1) % COUNTER_REPORT_EVERY;
DEBUG_MSG_P(PSTR("[COUNTER] Value: %d\n"), _counterValue);
// Update websocket clients
#if WEB_SUPPORT
char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("{\"counterVisible\": 1, \"counterValue\": %d}"), _counterValue);
wsSend(buffer);
#endif
// Do we have to report?
if (_counterBufferPointer == 0) {
// Send MQTT messages
mqttSend(getSetting("counterTopic", COUNTER_TOPIC).c_str(), String(_counterValue).c_str());
// Send to Domoticz
#if DOMOTICZ_SUPPORT
domoticzSend("dczCountIdx", 0, String(_counterValue).c_str());
#endif
// Send to InfluxDB
#if INFLUXDB_SUPPORT
influxDBSend(COUNTER_TOPIC, _counterValue);
#endif
}
}
#endif // COUNTER_SUPPORT

BIN
code/espurna/data/index.html.gz View File


+ 33
- 17
code/espurna/debug.ino View File

@ -6,10 +6,12 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if DEBUG_SUPPORT
#include <stdio.h>
#include <stdarg.h>
#ifdef DEBUG_UDP_SUPPORT
#if DEBUG_UDP_SUPPORT
#include <WiFiUdp.h>
WiFiUDP udpDebug;
#endif
@ -23,21 +25,27 @@ void debugSend(const char * format, ...) {
int len = ets_vsnprintf(buffer, DEBUG_MESSAGE_MAX_LENGTH, format, args);
va_end(args);
#ifdef DEBUG_SERIAL_SUPPORT
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.printf(buffer);
if (len > DEBUG_MESSAGE_MAX_LENGTH) {
DEBUG_PORT.printf(" (...)\n");
}
#endif
#ifdef DEBUG_UDP_SUPPORT
udpDebug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
udpDebug.write(buffer);
if (len > DEBUG_MESSAGE_MAX_LENGTH) {
udpDebug.write(" (...)\n");
#if DEBUG_UDP_SUPPORT
if (systemCheck()) {
udpDebug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
udpDebug.write(buffer);
if (len > DEBUG_MESSAGE_MAX_LENGTH) {
udpDebug.write(" (...)\n");
}
udpDebug.endPacket();
delay(1);
}
udpDebug.endPacket();
delay(1);
#endif
#if DEBUG_TELNET_SUPPORT
_telnetWrite(buffer, strlen(buffer));
#endif
}
@ -54,21 +62,29 @@ void debugSend_P(PGM_P format, ...) {
int len = ets_vsnprintf(buffer, DEBUG_MESSAGE_MAX_LENGTH, f, args);
va_end(args);
#ifdef DEBUG_SERIAL_SUPPORT
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.printf(buffer);
if (len > DEBUG_MESSAGE_MAX_LENGTH) {
DEBUG_PORT.printf(" (...)\n");
}
#endif
#ifdef DEBUG_UDP_SUPPORT
udpDebug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
udpDebug.write(buffer);
if (len > DEBUG_MESSAGE_MAX_LENGTH) {
udpDebug.write(" (...)\n");
#if DEBUG_UDP_SUPPORT
if (systemCheck()) {
udpDebug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
udpDebug.write(buffer);
if (len > DEBUG_MESSAGE_MAX_LENGTH) {
udpDebug.write(" (...)\n");
}
udpDebug.endPacket();
delay(1);
}
udpDebug.endPacket();
delay(1);
#endif
#if DEBUG_TELNET_SUPPORT
_telnetWrite(buffer, strlen(buffer));
#endif
}
#endif // DEBUG_SUPPORT

+ 141
- 41
code/espurna/espurna.ino View File

@ -47,6 +47,14 @@ void hardwareSetup() {
void hardwareLoop() {
// System check
static bool checked = false;
if (!checked && (millis() > CRASH_SAFE_TIME)) {
// Check system as stable
systemCheck(true);
checked = true;
}
// Heartbeat
static unsigned long last_uptime = 0;
if ((millis() - last_uptime > HEARTBEAT_INTERVAL) || (last_uptime == 0)) {
@ -67,74 +75,163 @@ unsigned int sectors(size_t size) {
void welcome() {
DEBUG_MSG_P(PSTR("\n\n"));
DEBUG_MSG_P(PSTR("%s %s\n"), (char *) APP_NAME, (char *) APP_VERSION);
DEBUG_MSG_P(PSTR("%s\n%s\n\n"), (char *) APP_AUTHOR, (char *) APP_WEBSITE);
DEBUG_MSG_P(PSTR("CPU chip ID: 0x%06X\n"), ESP.getChipId());
DEBUG_MSG_P(PSTR("CPU frequency: %d MHz\n"), ESP.getCpuFreqMHz());
DEBUG_MSG_P(PSTR("SDK version: %s\n"), ESP.getSdkVersion());
DEBUG_MSG_P(PSTR("Core version: %s\n"), ESP.getCoreVersion().c_str());
DEBUG_MSG_P(PSTR("[INIT] %s %s\n"), (char *) APP_NAME, (char *) APP_VERSION);
DEBUG_MSG_P(PSTR("[INIT] %s\n"), (char *) APP_AUTHOR);
DEBUG_MSG_P(PSTR("[INIT] %s\n\n"), (char *) APP_WEBSITE);
DEBUG_MSG_P(PSTR("[INIT] CPU chip ID: 0x%06X\n"), ESP.getChipId());
DEBUG_MSG_P(PSTR("[INIT] CPU frequency: %d MHz\n"), ESP.getCpuFreqMHz());
DEBUG_MSG_P(PSTR("[INIT] SDK version: %s\n"), ESP.getSdkVersion());
DEBUG_MSG_P(PSTR("[INIT] Core version: %s\n"), ESP.getCoreVersion().c_str());
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
FlashMode_t mode = ESP.getFlashChipMode();
DEBUG_MSG_P(PSTR("Flash chip ID: 0x%06X\n"), ESP.getFlashChipId());
DEBUG_MSG_P(PSTR("Flash speed: %u Hz\n"), ESP.getFlashChipSpeed());
DEBUG_MSG_P(PSTR("Flash mode: %s\n"), mode == FM_QIO ? "QIO" : mode == FM_QOUT ? "QOUT" : mode == FM_DIO ? "DIO" : mode == FM_DOUT ? "DOUT" : "UNKNOWN");
DEBUG_MSG_P(PSTR("[INIT] Flash chip ID: 0x%06X\n"), ESP.getFlashChipId());
DEBUG_MSG_P(PSTR("[INIT] Flash speed: %u Hz\n"), ESP.getFlashChipSpeed());
DEBUG_MSG_P(PSTR("[INIT] Flash mode: %s\n"), mode == FM_QIO ? "QIO" : mode == FM_QOUT ? "QOUT" : mode == FM_DIO ? "DIO" : mode == FM_DOUT ? "DOUT" : "UNKNOWN");
DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("Flash sector size: %8u bytes\n"), SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("Flash size (CHIP): %8u bytes\n"), ESP.getFlashChipRealSize());
DEBUG_MSG_P(PSTR("Flash size (SDK): %8u bytes / %4d sectors\n"), ESP.getFlashChipSize(), sectors(ESP.getFlashChipSize()));
DEBUG_MSG_P(PSTR("Firmware size: %8u bytes / %4d sectors\n"), ESP.getSketchSize(), sectors(ESP.getSketchSize()));
DEBUG_MSG_P(PSTR("OTA size: %8u bytes / %4d sectors\n"), ESP.getFreeSketchSpace(), sectors(ESP.getFreeSketchSpace()));
DEBUG_MSG_P(PSTR("[INIT] Flash sector size: %8u bytes\n"), SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("[INIT] Flash size (CHIP): %8u bytes\n"), ESP.getFlashChipRealSize());
DEBUG_MSG_P(PSTR("[INIT] Flash size (SDK): %8u bytes / %4d sectors\n"), ESP.getFlashChipSize(), sectors(ESP.getFlashChipSize()));
DEBUG_MSG_P(PSTR("[INIT] Firmware size: %8u bytes / %4d sectors\n"), ESP.getSketchSize(), sectors(ESP.getSketchSize()));
DEBUG_MSG_P(PSTR("[INIT] OTA size: %8u bytes / %4d sectors\n"), ESP.getFreeSketchSpace(), sectors(ESP.getFreeSketchSpace()));
#if SPIFFS_SUPPORT
FSInfo fs_info;
bool fs = SPIFFS.info(fs_info);
if (fs) {
DEBUG_MSG_P(PSTR("SPIFFS size: %8u bytes / %4d sectors\n"), fs_info.totalBytes, sectors(fs_info.totalBytes));
DEBUG_MSG_P(PSTR("[INIT] SPIFFS size: %8u bytes / %4d sectors\n"), fs_info.totalBytes, sectors(fs_info.totalBytes));
}
#else
DEBUG_MSG_P(PSTR("SPIFFS size: %8u bytes / %4d sectors\n"), 0, 0);
DEBUG_MSG_P(PSTR("[INIT] SPIFFS size: %8u bytes / %4d sectors\n"), 0, 0);
#endif
DEBUG_MSG_P(PSTR("EEPROM size: %8u bytes / %4d sectors\n"), settingsMaxSize(), sectors(settingsMaxSize()));
DEBUG_MSG_P(PSTR("Empty space: %8u bytes / 4 sectors\n"), 4 * SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("[INIT] EEPROM size: %8u bytes / %4d sectors\n"), settingsMaxSize(), sectors(settingsMaxSize()));
DEBUG_MSG_P(PSTR("[INIT] Empty space: %8u bytes / 4 sectors\n"), 4 * SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
#if SPIFFS_SUPPORT
if (fs) {
DEBUG_MSG_P(PSTR("[INIT] SPIFFS total size: %8u bytes\n"), fs_info.totalBytes);
DEBUG_MSG_P(PSTR("[INIT] used size: %8u bytes\n"), fs_info.usedBytes);
DEBUG_MSG_P(PSTR("[INIT] block size: %8u bytes\n"), fs_info.blockSize);
DEBUG_MSG_P(PSTR("[INIT] page size: %8u bytes\n"), fs_info.pageSize);
DEBUG_MSG_P(PSTR("[INIT] max files: %8u\n"), fs_info.maxOpenFiles);
DEBUG_MSG_P(PSTR("[INIT] max length: %8u\n"), fs_info.maxPathLength);
DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("SPIFFS total size: %8u bytes\n"), fs_info.totalBytes);
DEBUG_MSG_P(PSTR(" used size: %8u bytes\n"), fs_info.usedBytes);
DEBUG_MSG_P(PSTR(" block size: %8u bytes\n"), fs_info.blockSize);
DEBUG_MSG_P(PSTR(" page size: %8u bytes\n"), fs_info.pageSize);
DEBUG_MSG_P(PSTR(" max files: %8u\n"), fs_info.maxOpenFiles);
DEBUG_MSG_P(PSTR(" max length: %8u\n"), fs_info.maxPathLength);
}
#endif
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[INIT] MANUFACTURER: %s\n"), MANUFACTURER);
DEBUG_MSG_P(PSTR("[INIT] DEVICE: %s\n"), DEVICE);
DEBUG_MSG_P(PSTR("[INIT] SUPPORT:"));
#if ALEXA_SUPPORT
DEBUG_MSG_P(PSTR(" ALEXA"));
#endif
#if ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" ANALOG"));
#endif
#if COUNTER_SUPPORT
DEBUG_MSG_P(PSTR(" COUNTER"));
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_SERIAL"));
#endif
#if DEBUG_UDP_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_UDP"));
#endif
#if DHT_SUPPORT
DEBUG_MSG_P(PSTR(" DHT"));
#endif
#if DOMOTICZ_SUPPORT
DEBUG_MSG_P(PSTR(" DOMOTICZ"));
#endif
#if DS18B20_SUPPORT
DEBUG_MSG_P(PSTR(" DS18B20"));
#endif
#if EMON_SUPPORT
DEBUG_MSG_P(PSTR(" EMON"));
#endif
#if HOMEASSISTANT_SUPPORT
DEBUG_MSG_P(PSTR(" HOMEASSISTANT"));
#endif
#if I2C_SUPPORT
DEBUG_MSG_P(PSTR(" I2C"));
#endif
#if INFLUXDB_SUPPORT
DEBUG_MSG_P(PSTR(" INFLUXDB"));
#endif
#if MDNS_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS"));
#endif
#if NOFUSS_SUPPORT
DEBUG_MSG_P(PSTR(" NOFUSS"));
#endif
#if NTP_SUPPORT
DEBUG_MSG_P(PSTR(" NTP"));
#endif
#if RF_SUPPORT
DEBUG_MSG_P(PSTR(" RF"));
#endif
#if SPIFFS_SUPPORT
DEBUG_MSG_P(PSTR(" SPIFFS"));
#endif
#if TERMINAL_SUPPORT
DEBUG_MSG_P(PSTR(" TERMINAL"));
#endif
#if WEB_SUPPORT
DEBUG_MSG_P(PSTR(" WEB"));
#endif
DEBUG_MSG_P(PSTR("\n\n"));
// -------------------------------------------------------------------------
unsigned char custom_reset = customReset();
if (custom_reset > 0) {
char buffer[32];
strcpy_P(buffer, custom_reset_string[custom_reset-1]);
DEBUG_MSG_P(PSTR("Last reset reason: %s\n"), buffer);
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), buffer);
} else {
DEBUG_MSG_P(PSTR("Last reset reason: %s\n"), (char *) ESP.getResetReason().c_str());
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), (char *) ESP.getResetReason().c_str());
}
DEBUG_MSG_P(PSTR("Free heap: %u bytes\n"), ESP.getFreeHeap());
DEBUG_MSG_P(PSTR("\n\n"));
DEBUG_MSG_P(PSTR("[INIT] Free heap: %u bytes\n"), ESP.getFreeHeap());
DEBUG_MSG_P(PSTR("\n"));
}
void setup() {
// Init EEPROM, Serial and SPIFFS
hardwareSetup();
// Question system stability
systemCheck(false);
// Show welcome message and system configuration
welcome();
// Init persistance and terminal features
settingsSetup();
if (getSetting("hostname").length() == 0) {
setSetting("hostname", getIdentifier());
saveSettings();
}
delay(500);
wifiSetup();
otaSetup();
#if TELNET_SUPPORT
telnetSetup();
#endif
// Do not run the next services if system is flagged stable
if (!systemCheck()) return;
#if WEB_SUPPORT
webSetup();
#endif
@ -145,11 +242,6 @@ void setup() {
relaySetup();
buttonSetup();
ledSetup();
delay(500);
wifiSetup();
otaSetup();
mqttSetup();
#ifdef ITEAD_SONOFF_RFBRIDGE
@ -180,6 +272,9 @@ void setup() {
#if ANALOG_SUPPORT
analogSetup();
#endif
#if COUNTER_SUPPORT
counterSetup();
#endif
#if DHT_SUPPORT
dhtSetup();
#endif
@ -201,11 +296,16 @@ void setup() {
void loop() {
hardwareLoop();
settingsLoop();
wifiLoop();
otaLoop();
// Do not run the next services if system is flagged stable
if (!systemCheck()) return;
buttonLoop();
relayLoop();
ledLoop();
wifiLoop();
otaLoop();
mqttLoop();
#ifdef ITEAD_SONOFF_RFBRIDGE
@ -215,9 +315,6 @@ void loop() {
#if NTP_SUPPORT
ntpLoop();
#endif
#if TERMINAL_SUPPORT
settingsLoop();
#endif
#if ALEXA_SUPPORT
alexaLoop();
#endif
@ -233,6 +330,9 @@ void loop() {
#if ANALOG_SUPPORT
analogLoop();
#endif
#if COUNTER_SUPPORT
counterLoop();
#endif
#if DHT_SUPPORT
dhtLoop();
#endif


+ 6
- 0
code/espurna/light.ino View File

@ -534,10 +534,12 @@ void _lightAPISetup() {
void lightSetup() {
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
_my9291 = new my9291(MY9291_DI_PIN, MY9291_DCKI_PIN, MY9291_COMMAND, MY9291_CHANNELS);
for (unsigned char i=0; i<MY9291_CHANNELS; i++) {
_channels.push_back((channel_t) {0, false, 0});
}
#endif
#if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER
@ -568,8 +570,12 @@ void lightSetup() {
pinMode(_channels[i].pin, OUTPUT);
}
#endif
DEBUG_MSG_P(PSTR("[LIGHT] LIGHT_PROVIDER = %d\n"), LIGHT_PROVIDER);
DEBUG_MSG_P(PSTR("[LIGHT] Number of channels: %d\n"), _channels.size());
_lightColorRestore();
_lightAPISetup();
mqttRegister(_lightMQTTCallback);


+ 9
- 8
code/espurna/mqtt.ino View File

@ -340,10 +340,10 @@ void mqttConnect() {
char * host = strdup(getSetting("mqttServer", MQTT_SERVER).c_str());
if (strlen(host) == 0) return;
unsigned int port = getSetting("mqttPort", MQTT_PORT).toInt();
_mqtt_user = strdup(getSetting("mqttUser").c_str());
_mqtt_pass = strdup(getSetting("mqttPassword").c_str());
if (_mqtt_will) free(_mqtt_will);
_mqtt_will = strdup((_mqtt_topic + MQTT_TOPIC_STATUS).c_str());
_mqttUser = strdup(getSetting("mqttUser", MQTT_USER).c_str());
_mqttPass = strdup(getSetting("mqttPassword", MQTT_PASS).c_str());
if (_mqttWill) free(_mqttWill);
_mqttWill = strdup((_mqttTopic + MQTT_TOPIC_STATUS).c_str());
DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d\n"), host, port);
@ -462,7 +462,7 @@ void mqttConfigure() {
}
#ifdef MDNS_SUPPORT
#if MDNS_SUPPORT
boolean mqttDiscover() {
int count = MDNS.queryService("mqtt", "tcp");
@ -485,11 +485,12 @@ boolean mqttDiscover() {
void mqttSetup() {
#if MQTT_USE_ASYNC
DEBUG_MSG_P(PSTR("[MQTT] MQTT_USE_ASYNC = %d\n"), MQTT_USE_ASYNC);
DEBUG_MSG_P(PSTR("[MQTT] MQTT_AUTOCONNECT = %d\n"), MQTT_AUTOCONNECT);
DEBUG_MSG_P(PSTR("[MQTT] Using ASYNC MQTT library\n"));
#if MQTT_USE_ASYNC
_mqtt.onConnect([](bool sessionPresent) {
mqtt.onConnect([](bool sessionPresent) {
_mqttOnConnect();
});
_mqtt.onDisconnect([](AsyncMqttClientDisconnectReason reason) {


+ 96
- 45
code/espurna/nofuss.ino View File

@ -10,74 +10,125 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#include "NoFUSSClient.h"
unsigned long _nofussLastCheck = 0;
unsigned long _nofussInterval = 0;
bool _nofussEnabled = false;
// -----------------------------------------------------------------------------
// NOFUSS
// -----------------------------------------------------------------------------
void nofussSetup() {
NoFUSSClient.setServer(getSetting("nofussServer", NOFUSS_SERVER));
NoFUSSClient.setDevice(DEVICE);
NoFUSSClient.setVersion(APP_VERSION);
void nofussRun() {
NoFUSSClient.handle();
_nofussLastCheck = millis();
}
NoFUSSClient.onMessage([](nofuss_t code) {
void nofussConfigure() {
if (code == NOFUSS_START) {
DEBUG_MSG_P(PSTR("[NoFUSS] Start\n"));
String nofussServer = getSetting("nofussServer", NOFUSS_SERVER);
if (nofussServer.length() == 0) {
setSetting("nofussEnabled", 0);
_nofussEnabled = false;
} else {
_nofussEnabled = getSetting("nofussEnabled", NOFUSS_ENABLED).toInt() == 1;
}
_nofussInterval = getSetting("nofussInterval", NOFUSS_INTERVAL).toInt();
_nofussLastCheck = 0;
if (code == NOFUSS_UPTODATE) {
DEBUG_MSG_P(PSTR("[NoFUSS] Already in the last version\n"));
}
if (!_nofussEnabled) {
if (code == NOFUSS_PARSE_ERROR) {
DEBUG_MSG_P(PSTR("[NoFUSS] Error parsing server response\n"));
}
DEBUG_MSG_P(PSTR("[NOFUSS] Disabled\n"));
if (code == NOFUSS_UPDATING) {
DEBUG_MSG_P(PSTR("[NoFUSS] Updating"));
DEBUG_MSG_P(PSTR(" New version: %s\n"), (char *) NoFUSSClient.getNewVersion().c_str());
DEBUG_MSG_P(PSTR(" Firmware: %s\n"), (char *) NoFUSSClient.getNewFirmware().c_str());
DEBUG_MSG_P(PSTR(" File System: %s"), (char *) NoFUSSClient.getNewFileSystem().c_str());
}
} else {
if (code == NOFUSS_FILESYSTEM_UPDATE_ERROR) {
DEBUG_MSG_P(PSTR("[NoFUSS] File System Update Error: %s\n"), (char *) NoFUSSClient.getErrorString().c_str());
}
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%s-%s"), APP_NAME, DEVICE);
if (code == NOFUSS_FILESYSTEM_UPDATED) {
DEBUG_MSG_P(PSTR("[NoFUSS] File System Updated\n"));
}
NoFUSSClient.setServer(nofussServer);
NoFUSSClient.setDevice(buffer);
NoFUSSClient.setVersion(APP_VERSION);
if (code == NOFUSS_FIRMWARE_UPDATE_ERROR) {
DEBUG_MSG_P(PSTR("[NoFUSS] Firmware Update Error: %s\n"), (char *) NoFUSSClient.getErrorString().c_str());
}
DEBUG_MSG_P(PSTR("[NOFUSS] Server : %s\n"), nofussServer.c_str());
DEBUG_MSG_P(PSTR("[NOFUSS] Dervice: %s\n"), buffer);
DEBUG_MSG_P(PSTR("[NOFUSS] Version: %s\n"), APP_VERSION);
DEBUG_MSG_P(PSTR("[NOFUSS] Enabled\n"));
if (code == NOFUSS_FIRMWARE_UPDATED) {
DEBUG_MSG_P(PSTR("[NoFUSS] Firmware Updated\n"));
}
if (code == NOFUSS_RESET) {
DEBUG_MSG_P(PSTR("[NoFUSS] Resetting board\n"));
}
}
if (code == NOFUSS_END) {
DEBUG_MSG_P(PSTR("[NoFUSS] End\n"));
}
void nofussSetup() {
});
nofussConfigure();
NoFUSSClient.onMessage([](nofuss_t code) {
if (code == NOFUSS_START) {
DEBUG_MSG_P(PSTR("[NoFUSS] Start\n"));
}
if (code == NOFUSS_UPTODATE) {
DEBUG_MSG_P(PSTR("[NoFUSS] Already in the last version\n"));
}
if (code == NOFUSS_NO_RESPONSE_ERROR) {
DEBUG_MSG_P(PSTR("[NoFUSS] Wrong server response: %d %s\n"), NoFUSSClient.getErrorNumber(), (char *) NoFUSSClient.getErrorString().c_str());
}
if (code == NOFUSS_PARSE_ERROR) {
DEBUG_MSG_P(PSTR("[NoFUSS] Error parsing server response\n"));
}
if (code == NOFUSS_UPDATING) {
DEBUG_MSG_P(PSTR("[NoFUSS] Updating\n"));
DEBUG_MSG_P(PSTR(" New version: %s\n"), (char *) NoFUSSClient.getNewVersion().c_str());
DEBUG_MSG_P(PSTR(" Firmware: %s\n"), (char *) NoFUSSClient.getNewFirmware().c_str());
DEBUG_MSG_P(PSTR(" File System: %s\n"), (char *) NoFUSSClient.getNewFileSystem().c_str());
#if WEB_SUPPORT
wsSend_P(PSTR("{\"message\": \"Remote update started\"}"));
#endif
}
if (code == NOFUSS_FILESYSTEM_UPDATE_ERROR) {
DEBUG_MSG_P(PSTR("[NoFUSS] File System Update Error: %s\n"), (char *) NoFUSSClient.getErrorString().c_str());
}
if (code == NOFUSS_FILESYSTEM_UPDATED) {
DEBUG_MSG_P(PSTR("[NoFUSS] File System Updated\n"));
}
if (code == NOFUSS_FIRMWARE_UPDATE_ERROR) {
DEBUG_MSG_P(PSTR("[NoFUSS] Firmware Update Error: %s\n"), (char *) NoFUSSClient.getErrorString().c_str());
}
if (code == NOFUSS_FIRMWARE_UPDATED) {
DEBUG_MSG_P(PSTR("[NoFUSS] Firmware Updated\n"));
}
if (code == NOFUSS_RESET) {
DEBUG_MSG_P(PSTR("[NoFUSS] Resetting board\n"));
#if WEB_SUPPORT
wsSend_P(PSTR("{\"action\": \"reload\"}"));
#endif
delay(100);
}
if (code == NOFUSS_END) {
DEBUG_MSG_P(PSTR("[NoFUSS] End\n"));
}
});
}
void nofussLoop() {
static unsigned long last_check = 0;
if (!wifiConnected()) return;
unsigned long interval = getSetting("nofussInterval", NOFUSS_INTERVAL).toInt();
if ((last_check > 0) && ((millis() - last_check) < interval)) return;
last_check = millis();
NoFUSSClient.handle();
if (!_nofussEnabled) return;
if (!wifiConnected()) return;
if ((_nofussLastCheck > 0) && ((millis() - _nofussLastCheck) < _nofussInterval)) return;
nofussRun();
}
#endif
#endif // NOFUSS_SUPPORT

+ 1
- 1
code/espurna/ota.ino View File

@ -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"));


+ 39
- 2
code/espurna/relay.ino View File

@ -314,6 +314,35 @@ unsigned char relayCount() {
return _relays.size();
}
unsigned char _relayValueFromPayload(const char * payload) {
// Payload could be "OFF", "ON", "TOGGLE"
// or its number equivalents: 0, 1 or 2
// trim payload
char * p = ltrim((char *)payload);
// to lower
for (unsigned char i=0; i<strlen(p); i++) {
p[i] = tolower(p[i]);
}
unsigned int value;
if (strcmp(p, "off") == 0) {
value = 0;
} else if (strcmp(p, "on") == 0) {
value = 1;
} else if (strcmp(p, "toggle") == 0) {
value = 2;
} else {
value = p[0] - '0';
}
if (0 <= value && value <=2) return value;
return 0x99;
}
//------------------------------------------------------------------------------
// REST API
//------------------------------------------------------------------------------
@ -336,7 +365,11 @@ void relaySetupAPI() {
snprintf_P(buffer, len, PSTR("%d"), relayStatus(relayID) ? 1 : 0);
},
[relayID](const char * payload) {
unsigned int value = payload[0] - '0';
unsigned char value = _relayValueFromPayload(payload);
if (value == 0xFF) {
DEBUG_MSG_P(PSTR("[RELAY] Wrong payload (%s)\n"), payload);
return;
}
if (value == 2) {
relayToggle(relayID);
} else {
@ -407,7 +440,11 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
if (!t.startsWith(MQTT_TOPIC_RELAY)) return;
// Get value
unsigned int value = (char)payload[0] - '0';
unsigned char value = _relayValueFromPayload(payload);
if (value == 0xFF) {
DEBUG_MSG_P(PSTR("[RELAY] Wrong payload (%s)\n"), payload);
return;
}
// Pulse topic
if (t.endsWith("pulse")) {


+ 78
- 0
code/espurna/settings.h View File

@ -0,0 +1,78 @@
// -----------------------------------------------------------------------------
// Stream Injector
// -----------------------------------------------------------------------------
#pragma once
#define STREAM_INJECTOR_BUFFER_SIZE 32
class StreamInjector : public Stream {
public:
typedef std::function<void(uint8_t ch)> 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<len; i++) {
_buffer[_buffer_write] = data[i];
_buffer_write = (_buffer_write + 1) % STREAM_INJECTOR_BUFFER_SIZE;
}
}
private:
Stream& _stream;
unsigned char _buffer[STREAM_INJECTOR_BUFFER_SIZE];
unsigned char _buffer_write = 0;
unsigned char _buffer_read = 0;
writeCallback _callback = NULL;
};

+ 55
- 11
code/espurna/settings.ino View File

@ -11,18 +11,34 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#include "spi_flash.h"
#include <StreamString.h>
#define AUTO_SAVE 1
#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;
// -----------------------------------------------------------------------------
// 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,12 +98,18 @@ 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); },
[](size_t pos, char value) { EEPROM.write(pos, value); },
#if AUTO_SAVE
[]() { EEPROM.commit(); }
#if SETTINGS_AUTOSAVE
[]() { _settings_save = true; }
#else
[]() {}
#endif
@ -99,6 +121,8 @@ void settingsSetup() {
e->response(s);
}, 0);
// -------------------------------------------------------------------------
Embedis::command( F("RESET.WIFI"), [](Embedis* e) {
wifiConfigure();
wifiDisconnect();
@ -117,6 +141,20 @@ void settingsSetup() {
ESP.restart();
});
Embedis::command( F("ERASE.CONFIG"), [](Embedis* e) {
e->response(Embedis::OK);
customReset(CUSTOM_RESET_TERMINAL);
ESP.eraseConfig();
*((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494
});
#if NOFUSS_SUPPORT
Embedis::command( F("NOFUSS"), [](Embedis* e) {
e->response(Embedis::OK);
nofussRun();
});
#endif
Embedis::command( F("FACTORY.RESET"), [](Embedis* e) {
settingsFactoryReset();
e->response(Embedis::OK);
@ -246,7 +284,14 @@ void settingsDump() {
}
void settingsLoop() {
embedis.process();
if (_settings_save) {
DEBUG_MSG_P(PSTR("[SETTINGS] Saving\n"));
EEPROM.commit();
_settings_save = false;
}
#if TERMINAL_SUPPORT
embedis.process();
#endif
}
void moveSetting(const char * from, const char * to) {
@ -302,9 +347,8 @@ bool hasSetting(const String& key, unsigned int index) {
}
void saveSettings() {
DEBUG_MSG_P(PSTR("[SETTINGS] Saving\n"));
#if not AUTO_SAVE
EEPROM.commit();
#if not SETTINGS_AUTOSAVE
_settings_save = true;
#endif
//settingsDump();
}

+ 3533
- 3528
code/espurna/static/index.html.gz.h
File diff suppressed because it is too large
View File


+ 134
- 0
code/espurna/telnet.ino View File

@ -0,0 +1,134 @@
/*
TELNET MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
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 <ESPAsyncTCP.h>
AsyncServer * _telnetServer;
AsyncClient * _telnetClients[TELNET_MAX_CLIENTS];
// -----------------------------------------------------------------------------
// Private methods
// -----------------------------------------------------------------------------
void _telnetDisconnect(unsigned char clientId) {
_telnetClients[clientId]->free();
_telnetClients[clientId] = NULL;
delete _telnetClients[clientId];
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) || (strncmp(p, "quit", 4) == 0)) {
_telnetClients[clientId]->close();
return;
}
// Inject into Embedis stream
settingsInject(data, len);
}
void _telnetNewClient(AsyncClient *client) {
#if TELNET_ONLY_AP
if (client->localIP() != WiFi.softAPIP()) {
DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Only local connections\n"));
client->onDisconnect([](void *s, AsyncClient *c) {
c->free();
delete c;
});
client->close(true);
return;
}
#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->onDisconnect([](void *s, AsyncClient *c) {
c->free();
delete c;
});
client->close(true);
}
// -----------------------------------------------------------------------------
// 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

+ 35
- 0
code/espurna/utils.ino View File

@ -122,6 +122,8 @@ void heartbeat() {
}
// -----------------------------------------------------------------------------
void customReset(unsigned char status) {
EEPROM.write(EEPROM_CUSTOM_RESET, status);
EEPROM.commit();
@ -137,6 +139,39 @@ unsigned char customReset() {
return status;
}
// -----------------------------------------------------------------------------
// Call this method on boot with start=true to increase the crash counter
// Call it again once the system is stable to decrease the counter
// If the counter reaches CRASH_COUNT_MAX then the system is flagged as unstable
// setting _systemOK = false;
//
// An unstable system will only have serial access, WiFi in AP mode and OTA
bool _systemStable = true;
void systemCheck(bool stable) {
unsigned char value = EEPROM.read(EEPROM_CRASH_COUNTER);
if (stable) {
value = 0;
DEBUG_MSG_P(PSTR("[MAIN] System OK\n"));
} else {
if (++value > CRASH_COUNT_MAX) {
_systemStable = false;
value = 0;
DEBUG_MSG_P(PSTR("[MAIN] System UNSTABLE\n"));
}
}
EEPROM.write(EEPROM_CRASH_COUNTER, value);
EEPROM.commit();
}
bool systemCheck() {
return _systemStable;
}
// -----------------------------------------------------------------------------
char * ltrim(char * s) {
char *p = s;
while ((unsigned char) *p == ' ') ++p;


+ 34
- 11
code/espurna/web.ino View File

@ -369,6 +369,9 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
#if DOMOTICZ_SUPPORT
domoticzConfigure();
#endif
#if NOFUSS_SUPPORT
nofussConfigure();
#endif
#if RF_SUPPORT
rfBuildCodes();
#endif
@ -580,6 +583,11 @@ void _wsStart(uint32_t client_id) {
root["analogValue"] = getAnalog();
#endif
#if COUNTER_SUPPORT
root["counterVisible"] = 1;
root["counterValue"] = getCounter();
#endif
#if HLW8012_SUPPORT
root["powVisible"] = 1;
root["powActivePower"] = getActivePower();
@ -590,18 +598,24 @@ void _wsStart(uint32_t client_id) {
root["powPowerFactor"] = String(getPowerFactor(), 2);
#endif
#if NOFUSS_SUPPORT
root["nofussVisible"] = 1;
root["nofussEnabled"] = getSetting("nofussEnabled", NOFUSS_ENABLED).toInt() == 1;
root["nofussServer"] = getSetting("nofussServer", NOFUSS_SERVER);
#endif
#ifdef ITEAD_SONOFF_RFBRIDGE
root["rfbVisible"] = 1;
root["rfbCount"] = relayCount();
JsonArray& rfb = root.createNestedArray("rfb");
for (byte id=0; id<relayCount(); id++) {
for (byte status=0; status<2; status++) {
JsonObject& node = rfb.createNestedObject();
node["id"] = id;
node["status"] = status;
node["data"] = rfbRetrieve(id, status == 1);
root["rfbVisible"] = 1;
root["rfbCount"] = relayCount();
JsonArray& rfb = root.createNestedArray("rfb");
for (byte id=0; id<relayCount(); id++) {
for (byte status=0; status<2; status++) {
JsonObject& node = rfb.createNestedObject();
node["id"] = id;
node["status"] = status;
node["data"] = rfbRetrieve(id, status == 1);
}
}
}
#endif
root["wifiGain"] = getSetting("wifiGain", WIFI_GAIN).toFloat();
@ -1052,7 +1066,15 @@ int _onCertificate(void * arg, const char *filename, uint8_t **buf) {
#endif
void _onUpgrade(AsyncWebServerRequest *request) {
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", Update.hasError() ? "FAIL" : "OK");
char buffer[10];
if (Update.hasError()) {
sprintf_P(buffer, PSTR("OK"));
} else {
sprintf_P(buffer, PSTR("ERROR %d"), Update.getError());
}
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", buffer);
response->addHeader("Connection", "close");
if (!Update.hasError()) {
_web_defer.once_ms(100, []() {
@ -1061,6 +1083,7 @@ void _onUpgrade(AsyncWebServerRequest *request) {
});
}
request->send(response);
}
void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {


+ 22
- 4
code/espurna/wifi.ino View File

@ -59,6 +59,9 @@ void wifiConfigure() {
jw.setAPMode(WIFI_AP_MODE);
jw.cleanNetworks();
// If system is flagged unstable we do not init wifi networks
if (!systemCheck()) return;
int i;
for (i = 0; i< WIFI_MAX_NETWORKS; i++) {
if (getSetting("ssid" + String(i)).length() == 0) break;
@ -169,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"));
@ -223,19 +226,34 @@ void wifiSetup() {
DEBUG_MSG_P(PSTR("[WIFI] Could not create access point\n"));
}
#endif
#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


+ 20
- 25
code/html/custom.js View File

@ -1,9 +1,6 @@
var websock;
var password = false;
var maxNetworks;
var protocol;
var host;
var port;
var useWhite = false;
// http://www.the-art-of-web.com/javascript/validate-password/
@ -43,6 +40,10 @@ function valueSet(data, name, value) {
data.push({'name': name, 'value': value});
}
function zeroPad(number, positions) {
return ("0".repeat(positions) + number).slice(-positions);
}
function doUpdate() {
var form = $("#formSave");
@ -89,7 +90,7 @@ function doUpgrade() {
$.ajax({
// Your server script to process the upload
url: protocol + '//' + host + ':' + port + '/upgrade',
url: window.location.href + 'upgrade',
type: 'POST',
// Form data
@ -106,10 +107,10 @@ function doUpgrade() {
if (data == 'OK') {
alert("Firmware image uploaded, board rebooting. This page will be refreshed in 5 seconds.");
setTimeout(function() {
window.location = "/";
window.location.reload();
}, 5000);
} else {
alert("There was an error trying to upload the new image, please try again.");
alert("There was an error trying to upload the new image, please try again (" + data + ").");
}
},
@ -164,7 +165,7 @@ function doToggle(element, value) {
}
function backupSettings() {
document.getElementById('downloader').src = protocol + '//' + host + ':' + port + '/config';
document.getElementById('downloader').src = window.location.href + 'config';
return false;
}
@ -480,7 +481,7 @@ function processData(data) {
if (data.action == "reload") {
if (password) forgetCredentials();
setTimeout(function() {
window.location = "/";
window.location.reload();
}, 1000);
}
@ -539,7 +540,7 @@ function processData(data) {
var minutes = uptime % 60; uptime = parseInt(uptime / 60);
var hours = uptime % 24; uptime = parseInt(uptime / 24);
var days = uptime;
data[key] = days + 'd ' + ("00" + hours).slice(-2) + 'h ' + ("00" + minutes).slice(-2) + 'm ' + ("00" + seconds).slice(-2) + 's';
data[key] = days + 'd ' + zeroPad(hours, 2) + 'h ' + zeroPad(minutes, 2) + 'm ' + zeroPad(seconds, 2) + 's';
}
if (key == "useWhite") {
@ -684,21 +685,19 @@ function getJson(str) {
}
}
function connect(h, p) {
function connect(host) {
if (typeof h === 'undefined') {
h = window.location.hostname;
}
if (typeof p === 'undefined') {
p = location.port;
if (typeof host === 'undefined') {
host = window.location.href;
} else {
if (!host.startsWith("http")) {
host = "http://" + host + "/";
}
}
host = h;
port = p;
protocol = location.protocol;
wsproto = (protocol == 'https:') ? 'wss:' : 'ws:';
wshost = host.replace("http", "ws");
if (websock) websock.close();
websock = new WebSocket(wsproto + '//' + host + ':' + port + '/ws');
websock = new WebSocket(wshost + 'ws');
websock.onopen = function(evt) {
console.log("Connected");
};
@ -743,13 +742,9 @@ function init() {
websock.send(JSON.stringify({'action': 'ha_send', 'data': $("input[name='haPrefix']").val()}));
});
var protocol = location.protocol;
var host = window.location.hostname;
var port = location.port;
$.ajax({
'method': 'GET',
'url': protocol + '//' + host + ':' + port + '/auth'
'url': window.location.href + 'auth'
}).done(function(data) {
connect();
}).fail(function(){


+ 21
- 4
code/html/index.html View File

@ -133,7 +133,7 @@
<div class="footer">
&copy; 2016-2017<br />
Xose Prez<br/>
Xose Pérez<br/>
<a href="http://tinkerman.cat" target="_blank">http://tinkerman.cat</a><br/>
<a href="https://bitbucket.org/xoseperez/espurna" target="_blank">ESPurna @ Bitbucket</a><br/>
GPLv3 license<br/>
@ -170,6 +170,11 @@
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="analogValue" readonly />
</div>
<div class="pure-g module module-counter">
<label class="pure-u-1 pure-u-sm-1-4" for="counterValue">Counts / last minute</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="counterValue" readonly />
</div>
<div class="pure-g module module-ds">
<label class="pure-u-1 pure-u-sm-1-4" for="dsTmp">Temperature (<span id="tmpUnit"></span>)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="dsTmp" readonly />
@ -415,8 +420,8 @@
<div class="pure-g module module-ds module-dht">
<label class="pure-u-1 pure-u-sm-1-4" for="tmpUnits">Temperature units</label>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="12" value="0"> Celsius (C)</input></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="13" value="1"> Fahrenheit (F)</input></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="12" value="0"> Celsius (&deg;C)</input></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="13" value="1"> Fahrenheit (&deg;F)</input></div>
</div>
<div class="pure-g">
@ -481,6 +486,18 @@
</div>
</div>
<div class="pure-g module module-nofuss">
<div class="pure-u-1 pure-u-sm-1-4"><label for="nofussEnabled">Automatic remote updates (NoFUSS)</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="nofussEnabled" /></div>
</div>
<div class="pure-g module module-nofuss">
<label class="pure-u-1 pure-u-md-1-4" for="nofussServer">NoFUSS server</label>
<input name="nofussServer" class="pure-u-1 pure-u-md-3-4" type="text" tabindex="15" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">This name address of the NoFUSS server for automatic remote updates (see https://bitbucket.org/xoseperez/nofuss).</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4">Upgrade</label>
<input class="pure-u-1-2 pure-u-md-1-2" name="filename" type="text" readonly />
@ -488,7 +505,7 @@
<div class=" pure-u-1-8 pure-u-md-1-8"><button class="pure-button button-upgrade pure-u-23-24">Upgrade</button></div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4"><progress id="upgrade-progress"></progress></div>
<input name="upgrade" type="file" tabindex="15" />
<input name="upgrade" type="file" tabindex="16" />
</div>
</fieldset>


+ 15
- 2
code/platformio.ini View File

@ -24,8 +24,8 @@ lib_deps =
Brzo I2C
https://bitbucket.org/xoseperez/justwifi.git#1.1.4
https://bitbucket.org/xoseperez/hlw8012.git#1.0.1
https://bitbucket.org/xoseperez/fauxmoesp.git#2.1.1
https://bitbucket.org/xoseperez/nofuss.git#0.2.4
https://bitbucket.org/xoseperez/fauxmoesp.git#dev
https://bitbucket.org/xoseperez/nofuss.git#0.2.5
https://bitbucket.org/xoseperez/emonliteesp.git#0.2.0
https://bitbucket.org/xoseperez/debounceevent.git#2.0.1
https://github.com/xoseperez/my9291#2.0.0
@ -156,6 +156,19 @@ lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DITEAD_SONOFF_BASIC -DDHT_SUPPORT=1
monitor_baud = 115200
[env:itead-sonoff-basic-dht22-ota]
platform = espressif8266
framework = arduino
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DITEAD_SONOFF_BASIC -DDHT_SUPPORT=1
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
[env:itead-sonoff-basic-ds18b20]
platform = espressif8266
framework = arduino


Loading…
Cancel
Save