Browse Source

Merge branch 'dev' into sensors

Conflicts:
	code/espurna/sensors/BH1750Sensor.h
	code/espurna/sensors/SI7021Sensor.h
softuart
Xose Pérez 6 years ago
parent
commit
8c463c5a70
51 changed files with 4458 additions and 3961 deletions
  1. +3
    -0
      code/espurna/alexa.ino
  2. +32
    -0
      code/espurna/broker.ino
  3. +3
    -0
      code/espurna/button.ino
  4. +12
    -13
      code/espurna/config/all.h
  5. +1
    -0
      code/espurna/config/arduino.h
  6. +45
    -3
      code/espurna/config/general.h
  7. +7
    -4
      code/espurna/config/hardware.h
  8. +5
    -0
      code/espurna/config/prototypes.h
  9. +3
    -2
      code/espurna/config/sensors.h
  10. +1
    -1
      code/espurna/config/version.h
  11. BIN
      code/espurna/data/index.html.gz
  12. +36
    -30
      code/espurna/debug.ino
  13. +3
    -2
      code/espurna/domoticz.ino
  14. +35
    -323
      code/espurna/espurna.ino
  15. +15
    -2
      code/espurna/i2c.ino
  16. +5
    -0
      code/espurna/ir.ino
  17. +4
    -0
      code/espurna/led.ino
  18. +22
    -4
      code/espurna/light.ino
  19. +7
    -0
      code/espurna/mdns.ino
  20. +57
    -3
      code/espurna/mqtt.ino
  21. +3
    -0
      code/espurna/nofuss.ino
  22. +92
    -32
      code/espurna/ntp.ino
  23. +4
    -1
      code/espurna/ota.ino
  24. +7
    -0
      code/espurna/relay.ino
  25. +3
    -0
      code/espurna/rf.ino
  26. +3
    -0
      code/espurna/rfbridge.ino
  27. +71
    -59
      code/espurna/scheduler.ino
  28. +7
    -0
      code/espurna/sensor.ino
  29. +23
    -11
      code/espurna/sensors/BH1750Sensor.h
  30. +1
    -0
      code/espurna/sensors/BMX280Sensor.h
  31. +1
    -0
      code/espurna/sensors/DHTSensor.h
  32. +5
    -0
      code/espurna/sensors/DallasSensor.h
  33. +2
    -0
      code/espurna/sensors/EmonADC121Sensor.h
  34. +1
    -0
      code/espurna/sensors/EmonADS1X15Sensor.h
  35. +2
    -0
      code/espurna/sensors/EmonAnalogSensor.h
  36. +2
    -0
      code/espurna/sensors/MHZ19Sensor.h
  37. +2
    -0
      code/espurna/sensors/SHT3XI2CSensor.h
  38. +58
    -36
      code/espurna/sensors/SI7021Sensor.h
  39. +8
    -6
      code/espurna/settings.ino
  40. +3
    -0
      code/espurna/ssdp.ino
  41. +3175
    -3162
      code/espurna/static/index.html.gz.h
  42. +122
    -0
      code/espurna/system.ino
  43. +7
    -0
      code/espurna/thinkspeak.ino
  44. +272
    -105
      code/espurna/utils.ino
  45. +3
    -0
      code/espurna/wifi.ino
  46. +22
    -4
      code/espurna/ws.ino
  47. +179
    -119
      code/html/custom.css
  48. +50
    -0
      code/html/custom.js
  49. +33
    -38
      code/html/index.html
  50. +0
    -0
      code/ota.py
  51. +1
    -1
      code/platformio.ini

+ 3
- 0
code/espurna/alexa.ino View File

@ -68,6 +68,9 @@ void alexaSetup() {
return relayStatus(device_id); return relayStatus(device_id);
}); });
// Register loop
espurnaRegisterLoop(alexaLoop);
} }
void alexaLoop() { void alexaLoop() {


+ 32
- 0
code/espurna/broker.ino View File

@ -0,0 +1,32 @@
/*
BROKER MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if BROKER_SUPPORT
#include <vector>
std::vector<void (*)(const char *, unsigned char, const char *)> _broker_callbacks;
// -----------------------------------------------------------------------------
void brokerRegister(void (*callback)(const char *, unsigned char, const char *)) {
_broker_callbacks.push_back(callback);
}
void brokerPublish(const char * topic, unsigned char id, const char * message) {
//DEBUG_MSG_P(PSTR("[BROKER] Message %s[%u] => %s\n"), topic, id, message);
for (unsigned char i=0; i<_broker_callbacks.size(); i++) {
(_broker_callbacks[i])(topic, id, message);
}
}
void brokerPublish(const char * topic, const char * message) {
brokerPublish(topic, 0, message);
}
#endif // BROKER_SUPPORT

+ 3
- 0
code/espurna/button.ino View File

@ -184,6 +184,9 @@ void buttonSetup() {
DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), _buttons.size()); DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), _buttons.size());
// Register loop
espurnaRegisterLoop(buttonLoop);
} }
void buttonLoop() { void buttonLoop() {


+ 12
- 13
code/espurna/config/all.h View File

@ -1,15 +1,3 @@
#include "version.h"
#include "arduino.h"
#include "hardware.h"
#include "defaults.h"
#include "general.h"
#include "prototypes.h"
#include "sensors.h"
#ifdef USE_CORE_VERSION_H
#include "core_version.h"
#endif
/* /*
If you want to modify the stock configuration but you don't want to touch If you want to modify the stock configuration but you don't want to touch
the repo files you can either define USE_CUSTOM_H or remove the the repo files you can either define USE_CUSTOM_H or remove the
@ -20,7 +8,18 @@
(Define USE_CUSTOM_H on commandline for platformio: (Define USE_CUSTOM_H on commandline for platformio:
export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'" ) export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'" )
*/ */
#ifdef USE_CUSTOM_H #ifdef USE_CUSTOM_H
#include "custom.h" #include "custom.h"
#endif #endif
#include "version.h"
#include "arduino.h"
#include "hardware.h"
#include "defaults.h"
#include "general.h"
#include "prototypes.h"
#include "sensors.h"
#ifdef USE_CORE_VERSION_H
#include "core_version.h"
#endif

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

@ -76,6 +76,7 @@
//#define LLMNR_SUPPORT 1 // Only with Arduino Core 2.4.0 //#define LLMNR_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define MDNS_SERVER_SUPPORT 0 //#define MDNS_SERVER_SUPPORT 0
//#define MDNS_CLIENT_SUPPORT 1 //#define MDNS_CLIENT_SUPPORT 1
//#define BROKER_SUPPORT 0
//#define MQTT_SUPPORT 0 //#define MQTT_SUPPORT 0
//#define NETBIOS_SUPPORT 1 // Only with Arduino Core 2.4.0 //#define NETBIOS_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define NOFUSS_SUPPORT 1 //#define NOFUSS_SUPPORT 1


+ 45
- 3
code/espurna/config/general.h View File

@ -64,6 +64,11 @@
#define SERIAL_BAUDRATE 115200 // Default baudrate #define SERIAL_BAUDRATE 115200 // Default baudrate
#endif #endif
#ifndef DEBUG_ADD_TIMESTAMP
#define DEBUG_ADD_TIMESTAMP 1 // Add timestamp to debug messages
// (in millis overflowing every 1000 seconds)
#endif
// Second serial port (used for RX) // Second serial port (used for RX)
//#define SERIAL_RX_PORT Serial // This setting is usually defined //#define SERIAL_RX_PORT Serial // This setting is usually defined
@ -143,12 +148,17 @@
#define EEPROM_ENERGY_COUNT 1 // Address for the energy counter (4 bytes) #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_CUSTOM_RESET 5 // Address for the reset reason (1 byte)
#define EEPROM_CRASH_COUNTER 6 // Address for the crash counter (1 byte) #define EEPROM_CRASH_COUNTER 6 // Address for the crash counter (1 byte)
#define EEPROM_DATA_END 7 // End of custom EEPROM data block
#define EEPROM_MESSAGE_ID 7 // Address for the MQTT message id (4 bytes)
#define EEPROM_DATA_END 11 // End of custom EEPROM data block
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// HEARTBEAT // HEARTBEAT
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
#ifndef HEARTBEAT_ENABLED
#define HEARTBEAT_ENABLED 1
#endif
#define HEARTBEAT_INTERVAL 300000 // Interval between heartbeat messages (in ms) #define HEARTBEAT_INTERVAL 300000 // Interval between heartbeat messages (in ms)
#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value #define UPTIME_OVERFLOW 4294967295 // Uptime overflow value
@ -365,6 +375,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define WS_BUFFER_SIZE 5 // Max number of secured websocket connections #define WS_BUFFER_SIZE 5 // Max number of secured websocket connections
#define WS_TIMEOUT 1800000 // Timeout for secured websocket #define WS_TIMEOUT 1800000 // Timeout for secured websocket
#define WS_UPDATE_INTERVAL 30000 // Update clients every 30 seconds
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// API // API
@ -408,6 +419,11 @@ PROGMEM const char* const custom_reset_string[] = {
#define SSDP_SUPPORT 0 // Publish device using SSDP protocol by default (3.32Kb) #define SSDP_SUPPORT 0 // Publish device using SSDP protocol by default (3.32Kb)
#endif #endif
#if WEB_SUPPORT == 0
#undef SSDP_SUPPORT
#define SSDP_SUPPORT 0 // SSDP support requires web support
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// SPIFFS // SPIFFS
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -490,11 +506,22 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_USE_JSON_DELAY 100 // Wait this many ms before grouping messages #define MQTT_USE_JSON_DELAY 100 // Wait this many ms before grouping messages
#define MQTT_QUEUE_MAX_SIZE 10 // Size of the MQTT queue when MQTT_USE_JSON is enabled #define MQTT_QUEUE_MAX_SIZE 10 // Size of the MQTT queue when MQTT_USE_JSON is enabled
// These are the properties that will be send when useJson is true
// These are the properties that will be sent when useJson is true
#ifndef MQTT_ENQUEUE_IP
#define MQTT_ENQUEUE_IP 1 #define MQTT_ENQUEUE_IP 1
#endif
#ifndef MQTT_ENQUEUE_MAC
#define MQTT_ENQUEUE_MAC 1 #define MQTT_ENQUEUE_MAC 1
#endif
#ifndef MQTT_ENQUEUE_HOSTNAME
#define MQTT_ENQUEUE_HOSTNAME 1 #define MQTT_ENQUEUE_HOSTNAME 1
#endif
#ifndef MQTT_ENQUEUE_DATETIME
#define MQTT_ENQUEUE_DATETIME 1 #define MQTT_ENQUEUE_DATETIME 1
#endif
#ifndef MQTT_ENQUEUE_MESSAGE_ID
#define MQTT_ENQUEUE_MESSAGE_ID 1
#endif
// These particles will be concatenated to the MQTT_TOPIC base to form the actual topic // These particles will be concatenated to the MQTT_TOPIC base to form the actual topic
#define MQTT_TOPIC_JSON "data" #define MQTT_TOPIC_JSON "data"
@ -511,6 +538,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_TOPIC_STATUS "status" #define MQTT_TOPIC_STATUS "status"
#define MQTT_TOPIC_MAC "mac" #define MQTT_TOPIC_MAC "mac"
#define MQTT_TOPIC_RSSI "rssi" #define MQTT_TOPIC_RSSI "rssi"
#define MQTT_TOPIC_MESSAGE_ID "id"
#define MQTT_TOPIC_APP "app" #define MQTT_TOPIC_APP "app"
#define MQTT_TOPIC_INTERVAL "interval" #define MQTT_TOPIC_INTERVAL "interval"
#define MQTT_TOPIC_HOSTNAME "host" #define MQTT_TOPIC_HOSTNAME "host"
@ -541,11 +569,25 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_DISCONNECT_EVENT 1 #define MQTT_DISCONNECT_EVENT 1
#define MQTT_MESSAGE_EVENT 2 #define MQTT_MESSAGE_EVENT 2
#define MQTT_MESSAGE_ID_SHIFT 1000 // Store MQTT message id into EEPROM every these many
// Custom get and set postfixes // Custom get and set postfixes
// Use something like "/status" or "/set", with leading slash // Use something like "/status" or "/set", with leading slash
// Since 1.9.0 the default value is "" for getter and "/set" for setter // Since 1.9.0 the default value is "" for getter and "/set" for setter
#ifndef MQTT_GETTER
#define MQTT_GETTER "" #define MQTT_GETTER ""
#endif
#ifndef MQTT_SETTER
#define MQTT_SETTER "/set" #define MQTT_SETTER "/set"
#endif
// -----------------------------------------------------------------------------
// BROKER
// -----------------------------------------------------------------------------
#ifndef BROKER_SUPPORT
#define BROKER_SUPPORT 1 // The broker is a poor-man's pubsub manager
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// SETTINGS // SETTINGS
@ -710,7 +752,6 @@ PROGMEM const char* const custom_reset_string[] = {
#define NTP_SUPPORT 1 // Scheduler needs NTP #define NTP_SUPPORT 1 // Scheduler needs NTP
#endif #endif
#define SCHEDULER_UPDATE_SEC 5 // Scheduler perform switch at hh:mm:05
#define SCHEDULER_MAX_SCHEDULES 10 // Max schedules alowed #define SCHEDULER_MAX_SCHEDULES 10 // Max schedules alowed
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -725,6 +766,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define NTP_TIME_OFFSET 1 // Default timezone offset (GMT+1) #define NTP_TIME_OFFSET 1 // Default timezone offset (GMT+1)
#define NTP_DAY_LIGHT true // Enable daylight time saving by default #define NTP_DAY_LIGHT true // Enable daylight time saving by default
#define NTP_UPDATE_INTERVAL 1800 // NTP check every 30 minutes #define NTP_UPDATE_INTERVAL 1800 // NTP check every 30 minutes
#define NTP_START_DELAY 1000 // Delay NTP start 1 second
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// ALEXA // ALEXA


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

@ -1393,9 +1393,12 @@
#define RELAY8_TYPE RELAY_TYPE_NORMAL #define RELAY8_TYPE RELAY_TYPE_NORMAL
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Unknown hardware
// -----------------------------------------------------------------------------
#else
#error "UNSUPPORTED HARDWARE!"
#endif
// -----------------------------------------------------------------------------
// Check definitions
// -----------------------------------------------------------------------------
#if not defined(MANUFACTURER) || not defined(DEVICE)
#error "UNSUPPORTED HARDWARE!!"
#endif #endif

+ 5
- 0
code/espurna/config/prototypes.h View File

@ -47,6 +47,11 @@ typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callb
void mqttRegister(mqtt_callback_f callback); void mqttRegister(mqtt_callback_f callback);
String mqttTopicKey(char * topic); String mqttTopicKey(char * topic);
// -----------------------------------------------------------------------------
// Broker
// -----------------------------------------------------------------------------
void brokerRegister(void (*)(const char *, unsigned char, const char *));
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Settings // Settings
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


+ 3
- 2
code/espurna/config/sensors.h View File

@ -114,7 +114,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
#ifndef BH1750_SUPPORT #ifndef BH1750_SUPPORT
#define BH1750_SUPPORT 0
#define BH1750_SUPPORT 1
#endif #endif
#ifndef BH1750_ADDRESS #ifndef BH1750_ADDRESS
@ -478,7 +478,8 @@
#define I2C_CLOCK_STRETCH_TIME 200 // BRZO clock stretch time #define I2C_CLOCK_STRETCH_TIME 200 // BRZO clock stretch time
#define I2C_SCL_FREQUENCY 1000 // BRZO SCL frequency #define I2C_SCL_FREQUENCY 1000 // BRZO SCL frequency
#define I2C_CLEAR_BUS 0 // Clear I2C bus at boot
#define I2C_CLEAR_BUS 0 // Clear I2C bus on boot
#define I2C_PERFORM_SCAN 1 // Perform a bus scan on boot
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Internal power monitor // Internal power monitor


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

@ -1,5 +1,5 @@
#define APP_NAME "ESPURNA" #define APP_NAME "ESPURNA"
#define APP_VERSION "1.12.2a"
#define APP_VERSION "1.12.2b"
#define APP_AUTHOR "xose.perez@gmail.com" #define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat" #define APP_WEBSITE "http://tinkerman.cat"
#define CFG_VERSION 3 #define CFG_VERSION 3

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


+ 36
- 30
code/espurna/debug.ino View File

@ -10,6 +10,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <stdio.h> #include <stdio.h>
#include <stdarg.h> #include <stdarg.h>
#include <EEPROM.h>
extern "C" { extern "C" {
#include "user_interface.h" #include "user_interface.h"
@ -20,18 +21,20 @@ extern "C" {
WiFiUDP _udp_debug; WiFiUDP _udp_debug;
#endif #endif
void debugSend(const char * format, ...) {
void _debugSend(char * message) {
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);
#if DEBUG_ADD_TIMESTAMP
static bool add_timestamp = true;
char timestamp[10] = {0};
if (add_timestamp) snprintf_P(timestamp, sizeof(timestamp), PSTR("[%06lu] "), millis() % 1000000);
add_timestamp = (message[strlen(message)-1] == 10);
#endif
#if DEBUG_SERIAL_SUPPORT #if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.printf(buffer);
#if DEBUG_ADD_TIMESTAMP
DEBUG_PORT.printf(timestamp);
#endif
DEBUG_PORT.printf(message);
#endif #endif
#if DEBUG_UDP_SUPPORT #if DEBUG_UDP_SUPPORT
@ -39,7 +42,10 @@ void debugSend(const char * format, ...) {
if (systemCheck()) { if (systemCheck()) {
#endif #endif
_udp_debug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT); _udp_debug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
_udp_debug.write(buffer);
#if DEBUG_ADD_TIMESTAMP
_udp_debug.write(timestamp);
#endif
_udp_debug.write(message);
_udp_debug.endPacket(); _udp_debug.endPacket();
#if SYSTEM_CHECK_ENABLED #if SYSTEM_CHECK_ENABLED
} }
@ -47,9 +53,27 @@ void debugSend(const char * format, ...) {
#endif #endif
#if DEBUG_TELNET_SUPPORT #if DEBUG_TELNET_SUPPORT
_telnetWrite(buffer, strlen(buffer));
#if DEBUG_ADD_TIMESTAMP
_telnetWrite(timestamp, strlen(timestamp));
#endif
_telnetWrite(message, strlen(message));
#endif #endif
}
void debugSend(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);
_debugSend(buffer);
delete[] buffer; delete[] buffer;
} }
@ -67,25 +91,7 @@ void debugSend_P(PGM_P format_P, ...) {
ets_vsnprintf(buffer, len, format, args); ets_vsnprintf(buffer, len, format, args);
va_end(args); va_end(args);
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.printf(buffer);
#endif
#if DEBUG_UDP_SUPPORT
#if SYSTEM_CHECK_ENABLED
if (systemCheck()) {
#endif
_udp_debug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
_udp_debug.write(buffer);
_udp_debug.endPacket();
#if SYSTEM_CHECK_ENABLED
}
#endif
#endif
#if DEBUG_TELNET_SUPPORT
_telnetWrite(buffer, strlen(buffer));
#endif
_debugSend(buffer);
delete[] buffer; delete[] buffer;


+ 3
- 2
code/espurna/domoticz.ino View File

@ -104,8 +104,9 @@ void _domoticzWebSocketOnSend(JsonObject& root) {
#endif // WEB_SUPPORT #endif // WEB_SUPPORT
void _domoticzConfigure() { void _domoticzConfigure() {
_dcz_enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
_domoticzMqttSubscribe(_dcz_enabled);
bool enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
if (enabled != _dcz_enabled) _domoticzMqttSubscribe(enabled);
_dcz_enabled = enabled;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------


+ 35
- 323
code/espurna/espurna.ino View File

@ -20,300 +20,61 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "config/all.h" #include "config/all.h"
#include <EEPROM.h>
#include <vector>
std::vector<void (*)()> _loop_callbacks;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// METHODS
// REGISTER
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
unsigned long _loopDelay = 0;
void hardwareSetup() {
EEPROM.begin(EEPROM_SIZE);
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.begin(SERIAL_BAUDRATE);
#if DEBUG_ESP_WIFI
DEBUG_PORT.setDebugOutput(true);
#endif
#elif defined(SERIAL_BAUDRATE)
Serial.begin(SERIAL_BAUDRATE);
#endif
#if SPIFFS_SUPPORT
SPIFFS.begin();
#endif
#if defined(ESPLIVE)
//The ESPLive has an ADC MUX which needs to be configured.
pinMode(16, OUTPUT);
digitalWrite(16, HIGH); //Defualt CT input (pin B, solder jumper B)
#endif
}
void hardwareLoop() {
// Heartbeat
static unsigned long last = 0;
if ((last == 0) || (millis() - last > HEARTBEAT_INTERVAL)) {
last = millis();
heartbeat();
}
void espurnaRegisterLoop(void (*callback)()) {
_loop_callbacks.push_back(callback);
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// BOOTING // BOOTING
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
unsigned int sectors(size_t size) {
return (int) (size + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE;
}
void welcome() {
DEBUG_MSG_P(PSTR("\n\n"));
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: %u MHz\n"), ESP.getCpuFreqMHz());
DEBUG_MSG_P(PSTR("[INIT] SDK version: %s\n"), ESP.getSdkVersion());
DEBUG_MSG_P(PSTR("[INIT] Core version: %s\n"), getCoreVersion().c_str());
DEBUG_MSG_P(PSTR("[INIT] Core revision: %s\n"), getCoreRevision().c_str());
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
FlashMode_t mode = ESP.getFlashChipMode();
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("[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("[INIT] SPIFFS size: %8u bytes / %4d sectors\n"), fs_info.totalBytes, sectors(fs_info.totalBytes));
}
#else
DEBUG_MSG_P(PSTR("[INIT] SPIFFS size: %8u bytes / %4d sectors\n"), 0, 0);
#endif
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);
} else {
DEBUG_MSG_P(PSTR("[INIT] No SPIFFS partition\n"));
}
DEBUG_MSG_P(PSTR("\n"));
#endif
void setup() {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[INIT] BOARD: %s\n"), getBoardName().c_str());
DEBUG_MSG_P(PSTR("[INIT] SUPPORT:"));
#if ALEXA_SUPPORT
DEBUG_MSG_P(PSTR(" ALEXA"));
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_SERIAL"));
#endif
#if DEBUG_TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_TELNET"));
#endif
#if DEBUG_UDP_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_UDP"));
#endif
#if DOMOTICZ_SUPPORT
DEBUG_MSG_P(PSTR(" DOMOTICZ"));
#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 LLMNR_SUPPORT
DEBUG_MSG_P(PSTR(" LLMNR"));
#endif
#if MDNS_SERVER_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS"));
#endif
#if NETBIOS_SUPPORT
DEBUG_MSG_P(PSTR(" NETBIOS"));
#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 SCHEDULER_SUPPORT
DEBUG_MSG_P(PSTR(" SCHEDULER"));
#endif
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR(" SENSOR"));
#endif
#if SPIFFS_SUPPORT
DEBUG_MSG_P(PSTR(" SPIFFS"));
#endif
#if SSDP_SUPPORT
DEBUG_MSG_P(PSTR(" SSDP"));
#endif
#if TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" TELNET"));
#endif
#if TERMINAL_SUPPORT
DEBUG_MSG_P(PSTR(" TERMINAL"));
#endif
#if THINGSPEAK_SUPPORT
DEBUG_MSG_P(PSTR(" THINGSPEAK"));
#endif
#if WEB_SUPPORT
DEBUG_MSG_P(PSTR(" WEB"));
#endif
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n[INIT] SENSORS:"));
#if ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" ANALOG"));
#endif
#if BMX280_SUPPORT
DEBUG_MSG_P(PSTR(" BMX280"));
#endif
#if DALLAS_SUPPORT
DEBUG_MSG_P(PSTR(" DALLAS"));
#endif
#if DHT_SUPPORT
DEBUG_MSG_P(PSTR(" DHTXX"));
#endif
#if DIGITAL_SUPPORT
DEBUG_MSG_P(PSTR(" DIGITAL"));
#endif
#if ECH1560_SUPPORT
DEBUG_MSG_P(PSTR(" ECH1560"));
#endif
#if EMON_ADC121_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ADC121"));
#endif
#if EMON_ADS1X15_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ADX1X15"));
#endif
#if EMON_ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ANALOG"));
#endif
#if EVENTS_SUPPORT
DEBUG_MSG_P(PSTR(" EVENTS"));
#endif
#if HLW8012_SUPPORT
DEBUG_MSG_P(PSTR(" HLW8012"));
#endif
#if MHZ19_SUPPORT
DEBUG_MSG_P(PSTR(" MHZ19"));
#endif
#if PMSX003_SUPPORT
DEBUG_MSG_P(PSTR(" PMSX003"));
#endif
#if SHT3X_I2C_SUPPORT
DEBUG_MSG_P(PSTR(" SHT3X_I2C"));
#endif
#if SI7021_SUPPORT
DEBUG_MSG_P(PSTR(" SI7021"));
#endif
#if V9261F_SUPPORT
DEBUG_MSG_P(PSTR(" V9261F"));
#endif
#endif // SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n\n"));
// Basic modules, will always run
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
unsigned char reason = resetReason();
if (reason > 0) {
char buffer[32];
strcpy_P(buffer, custom_reset_string[reason-1]);
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), buffer);
} else {
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), (char *) ESP.getResetReason().c_str());
}
DEBUG_MSG_P(PSTR("[INIT] Free heap: %u bytes\n"), getFreeHeap());
#if ADC_VCC_ENABLED
DEBUG_MSG_P(PSTR("[INIT] Power: %u mV\n"), ESP.getVcc());
#endif
DEBUG_MSG_P(PSTR("[INIT] Power saving delay value: %lu ms\n"), _loopDelay);
if (!systemCheck()) DEBUG_MSG_P(PSTR("\n[INIT] Device is in SAFE MODE\n"));
DEBUG_MSG_P(PSTR("\n"));
}
void setup() {
// Init EEPROM, Serial and SPIFFS
hardwareSetup();
// Question system stability
#if SYSTEM_CHECK_ENABLED
systemCheck(false);
#endif
// Init EEPROM, Serial, SPIFFS and system check
systemSetup();
// Init persistance and terminal features // Init persistance and terminal features
settingsSetup(); settingsSetup();
// Hostname & board name initialization
if (getSetting("hostname").length() == 0) { if (getSetting("hostname").length() == 0) {
setSetting("hostname", getIdentifier()); setSetting("hostname", getIdentifier());
} }
setBoardName(); setBoardName();
// Cache loop delay value to speed things (recommended max 250ms)
_loopDelay = atol(getSetting("loopDelay", LOOP_DELAY_TIME).c_str());
// Show welcome message and system configuration // Show welcome message and system configuration
welcome();
info();
// Basic modules, will always run
wifiSetup(); wifiSetup();
otaSetup(); otaSetup();
#if TELNET_SUPPORT #if TELNET_SUPPORT
telnetSetup(); telnetSetup();
#endif #endif
// Do not run the next services if system is flagged stable
// -------------------------------------------------------------------------
// Check if system is stable
// -------------------------------------------------------------------------
#if SYSTEM_CHECK_ENABLED #if SYSTEM_CHECK_ENABLED
if (!systemCheck()) return; if (!systemCheck()) return;
#endif #endif
// -------------------------------------------------------------------------
// Next modules will be only loaded if system is flagged as stable
// -------------------------------------------------------------------------
// Init webserver required before any module that uses API // Init webserver required before any module that uses API
#if WEB_SUPPORT #if WEB_SUPPORT
webSetup(); webSetup();
@ -321,19 +82,23 @@ void setup() {
apiSetup(); apiSetup();
#endif #endif
// lightSetup must be called before relaySetup
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
lightSetup(); lightSetup();
#endif #endif
relaySetup(); relaySetup();
buttonSetup(); buttonSetup();
ledSetup(); ledSetup();
#if MQTT_SUPPORT #if MQTT_SUPPORT
mqttSetup(); mqttSetup();
#endif #endif
#if MDNS_SERVER_SUPPORT #if MDNS_SERVER_SUPPORT
mdnsServerSetup(); mdnsServerSetup();
#endif #endif
#if MDNS_CLIENT_SUPPORT
mdnsClientSetup();
#endif
#if LLMNR_SUPPORT #if LLMNR_SUPPORT
llmnrSetup(); llmnrSetup();
#endif #endif
@ -348,12 +113,7 @@ void setup() {
#endif #endif
#if I2C_SUPPORT #if I2C_SUPPORT
i2cSetup(); i2cSetup();
#if I2C_CLEAR_BUS
i2cClearBus();
#endif
i2cScan();
#endif #endif
#ifdef ITEAD_SONOFF_RFBRIDGE #ifdef ITEAD_SONOFF_RFBRIDGE
rfbSetup(); rfbSetup();
#endif #endif
@ -388,6 +148,11 @@ void setup() {
schSetup(); schSetup();
#endif #endif
// 3rd party code hook
#if USE_EXTRA
extraSetup();
#endif
// Prepare configuration for version 2.0 // Prepare configuration for version 2.0
migrate(); migrate();
@ -397,62 +162,9 @@ void setup() {
void loop() { void loop() {
hardwareLoop();
settingsLoop();
wifiLoop();
otaLoop();
#if SYSTEM_CHECK_ENABLED
systemCheckLoop();
// Do not run the next services if system is flagged stable
if (!systemCheck()) return;
#endif
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
lightLoop();
#endif
relayLoop();
buttonLoop();
ledLoop();
#if MQTT_SUPPORT
mqttLoop();
#endif
#ifdef ITEAD_SONOFF_RFBRIDGE
rfbLoop();
#endif
#if SSDP_SUPPORT
ssdpLoop();
#endif
#if NTP_SUPPORT
ntpLoop();
#endif
#if ALEXA_SUPPORT
alexaLoop();
#endif
#if NOFUSS_SUPPORT
nofussLoop();
#endif
#if RF_SUPPORT
rfLoop();
#endif
#if IR_SUPPORT
irLoop();
#endif
#if SENSOR_SUPPORT
sensorLoop();
#endif
#if THINGSPEAK_SUPPORT
tspkLoop();
#endif
#if SCHEDULER_SUPPORT
schLoop();
#endif
#if MDNS_CLIENT_SUPPORT
mdnsClientLoop();
#endif
// Power saving delay
delay(_loopDelay);
// Call registered loop callbacks
for (unsigned char i = 0; i < _loop_callbacks.size(); i++) {
(_loop_callbacks[i])();
}
} }

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

@ -232,12 +232,17 @@ void i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value) {
} }
void i2c_write_uint16(uint8_t address, uint8_t reg, uint16_t value) { void i2c_write_uint16(uint8_t address, uint8_t reg, uint16_t value) {
uint8_t buffer[3] = {reg, value >> 8, value & 0xFF};
uint8_t buffer[3];
buffer[0] = reg;
buffer[1] = (value >> 8) & 0xFF;
buffer[2] = (value >> 0) & 0xFF;
i2c_write_buffer(address, buffer, 3); i2c_write_buffer(address, buffer, 3);
} }
void i2c_write_uint16(uint8_t address, uint16_t value) { void i2c_write_uint16(uint8_t address, uint16_t value) {
uint8_t buffer[2] = {value >> 8, value & 0xFF};
uint8_t buffer[2];
buffer[0] = (value >> 8) & 0xFF;
buffer[1] = (value >> 0) & 0xFF;
i2c_write_buffer(address, buffer, 2); i2c_write_buffer(address, buffer, 2);
} }
@ -346,6 +351,14 @@ void i2cSetup() {
DEBUG_MSG_P(PSTR("[I2C] Using GPIO%u for SDA and GPIO%u for SCL\n"), sda, scl); DEBUG_MSG_P(PSTR("[I2C] Using GPIO%u for SDA and GPIO%u for SCL\n"), sda, scl);
#if I2C_CLEAR_BUS
i2cClearBus();
#endif
#if I2C_PERFORM_SCAN
i2cScan();
#endif
} }
#endif #endif

+ 5
- 0
code/espurna/ir.ino View File

@ -92,8 +92,13 @@ void _irProcessCode(unsigned long code) {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void irSetup() { void irSetup() {
_ir_recv = new IRrecv(IR_PIN); _ir_recv = new IRrecv(IR_PIN);
_ir_recv->enableIRIn(); _ir_recv->enableIRIn();
// Register loop
espurnaRegisterLoop(irLoop);
} }
void irLoop() { void irLoop() {


+ 4
- 0
code/espurna/led.ino View File

@ -167,6 +167,10 @@ void ledSetup() {
DEBUG_MSG_P(PSTR("[LED] Number of leds: %d\n"), _leds.size()); DEBUG_MSG_P(PSTR("[LED] Number of leds: %d\n"), _leds.size());
// Register loop
espurnaRegisterLoop(ledLoop);
} }
void ledLoop() { void ledLoop() {


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

@ -571,7 +571,7 @@ void lightMQTT() {
// Channels // Channels
for (unsigned int i=0; i < _light_channel.size(); i++) { for (unsigned int i=0; i < _light_channel.size(); i++) {
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _light_channel[i].value);
itoa(_light_channel[i].value, buffer, 10);
mqttSend(MQTT_TOPIC_CHANNEL, i, buffer); mqttSend(MQTT_TOPIC_CHANNEL, i, buffer);
} }
@ -588,6 +588,22 @@ void lightMQTTGroup() {
#endif #endif
// -----------------------------------------------------------------------------
// Broker
// -----------------------------------------------------------------------------
#if BROKER_SUPPORT
void lightBroker() {
char buffer[10];
for (unsigned int i=0; i < _light_channel.size(); i++) {
itoa(_light_channel[i].value, buffer, 10);
brokerPublish(MQTT_TOPIC_CHANNEL, i, buffer);
}
}
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// API // API
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -610,6 +626,11 @@ void lightUpdate(bool save, bool forward, bool group_forward) {
_light_steps_left = _light_use_transitions ? LIGHT_TRANSITION_STEPS : 1; _light_steps_left = _light_use_transitions ? LIGHT_TRANSITION_STEPS : 1;
_light_transition_ticker.attach_ms(LIGHT_TRANSITION_STEP, _lightProviderUpdate); _light_transition_ticker.attach_ms(LIGHT_TRANSITION_STEP, _lightProviderUpdate);
// Report channels to local broker
#if BROKER_SUPPORT
lightBroker();
#endif
// Report color & brightness to MQTT broker // Report color & brightness to MQTT broker
#if MQTT_SUPPORT #if MQTT_SUPPORT
if (forward) lightMQTT(); if (forward) lightMQTT();
@ -1041,7 +1062,4 @@ void lightSetup() {
} }
void lightLoop(){
}
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE #endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE

+ 7
- 0
code/espurna/mdns.ino View File

@ -118,6 +118,13 @@ String mdnsResolve(String name) {
return mdnsResolve((char *) name.c_str()); return mdnsResolve((char *) name.c_str());
} }
void mdnsClientSetup() {
// Register loop
espurnaRegisterLoop(mdnsClientLoop);
}
void mdnsClientLoop() { void mdnsClientLoop() {
_mdns_resolver.loop(); _mdns_resolver.loop();
} }


+ 57
- 3
code/espurna/mqtt.ino View File

@ -8,6 +8,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#if MQTT_SUPPORT #if MQTT_SUPPORT
#include <EEPROM.h>
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESP8266mDNS.h> #include <ESP8266mDNS.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
@ -244,6 +245,48 @@ void _mqttConfigure() {
} }
unsigned long _mqttNextMessageId() {
static unsigned long id = 0;
// just reboot, get last count from EEPROM
if (id == 0) {
// read id from EEPROM and shift it
id = EEPROM.read(EEPROM_MESSAGE_ID);
if (id == 0xFF) {
// There was nothing in EEPROM,
// next message is first message
id = 0;
} else {
id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 1);
id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 2);
id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 3);
// Calculate next block and start from there
id = MQTT_MESSAGE_ID_SHIFT * (1 + (id / MQTT_MESSAGE_ID_SHIFT));
}
}
// Save to EEPROM every MQTT_MESSAGE_ID_SHIFT
if (id % MQTT_MESSAGE_ID_SHIFT == 0) {
EEPROM.write(EEPROM_MESSAGE_ID + 0, (id >> 24) & 0xFF);
EEPROM.write(EEPROM_MESSAGE_ID + 1, (id >> 16) & 0xFF);
EEPROM.write(EEPROM_MESSAGE_ID + 2, (id >> 8) & 0xFF);
EEPROM.write(EEPROM_MESSAGE_ID + 3, (id >> 0) & 0xFF);
EEPROM.commit();
}
id++;
return id;
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// WEB // WEB
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -302,8 +345,8 @@ void _mqttCallback(unsigned int type, const char * topic, const char * payload)
// Subscribe to internal action topics // Subscribe to internal action topics
mqttSubscribe(MQTT_TOPIC_ACTION); mqttSubscribe(MQTT_TOPIC_ACTION);
// Send heartbeat messages
heartbeat();
// Flag system to send heartbeat
systemSendHeartbeat();
} }
@ -428,6 +471,7 @@ void mqttSendRaw(const char * topic, const char * message) {
void mqttFlush() { void mqttFlush() {
if (!_mqtt.connected()) return;
if (_mqtt_queue.size() == 0) return; if (_mqtt_queue.size() == 0) return;
DynamicJsonBuffer jsonBuffer; DynamicJsonBuffer jsonBuffer;
@ -441,7 +485,7 @@ void mqttFlush() {
// Add extra propeties // Add extra propeties
#if NTP_SUPPORT && MQTT_ENQUEUE_DATETIME #if NTP_SUPPORT && MQTT_ENQUEUE_DATETIME
if (ntpConnected()) root[MQTT_TOPIC_TIME] = ntpDateTime();
if (ntpSynced()) root[MQTT_TOPIC_TIME] = ntpDateTime();
#endif #endif
#if MQTT_ENQUEUE_MAC #if MQTT_ENQUEUE_MAC
root[MQTT_TOPIC_MAC] = WiFi.macAddress(); root[MQTT_TOPIC_MAC] = WiFi.macAddress();
@ -452,6 +496,9 @@ void mqttFlush() {
#if MQTT_ENQUEUE_IP #if MQTT_ENQUEUE_IP
root[MQTT_TOPIC_IP] = getIP(); root[MQTT_TOPIC_IP] = getIP();
#endif #endif
#if MQTT_ENQUEUE_MESSAGE_ID
root[MQTT_TOPIC_MESSAGE_ID] = _mqttNextMessageId();
#endif
// Send // Send
String output; String output;
@ -478,6 +525,10 @@ void mqttQueueTopic(const char * topic) {
void mqttEnqueue(const char * topic, const char * message) { void mqttEnqueue(const char * topic, const char * message) {
// Queue is not meant to send message "offline"
// We must prevent the queue does not get full while offline
if (!_mqtt.connected()) return;
// Force flusing the queue if the MQTT_QUEUE_MAX_SIZE has been reached // Force flusing the queue if the MQTT_QUEUE_MAX_SIZE has been reached
if (_mqtt_queue.size() >= MQTT_QUEUE_MAX_SIZE) mqttFlush(); if (_mqtt_queue.size() >= MQTT_QUEUE_MAX_SIZE) mqttFlush();
@ -676,6 +727,9 @@ void mqttSetup() {
_mqttInitCommands(); _mqttInitCommands();
#endif #endif
// Register loop
espurnaRegisterLoop(mqttLoop);
} }
void mqttLoop() { void mqttLoop() {


+ 3
- 0
code/espurna/nofuss.ino View File

@ -153,6 +153,9 @@ void nofussSetup() {
_nofussInitCommands(); _nofussInitCommands();
#endif #endif
// Register loop
espurnaRegisterLoop(nofussLoop);
} }
void nofussLoop() { void nofussLoop() {


+ 92
- 32
code/espurna/ntp.ino View File

@ -13,63 +13,124 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <WiFiClient.h> #include <WiFiClient.h>
#include <Ticker.h> #include <Ticker.h>
Ticker _ntp_delay;
unsigned long _ntp_start = 0;
bool _ntp_update = false;
bool _ntp_configure = false;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// NTP // NTP
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void _ntpWebSocketOnSend(JsonObject& root) { void _ntpWebSocketOnSend(JsonObject& root) {
root["time"] = ntpDateTime();
root["ntpVisible"] = 1; root["ntpVisible"] = 1;
root["ntpStatus"] = ntpConnected();
root["ntpServer1"] = getSetting("ntpServer1", NTP_SERVER);
root["ntpServer2"] = getSetting("ntpServer2");
root["ntpServer3"] = getSetting("ntpServer3");
root["ntpStatus"] = (timeStatus() == timeSet);
root["ntpServer"] = getSetting("ntpServer", NTP_SERVER);
root["ntpOffset"] = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt(); root["ntpOffset"] = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
root["ntpDST"] = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1; root["ntpDST"] = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1;
if (ntpSynced()) root["now"] = now();
}
void _ntpStart() {
_ntp_start = 0;
NTP.begin(getSetting("ntpServer", NTP_SERVER));
NTP.setInterval(NTP_UPDATE_INTERVAL);
_ntpConfigure();
}
void _ntpConfigure() {
_ntp_configure = false;
int offset = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
int sign = offset > 0 ? 1 : -1;
offset = abs(offset);
int tz_hours = sign * (offset / 60);
int tz_minutes = sign * (offset % 60);
if (NTP.getTimeZone() != tz_hours || NTP.getTimeZoneMinutes() != tz_minutes) {
NTP.setTimeZone(tz_hours, tz_minutes);
_ntp_update = true;
}
bool daylight = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1;
if (NTP.getDayLight() != daylight) {
NTP.setDayLight(daylight);
_ntp_update = true;
}
String server = getSetting("ntpServer", NTP_SERVER);
if (!NTP.getNtpServerName().equals(server)) {
NTP.setNtpServerName(server);
}
} }
void _ntpUpdate() { void _ntpUpdate() {
_ntp_update = false;
#if WEB_SUPPORT #if WEB_SUPPORT
wsSend(_ntpWebSocketOnSend); wsSend(_ntpWebSocketOnSend);
#endif #endif
DEBUG_MSG_P(PSTR("[NTP] Time: %s\n"), (char *) ntpDateTime().c_str()); DEBUG_MSG_P(PSTR("[NTP] Time: %s\n"), (char *) ntpDateTime().c_str());
} }
void _ntpConfigure() {
NTP.begin(
getSetting("ntpServer1", NTP_SERVER),
getSetting("ntpOffset", NTP_TIME_OFFSET).toInt(),
getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1
);
if (getSetting("ntpServer2")) NTP.setNtpServerName(getSetting("ntpServer2"), 1);
if (getSetting("ntpServer3")) NTP.setNtpServerName(getSetting("ntpServer3"), 2);
NTP.setInterval(NTP_UPDATE_INTERVAL);
void _ntpLoop() {
if (0 < _ntp_start && _ntp_start < millis()) _ntpStart();
if (_ntp_configure) _ntpConfigure();
if (_ntp_update) _ntpUpdate();
now();
#if BROKER_SUPPORT
static unsigned char last_minute = 60;
if (ntpSynced() && (minute() != last_minute)) {
last_minute = minute();
brokerPublish(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
}
#endif
}
void _ntpBackwards() {
moveSetting("ntpServer1", "ntpServer");
delSetting("ntpServer2");
delSetting("ntpServer3");
int offset = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
if (-30 < offset && offset < 30) {
offset *= 60;
setSetting("ntpOffset", offset);
}
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
bool ntpConnected() {
return (timeStatus() == timeSet);
bool ntpSynced() {
return (year() > 2017);
} }
String ntpDateTime() { String ntpDateTime() {
if (!ntpConnected()) return String("Not set");
String value = NTP.getTimeDateString();
int hour = value.substring(0, 2).toInt();
int minute = value.substring(3, 5).toInt();
int second = value.substring(6, 8).toInt();
int day = value.substring(9, 11).toInt();
int month = value.substring(12, 14).toInt();
int year = value.substring(15, 19).toInt();
if (!ntpSynced()) return String();
char buffer[20]; char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%04d-%02d-%02d %02d:%02d:%02d"), year, month, day, hour, minute, second);
time_t t = now();
snprintf_P(buffer, sizeof(buffer),
PSTR("%04d-%02d-%02d %02d:%02d:%02d"),
year(t), month(t), day(t), hour(t), minute(t), second(t)
);
return String(buffer); return String(buffer);
} }
// -----------------------------------------------------------------------------
void ntpSetup() { void ntpSetup() {
_ntpBackwards();
NTP.onNTPSyncEvent([](NTPSyncEvent_t error) { NTP.onNTPSyncEvent([](NTPSyncEvent_t error) {
if (error) { if (error) {
#if WEB_SUPPORT #if WEB_SUPPORT
@ -81,23 +142,22 @@ void ntpSetup() {
DEBUG_MSG_P(PSTR("[NTP] Error: Invalid NTP server address\n")); DEBUG_MSG_P(PSTR("[NTP] Error: Invalid NTP server address\n"));
} }
} else { } else {
_ntp_delay.once_ms(100, _ntpUpdate);
_ntp_update = true;
} }
}); });
wifiRegister([](justwifi_messages_t code, char * parameter) { wifiRegister([](justwifi_messages_t code, char * parameter) {
if (code == MESSAGE_CONNECTED) _ntpConfigure();
if (code == MESSAGE_CONNECTED) _ntp_start = millis() + NTP_START_DELAY;
}); });
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_ntpWebSocketOnSend); wsOnSendRegister(_ntpWebSocketOnSend);
wsOnAfterParseRegister(_ntpConfigure);
wsOnAfterParseRegister([]() { _ntp_configure = true; });
#endif #endif
}
// Register loop
espurnaRegisterLoop(_ntpLoop);
void ntpLoop() {
now();
} }
#endif // NTP_SUPPORT #endif // NTP_SUPPORT

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

@ -45,7 +45,7 @@ void otaSetup() {
}); });
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
DEBUG_MSG_P(PSTR("[OTA] Progress: %u%%%%\r"), (progress / (total / 100)));
DEBUG_MSG_P(PSTR("[OTA] Progress: %u%%%% \r"), (progress / (total / 100)));
}); });
ArduinoOTA.onError([](ota_error_t error) { ArduinoOTA.onError([](ota_error_t error) {
@ -61,6 +61,9 @@ void otaSetup() {
ArduinoOTA.begin(); ArduinoOTA.begin();
// Register loop
espurnaRegisterLoop(otaLoop);
} }
void otaLoop() { void otaLoop() {


+ 7
- 0
code/espurna/relay.ino View File

@ -728,6 +728,8 @@ void relaySetup() {
_relayBoot(); _relayBoot();
relayLoop(); relayLoop();
espurnaRegisterLoop(relayLoop);
#if WEB_SUPPORT #if WEB_SUPPORT
relaySetupAPI(); relaySetupAPI();
relaySetupWS(); relaySetupWS();
@ -760,6 +762,11 @@ void relayLoop(void) {
// Call the provider to perform the action // Call the provider to perform the action
_relayProviderStatus(id, status); _relayProviderStatus(id, status);
// Send to Broker
#if BROKER_SUPPORT
brokerPublish(MQTT_TOPIC_RELAY, id, status ? "1" : "0");
#endif
// Send MQTT // Send MQTT
#if MQTT_SUPPORT #if MQTT_SUPPORT
relayMQTT(id); relayMQTT(id);


+ 3
- 0
code/espurna/rf.ino View File

@ -101,6 +101,9 @@ void rfSetup() {
wsOnAfterParseRegister(_rfBuildCodes); wsOnAfterParseRegister(_rfBuildCodes);
#endif #endif
// Register loop
espurnaRegisterLoop(rfLoop);
} }
#endif #endif

+ 3
- 0
code/espurna/rfbridge.ino View File

@ -505,6 +505,9 @@ void rfbSetup() {
wsOnActionRegister(_rfbWebSocketOnAction); wsOnActionRegister(_rfbWebSocketOnAction);
#endif #endif
// Register oop
espurnaRegisterLoop(rfbLoop);
} }
void rfbLoop() { void rfbLoop() {


+ 71
- 59
code/espurna/scheduler.ino View File

@ -9,7 +9,9 @@ Adapted by Xose Pérez <xose dot perez at gmail dot com>
#if SCHEDULER_SUPPORT #if SCHEDULER_SUPPORT
#include <NtpClientLib.h>
#include <TimeLib.h>
// -----------------------------------------------------------------------------
#if WEB_SUPPORT #if WEB_SUPPORT
@ -30,6 +32,8 @@ void _schWebSocketOnSend(JsonObject &root){
#endif // WEB_SUPPORT #endif // WEB_SUPPORT
// -----------------------------------------------------------------------------
void _schConfigure() { void _schConfigure() {
bool delete_flag = false; bool delete_flag = false;
@ -71,7 +75,7 @@ void _schConfigure() {
bool _schIsThisWeekday(String weekdays){ bool _schIsThisWeekday(String weekdays){
// Monday = 1, Tuesday = 2 ... Sunday = 7
// Convert from Sunday to Monday as day 1
int w = weekday(now()) - 1; int w = weekday(now()) - 1;
if (w == 0) w = 7; if (w == 0) w = 7;
@ -86,21 +90,72 @@ bool _schIsThisWeekday(String weekdays){
} }
int _schMinutesLeft(unsigned char schedule_hour, unsigned char schedule_minute){ int _schMinutesLeft(unsigned char schedule_hour, unsigned char schedule_minute){
time_t t = now();
unsigned char now_hour = hour(t);
unsigned char now_minute = minute(t);
return (schedule_hour - now_hour) * 60 + schedule_minute - now_minute;
}
void _schCheck() {
// Check schedules
for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
int sch_switch = getSetting("schSwitch", i, 0xFF).toInt();
if (sch_switch == 0xFF) break;
String sch_weekdays = getSetting("schWDs", i, "");
if (_schIsThisWeekday(sch_weekdays)) {
int sch_hour = getSetting("schHour", i, 0).toInt();
int sch_minute = getSetting("schMinute", i, 0).toInt();
int minutes_to_trigger = _schMinutesLeft(sch_hour, sch_minute);
if (minutes_to_trigger == 0) {
int sch_action = getSetting("schAction", i, 0).toInt();
if (sch_action == 2) {
relayToggle(sch_switch);
} else {
relayStatus(sch_switch, sch_action);
}
DEBUG_MSG_P(PSTR("[SCH] Schedule #%d TRIGGERED!!\n"), sch_switch);
// Show minutes to trigger every 15 minutes
// or every minute if less than 15 minutes to scheduled time.
// This only works for schedules on this same day.
// For instance, if your scheduler is set for 00:01 you will only
// get one notification before the trigger (at 00:00)
} else if (minutes_to_trigger > 0) {
#if DEBUG_SUPPORT
if ((minutes_to_trigger % 15 == 0) || (minutes_to_trigger < 15)) {
DEBUG_MSG_P(
PSTR("[SCH] %d minutes to trigger schedule #%d\n"),
minutes_to_trigger, sch_switch
);
}
#endif
}
}
unsigned char now_hour;
unsigned char now_minute;
if (ntpConnected()) {
String value = NTP.getTimeDateString();
now_hour = value.substring(0, 2).toInt();
now_minute = value.substring(3, 5).toInt();
} else {
time_t t = now();
now_hour = hour(t);
now_minute = minute(t);
} }
return (schedule_hour - now_hour) * 60 + schedule_minute - now_minute;
}
void _schLoop() {
// Check time has been sync'ed
if (!ntpSynced()) return;
// Check schedules every minute at hh:mm:00
static unsigned long last_minute = 60;
unsigned char current_minute = minute();
if (current_minute != last_minute) {
last_minute = current_minute;
_schCheck();
}
} }
@ -116,52 +171,9 @@ void schSetup() {
wsOnAfterParseRegister(_schConfigure); wsOnAfterParseRegister(_schConfigure);
#endif #endif
}
void schLoop() {
static unsigned long last_update = 0;
static int update_time = 0;
// Check if we should compare scheduled and actual times
if ((millis() - last_update > update_time) || (last_update == 0)) {
last_update = millis();
// Register loop
espurnaRegisterLoop(_schLoop);
// Calculate next update time
unsigned char current_second = ntpConnected() ?
NTP.getTimeDateString().substring(6, 8).toInt() :
second(now())
;
update_time = (SCHEDULER_UPDATE_SEC + 60 - current_second) * 1000;
for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
int sch_switch = getSetting("schSwitch", i, 0xFF).toInt();
if (sch_switch == 0xFF) break;
String sch_weekdays = getSetting("schWDs", i, "");
if (_schIsThisWeekday(sch_weekdays)) {
int sch_hour = getSetting("schHour", i, 0).toInt();
int sch_minute = getSetting("schMinute", i, 0).toInt();
int minutes_to_trigger = _schMinutesLeft(sch_hour, sch_minute);
if (minutes_to_trigger == 0) {
int sch_action = getSetting("schAction", i, 0).toInt();
if (sch_action == 2) {
relayToggle(sch_switch);
} else {
relayStatus(sch_switch, sch_action);
}
DEBUG_MSG_P(PSTR("[SCH] Schedule #%d TRIGGERED!!\n"), sch_switch);
} else if (minutes_to_trigger > 0) {
DEBUG_MSG_P(
PSTR("[SCH] %d minutes to trigger schedule #%d\n"),
minutes_to_trigger, sch_switch
);
}
}
}
}
} }
#endif // SCHEDULER_SUPPORT #endif // SCHEDULER_SUPPORT

+ 7
- 0
code/espurna/sensor.ino View File

@ -619,6 +619,9 @@ void sensorSetup() {
_sensorInitCommands(); _sensorInitCommands();
#endif #endif
// Register loop
espurnaRegisterLoop(sensorLoop);
} }
void sensorLoop() { void sensorLoop() {
@ -689,6 +692,10 @@ void sensorLoop() {
_magnitudes[i].reported = filtered; _magnitudes[i].reported = filtered;
dtostrf(filtered, 1-sizeof(buffer), decimals, buffer); dtostrf(filtered, 1-sizeof(buffer), decimals, buffer);
#if BROKER_SUPPORT
brokerPublish(_magnitudeTopic(magnitude.type).c_str(), magnitude.local, buffer);
#endif
#if MQTT_SUPPORT #if MQTT_SUPPORT
if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) { if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) {


+ 23
- 11
code/espurna/sensors/BH1750Sensor.h View File

@ -62,9 +62,8 @@ class BH1750Sensor : public I2CSensor {
_address = _begin_i2c(_address, sizeof(addresses), addresses); _address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return; if (_address == 0) return;
// Configure
_configure();
delay(10);
// Run configuration on next update
_run_configure = true;
} }
@ -83,26 +82,29 @@ class BH1750Sensor : public I2CSensor {
return MAGNITUDE_NONE; return MAGNITUDE_NONE;
} }
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
_lux = _read();
}
// Current value for slot # index // Current value for slot # index
double value(unsigned char index) { double value(unsigned char index) {
_error = SENSOR_ERROR_OK; _error = SENSOR_ERROR_OK;
if (index == 0) return _read();
if (index == 0) return _lux;
_error = SENSOR_ERROR_OUT_OF_RANGE; _error = SENSOR_ERROR_OUT_OF_RANGE;
return 0; return 0;
} }
protected: protected:
void _configure() {
i2c_write_uint8(_address, _mode);
}
double _read() { double _read() {
// For one-shot modes reconfigure sensor & wait for conversion // For one-shot modes reconfigure sensor & wait for conversion
if (_mode & 0x20) {
if (_run_configure) {
_configure();
// Configure mode
i2c_write_uint8(_address, _mode);
// According to datasheet // According to datasheet
// conversion time is ~16ms for low resolution // conversion time is ~16ms for low resolution
@ -112,15 +114,25 @@ class BH1750Sensor : public I2CSensor {
unsigned long start = millis(); unsigned long start = millis();
while (millis() - start < wait) delay(1); while (millis() - start < wait) delay(1);
// Keep on running configure each time if one-shot mode
_run_configure = _mode & 0x20;
} }
double level = (double) i2c_read_uint16(_address); double level = (double) i2c_read_uint16(_address);
if (level == 0xFFFF) {
_error = SENSOR_ERROR_CRC;
_run_configure = true;
return 0;
}
return level / 1.2; return level / 1.2;
} }
unsigned char _mode; unsigned char _mode;
bool _run_configure = false;
double _lux = 0;
}; };
#endif // SENSOR_SUPPORT && SI7021_SUPPORT
#endif // SENSOR_SUPPORT && BH1750_SUPPORT

+ 1
- 0
code/espurna/sensors/BMX280Sensor.h View File

@ -116,6 +116,7 @@ class BMX280Sensor : public I2CSensor {
_error = SENSOR_ERROR_UNKNOWN_ID; _error = SENSOR_ERROR_UNKNOWN_ID;
return; return;
} }
_error = SENSOR_ERROR_OK;
#if BMX280_MODE == 1 #if BMX280_MODE == 1
_forceRead(); _forceRead();


+ 1
- 0
code/espurna/sensors/DHTSensor.h View File

@ -80,6 +80,7 @@ class DHTSensor : public BaseSensor {
// Pre-read hook (usually to populate registers with up-to-date data) // Pre-read hook (usually to populate registers with up-to-date data)
void pre() { void pre() {
_error = SENSOR_ERROR_OK;
_read(); _read();
} }


+ 5
- 0
code/espurna/sensors/DallasSensor.h View File

@ -209,6 +209,11 @@ class DallasSensor : public BaseSensor {
return MAGNITUDE_NONE; return MAGNITUDE_NONE;
} }
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
}
// Current value for slot # index // Current value for slot # index
double value(unsigned char index) { double value(unsigned char index) {


+ 2
- 0
code/espurna/sensors/EmonADC121Sensor.h View File

@ -94,6 +94,8 @@ class EmonADC121Sensor : public EmonSensor {
last = millis(); last = millis();
#endif #endif
_error = SENSOR_ERROR_OK;
} }
// Type for slot # index // Type for slot # index


+ 1
- 0
code/espurna/sensors/EmonADS1X15Sensor.h View File

@ -235,6 +235,7 @@ class EmonADS1X15Sensor : public EmonSensor {
#endif #endif
} }
last = millis(); last = millis();
_error = SENSOR_ERROR_OK;
} }
// Current value for slot # index // Current value for slot # index


+ 2
- 0
code/espurna/sensors/EmonAnalogSensor.h View File

@ -94,6 +94,8 @@ class EmonAnalogSensor : public EmonSensor {
last = millis(); last = millis();
#endif #endif
_error = SENSOR_ERROR_OK;
} }
// Current value for slot # index // Current value for slot # index


+ 2
- 0
code/espurna/sensors/MHZ19Sensor.h View File

@ -197,6 +197,8 @@ class MHZ19Sensor : public BaseSensor {
_error = SENSOR_ERROR_OUT_OF_RANGE; _error = SENSOR_ERROR_OUT_OF_RANGE;
} }
} else {
_error = SENSOR_ERROR_CRC;
} }
} }


+ 2
- 0
code/espurna/sensors/SHT3XI2CSensor.h View File

@ -61,6 +61,8 @@ class SHT3XI2CSensor : public I2CSensor {
// Pre-read hook (usually to populate registers with up-to-date data) // Pre-read hook (usually to populate registers with up-to-date data)
void pre() { void pre() {
_error = SENSOR_ERROR_OK;
unsigned char buffer[6]; unsigned char buffer[6];
i2c_write_uint8(_address, 0x2C, 0x06); i2c_write_uint8(_address, 0x2C, 0x06);
delay(500); delay(500);


+ 58
- 36
code/espurna/sensors/SI7021Sensor.h View File

@ -20,6 +20,9 @@
#define SI7021_CMD_TMP_NOHOLD 0xF3 #define SI7021_CMD_TMP_NOHOLD 0xF3
#define SI7021_CMD_HUM_NOHOLD 0xF5 #define SI7021_CMD_HUM_NOHOLD 0xF5
PROGMEM const char si7021_chip_si7021_name[] = "SI7021";
PROGMEM const char si7021_chip_htu21d_name[] = "HTU21D";
class SI7021Sensor : public I2CSensor { class SI7021Sensor : public I2CSensor {
public: public:
@ -47,23 +50,22 @@ class SI7021Sensor : public I2CSensor {
_address = _begin_i2c(_address, sizeof(addresses), addresses); _address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return; if (_address == 0) return;
// Check device
i2c_write_uint8(_address, 0xFC, 0xC9);
_chip = i2c_read_uint8(_address);
if ((_chip != SI7021_CHIP_SI7021) & (_chip != SI7021_CHIP_HTU21D)) {
i2cReleaseLock(_address);
_error = SENSOR_ERROR_UNKNOWN_ID;
} else {
_count = 2;
}
// Initialize sensor
_init();
} }
// Descriptive name of the sensor // Descriptive name of the sensor
String description() { String description() {
char name[10];
strncpy_P(name,
_chip == SI7021_CHIP_SI7021 ?
si7021_chip_si7021_name :
si7021_chip_htu21d_name,
sizeof(name)
);
char buffer[25]; char buffer[25];
snprintf(buffer, sizeof(buffer), "%s @ I2C (0x%02X)", chipAsString().c_str(), _address);
snprintf(buffer, sizeof(buffer), "%s @ I2C (0x%02X)", name, _address);
return String(buffer); return String(buffer);
} }
@ -74,30 +76,38 @@ class SI7021Sensor : public I2CSensor {
// Type for slot # index // Type for slot # index
unsigned char type(unsigned char index) { unsigned char type(unsigned char index) {
if (index < _count) {
_error = SENSOR_ERROR_OK;
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
}
_error = SENSOR_ERROR_OK;
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
_error = SENSOR_ERROR_OUT_OF_RANGE; _error = SENSOR_ERROR_OUT_OF_RANGE;
return MAGNITUDE_NONE; return MAGNITUDE_NONE;
} }
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_UNKNOWN_ID;
if (_chip == 0) return;
_error = SENSOR_ERROR_OK;
double value;
value = _read(SI7021_CMD_TMP_NOHOLD);
if (_error != SENSOR_ERROR_OK) return;
_temperature = (175.72 * value / 65536) - 46.85;
value = _read(SI7021_CMD_HUM_NOHOLD);
if (_error != SENSOR_ERROR_OK) return;
value = (125.0 * value / 65536) - 6;
_humidity = constrain(value, 0, 100);
}
// Current value for slot # index // Current value for slot # index
double value(unsigned char index) { double value(unsigned char index) {
if (index < _count) {
double value;
if (index == 0) {
value = read(SI7021_CMD_TMP_NOHOLD);
value = (175.72 * value / 65536) - 46.85;
}
if (index == 1) {
value = read(SI7021_CMD_HUM_NOHOLD);
value = (125.0 * value / 65536) - 6;
value = constrain(value, 0, 100);
}
return value;
}
_error = SENSOR_ERROR_OK;
if (index == 0) return _temperature;
if (index == 1) return _humidity;
_error = SENSOR_ERROR_OUT_OF_RANGE; _error = SENSOR_ERROR_OUT_OF_RANGE;
return 0; return 0;
} }
@ -108,7 +118,23 @@ class SI7021Sensor : public I2CSensor {
// Protected // Protected
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
unsigned int read(uint8_t command) {
void _init() {
// Check device
i2c_write_uint8(_address, 0xFC, 0xC9);
_chip = i2c_read_uint8(_address);
if ((_chip != SI7021_CHIP_SI7021) & (_chip != SI7021_CHIP_HTU21D)) {
i2cReleaseLock(_address);
_error = SENSOR_ERROR_UNKNOWN_ID;
_count = 0;
} else {
_count = 2;
}
}
unsigned int _read(uint8_t command) {
// Request measurement // Request measurement
i2c_write_uint8(_address, command); i2c_write_uint8(_address, command);
@ -131,13 +157,9 @@ class SI7021Sensor : public I2CSensor {
} }
String chipAsString() {
if (_chip == SI7021_CHIP_SI7021) return String("SI7021");
if (_chip == SI7021_CHIP_HTU21D) return String("HTU21D");
return String("Unknown");
}
unsigned char _chip; unsigned char _chip;
double _temperature = 0;
double _humidity = 0;
}; };


+ 8
- 6
code/espurna/settings.ino View File

@ -41,7 +41,7 @@ bool _settings_save = false;
// Reverse engineering EEPROM storage format // Reverse engineering EEPROM storage format
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
unsigned long _settingsSize() {
unsigned long settingsSize() {
unsigned pos = SPI_FLASH_SEC_SIZE - 1; unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) { while (size_t len = EEPROM.read(pos)) {
pos = pos - len - 2; pos = pos - len - 2;
@ -49,6 +49,8 @@ unsigned long _settingsSize() {
return SPI_FLASH_SEC_SIZE - pos; return SPI_FLASH_SEC_SIZE - pos;
} }
// -----------------------------------------------------------------------------
unsigned int _settingsKeyCount() { unsigned int _settingsKeyCount() {
unsigned count = 0; unsigned count = 0;
unsigned pos = SPI_FLASH_SEC_SIZE - 1; unsigned pos = SPI_FLASH_SEC_SIZE - 1;
@ -156,7 +158,7 @@ void _settingsKeys() {
DEBUG_MSG_P(PSTR("> %s => %s\n"), (keys[i]).c_str(), value.c_str()); DEBUG_MSG_P(PSTR("> %s => %s\n"), (keys[i]).c_str(), value.c_str());
} }
unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - _settingsSize();
unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize();
DEBUG_MSG_P(PSTR("Number of keys: %d\n"), keys.size()); DEBUG_MSG_P(PSTR("Number of keys: %d\n"), keys.size());
DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE); DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
@ -243,7 +245,7 @@ void _settingsInitCommands() {
}); });
settingsRegisterCommand(F("INFO"), [](Embedis* e) { settingsRegisterCommand(F("INFO"), [](Embedis* e) {
welcome();
info();
wifiStatus(); wifiStatus();
//StreamString s; //StreamString s;
//WiFi.printDiag(s); //WiFi.printDiag(s);
@ -407,15 +409,15 @@ void settingsSetup() {
_settingsInitCommands(); _settingsInitCommands();
DEBUG_MSG_P(PSTR("[SETTINGS] EEPROM size: %d bytes\n"), SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("[SETTINGS] Settings size: %d bytes\n"), _settingsSize());
#if TERMINAL_SUPPORT #if TERMINAL_SUPPORT
#ifdef SERIAL_RX_PORT #ifdef SERIAL_RX_PORT
SERIAL_RX_PORT.begin(SERIAL_RX_BAUDRATE); SERIAL_RX_PORT.begin(SERIAL_RX_BAUDRATE);
#endif #endif
#endif #endif
// Register loop
espurnaRegisterLoop(settingsLoop);
} }
void settingsLoop() { void settingsLoop() {


+ 3
- 0
code/espurna/ssdp.ino View File

@ -31,6 +31,9 @@ void ssdpSetup() {
}); });
#endif #endif
// Register loop
espurnaRegisterLoop(ssdpLoop);
} }
void ssdpLoop() { void ssdpLoop() {


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


+ 122
- 0
code/espurna/system.ino View File

@ -0,0 +1,122 @@
/*
SYSTEM MODULE
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <EEPROM.h>
// -----------------------------------------------------------------------------
unsigned long _loopDelay = 0;
bool _system_send_heartbeat = false;
// -----------------------------------------------------------------------------
#if SYSTEM_CHECK_ENABLED
// 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 SYSTEM_CHECK_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 > SYSTEM_CHECK_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;
}
void systemCheckLoop() {
static bool checked = false;
if (!checked && (millis() > SYSTEM_CHECK_TIME)) {
// Check system as stable
systemCheck(true);
checked = true;
}
}
#endif
// -----------------------------------------------------------------------------
void systemSendHeartbeat() {
_system_send_heartbeat = true;
}
void systemLoop() {
// Check system stability
#if SYSTEM_CHECK_ENABLED
systemCheckLoop();
#endif
#if HEARTBEAT_ENABLED
// Heartbeat
static unsigned long last = 0;
if (_system_send_heartbeat || (last == 0) || (millis() - last > HEARTBEAT_INTERVAL)) {
_system_send_heartbeat = false;
last = millis();
heartbeat();
}
#endif // HEARTBEAT_ENABLED
// Power saving delay
delay(_loopDelay);
}
void systemSetup() {
EEPROM.begin(EEPROM_SIZE);
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.begin(SERIAL_BAUDRATE);
#if DEBUG_ESP_WIFI
DEBUG_PORT.setDebugOutput(true);
#endif
#elif defined(SERIAL_BAUDRATE)
Serial.begin(SERIAL_BAUDRATE);
#endif
#if SPIFFS_SUPPORT
SPIFFS.begin();
#endif
// Question system stability
#if SYSTEM_CHECK_ENABLED
systemCheck(false);
#endif
#if defined(ESPLIVE)
//The ESPLive has an ADC MUX which needs to be configured.
pinMode(16, OUTPUT);
digitalWrite(16, HIGH); //Defualt CT input (pin B, solder jumper B)
#endif
// Cache loop delay value to speed things (recommended max 250ms)
_loopDelay = atol(getSetting("loopDelay", LOOP_DELAY_TIME).c_str());
// Register Loop
espurnaRegisterLoop(systemLoop);
}

+ 7
- 0
code/espurna/thinkspeak.ino View File

@ -242,15 +242,22 @@ bool tspkEnabled() {
} }
void tspkSetup() { void tspkSetup() {
_tspkConfigure(); _tspkConfigure();
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_tspkWebSocketOnSend); wsOnSendRegister(_tspkWebSocketOnSend);
wsOnAfterParseRegister(_tspkConfigure); wsOnAfterParseRegister(_tspkConfigure);
#endif #endif
DEBUG_MSG_P(PSTR("[THINGSPEAK] Async %s, SSL %s\n"), DEBUG_MSG_P(PSTR("[THINGSPEAK] Async %s, SSL %s\n"),
THINGSPEAK_USE_ASYNC ? "ENABLED" : "DISABLED", THINGSPEAK_USE_ASYNC ? "ENABLED" : "DISABLED",
THINGSPEAK_USE_SSL ? "ENABLED" : "DISABLED" THINGSPEAK_USE_SSL ? "ENABLED" : "DISABLED"
); );
// Register loop
espurnaRegisterLoop(tspkLoop);
} }
void tspkLoop() { void tspkLoop() {


+ 272
- 105
code/espurna/utils.ino View File

@ -94,60 +94,14 @@ unsigned long getUptime() {
} }
#if HEARTBEAT_ENABLED
void heartbeat() { void heartbeat() {
unsigned long uptime_seconds = getUptime(); unsigned long uptime_seconds = getUptime();
unsigned int free_heap = getFreeHeap(); unsigned int free_heap = getFreeHeap();
// -------------------------------------------------------------------------
// MQTT
// -------------------------------------------------------------------------
#if MQTT_SUPPORT #if MQTT_SUPPORT
#if (HEARTBEAT_REPORT_INTERVAL)
mqttSend(MQTT_TOPIC_INTERVAL, HEARTBEAT_INTERVAL / 1000);
#endif
#if (HEARTBEAT_REPORT_APP)
mqttSend(MQTT_TOPIC_APP, APP_NAME);
#endif
#if (HEARTBEAT_REPORT_VERSION)
mqttSend(MQTT_TOPIC_VERSION, APP_VERSION);
#endif
#if (HEARTBEAT_REPORT_HOSTNAME)
mqttSend(MQTT_TOPIC_HOSTNAME, getSetting("hostname").c_str());
#endif
#if (HEARTBEAT_REPORT_IP)
mqttSend(MQTT_TOPIC_IP, getIP().c_str());
#endif
#if (HEARTBEAT_REPORT_MAC)
mqttSend(MQTT_TOPIC_MAC, WiFi.macAddress().c_str());
#endif
#if (HEARTBEAT_REPORT_RSSI)
mqttSend(MQTT_TOPIC_RSSI, String(WiFi.RSSI()).c_str());
#endif
#if (HEARTBEAT_REPORT_UPTIME)
mqttSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str());
#endif
#if (HEARTBEAT_REPORT_DATETIME) & (NTP_SUPPORT)
mqttSend(MQTT_TOPIC_DATETIME, String(ntpDateTime()).c_str());
#endif
#if (HEARTBEAT_REPORT_FREEHEAP)
mqttSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str());
#endif
#if (HEARTBEAT_REPORT_RELAY)
relayMQTT();
#endif
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) & (HEARTBEAT_REPORT_LIGHT)
lightMQTT();
#endif
#if (HEARTBEAT_REPORT_VCC)
#if ADC_VCC_ENABLED
mqttSend(MQTT_TOPIC_VCC, String(ESP.getVcc()).c_str());
#endif
#endif
#if (HEARTBEAT_REPORT_STATUS)
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
#endif
bool serial = !mqttConnected(); bool serial = !mqttConnected();
#else #else
bool serial = true; bool serial = true;
@ -163,9 +117,62 @@ void heartbeat() {
#if ADC_VCC_ENABLED #if ADC_VCC_ENABLED
DEBUG_MSG_P(PSTR("[MAIN] Power: %lu mV\n"), ESP.getVcc()); DEBUG_MSG_P(PSTR("[MAIN] Power: %lu mV\n"), ESP.getVcc());
#endif #endif
#if NTP_SUPPORT
if (ntpSynced()) DEBUG_MSG_P(PSTR("[MAIN] Time: %s\n"), (char *) ntpDateTime().c_str());
#endif
} }
#if NTP_SUPPORT
DEBUG_MSG_P(PSTR("[MAIN] Time: %s\n"), (char *) ntpDateTime().c_str());
// -------------------------------------------------------------------------
// MQTT
// -------------------------------------------------------------------------
#if MQTT_SUPPORT
if (!serial) {
#if (HEARTBEAT_REPORT_INTERVAL)
mqttSend(MQTT_TOPIC_INTERVAL, HEARTBEAT_INTERVAL / 1000);
#endif
#if (HEARTBEAT_REPORT_APP)
mqttSend(MQTT_TOPIC_APP, APP_NAME);
#endif
#if (HEARTBEAT_REPORT_VERSION)
mqttSend(MQTT_TOPIC_VERSION, APP_VERSION);
#endif
#if (HEARTBEAT_REPORT_HOSTNAME)
mqttSend(MQTT_TOPIC_HOSTNAME, getSetting("hostname").c_str());
#endif
#if (HEARTBEAT_REPORT_IP)
mqttSend(MQTT_TOPIC_IP, getIP().c_str());
#endif
#if (HEARTBEAT_REPORT_MAC)
mqttSend(MQTT_TOPIC_MAC, WiFi.macAddress().c_str());
#endif
#if (HEARTBEAT_REPORT_RSSI)
mqttSend(MQTT_TOPIC_RSSI, String(WiFi.RSSI()).c_str());
#endif
#if (HEARTBEAT_REPORT_UPTIME)
mqttSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str());
#endif
#if (HEARTBEAT_REPORT_DATETIME) && (NTP_SUPPORT)
if (ntpSynced()) mqttSend(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
#endif
#if (HEARTBEAT_REPORT_FREEHEAP)
mqttSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str());
#endif
#if (HEARTBEAT_REPORT_RELAY)
relayMQTT();
#endif
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) & (HEARTBEAT_REPORT_LIGHT)
lightMQTT();
#endif
#if (HEARTBEAT_REPORT_VCC)
#if ADC_VCC_ENABLED
mqttSend(MQTT_TOPIC_VCC, String(ESP.getVcc()).c_str());
#endif
#endif
#if (HEARTBEAT_REPORT_STATUS)
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
#endif
}
#endif #endif
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -181,24 +188,228 @@ void heartbeat() {
#endif #endif
#endif #endif
}
#endif /// HEARTBEAT_ENABLED
unsigned int sectors(size_t size) {
return (int) (size + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE;
}
void info() {
DEBUG_MSG_P(PSTR("\n\n"));
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: %u MHz\n"), ESP.getCpuFreqMHz());
DEBUG_MSG_P(PSTR("[INIT] SDK version: %s\n"), ESP.getSdkVersion());
DEBUG_MSG_P(PSTR("[INIT] Core version: %s\n"), getCoreVersion().c_str());
DEBUG_MSG_P(PSTR("[INIT] Core revision: %s\n"), getCoreRevision().c_str());
DEBUG_MSG_P(PSTR("\n"));
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// WebSockets
FlashMode_t mode = ESP.getFlashChipMode();
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("[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("[INIT] SPIFFS size: %8u bytes / %4d sectors\n"), fs_info.totalBytes, sectors(fs_info.totalBytes));
}
#else
DEBUG_MSG_P(PSTR("[INIT] SPIFFS size: %8u bytes / %4d sectors\n"), 0, 0);
#endif
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 WEB_SUPPORT
#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);
} else {
DEBUG_MSG_P(PSTR("[INIT] No SPIFFS partition\n"));
}
DEBUG_MSG_P(PSTR("\n"));
#endif
// -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[INIT] BOARD: %s\n"), getBoardName().c_str());
DEBUG_MSG_P(PSTR("[INIT] SUPPORT:"));
#if ALEXA_SUPPORT
DEBUG_MSG_P(PSTR(" ALEXA"));
#endif
#if BROKER_SUPPORT
DEBUG_MSG_P(PSTR(" BROKER"));
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_SERIAL"));
#endif
#if DEBUG_TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_TELNET"));
#endif
#if DEBUG_UDP_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_UDP"));
#endif
#if DOMOTICZ_SUPPORT
DEBUG_MSG_P(PSTR(" DOMOTICZ"));
#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 LLMNR_SUPPORT
DEBUG_MSG_P(PSTR(" LLMNR"));
#endif
#if MDNS_SERVER_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS_SERVER"));
#endif
#if MDNS_CLIENT_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS_CLIENT"));
#endif
#if NETBIOS_SUPPORT
DEBUG_MSG_P(PSTR(" NETBIOS"));
#endif
#if NOFUSS_SUPPORT
DEBUG_MSG_P(PSTR(" NOFUSS"));
#endif
#if NTP_SUPPORT #if NTP_SUPPORT
{
char buffer[200];
snprintf_P(
buffer,
sizeof(buffer) - 1,
PSTR("{\"time\": \"%s\", \"uptime\": %lu, \"heap\": %lu}"),
ntpDateTime().c_str(), uptime_seconds, free_heap
);
wsSend(buffer);
DEBUG_MSG_P(PSTR(" NTP"));
#endif
#if RF_SUPPORT
DEBUG_MSG_P(PSTR(" RF"));
#endif
#if SCHEDULER_SUPPORT
DEBUG_MSG_P(PSTR(" SCHEDULER"));
#endif
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR(" SENSOR"));
#endif
#if SPIFFS_SUPPORT
DEBUG_MSG_P(PSTR(" SPIFFS"));
#endif
#if SSDP_SUPPORT
DEBUG_MSG_P(PSTR(" SSDP"));
#endif
#if TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" TELNET"));
#endif
#if TERMINAL_SUPPORT
DEBUG_MSG_P(PSTR(" TERMINAL"));
#endif
#if THINGSPEAK_SUPPORT
DEBUG_MSG_P(PSTR(" THINGSPEAK"));
#endif
#if WEB_SUPPORT
DEBUG_MSG_P(PSTR(" WEB"));
#endif
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n[INIT] SENSORS:"));
#if ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" ANALOG"));
#endif
#if BMX280_SUPPORT
DEBUG_MSG_P(PSTR(" BMX280"));
#endif
#if DALLAS_SUPPORT
DEBUG_MSG_P(PSTR(" DALLAS"));
#endif
#if DHT_SUPPORT
DEBUG_MSG_P(PSTR(" DHTXX"));
#endif
#if DIGITAL_SUPPORT
DEBUG_MSG_P(PSTR(" DIGITAL"));
#endif
#if ECH1560_SUPPORT
DEBUG_MSG_P(PSTR(" ECH1560"));
#endif
#if EMON_ADC121_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ADC121"));
#endif
#if EMON_ADS1X15_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ADX1X15"));
#endif
#if EMON_ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ANALOG"));
#endif
#if EVENTS_SUPPORT
DEBUG_MSG_P(PSTR(" EVENTS"));
#endif
#if HLW8012_SUPPORT
DEBUG_MSG_P(PSTR(" HLW8012"));
#endif
#if MHZ19_SUPPORT
DEBUG_MSG_P(PSTR(" MHZ19"));
#endif
#if PMSX003_SUPPORT
DEBUG_MSG_P(PSTR(" PMSX003"));
#endif
#if SHT3X_I2C_SUPPORT
DEBUG_MSG_P(PSTR(" SHT3X_I2C"));
#endif
#if SI7021_SUPPORT
DEBUG_MSG_P(PSTR(" SI7021"));
#endif
#if V9261F_SUPPORT
DEBUG_MSG_P(PSTR(" V9261F"));
#endif
#endif // SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n\n"));
// -------------------------------------------------------------------------
unsigned char reason = resetReason();
if (reason > 0) {
char buffer[32];
strcpy_P(buffer, custom_reset_string[reason-1]);
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), buffer);
} else {
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), (char *) ESP.getResetReason().c_str());
} }
DEBUG_MSG_P(PSTR("[INIT] Settings size: %u bytes\n"), settingsSize());
DEBUG_MSG_P(PSTR("[INIT] Free heap: %u bytes\n"), getFreeHeap());
#if ADC_VCC_ENABLED
DEBUG_MSG_P(PSTR("[INIT] Power: %u mV\n"), ESP.getVcc());
#endif #endif
DEBUG_MSG_P(PSTR("[INIT] Power saving delay value: %lu ms\n"), _loopDelay);
#if SYSTEM_CHECK_ENABLED
if (!systemCheck()) DEBUG_MSG_P(PSTR("\n[INIT] Device is in SAFE MODE\n"));
#endif #endif
DEBUG_MSG_P(PSTR("\n"));
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -274,50 +485,6 @@ void deferredReset(unsigned long delay, unsigned char reason) {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#if SYSTEM_CHECK_ENABLED
// 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 SYSTEM_CHECK_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 > SYSTEM_CHECK_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;
}
void systemCheckLoop() {
static bool checked = false;
if (!checked && (millis() > SYSTEM_CHECK_TIME)) {
// Check system as stable
systemCheck(true);
checked = true;
}
}
#endif
// -----------------------------------------------------------------------------
char * ltrim(char * s) { char * ltrim(char * s) {
char *p = s; char *p = s;
while ((unsigned char) *p == ' ') ++p; while ((unsigned char) *p == ' ') ++p;


+ 3
- 0
code/espurna/wifi.ino View File

@ -447,6 +447,9 @@ void wifiSetup() {
_wifiInitCommands(); _wifiInitCommands();
#endif #endif
// Register loop
espurnaRegisterLoop(wifiLoop);
} }
void wifiLoop() { void wifiLoop() {


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

@ -200,6 +200,16 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
} }
void _wsUpdate(JsonObject& root) {
root["heap"] = getFreeHeap();
root["uptime"] = getUptime();
root["rssi"] = WiFi.RSSI();
root["distance"] = wifiDistance(WiFi.RSSI());
#if NTP_SUPPORT
if (ntpSynced()) root["now"] = now();
#endif
}
void _wsOnStart(JsonObject& root) { void _wsOnStart(JsonObject& root) {
#if USE_PASSWORD && WEB_FORCE_PASS_CHANGE #if USE_PASSWORD && WEB_FORCE_PASS_CHANGE
@ -234,17 +244,15 @@ void _wsOnStart(JsonObject& root) {
root["mac"] = WiFi.macAddress(); root["mac"] = WiFi.macAddress();
root["bssid"] = String(bssid_str); root["bssid"] = String(bssid_str);
root["channel"] = WiFi.channel(); root["channel"] = WiFi.channel();
root["rssi"] = WiFi.RSSI();
root["distance"] = wifiDistance(WiFi.RSSI());
root["device"] = DEVICE; root["device"] = DEVICE;
root["hostname"] = getSetting("hostname"); root["hostname"] = getSetting("hostname");
root["network"] = getNetwork(); root["network"] = getNetwork();
root["deviceip"] = getIP(); root["deviceip"] = getIP();
root["uptime"] = getUptime();
root["heap"] = getFreeHeap();
root["sketch_size"] = ESP.getSketchSize(); root["sketch_size"] = ESP.getSketchSize();
root["free_size"] = ESP.getFreeSketchSpace(); root["free_size"] = ESP.getFreeSketchSpace();
_wsUpdate(root);
root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt(); root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt();
root["webPort"] = getSetting("webPort", WEB_PORT).toInt(); root["webPort"] = getSetting("webPort", WEB_PORT).toInt();
root["tmpUnits"] = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt(); root["tmpUnits"] = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt();
@ -292,6 +300,15 @@ void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTy
} }
void _wsLoop() {
static unsigned long last = 0;
if (!wsConnected()) return;
if (millis() - last > WS_UPDATE_INTERVAL) {
last = millis();
wsSend(_wsUpdate);
}
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Piblic API // Piblic API
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -371,6 +388,7 @@ void wsSetup() {
#endif #endif
wsOnSendRegister(_wsOnStart); wsOnSendRegister(_wsOnStart);
wsOnAfterParseRegister(wsConfigure); wsOnAfterParseRegister(wsConfigure);
espurnaRegisterLoop(_wsLoop);
} }
#endif // WEB_SUPPORT #endif // WEB_SUPPORT

+ 179
- 119
code/html/custom.css View File

@ -1,145 +1,99 @@
/* -----------------------------------------------------------------------------
General
-------------------------------------------------------------------------- */
#menu .pure-menu-heading { #menu .pure-menu-heading {
font-size: 100%; font-size: 100%;
padding: .5em .5em; padding: .5em .5em;
} }
.header h2 {
font-size: 1em;
}
.panel {
display: none;
}
.footer {
padding: 10px;
font-size: 80%;
color: #999;
}
#menu .footer a {
text-decoration: none;
padding: 0px;
}
.content {
margin: 0px;
}
.page {
margin-top: 10px;
}
.pure-button {
color: white;
padding: 8px 8px;
border-radius: 4px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}
.main-buttons {
margin: 50px auto;
text-align: center;
}
.main-buttons button {
width: 100px;
margin: 5px auto;
}
.button-update-password,
.button-update {
background: #1f8dd6;
}
.button-reboot,
.button-reconnect,
.button-ha-del,
.button-rfb-forget {
background: rgb(202, 60, 60);
}
.button-upgrade {
background: rgb(202, 60, 60);
margin-left: 5px;
}
.button-upgrade-browse,
.button-ha-add,
.button-apikey {
background: rgb(0, 202, 0);
margin-left: 5px;
}
.button-add-network,
.button-add-schedule,
.button-rfb-learn {
background: rgb(28, 184, 65);
}
.button-del-network,
.button-del-schedule {
background: rgb(202, 60, 60);
}
.pure-button {
letter-spacing: 0;
}
.button-more-network,
.button-more-schedule,
.button-wifi-scan,
.button-rfb-send {
background: rgb(223, 117, 20);
}
.button-settings-backup,
.button-settings-restore {
background: rgb(0, 202, 0);
margin-bottom: 10px;
}
.pure-g { .pure-g {
margin-bottom: 0px; margin-bottom: 0px;
} }
.pure-form legend { .pure-form legend {
font-weight: bold; font-weight: bold;
letter-spacing: 0; letter-spacing: 0;
margin: 10px 0 1em 0; margin: 10px 0 1em 0;
} }
.l-box {
padding-right: 1px;
.pure-form .pure-g > label {
margin: .4em 0 .2em;
} }
.pure-form input { .pure-form input {
margin-bottom: 10px; margin-bottom: 10px;
} }
.pure-form input[type=text][disabled] { .pure-form input[type=text][disabled] {
color: #777777; color: #777777;
} }
.header h2 {
font-size: 1em;
}
.panel {
display: none;
}
.content {
margin: 0px;
}
.page {
margin-top: 10px;
}
div.hint { div.hint {
font-size: 80%; font-size: 80%;
color: #ccc; color: #ccc;
margin: -10px 0 10px 0; margin: -10px 0 10px 0;
} }
.break {
margin-top: 5px;
}
#networks .pure-g,
#schedules .pure-g {
padding: 10px 0 10px 0;
margin-bottom: 10px;
border-bottom: 1px solid #eee;
}
#networks .more {
display: none;
}
legend.module, legend.module,
.module { .module {
display: none; display: none;
} }
.template { .template {
display: none; display: none;
} }
input[name=upgrade] { input[name=upgrade] {
display: none; display: none;
} }
#upgrade-progress {
display: none;
.panel.block {
display: block;
}
select {
width: 100%; width: 100%;
height: 20px;
margin-top: 10px;
margin-bottom: 10px;
} }
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #eee;
margin: 1em 0;
padding: 0;
}
input.center { input.center {
margin-bottom: 0px; margin-bottom: 0px;
} }
div.center { div.center {
margin: .5em 0 1em; margin: .5em 0 1em;
} }
.webmode { .webmode {
display: none; display: none;
} }
#credentials { #credentials {
font-size: 200%; font-size: 200%;
text-align: center; text-align: center;
@ -151,6 +105,7 @@ div.center {
margin-top: -50px; margin-top: -50px;
margin-left: -200px; margin-left: -200px;
} }
div.state { div.state {
border-top: 1px solid #eee; border-top: 1px solid #eee;
margin-top: 20px; margin-top: 20px;
@ -165,65 +120,170 @@ div.state {
font-size: 80%; font-size: 80%;
font-weight: bold; font-weight: bold;
} }
.right { .right {
text-align: right; text-align: right;
} }
/* -----------------------------------------------------------------------------
Buttons
-------------------------------------------------------------------------- */
.pure-button {
color: white;
padding: 8px 8px;
border-radius: 4px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
letter-spacing: 0;
margin-bottom: 10px;
}
.main-buttons {
margin: 20px auto;
text-align: center;
}
.main-buttons button {
width: 100px;
}
.button-reboot,
.button-reconnect,
.button-ha-del,
.button-rfb-forget,
.button-del-network,
.button-del-schedule,
.button-upgrade {
background: rgb(192, 0, 0); /* redish */
}
.button-update,
.button-update-password,
.button-add-network,
.button-add-schedule,
.button-rfb-learn,
.button-upgrade-browse,
.button-ha-add,
.button-settings-backup,
.button-settings-restore,
.button-apikey {
background: rgb(0, 192, 0); /* green */
}
.button-more-network,
.button-more-schedule,
.button-wifi-scan,
.button-rfb-send {
background: rgb(255, 128, 0); /* orange */
}
.button-upgrade-browse,
.button-ha-add,
.button-apikey,
.button-upgrade {
margin-left: 5px;
}
/* -----------------------------------------------------------------------------
Sliders
-------------------------------------------------------------------------- */
input.slider { input.slider {
margin-top: 10px; margin-top: 10px;
} }
span.slider { span.slider {
font-size: 70%; font-size: 70%;
margin-left: 10px; margin-left: 10px;
margin-top: 7px; margin-top: 7px;
letter-spacing: 0; letter-spacing: 0;
} }
/* -----------------------------------------------------------------------------
Loading
-------------------------------------------------------------------------- */
div.loading {
background-image: url('images/loading.gif');
width: 20px;
height: 20px;
margin: 8px 0 0 10px;
display: none;
}
/* -----------------------------------------------------------------------------
Menu
-------------------------------------------------------------------------- */
#menu span.small {
font-size: 60%;
padding-left: 9px;
}
#menu div.footer {
padding: 10px;
font-size: 80%;
color: #999;
}
#menu div.footer a {
text-decoration: none;
padding: 0px;
}
/* -----------------------------------------------------------------------------
RF Bridge panel
-------------------------------------------------------------------------- */
#panel-rfb fieldset { #panel-rfb fieldset {
margin: 10px 2px; margin: 10px 2px;
padding: 20px; padding: 20px;
} }
#panel-rfb input { #panel-rfb input {
margin-right: 5px; margin-right: 5px;
} }
#panel-rfb label { #panel-rfb label {
padding-top: 5px; padding-top: 5px;
} }
#panel-rfb input { #panel-rfb input {
text-align: center; text-align: center;
} }
/* -----------------------------------------------------------------------------
Admin panel
-------------------------------------------------------------------------- */
#upgrade-progress {
display: none;
width: 100%;
height: 20px;
margin-top: 10px;
}
#uploader, #uploader,
#downloader { #downloader {
display: none; display: none;
} }
.panel.block {
display: block;
}
select {
width: 100%;
/* -----------------------------------------------------------------------------
Wifi panel
-------------------------------------------------------------------------- */
#networks .pure-g,
#schedules .pure-g {
padding: 10px 0 10px 0;
margin-bottom: 10px; margin-bottom: 10px;
border-bottom: 1px solid #eee;
} }
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #eee;
margin: 1em 0;
padding: 0;
#networks .more {
display: none;
} }
#scanResult { #scanResult {
margin-top: 10px; margin-top: 10px;
font-size: 60%; font-size: 60%;
color: #888; color: #888;
font-family: 'Courier New', monospace; font-family: 'Courier New', monospace;
} }
div.loading {
background-image: url('images/loading.gif');
width: 20px;
height: 20px;
margin: 8px 0 0 10px;
display: none;
}
#menu span.small {
font-size: 60%;
padding-left: 9px;
}

+ 50
- 0
code/html/custom.js View File

@ -13,6 +13,9 @@ var numReload = 0;
var useWhite = false; var useWhite = false;
var manifest; var manifest;
var now = 0;
var ago = 0;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Messages // Messages
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -71,6 +74,17 @@ function magnitudeError(error) {
// Utils // Utils
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
function keepTime() {
if (now === 0) return;
var date = new Date(now * 1000);
var text = date.toISOString().substring(0, 19).replace("T", " ");
$("input[name='now']").val(text);
$("span[name='now']").html(text);
$("span[name='ago']").html(ago);
now++;
ago++;
}
// http://www.the-art-of-web.com/javascript/validate-password/ // http://www.the-art-of-web.com/javascript/validate-password/
function checkPassword(str) { function checkPassword(str) {
// at least one lowercase and one uppercase letter or number // at least one lowercase and one uppercase letter or number
@ -87,6 +101,34 @@ function zeroPad(number, positions) {
return (zeros + number).slice(-positions); return (zeros + number).slice(-positions);
} }
function loadTimeZones() {
var time_zones = [
-720, -660, -600, -570, -540,
-480, -420, -360, -300, -240,
-210, -180, -120, -60, 0,
60, 120, 180, 210, 240,
270, 300, 330, 345, 360,
390, 420, 480, 510, 525,
540, 570, 600, 630, 660,
720, 765, 780, 840
];
for (var i in time_zones) {
var value = parseInt(time_zones[i], 10);
var offset = value >= 0 ? value : -value;
var text = "GMT" + (value >= 0 ? "+" : "-") +
zeroPad(parseInt(offset / 60, 10), 2) + ":" +
zeroPad(offset % 60, 2);
$("select[name='ntpOffset']").append(
$("<option></option>").
attr("value",value).
text(text)
);
}
}
function validateForm(form) { function validateForm(form) {
// password // password
@ -1037,6 +1079,12 @@ function processData(data) {
return; return;
} }
if (key === "now") {
now = data[key];
ago = 0;
return;
}
// Pre-process // Pre-process
if (key === "network") { if (key === "network") {
data.network = data.network.toUpperCase(); data.network = data.network.toUpperCase();
@ -1169,6 +1217,8 @@ function connect(host) {
$(function() { $(function() {
initMessages(); initMessages();
loadTimeZones();
setInterval(function() { keepTime(); }, 1000);
$("#menuLink").on("click", toggleMenu); $("#menuLink").on("click", toggleMenu);
$(".pure-menu-link").on("click", showPanel); $(".pure-menu-link").on("click", showPanel);


+ 33
- 38
code/html/index.html View File

@ -51,7 +51,8 @@
<input name="adminPass" class="pure-u-1 pure-u-lg-3-4" type="password" tabindex="2" autocomplete="false" /> <input name="adminPass" class="pure-u-1 pure-u-lg-3-4" type="password" tabindex="2" autocomplete="false" />
</div> </div>
<button class="pure-button button-update-password">Update</button>
<div class="pure-u-0 pure-u-lg-1-4 more"></div>
<button class="pure-button button-update-password" type="button">Update</button>
</fieldset> </fieldset>
</div> </div>
@ -213,7 +214,7 @@
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="app_build"></span></div> <div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="app_build"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Current time</div> <div class="pure-u-1-2 pure-u-lg-1-4">Current time</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="time"></span></div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="now"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Uptime</div> <div class="pure-u-1-2 pure-u-lg-1-4">Uptime</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="uptime"></span></div> <div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="uptime"></span></div>
@ -233,6 +234,8 @@
<div class="pure-u-1-2 pure-u-lg-1-4">NTP Status</div> <div class="pure-u-1-2 pure-u-lg-1-4">NTP Status</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="ntpStatus">NOT AVAILABLE</span></div> <div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="ntpStatus">NOT AVAILABLE</span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Last update</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="ago">?</span><span> seconds ago</span></div>
</div> </div>
@ -302,12 +305,12 @@
</div> </div>
<div class="pure-g module module-alexa"> <div class="pure-g module module-alexa">
<div class="pure-u-1 pure-u-lg-1-4"><label>Alexa integration</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Alexa integration</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="alexaEnabled" tabindex="13" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="alexaEnabled" tabindex="13" /></div>
</div> </div>
<div class="pure-g module module-ha"> <div class="pure-g module module-ha">
<div class="pure-u-1 pure-u-lg-1-4"><label>Home Assistant</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Home Assistant</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="haEnabled" tabindex="14" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="haEnabled" tabindex="14" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
@ -370,7 +373,7 @@
<fieldset> <fieldset>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Use colorpicker</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Use colorpicker</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useColor" action="reload" tabindex="8" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useColor" action="reload" tabindex="8" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
@ -378,7 +381,7 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Use RGB picker</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Use RGB picker</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useRGB" action="reload" tabindex="11" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useRGB" action="reload" tabindex="11" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
@ -386,7 +389,7 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Use white channel</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Use white channel</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useWhite" action="reload" tabindex="9" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useWhite" action="reload" tabindex="9" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
@ -394,7 +397,7 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Use gamma correction</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Use gamma correction</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useGamma" tabindex="10" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useGamma" tabindex="10" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
@ -402,7 +405,7 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Use CSS style</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Use CSS style</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useCSS" tabindex="11" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useCSS" tabindex="11" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
@ -410,7 +413,7 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Color transitions</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Color transitions</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useTransitions" tabindex="12" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useTransitions" tabindex="12" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
@ -465,12 +468,12 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Enable HTTP API</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Enable HTTP API</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiEnabled" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiEnabled" /></div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Real time API</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Real time API</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiRealTime" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiRealTime" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
@ -493,7 +496,7 @@
</div> </div>
<div class="pure-g module module-telnet"> <div class="pure-g module module-telnet">
<div class="pure-u-1 pure-u-lg-1-4"><label>Enable TELNET</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Enable TELNET</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="telnetSTA" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="telnetSTA" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
@ -502,7 +505,7 @@
<div class="pure-g module module-nofuss"> <div class="pure-g module module-nofuss">
<div class="pure-u-1 pure-u-lg-1-4"><label>Automatic remote updates (NoFUSS)</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Automatic remote updates (NoFUSS)</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="nofussEnabled" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="nofussEnabled" /></div>
</div> </div>
@ -516,7 +519,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Settings</label> <label class="pure-u-1 pure-u-lg-1-4">Settings</label>
<div class="pure-u-1-2 pure-u-lg-1-8"><button class="pure-button button-settings-backup pure-u-23-24">Backup</button></div> <div class="pure-u-1-2 pure-u-lg-1-8"><button class="pure-button button-settings-backup pure-u-23-24">Backup</button></div>
<div class="pure-u-1-2 pure-u-lg-1-8"><button class="pure-button button-settings-restore pure-u-23-24">Restore</button></div>
<div class="pure-u-1-2 pure-u-lg-1-8"><button class="pure-button button-settings-restore pure-u-1">Restore</button></div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
@ -547,7 +550,7 @@
<legend>General</legend> <legend>General</legend>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Scan networks</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Scan networks</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="wifiScan" tabindex="1" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="wifiScan" tabindex="1" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
@ -610,7 +613,7 @@
<fieldset> <fieldset>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Enable MQTT</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Enable MQTT</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="mqttEnabled" tabindex="30" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="mqttEnabled" tabindex="30" /></div>
</div> </div>
@ -697,7 +700,7 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Use JSON payload</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Use JSON payload</label>
<div class="pure-u-1 pure-u-lg-3-4"><input type="checkbox" name="mqttUseJson" tabindex="32" /></div> <div class="pure-u-1 pure-u-lg-3-4"><input type="checkbox" name="mqttUseJson" tabindex="32" /></div>
<div class="pure-u-1 pure-u-lg-1-4"></div> <div class="pure-u-1 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"> <div class="pure-u-1 pure-u-lg-3-4 hint">
@ -725,30 +728,22 @@
<fieldset> <fieldset>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">NTP Server #0</label>
<input class="pure-u-1 pure-u-lg-3-4" name="ntpServer1" type="text" tabindex="41" />
<label class="pure-u-1 pure-u-lg-1-4">Device Current Time</label>
<input class="pure-u-1 pure-u-lg-3-4" name="now" type="text" readonly />
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">NTP Server #1</label>
<input class="pure-u-1 pure-u-lg-3-4" name="ntpServer2" type="text" tabindex="42" />
<label class="pure-u-1 pure-u-lg-1-4">NTP Server</label>
<input class="pure-u-1 pure-u-lg-3-4" name="ntpServer" type="text" tabindex="41" />
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">NTP Server #2</label>
<input class="pure-u-1 pure-u-lg-3-4" name="ntpServer3" type="text" tabindex="43" />
<label class="pure-u-1 pure-u-lg-1-4">Time Zone</label>
<select class="pure-u-1 pure-u-lg-1-4" name="ntpOffset" tabindex="42"></select>
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Time offset</label>
<div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-1 pure-u-lg-23-24" name="ntpOffset" type="number" min="-11" max="14" tabindex="44" data="0" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-1-2 hint">Set to 0 for UTC time</div>
</div>
<div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Enable DST</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Enable DST</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="ntpDST" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="ntpDST" /></div>
</div> </div>
@ -773,7 +768,7 @@
<legend>General</legend> <legend>General</legend>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Enable Domoticz</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Enable Domoticz</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="dczEnabled" tabindex="30" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="dczEnabled" tabindex="30" /></div>
</div> </div>
@ -818,7 +813,7 @@
<legend>General</legend> <legend>General</legend>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Enable Thingspeak</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Enable Thingspeak</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="tspkEnabled" tabindex="30" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="tspkEnabled" tabindex="30" /></div>
</div> </div>
@ -856,7 +851,7 @@
<fieldset> <fieldset>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Enable InfluxDB</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Enable InfluxDB</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="idbEnabled" tabindex="40" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="idbEnabled" tabindex="40" /></div>
</div> </div>
@ -981,7 +976,7 @@
</div> </div>
<div class="pure-g module module-hlw module-emon"> <div class="pure-g module module-hlw module-emon">
<div class="pure-u-1 pure-u-lg-1-4"><label>Reset calibration</label></div>
<label class="pure-u-1 pure-u-lg-1-4">Reset calibration</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="pwrResetCalibration" tabindex="55" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="pwrResetCalibration" tabindex="55" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
@ -1122,7 +1117,7 @@
<div id="relayTemplate" class="template"> <div id="relayTemplate" class="template">
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Switch #<span class="id"></span></label></div>
<label class="pure-u-1 pure-u-lg-1-4">Switch #<span class="id"></span></label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" class="relayStatus pure-u-1 pure-u-lg-1-4" data="0" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" class="relayStatus pure-u-1 pure-u-lg-1-4" data="0" /></div>
</div> </div>
</div> </div>


+ 0
- 0
code/ota.py View File


+ 1
- 1
code/platformio.ini View File

@ -18,7 +18,7 @@ lib_deps =
https://github.com/marvinroger/async-mqtt-client#v0.8.1 https://github.com/marvinroger/async-mqtt-client#v0.8.1
PubSubClient PubSubClient
Embedis Embedis
NtpClientLib
https://github.com/xoseperez/NtpClient.git#b35e249
OneWire OneWire
Brzo I2C Brzo I2C
https://github.com/krosk93/espsoftwareserial#a770677 https://github.com/krosk93/espsoftwareserial#a770677


Loading…
Cancel
Save