Browse Source

Merge remote-tracking branch 'xoseperez/dev' into lightfox-dual

rules-rpn
Andrey F. Kupreychik 5 years ago
parent
commit
86bccaf74f
61 changed files with 22382 additions and 21510 deletions
  1. +0
    -0
      CONTRIBUTING.md
  2. +8
    -0
      SUPPORT.md
  3. +16
    -11
      code/espurna/alexa.ino
  4. +6
    -6
      code/espurna/broker.ino
  5. +36
    -12
      code/espurna/button.ino
  6. +1
    -0
      code/espurna/config/all.h
  7. +5
    -1
      code/espurna/config/arduino.h
  8. +9
    -0
      code/espurna/config/deprecated.h
  9. +11
    -1
      code/espurna/config/general.h
  10. +96
    -7
      code/espurna/config/hardware.h
  11. +16
    -4
      code/espurna/config/prototypes.h
  12. +13
    -0
      code/espurna/config/types.h
  13. BIN
      code/espurna/data/index.all.html.gz
  14. BIN
      code/espurna/data/index.light.html.gz
  15. BIN
      code/espurna/data/index.lightfox.html.gz
  16. BIN
      code/espurna/data/index.rfbridge.html.gz
  17. BIN
      code/espurna/data/index.rfm69.html.gz
  18. BIN
      code/espurna/data/index.sensor.html.gz
  19. BIN
      code/espurna/data/index.small.html.gz
  20. +42
    -35
      code/espurna/debug.ino
  21. +35
    -14
      code/espurna/domoticz.ino
  22. +12
    -12
      code/espurna/eeprom.ino
  23. +1
    -1
      code/espurna/encoder.ino
  24. +7
    -2
      code/espurna/espurna.ino
  25. +73
    -22
      code/espurna/homeassistant.ino
  26. +18
    -0
      code/espurna/i2c.ino
  27. +5
    -2
      code/espurna/influxdb.ino
  28. +6
    -1
      code/espurna/led.ino
  29. +54
    -40
      code/espurna/light.ino
  30. +2
    -2
      code/espurna/lightfox.ino
  31. +24
    -1
      code/espurna/migrate.ino
  32. +13
    -14
      code/espurna/mqtt.ino
  33. +2
    -2
      code/espurna/nofuss.ino
  34. +1
    -2
      code/espurna/ntp.ino
  35. +9
    -4
      code/espurna/ota.ino
  36. +58
    -39
      code/espurna/relay.ino
  37. +33
    -18
      code/espurna/rfbridge.ino
  38. +33
    -18
      code/espurna/scheduler.ino
  39. +62
    -33
      code/espurna/sensor.ino
  40. +7
    -1
      code/espurna/sensors/PMSX003Sensor.h
  41. +7
    -286
      code/espurna/settings.ino
  42. +3155
    -3136
      code/espurna/static/index.all.html.gz.h
  43. +3031
    -3013
      code/espurna/static/index.light.html.gz.h
  44. +2592
    -2574
      code/espurna/static/index.lightfox.html.gz.h
  45. +2632
    -2612
      code/espurna/static/index.rfbridge.html.gz.h
  46. +4114
    -4096
      code/espurna/static/index.rfm69.html.gz.h
  47. +2695
    -2677
      code/espurna/static/index.sensor.html.gz.h
  48. +2592
    -2574
      code/espurna/static/index.small.html.gz.h
  49. +35
    -9
      code/espurna/system.ino
  50. +11
    -5
      code/espurna/telnet.ino
  51. +283
    -0
      code/espurna/terminal.ino
  52. +13
    -11
      code/espurna/thinkspeak.ino
  53. +23
    -7
      code/espurna/utils.ino
  54. +12
    -12
      code/espurna/wifi.ino
  55. +131
    -62
      code/espurna/ws.ino
  56. +75
    -42
      code/html/custom.js
  57. +42
    -2
      code/html/index.html
  58. +27
    -25
      code/memanalyzer.py
  59. +56
    -45
      code/ota.py
  60. +131
    -10
      code/platformio.ini
  61. +11
    -7
      pre-commit

.github/contribute.md → CONTRIBUTING.md View File


+ 8
- 0
SUPPORT.md View File

@ -0,0 +1,8 @@
# ESPurna Support
If you're looking for support for ESPurna there are some options available:
* [Issues](https://github.com/xoseperez/espurna/issues?utf8=%E2%9C%93&q=is%3Aissue): this is the most dinamic channel at the moment, you might find an answer to your question by searching current or closed issues.
* [Wiki pages](https://github.com/xoseperez/espurna/wiki): might not be as up-to-date as we all would like (hey, you can also contribute in the documentation!).
* [Gitter channel](https://gitter.im/tinkerman-cat/espurna): you have better chances to get fast answers from me or other ESPurna users.
* [Issue a question](https://github.com/xoseperez/espurna/issues/new/choose): as a last resort, you can open new *question* issues on GitHub. Just remember: the more info you provide the more chances you'll have to get an accurate answer.

+ 16
- 11
code/espurna/alexa.ino View File

@ -36,18 +36,23 @@ void _alexaConfigure() {
alexa.enable(wifiConnected() && alexaEnabled()); alexa.enable(wifiConnected() && alexaEnabled());
} }
bool _alexaBodyCallback(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
return alexa.process(request->client(), request->method() == HTTP_GET, request->url(), String((char *)data));
}
#if WEB_SUPPORT
bool _alexaBodyCallback(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
return alexa.process(request->client(), request->method() == HTTP_GET, request->url(), String((char *)data));
}
bool _alexaRequestCallback(AsyncWebServerRequest *request) {
String body = (request->hasParam("body", true)) ? request->getParam("body", true)->value() : String();
return alexa.process(request->client(), request->method() == HTTP_GET, request->url(), body);
}
bool _alexaRequestCallback(AsyncWebServerRequest *request) {
String body = (request->hasParam("body", true)) ? request->getParam("body", true)->value() : String();
return alexa.process(request->client(), request->method() == HTTP_GET, request->url(), body);
}
#endif
#if BROKER_SUPPORT #if BROKER_SUPPORT
void _alexaBrokerCallback(const char * topic, unsigned char id, const char * payload) {
void _alexaBrokerCallback(const unsigned char type, const char * topic, unsigned char id, const char * payload) {
// Only process status messages
if (BROKER_MSG_TYPE_STATUS != type) return;
unsigned char value = atoi(payload); unsigned char value = atoi(payload);
if (strcmp(MQTT_TOPIC_CHANNEL, topic) == 0) { if (strcmp(MQTT_TOPIC_CHANNEL, topic) == 0) {
@ -76,7 +81,7 @@ void alexaSetup() {
moveSetting("fauxmoEnabled", "alexaEnabled"); moveSetting("fauxmoEnabled", "alexaEnabled");
// Basic fauxmoESP configuration // Basic fauxmoESP configuration
alexa.createServer(false);
alexa.createServer(!WEB_SUPPORT);
alexa.setPort(80); alexa.setPort(80);
// Uses hostname as base name for all devices // Uses hostname as base name for all devices
@ -113,6 +118,8 @@ void alexaSetup() {
// Websockets // Websockets
#if WEB_SUPPORT #if WEB_SUPPORT
webBodyRegister(_alexaBodyCallback);
webRequestRegister(_alexaRequestCallback);
wsOnSendRegister(_alexaWebSocketOnSend); wsOnSendRegister(_alexaWebSocketOnSend);
wsOnReceiveRegister(_alexaWebSocketOnReceive); wsOnReceiveRegister(_alexaWebSocketOnReceive);
#endif #endif
@ -134,8 +141,6 @@ void alexaSetup() {
}); });
// Register main callbacks // Register main callbacks
webBodyRegister(_alexaBodyCallback);
webRequestRegister(_alexaRequestCallback);
#if BROKER_SUPPORT #if BROKER_SUPPORT
brokerRegister(_alexaBrokerCallback); brokerRegister(_alexaBrokerCallback);
#endif #endif


+ 6
- 6
code/espurna/broker.ino View File

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

+ 36
- 12
code/espurna/button.ino View File

@ -105,37 +105,61 @@ void buttonEvent(unsigned int id, unsigned char event) {
} }
#endif #endif
if (action == BUTTON_MODE_TOGGLE) {
if (BUTTON_MODE_TOGGLE == action) {
if (_buttons[id].relayID > 0) { if (_buttons[id].relayID > 0) {
relayToggle(_buttons[id].relayID - 1); relayToggle(_buttons[id].relayID - 1);
} }
} }
if (action == BUTTON_MODE_ON) {
if (BUTTON_MODE_ON == action) {
if (_buttons[id].relayID > 0) { if (_buttons[id].relayID > 0) {
relayStatus(_buttons[id].relayID - 1, true); relayStatus(_buttons[id].relayID - 1, true);
} }
} }
if (action == BUTTON_MODE_OFF) {
if (BUTTON_MODE_OFF == action) {
if (_buttons[id].relayID > 0) { if (_buttons[id].relayID > 0) {
relayStatus(_buttons[id].relayID - 1, false); relayStatus(_buttons[id].relayID - 1, false);
} }
} }
if (action == BUTTON_MODE_AP) wifiStartAP();
#if defined(JUSTWIFI_ENABLE_WPS)
if (action == BUTTON_MODE_WPS) wifiStartWPS();
#endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
if (action == BUTTON_MODE_SMART_CONFIG) wifiStartSmartConfig();
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
if (action == BUTTON_MODE_RESET) {
if (BUTTON_MODE_AP == action) {
wifiStartAP();
}
if (BUTTON_MODE_RESET == action) {
deferredReset(100, CUSTOM_RESET_HARDWARE); deferredReset(100, CUSTOM_RESET_HARDWARE);
} }
if (action == BUTTON_MODE_FACTORY) {
if (BUTTON_MODE_FACTORY == action) {
DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n")); DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
resetSettings(); resetSettings();
deferredReset(100, CUSTOM_RESET_FACTORY); deferredReset(100, CUSTOM_RESET_FACTORY);
} }
#if defined(JUSTWIFI_ENABLE_WPS)
if (BUTTON_MODE_WPS == action) {
wifiStartWPS();
}
#endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
if (BUTTON_MODE_SMART_CONFIG == action) {
wifiStartSmartConfig();
}
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (BUTTON_MODE_DIM_UP == action) {
lightBrightnessStep(1);
lightUpdate(true, true);
}
if (BUTTON_MODE_DIM_DOWN == action) {
lightBrightnessStep(-1);
lightUpdate(true, true);
}
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
} }
void buttonSetup() { void buttonSetup() {


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

@ -28,6 +28,7 @@
#include "arduino.h" #include "arduino.h"
#include "hardware.h" #include "hardware.h"
#include "defaults.h" #include "defaults.h"
#include "deprecated.h"
#include "general.h" #include "general.h"
#include "dependencies.h" #include "dependencies.h"
#include "debug.h" #include "debug.h"


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

@ -41,6 +41,7 @@
//#define ELECTRODRAGON_WIFI_IOT //#define ELECTRODRAGON_WIFI_IOT
//#define WORKCHOICE_ECOPLUG //#define WORKCHOICE_ECOPLUG
//#define AITHINKER_AI_LIGHT //#define AITHINKER_AI_LIGHT
//#define LYASI_LIGHT
//#define MAGICHOME_LED_CONTROLLER //#define MAGICHOME_LED_CONTROLLER
//#define MAGICHOME_LED_CONTROLLER_20 //#define MAGICHOME_LED_CONTROLLER_20
//#define HUACANXING_H801 //#define HUACANXING_H801
@ -72,6 +73,7 @@
//#define LINGAN_SWA1 //#define LINGAN_SWA1
//#define HEYGO_HY02 //#define HEYGO_HY02
//#define MAXCIO_WUS002S //#define MAXCIO_WUS002S
//#define OUKITEL_P1
//#define YIDIAN_XSSSA05 //#define YIDIAN_XSSSA05
//#define TONBUX_XSSSA06 //#define TONBUX_XSSSA06
//#define TONBUX_XSSSA01 //#define TONBUX_XSSSA01
@ -90,7 +92,7 @@
//#define NEO_COOLCAM_NAS_WR01W //#define NEO_COOLCAM_NAS_WR01W
//#define ESTINK_WIFI_POWER_STRIP //#define ESTINK_WIFI_POWER_STRIP
//#define PILOTAK_ESP_DIN_V1 //#define PILOTAK_ESP_DIN_V1
//#define BLITZWOLF_BWSHP2
//#define BLITZWOLF_BWSHPX
//#define BH_ONOFRE //#define BH_ONOFRE
//#define ITEAD_SONOFF_IFAN02 //#define ITEAD_SONOFF_IFAN02
//#define GENERIC_AG_L4 //#define GENERIC_AG_L4
@ -111,7 +113,9 @@
//#define GBLIFE_RGBW_SOCKET //#define GBLIFE_RGBW_SOCKET
//#define SMARTLIFE_MINI_SMART_SOCKET //#define SMARTLIFE_MINI_SMART_SOCKET
//#define GOSUND_SP1_V23 //#define GOSUND_SP1_V23
//#define GOSUND_WS1
//#define ARILUX_AL_LC02_V14 //#define ARILUX_AL_LC02_V14
//#define BLITZWOLF_BWSHPX_V23
//#define FOXEL_LIGHTFOX_DUAL //#define FOXEL_LIGHTFOX_DUAL
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------


+ 9
- 0
code/espurna/config/deprecated.h View File

@ -0,0 +1,9 @@
#pragma once
// 1.13.3 added TELNET_PASSWORD build-only flag
// 1.13.4 replaces it with TELNET_AUTHENTICATION runtime setting default
// TODO warning should be removed eventually
#ifdef TELNET_PASSWORD
#warning TELNET_PASSWORD is deprecated! Please replace it with TELNET_AUTHENTICATION
#define TELNET_AUTHENTICATION TELNET_PASSWORD
#endif

+ 11
- 1
code/espurna/config/general.h View File

@ -169,6 +169,7 @@
#define HEARTBEAT_NONE 0 // Never send heartbeat #define HEARTBEAT_NONE 0 // Never send heartbeat
#define HEARTBEAT_ONCE 1 // Send it only once upon MQTT connection #define HEARTBEAT_ONCE 1 // Send it only once upon MQTT connection
#define HEARTBEAT_REPEAT 2 // Send it upon MQTT connection and every HEARTBEAT_INTERVAL #define HEARTBEAT_REPEAT 2 // Send it upon MQTT connection and every HEARTBEAT_INTERVAL
#define HEARTBEAT_REPEAT_STATUS 3 // Send it upon MQTT connection and every HEARTBEAT_INTERVAL only STATUS report
// Backwards compatibility check // Backwards compatibility check
#if defined(HEARTBEAT_ENABLED) && (HEARTBEAT_ENABLED == 0) #if defined(HEARTBEAT_ENABLED) && (HEARTBEAT_ENABLED == 0)
@ -180,7 +181,7 @@
#endif #endif
#ifndef HEARTBEAT_INTERVAL #ifndef HEARTBEAT_INTERVAL
#define HEARTBEAT_INTERVAL 300000 // Interval between heartbeat messages (in ms)
#define HEARTBEAT_INTERVAL 300 // Interval between heartbeat messages (in sec)
#endif #endif
#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value #define UPTIME_OVERFLOW 4294967295 // Uptime overflow value
@ -234,6 +235,10 @@
#define HEARTBEAT_REPORT_HOSTNAME 1 #define HEARTBEAT_REPORT_HOSTNAME 1
#endif #endif
#ifndef HEARTBEAT_REPORT_DESCRIPTION
#define HEARTBEAT_REPORT_DESCRIPTION 1
#endif
#ifndef HEARTBEAT_REPORT_APP #ifndef HEARTBEAT_REPORT_APP
#define HEARTBEAT_REPORT_APP 1 #define HEARTBEAT_REPORT_APP 1
#endif #endif
@ -801,6 +806,7 @@
#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"
#define MQTT_TOPIC_DESCRIPTION "desc"
#define MQTT_TOPIC_TIME "time" #define MQTT_TOPIC_TIME "time"
#define MQTT_TOPIC_RFOUT "rfout" #define MQTT_TOPIC_RFOUT "rfout"
#define MQTT_TOPIC_RFIN "rfin" #define MQTT_TOPIC_RFIN "rfin"
@ -881,6 +887,10 @@
#define LIGHT_SAVE_ENABLED 1 // Light channel values saved by default after each change #define LIGHT_SAVE_ENABLED 1 // Light channel values saved by default after each change
#endif #endif
#ifndef LIGHT_COMMS_DELAY
#define LIGHT_COMMS_DELAY 100 // Delay communication after light update (in ms)
#endif
#ifndef LIGHT_SAVE_DELAY #ifndef LIGHT_SAVE_DELAY
#define LIGHT_SAVE_DELAY 5 // Persist color after 5 seconds to avoid wearing out #define LIGHT_SAVE_DELAY 5 // Persist color after 5 seconds to avoid wearing out
#endif #endif


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

@ -112,7 +112,7 @@
#define MANUFACTURER "WEMOS" #define MANUFACTURER "WEMOS"
#define DEVICE "D1_MINI_RELAYSHIELD" #define DEVICE "D1_MINI_RELAYSHIELD"
// Buttons
// Buttons
// No buttons on the D1 MINI alone, but defining it without adding a button doen't create problems // No buttons on the D1 MINI alone, but defining it without adding a button doen't create problems
#define BUTTON1_PIN 0 // Connect a pushbutton between D3 and GND, #define BUTTON1_PIN 0 // Connect a pushbutton between D3 and GND,
// it's the same as using a Wemos one button shield // it's the same as using a Wemos one button shield
@ -1151,6 +1151,28 @@
#define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT #define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT
#define MY92XX_MAPPING 0, 1, 2, 3 #define MY92XX_MAPPING 0, 1, 2, 3
// -----------------------------------------------------------------------------
// Lyasi LED
// -----------------------------------------------------------------------------
#elif defined(LYASI_LIGHT)
// Info
#define MANUFACTURER "LYASI"
#define DEVICE "RGB-LED"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX
#define DUMMY_RELAY_COUNT 1
// Light
#define LIGHT_CHANNELS 4
#define MY92XX_MODEL MY92XX_MODEL_MY9291
#define MY92XX_CHIPS 1
#define MY92XX_DI_PIN 4
#define MY92XX_DCKI_PIN 5
#define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT
#define MY92XX_MAPPING 0, 1, 2, 3
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// LED Controller // LED Controller
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -2079,6 +2101,38 @@
#define LED1_PIN 13 #define LED1_PIN 13
#define LED1_PIN_INVERSE 1 #define LED1_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// Oukitel - P1
// -----------------------------------------------------------------------------
#elif defined(OUKITEL_P1)
// -----------------------------------------------------------------------------
// Oukitel P1 Smart Plug
// https://www.amazon.com/Docooler-OUKITEL-Control-Wireless-Adaptor/dp/B07J3BYFJX/ref=sr_1_fkmrnull_2?keywords=oukitel+p1+smart+switch&qid=1550424399&s=gateway&sr=8-2-fkmrnull
// -----------------------------------------------------------------------------
// Info
#define MANUFACTURER "Oukitel"
#define DEVICE "P1"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
// Right
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// Left
#define RELAY2_PIN 15
#define RELAY2_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 0 // blue
#define LED1_PIN_INVERSE 1
#define LED1_MODE LED_MODE_WIFI
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// YiDian XS-SSA05 // YiDian XS-SSA05
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -2277,7 +2331,9 @@
// Relays // Relays
#define RELAY1_PIN 0 #define RELAY1_PIN 0
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#ifndef RELAY1_TYPE
#define RELAY1_TYPE RELAY_TYPE_NORMAL // See #1504 and #1554
#endif
// LEDs // LEDs
#define LED1_PIN 2 #define LED1_PIN 2
@ -2667,7 +2723,8 @@
#define RELAY2_TYPE RELAY_TYPE_NORMAL #define RELAY2_TYPE RELAY_TYPE_NORMAL
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Several boards under different names uing a power chip labelled BL0937 or HJL-01
// BlitzWolf SHP2 and SHP6
// Also several boards under different names uing a power chip labelled BL0937 or HJL-01
// * Blitzwolf (https://www.amazon.es/Inteligente-Temporización-Dispositivos-Cualquier-BlitzWolf/dp/B07BMQP142) // * Blitzwolf (https://www.amazon.es/Inteligente-Temporización-Dispositivos-Cualquier-BlitzWolf/dp/B07BMQP142)
// * HomeCube (https://www.amazon.de/Steckdose-Homecube-intelligente-Verbrauchsanzeige-funktioniert/dp/B076Q2LKHG) // * HomeCube (https://www.amazon.de/Steckdose-Homecube-intelligente-Verbrauchsanzeige-funktioniert/dp/B076Q2LKHG)
// * Coosa (https://www.amazon.com/COOSA-Monitoring-Function-Campatible-Assiatant/dp/B0788W9TDR) // * Coosa (https://www.amazon.com/COOSA-Monitoring-Function-Campatible-Assiatant/dp/B0788W9TDR)
@ -2675,11 +2732,11 @@
// * Ablue (https://www.amazon.de/Intelligente-Steckdose-Ablue-Funktioniert-Assistant/dp/B076DRFRZC) // * Ablue (https://www.amazon.de/Intelligente-Steckdose-Ablue-Funktioniert-Assistant/dp/B076DRFRZC)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#elif defined(BLITZWOLF_BWSHP2)
#elif defined(BLITZWOLF_BWSHPX)
// Info // Info
#define MANUFACTURER "BLITZWOLF" #define MANUFACTURER "BLITZWOLF"
#define DEVICE "BWSHP2"
#define DEVICE "BWSHPX"
// Buttons // Buttons
#define BUTTON1_PIN 13 #define BUTTON1_PIN 13
@ -2714,13 +2771,15 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Same as the above but new board version marked V2.3 // Same as the above but new board version marked V2.3
// BlitzWolf SHP2 V2.3
// Gosund SP1 V2.3
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#elif defined(BLITZWOLF_BWSHP2_V23)
#elif defined(BLITZWOLF_BWSHPX_V23)
// Info // Info
#define MANUFACTURER "BLITZWOLF" #define MANUFACTURER "BLITZWOLF"
#define DEVICE "BWSHP2V2.3"
#define DEVICE "BWSHPX_V23"
// Buttons // Buttons
#define BUTTON1_PIN 3 #define BUTTON1_PIN 3
@ -2798,6 +2857,26 @@
// Several boards under different names uing a power chip labelled BL0937 or HJL-01 // Several boards under different names uing a power chip labelled BL0937 or HJL-01
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#elif defined(GOSUND_WS1)
// Info
#define MANUFACTURER "GOSUND"
#define DEVICE "WS1"
// Buttons
#define BUTTON1_PIN 0
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 1
#define LED1_PIN_INVERSE 1
// This one is the same as the BLITZWOLF_BWSHPX_V23
#elif defined(GOSUND_SP1_V23) #elif defined(GOSUND_SP1_V23)
// Info // Info
@ -3357,6 +3436,7 @@
#define PMSX003_SUPPORT 1 #define PMSX003_SUPPORT 1
#define SENSEAIR_SUPPORT 1 #define SENSEAIR_SUPPORT 1
#define VL53L1X_SUPPORT 1 #define VL53L1X_SUPPORT 1
#define MAX6675_SUPPORT 1
// A bit of lights - pin 5 // A bit of lights - pin 5
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
@ -3392,6 +3472,14 @@
#define MICS2710_SUPPORT 1 #define MICS2710_SUPPORT 1
#define MICS5525_SUPPORT 1 #define MICS5525_SUPPORT 1
// MAX6675 14 11 10
#ifndef MAX6675_SUPPORT
#define MAX6675_SUPPORT 1
#endif
#define MAX6675_CS_PIN 14
#define MAX6675_SO_PIN 11
#define MAX6675_SCK_PIN 10
#elif defined(TRAVIS02) #elif defined(TRAVIS02)
// Relay provider dual // Relay provider dual
@ -3498,6 +3586,7 @@
#define LLMNR_SUPPORT 1 #define LLMNR_SUPPORT 1
#define NETBIOS_SUPPORT 1 #define NETBIOS_SUPPORT 1
#define SSDP_SUPPORT 1 #define SSDP_SUPPORT 1
#define RF_SUPPORT 1
#endif #endif


+ 16
- 4
code/espurna/config/prototypes.h View File

@ -24,7 +24,7 @@ extern "C" {
// Broker // Broker
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#if BROKER_SUPPORT #if BROKER_SUPPORT
void brokerRegister(void (*)(const char *, unsigned char, const char *));
void brokerRegister(void (*)(const unsigned char, const char *, unsigned char, const char *));
#endif #endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -142,9 +142,15 @@ template<typename T> String getSetting(const String& key, T defaultValue);
template<typename T> String getSetting(const String& key, unsigned int index, T defaultValue); template<typename T> String getSetting(const String& key, unsigned int index, T defaultValue);
void settingsGetJson(JsonObject& data); void settingsGetJson(JsonObject& data);
bool settingsRestoreJson(JsonObject& data); bool settingsRestoreJson(JsonObject& data);
void settingsRegisterCommand(const String& name, void (*call)(Embedis*));
void settingsInject(void *data, size_t len);
Stream & settingsSerial();
// -----------------------------------------------------------------------------
// Terminal
// -----------------------------------------------------------------------------
#if TERMINAL_SUPPORT
void terminalRegisterCommand(const String& name, void (*call)(Embedis*));
void terminalInject(void *data, size_t len);
Stream & terminalSerial();
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Utils // Utils
@ -178,6 +184,8 @@ void webRequestRegister(web_request_callback_f callback);
#if WEB_SUPPORT #if WEB_SUPPORT
typedef std::function<void(JsonObject&)> ws_on_send_callback_f; typedef std::function<void(JsonObject&)> ws_on_send_callback_f;
void wsOnSendRegister(ws_on_send_callback_f callback); void wsOnSendRegister(ws_on_send_callback_f callback);
void wsSend(uint32_t, JsonObject& root);
void wsSend(JsonObject& root);
void wsSend(ws_on_send_callback_f sender); void wsSend(ws_on_send_callback_f sender);
typedef std::function<void(uint32_t, const char *, JsonObject&)> ws_on_action_callback_f; typedef std::function<void(uint32_t, const char *, JsonObject&)> ws_on_action_callback_f;
@ -185,6 +193,10 @@ void webRequestRegister(web_request_callback_f callback);
typedef std::function<bool(const char *, JsonVariant&)> ws_on_receive_callback_f; typedef std::function<bool(const char *, JsonVariant&)> ws_on_receive_callback_f;
void wsOnReceiveRegister(ws_on_receive_callback_f callback); void wsOnReceiveRegister(ws_on_receive_callback_f callback);
bool wsConnected();
bool wsConnected(uint32_t);
bool wsDebugSend(const char*, const char*);
#else #else
#define ws_on_send_callback_f void * #define ws_on_send_callback_f void *
#define ws_on_action_callback_f void * #define ws_on_action_callback_f void *


+ 13
- 0
code/espurna/config/types.h View File

@ -3,6 +3,15 @@
// Do not touch this definitions // Do not touch this definitions
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// BROKER
// -----------------------------------------------------------------------------
#define BROKER_MSG_TYPE_SYSTEM 0
#define BROKER_MSG_TYPE_DATETIME 1
#define BROKER_MSG_TYPE_STATUS 2
#define BROKER_MSG_TYPE_SENSOR 3
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// WIFI // WIFI
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -39,6 +48,9 @@
#define BUTTON_MODE_FACTORY 7 #define BUTTON_MODE_FACTORY 7
#define BUTTON_MODE_WPS 8 #define BUTTON_MODE_WPS 8
#define BUTTON_MODE_SMART_CONFIG 9 #define BUTTON_MODE_SMART_CONFIG 9
#define BUTTON_MODE_DIM_UP 10
#define BUTTON_MODE_DIM_DOWN 11
// Needed for ESP8285 boards under Windows using PlatformIO (?) // Needed for ESP8285 boards under Windows using PlatformIO (?)
#ifndef BUTTON_PUSHBUTTON #ifndef BUTTON_PUSHBUTTON
@ -284,6 +296,7 @@
#define SENSOR_VL53L1X_ID 32 #define SENSOR_VL53L1X_ID 32
#define SENSOR_EZOPH_ID 33 #define SENSOR_EZOPH_ID 33
#define SENSOR_BMP180_ID 34 #define SENSOR_BMP180_ID 34
#define SENSOR_MAX6675_ID 35
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Magnitudes // Magnitudes


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


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


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


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


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


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


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


+ 42
- 35
code/espurna/debug.ino View File

@ -16,22 +16,43 @@ char _udp_syslog_header[40] = {0};
#endif #endif
#endif #endif
void _debugSend(char * message) {
#if DEBUG_SERIAL_SUPPORT
void _debugSendSerial(const char* prefix, const char* data) {
if (prefix && (prefix[0] != '\0')) {
Serial.print(prefix);
}
Serial.print(data);
}
#endif
#if DEBUG_TELNET_SUPPORT
void _debugSendTelnet(const char* prefix, const char* data) {
if (prefix && (prefix[0] != '\0')) {
_telnetWrite(prefix);
}
_telnetWrite(data);
}
#endif
void _debugSend(const char * message) {
const size_t msg_len = strlen(message);
bool pause = false; bool pause = false;
char timestamp[10] = {0};
#if DEBUG_ADD_TIMESTAMP #if DEBUG_ADD_TIMESTAMP
static bool add_timestamp = true; 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) || (message[strlen(message)-1] == 13);
if (add_timestamp) {
snprintf(timestamp, sizeof(timestamp), "[%06lu] ", millis() % 1000000);
}
add_timestamp = (message[msg_len - 1] == 10) || (message[msg_len - 1] == 13);
#endif #endif
#if DEBUG_SERIAL_SUPPORT #if DEBUG_SERIAL_SUPPORT
#if DEBUG_ADD_TIMESTAMP
DEBUG_PORT.printf(timestamp);
#endif
DEBUG_PORT.printf(message);
_debugSendSerial(timestamp, message);
#endif #endif
#if DEBUG_UDP_SUPPORT #if DEBUG_UDP_SUPPORT
@ -51,31 +72,13 @@ void _debugSend(char * message) {
#endif #endif
#if DEBUG_TELNET_SUPPORT #if DEBUG_TELNET_SUPPORT
#if DEBUG_ADD_TIMESTAMP
_telnetWrite(timestamp, strlen(timestamp));
#endif
_telnetWrite(message, strlen(message));
_debugSendTelnet(timestamp, message);
pause = true; pause = true;
#endif #endif
#if DEBUG_WEB_SUPPORT #if DEBUG_WEB_SUPPORT
if (wsConnected() && (getFreeHeap() > 10000)) {
DynamicJsonBuffer jsonBuffer(JSON_OBJECT_SIZE(1) + strlen(message) + 17);
JsonObject &root = jsonBuffer.createObject();
#if DEBUG_ADD_TIMESTAMP
char buffer[strlen(timestamp) + strlen(message) + 1];
snprintf_P(buffer, sizeof(buffer), "%s%s", timestamp, message);
root.set("weblog", buffer);
#else
root.set("weblog", message);
#endif
String out;
root.printTo(out);
jsonBuffer.clear();
wsSend(out.c_str());
pause = true;
}
wsDebugSend(timestamp, message);
pause = true;
#endif #endif
if (pause) optimistic_yield(100); if (pause) optimistic_yield(100);
@ -128,12 +131,16 @@ void debugWebSetup() {
}); });
wsOnActionRegister([](uint32_t client_id, const char * action, JsonObject& data) { wsOnActionRegister([](uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "dbgcmd") == 0) {
const char* command = data.get<const char*>("command");
char buffer[strlen(command) + 2];
snprintf(buffer, sizeof(buffer), "%s\n", command);
settingsInject((void*) buffer, strlen(buffer));
}
#if TERMINAL_SUPPORT
if (strcmp(action, "dbgcmd") == 0) {
const char* command = data.get<const char*>("command");
char buffer[strlen(command) + 2];
snprintf(buffer, sizeof(buffer), "%s\n", command);
terminalInject((void*) buffer, strlen(buffer));
}
#endif
}); });
#if DEBUG_UDP_SUPPORT #if DEBUG_UDP_SUPPORT


+ 35
- 14
code/espurna/domoticz.ino View File

@ -11,6 +11,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h> #include <ArduinoJson.h>
bool _dcz_enabled = false; bool _dcz_enabled = false;
std::vector<bool> _dcz_relay_state;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Private methods // Private methods
@ -36,6 +37,15 @@ void _domoticzMqttSubscribe(bool value) {
} }
bool _domoticzStatus(unsigned char id) {
return _dcz_relay_state[id];
}
void _domoticzStatus(unsigned char id, bool status) {
_dcz_relay_state[id] = status;
relayStatus(id, status);
}
void _domoticzMqtt(unsigned int type, const char * topic, const char * payload) { void _domoticzMqtt(unsigned int type, const char * topic, const char * payload) {
if (!_dcz_enabled) return; if (!_dcz_enabled) return;
@ -70,6 +80,7 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
String stype = root["stype"]; String stype = root["stype"];
if ( if (
(stype.equals("RGB") || stype.equals("RGBW") || stype.equals("RGBWW")) (stype.equals("RGB") || stype.equals("RGBW") || stype.equals("RGBWW"))
&& domoticzIdx(0) == idx
) { ) {
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (lightHasColor()) { if (lightHasColor()) {
@ -127,7 +138,7 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
if (relayID >= 0) { if (relayID >= 0) {
unsigned char value = root["nvalue"]; unsigned char value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx); DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx);
relayStatus(relayID, value > 0);
_domoticzStatus(relayID, value > 0);
} }
} }
#else #else
@ -138,7 +149,7 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
if (relayID >= 0) { if (relayID >= 0) {
unsigned char value = root["nvalue"]; unsigned char value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx); DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx);
relayStatus(relayID, value == 1);
_domoticzStatus(relayID, value == 1);
} }
} }
@ -149,11 +160,18 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
}; };
#if BROKER_SUPPORT #if BROKER_SUPPORT
void _domoticzBrokerCallback(const char * topic, unsigned char id, const char * payload) {
void _domoticzBrokerCallback(const unsigned char type, const char * topic, unsigned char id, const char * payload) {
// Only process status messages
if (BROKER_MSG_TYPE_STATUS != type) return;
if (strcmp(MQTT_TOPIC_RELAY, topic) == 0) { if (strcmp(MQTT_TOPIC_RELAY, topic) == 0) {
unsigned char value = atoi(payload);
domoticzSendRelay(id, value == 1);
bool status = atoi(payload) == 1;
if (_domoticzStatus(id) == status) return;
_dcz_relay_state[id] = status;
domoticzSendRelay(id, status);
} }
} }
#endif // BROKER_SUPPORT #endif // BROKER_SUPPORT
@ -165,7 +183,7 @@ bool _domoticzWebSocketOnReceive(const char * key, JsonVariant& value) {
void _domoticzWebSocketOnSend(JsonObject& root) { void _domoticzWebSocketOnSend(JsonObject& root) {
root["dczVisible"] = 1;
unsigned char visible = 0;
root["dczEnabled"] = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1; root["dczEnabled"] = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC); root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC);
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC); root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
@ -174,18 +192,15 @@ void _domoticzWebSocketOnSend(JsonObject& root) {
for (unsigned char i=0; i<relayCount(); i++) { for (unsigned char i=0; i<relayCount(); i++) {
relays.add(domoticzIdx(i)); relays.add(domoticzIdx(i));
} }
visible = (relayCount() > 0);
#if SENSOR_SUPPORT #if SENSOR_SUPPORT
JsonArray& list = root.createNestedArray("dczMagnitudes");
for (byte i=0; i<magnitudeCount(); i++) {
JsonObject& element = list.createNestedObject();
element["name"] = magnitudeName(i);
element["type"] = magnitudeType(i);
element["index"] = magnitudeIndex(i);
element["idx"] = getSetting("dczMagnitude", i, 0).toInt();
}
_sensorWebSocketMagnitudes(root, "dcz");
visible = visible || (magnitudeCount() > 0);
#endif #endif
root["dczVisible"] = visible;
} }
#endif // WEB_SUPPORT #endif // WEB_SUPPORT
@ -193,6 +208,12 @@ void _domoticzWebSocketOnSend(JsonObject& root) {
void _domoticzConfigure() { void _domoticzConfigure() {
bool enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1; bool enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
if (enabled != _dcz_enabled) _domoticzMqttSubscribe(enabled); if (enabled != _dcz_enabled) _domoticzMqttSubscribe(enabled);
_dcz_relay_state.reserve(relayCount());
for (size_t n = 0; n < relayCount(); ++n) {
_dcz_relay_state[n] = relayStatus(n);
}
_dcz_enabled = enabled; _dcz_enabled = enabled;
} }


+ 12
- 12
code/espurna/eeprom.ino View File

@ -58,43 +58,43 @@ void eepromCommit() {
void _eepromInitCommands() { void _eepromInitCommands() {
settingsRegisterCommand(F("EEPROM"), [](Embedis* e) {
terminalRegisterCommand(F("EEPROM"), [](Embedis* e) {
infoMemory("EEPROM", SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE - settingsSize()); infoMemory("EEPROM", SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE - settingsSize());
eepromSectorsDebug(); eepromSectorsDebug();
if (_eeprom_commit_count > 0) { if (_eeprom_commit_count > 0) {
DEBUG_MSG_P(PSTR("[MAIN] Commits done: %lu\n"), _eeprom_commit_count); DEBUG_MSG_P(PSTR("[MAIN] Commits done: %lu\n"), _eeprom_commit_count);
DEBUG_MSG_P(PSTR("[MAIN] Last result: %s\n"), _eeprom_last_commit_result ? "OK" : "ERROR"); DEBUG_MSG_P(PSTR("[MAIN] Last result: %s\n"), _eeprom_last_commit_result ? "OK" : "ERROR");
} }
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
settingsRegisterCommand(F("EEPROM.COMMIT"), [](Embedis* e) {
terminalRegisterCommand(F("EEPROM.COMMIT"), [](Embedis* e) {
const bool res = _eepromCommit(); const bool res = _eepromCommit();
if (res) { if (res) {
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
} else { } else {
DEBUG_MSG_P(PSTR("-ERROR\n")); DEBUG_MSG_P(PSTR("-ERROR\n"));
} }
}); });
settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) {
EEPROMr.dump(settingsSerial());
DEBUG_MSG_P(PSTR("\n+OK\n"));
terminalRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) {
EEPROMr.dump(terminalSerial());
terminalOK();
}); });
settingsRegisterCommand(F("FLASH.DUMP"), [](Embedis* e) {
terminalRegisterCommand(F("FLASH.DUMP"), [](Embedis* e) {
if (e->argc < 2) { if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
return; return;
} }
uint32_t sector = String(e->argv[1]).toInt(); uint32_t sector = String(e->argv[1]).toInt();
uint32_t max = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE; uint32_t max = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE;
if (sector >= max) { if (sector >= max) {
DEBUG_MSG_P(PSTR("-ERROR: Sector out of range\n"));
terminalError(F("Sector out of range"));
return; return;
} }
EEPROMr.dump(settingsSerial(), sector);
DEBUG_MSG_P(PSTR("\n+OK\n"));
EEPROMr.dump(terminalSerial(), sector);
terminalOK();
}); });
} }


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

@ -105,7 +105,7 @@ void _encoderLoop() {
// action // action
if (encoder.button_pin == GPIO_NONE) { if (encoder.button_pin == GPIO_NONE) {
// if there is no button, the encoder driver the CHANNEL1
// if there is no button, the encoder drives CHANNEL1
lightChannelStep(encoder.channel1, delta); lightChannelStep(encoder.channel1, delta);
} else { } else {


+ 7
- 2
code/espurna/espurna.ino View File

@ -64,11 +64,16 @@ void setup() {
// Init EEPROM // Init EEPROM
eepromSetup(); eepromSetup();
// Init persistance
settingsSetup();
// Init Serial, SPIFFS and system check // Init Serial, SPIFFS and system check
systemSetup(); systemSetup();
// Init persistance and terminal features
settingsSetup();
// Init terminal features
#if TERMINAL_SUPPORT
terminalSetup();
#endif
// Hostname & board name initialization // Hostname & board name initialization
if (getSetting("hostname").length() == 0) { if (getSetting("hostname").length() == 0) {


+ 73
- 22
code/espurna/homeassistant.ino View File

@ -9,6 +9,7 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
#if HOMEASSISTANT_SUPPORT #if HOMEASSISTANT_SUPPORT
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <queue>
bool _haEnabled = false; bool _haEnabled = false;
bool _haSendFlag = false; bool _haSendFlag = false;
@ -147,9 +148,7 @@ void _haSendSwitches() {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
String _haGetConfig() {
String output;
void _haDumpConfig(std::function<void(String&)> printer, bool wrapJson = false) {
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER)) #if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light"); String type = String("light");
@ -163,8 +162,16 @@ String _haGetConfig() {
JsonObject& config = jsonBuffer.createObject(); JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config); _haSendSwitch(i, config);
output += "\n" + type + ":\n";
String output;
output.reserve(config.measureLength() + 32);
if (wrapJson) {
output += "{\"haConfig\": \"";
}
output += "\n\n" + type + ":\n";
bool first = true; bool first = true;
for (auto kv : config) { for (auto kv : config) {
if (first) { if (first) {
output += " - "; output += " - ";
@ -172,11 +179,21 @@ String _haGetConfig() {
} else { } else {
output += " "; output += " ";
} }
output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
output += kv.key;
output += ": ";
output += kv.value.as<String>();
output += "\n";
}
output += " ";
if (wrapJson) {
output += "\"}";
} }
jsonBuffer.clear(); jsonBuffer.clear();
printer(output);
} }
#if SENSOR_SUPPORT #if SENSOR_SUPPORT
@ -187,8 +204,16 @@ String _haGetConfig() {
JsonObject& config = jsonBuffer.createObject(); JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config); _haSendMagnitude(i, config);
output += "\nsensor:\n";
String output;
output.reserve(config.measureLength() + 32);
if (wrapJson) {
output += "{\"haConfig\": \"";
}
output += "\n\nsensor:\n";
bool first = true; bool first = true;
for (auto kv : config) { for (auto kv : config) {
if (first) { if (first) {
output += " - "; output += " - ";
@ -198,18 +223,25 @@ String _haGetConfig() {
} }
String value = kv.value.as<String>(); String value = kv.value.as<String>();
value.replace("%", "'%'"); value.replace("%", "'%'");
output += kv.key + String(": ") + value + String("\n");
output += kv.key;
output += ": ";
output += value;
output += "\n";
}
output += " ";
if (wrapJson) {
output += "\"}";
} }
output += "\n";
jsonBuffer.clear(); jsonBuffer.clear();
printer(output);
} }
#endif #endif
return output;
} }
void _haSend() { void _haSend() {
@ -241,6 +273,8 @@ void _haConfigure() {
#if WEB_SUPPORT #if WEB_SUPPORT
std::queue<uint32_t> _ha_send_config;
bool _haWebSocketOnReceive(const char * key, JsonVariant& value) { bool _haWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "ha", 2) == 0); return (strncmp(key, "ha", 2) == 0);
} }
@ -253,11 +287,7 @@ void _haWebSocketOnSend(JsonObject& root) {
void _haWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { void _haWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "haconfig") == 0) { if (strcmp(action, "haconfig") == 0) {
String output = _haGetConfig();
output.replace(" ", "&nbsp;");
output.replace("\n", "<br />");
output = String("{\"haConfig\": \"") + output + String("\"}");
wsSend(client_id, output.c_str());
_ha_send_config.push(client_id);
} }
} }
@ -267,27 +297,30 @@ void _haWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& d
void _haInitCommands() { void _haInitCommands() {
settingsRegisterCommand(F("HA.CONFIG"), [](Embedis* e) {
DEBUG_MSG(_haGetConfig().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
terminalRegisterCommand(F("HA.CONFIG"), [](Embedis* e) {
_haDumpConfig([](String& data) {
DEBUG_MSG(data.c_str());
});
DEBUG_MSG("\n");
terminalOK();
}); });
settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) {
terminalRegisterCommand(F("HA.SEND"), [](Embedis* e) {
setSetting("haEnabled", "1"); setSetting("haEnabled", "1");
_haConfigure(); _haConfigure();
#if WEB_SUPPORT #if WEB_SUPPORT
wsSend(_haWebSocketOnSend); wsSend(_haWebSocketOnSend);
#endif #endif
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
terminalRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
setSetting("haEnabled", "0"); setSetting("haEnabled", "0");
_haConfigure(); _haConfigure();
#if WEB_SUPPORT #if WEB_SUPPORT
wsSend(_haWebSocketOnSend); wsSend(_haWebSocketOnSend);
#endif #endif
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
} }
@ -296,6 +329,23 @@ void _haInitCommands() {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#if WEB_SUPPORT
void _haLoop() {
if (_ha_send_config.empty()) return;
uint32_t client_id = _ha_send_config.front();
_ha_send_config.pop();
if (!wsConnected(client_id)) return;
// TODO check wsConnected after each "printer" call?
_haDumpConfig([client_id](String& output) {
wsSend(client_id, output.c_str());
yield();
}, true);
}
#endif
void haSetup() { void haSetup() {
_haConfigure(); _haConfigure();
@ -304,6 +354,7 @@ void haSetup() {
wsOnSendRegister(_haWebSocketOnSend); wsOnSendRegister(_haWebSocketOnSend);
wsOnActionRegister(_haWebSocketOnAction); wsOnActionRegister(_haWebSocketOnAction);
wsOnReceiveRegister(_haWebSocketOnReceive); wsOnReceiveRegister(_haWebSocketOnReceive);
espurnaRegisterLoop(_haLoop);
#endif #endif
#if TERMINAL_SUPPORT #if TERMINAL_SUPPORT


+ 18
- 0
code/espurna/i2c.ino View File

@ -351,6 +351,20 @@ void i2cScan() {
if (nDevices == 0) DEBUG_MSG_P(PSTR("[I2C] No devices found\n")); if (nDevices == 0) DEBUG_MSG_P(PSTR("[I2C] No devices found\n"));
} }
void i2cCommands() {
terminalRegisterCommand(F("I2C.SCAN"), [](Embedis* e) {
i2cScan();
terminalOK();
});
terminalRegisterCommand(F("I2C.CLEAR"), [](Embedis* e) {
i2cClearBus();
terminalOK();
});
}
void i2cSetup() { void i2cSetup() {
unsigned char sda = getSetting("i2cSDA", I2C_SDA_PIN).toInt(); unsigned char sda = getSetting("i2cSDA", I2C_SDA_PIN).toInt();
@ -366,6 +380,10 @@ 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 TERMINAL_SUPPORT
i2cCommands();
#endif
#if I2C_CLEAR_BUS #if I2C_CLEAR_BUS
i2cClearBus(); i2cClearBus();
#endif #endif


+ 5
- 2
code/espurna/influxdb.ino View File

@ -39,10 +39,13 @@ void _idbConfigure() {
} }
#if BROKER_SUPPORT #if BROKER_SUPPORT
void _idbBrokerCallback(const char * topic, unsigned char id, const char * payload) {
if (strcmp(MQTT_TOPIC_RELAY, topic) == 0) {
void _idbBrokerCallback(const unsigned char type, const char * topic, unsigned char id, const char * payload) {
// Only process status & senssor messages
if ((BROKER_MSG_TYPE_STATUS == type) || (BROKER_MSG_TYPE_SENSOR == type)) {
idbSend(topic, id, (char *) payload); idbSend(topic, id, (char *) payload);
} }
} }
#endif // BROKER_SUPPORT #endif // BROKER_SUPPORT


+ 6
- 1
code/espurna/led.ino View File

@ -74,10 +74,15 @@ void _ledWebSocketOnSend(JsonObject& root) {
#endif #endif
#if BROKER_SUPPORT #if BROKER_SUPPORT
void _ledBrokerCallback(const char * topic, unsigned char id, const char * payload) {
void _ledBrokerCallback(const unsigned char type, const char * topic, unsigned char id, const char * payload) {
// Only process status messages
if (BROKER_MSG_TYPE_STATUS != type) return;
if (strcmp(MQTT_TOPIC_RELAY, topic) == 0) { if (strcmp(MQTT_TOPIC_RELAY, topic) == 0) {
ledUpdate(true); ledUpdate(true);
} }
} }
#endif // BROKER_SUPPORT #endif // BROKER_SUPPORT


+ 54
- 40
code/espurna/light.ino View File

@ -25,6 +25,7 @@ extern "C" {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
Ticker _light_comms_ticker;
Ticker _light_save_ticker; Ticker _light_save_ticker;
Ticker _light_transition_ticker; Ticker _light_transition_ticker;
@ -640,7 +641,7 @@ void lightBroker() {
char buffer[10]; char buffer[10];
for (unsigned int i=0; i < _light_channel.size(); i++) { for (unsigned int i=0; i < _light_channel.size(); i++) {
itoa(_light_channel[i].inputValue, buffer, 10); itoa(_light_channel[i].inputValue, buffer, 10);
brokerPublish(MQTT_TOPIC_CHANNEL, i, buffer);
brokerPublish(BROKER_MSG_TYPE_STATUS, MQTT_TOPIC_CHANNEL, i, buffer);
} }
} }
@ -658,6 +659,26 @@ bool lightHasColor() {
return _light_has_color; return _light_has_color;
} }
void _lightComms(unsigned char mask) {
// Report color & brightness to MQTT broker
#if MQTT_SUPPORT
if (mask & 0x01) lightMQTT();
if (mask & 0x02) lightMQTTGroup();
#endif
// Report color to WS clients (using current brightness setting)
#if WEB_SUPPORT
wsSend(_lightWebSocketStatus);
#endif
// Report channels to local broker
#if BROKER_SUPPORT
lightBroker();
#endif
}
void lightUpdate(bool save, bool forward, bool group_forward) { void lightUpdate(bool save, bool forward, bool group_forward) {
_generateBrightness(); _generateBrightness();
@ -672,21 +693,11 @@ void lightUpdate(bool save, bool forward, bool group_forward) {
_light_steps_left = _light_use_transitions ? _light_transition_time / LIGHT_TRANSITION_STEP : 1; _light_steps_left = _light_use_transitions ? _light_transition_time / LIGHT_TRANSITION_STEP : 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
#if MQTT_SUPPORT
if (forward) lightMQTT();
if (group_forward) lightMQTTGroup();
#endif
// Report color to WS clients (using current brightness setting)
#if WEB_SUPPORT
wsSend(_lightWebSocketOnSend);
#endif
// Delay every communication 100ms to avoid jamming
unsigned char mask = 0;
if (forward) mask += 1;
if (group_forward) mask += 2;
_light_comms_ticker.once_ms(LIGHT_COMMS_DELAY, _lightComms, mask);
#if LIGHT_SAVE_ENABLED #if LIGHT_SAVE_ENABLED
// Delay saving to EEPROM 5 seconds to avoid wearing it out unnecessarily // Delay saving to EEPROM 5 seconds to avoid wearing it out unnecessarily
@ -813,23 +824,13 @@ bool _lightWebSocketOnReceive(const char * key, JsonVariant& value) {
return false; return false;
} }
void _lightWebSocketOnSend(JsonObject& root) {
root["colorVisible"] = 1;
root["mqttGroupColor"] = getSetting("mqttGroupColor");
root["useColor"] = _light_has_color;
root["useWhite"] = _light_use_white;
root["useGamma"] = _light_use_gamma;
root["useTransitions"] = _light_use_transitions;
root["lightTime"] = _light_transition_time;
root["useCSS"] = getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1;
bool useRGB = getSetting("useRGB", LIGHT_USE_RGB).toInt() == 1;
root["useRGB"] = useRGB;
void _lightWebSocketStatus(JsonObject& root) {
if (_light_has_color) { if (_light_has_color) {
if (_light_use_cct) { if (_light_use_cct) {
root["useCCT"] = _light_use_cct; root["useCCT"] = _light_use_cct;
root["mireds"] = _light_mireds; root["mireds"] = _light_mireds;
} }
if (useRGB) {
if (getSetting("useRGB", LIGHT_USE_RGB).toInt() == 1) {
root["rgb"] = lightColor(true); root["rgb"] = lightColor(true);
} else { } else {
root["hsv"] = lightColor(false); root["hsv"] = lightColor(false);
@ -839,7 +840,20 @@ void _lightWebSocketOnSend(JsonObject& root) {
for (unsigned char id=0; id < _light_channel.size(); id++) { for (unsigned char id=0; id < _light_channel.size(); id++) {
channels.add(lightChannel(id)); channels.add(lightChannel(id));
} }
root["brightness"] = lightBrightness();
}
void _lightWebSocketOnSend(JsonObject& root) {
root["colorVisible"] = 1;
root["mqttGroupColor"] = getSetting("mqttGroupColor");
root["useColor"] = _light_has_color;
root["useWhite"] = _light_use_white;
root["useGamma"] = _light_use_gamma;
root["useTransitions"] = _light_use_transitions;
root["useCSS"] = getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1;
root["useRGB"] = getSetting("useRGB", LIGHT_USE_RGB).toInt() == 1;
root["lightTime"] = _light_transition_time;
_lightWebSocketStatus(root);
} }
void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
@ -972,18 +986,18 @@ void _lightAPISetup() {
void _lightInitCommands() { void _lightInitCommands() {
settingsRegisterCommand(F("BRIGHTNESS"), [](Embedis* e) {
terminalRegisterCommand(F("BRIGHTNESS"), [](Embedis* e) {
if (e->argc > 1) { if (e->argc > 1) {
lightBrightness(String(e->argv[1]).toInt()); lightBrightness(String(e->argv[1]).toInt());
lightUpdate(true, true); lightUpdate(true, true);
} }
DEBUG_MSG_P(PSTR("Brightness: %d\n"), lightBrightness()); DEBUG_MSG_P(PSTR("Brightness: %d\n"), lightBrightness());
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
settingsRegisterCommand(F("CHANNEL"), [](Embedis* e) {
terminalRegisterCommand(F("CHANNEL"), [](Embedis* e) {
if (e->argc < 2) { if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
} }
int id = String(e->argv[1]).toInt(); int id = String(e->argv[1]).toInt();
if (e->argc > 2) { if (e->argc > 2) {
@ -992,37 +1006,37 @@ void _lightInitCommands() {
lightUpdate(true, true); lightUpdate(true, true);
} }
DEBUG_MSG_P(PSTR("Channel #%d: %d\n"), id, lightChannel(id)); DEBUG_MSG_P(PSTR("Channel #%d: %d\n"), id, lightChannel(id));
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
settingsRegisterCommand(F("COLOR"), [](Embedis* e) {
terminalRegisterCommand(F("COLOR"), [](Embedis* e) {
if (e->argc > 1) { if (e->argc > 1) {
String color = String(e->argv[1]); String color = String(e->argv[1]);
lightColor(color.c_str()); lightColor(color.c_str());
lightUpdate(true, true); lightUpdate(true, true);
} }
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
settingsRegisterCommand(F("KELVIN"), [](Embedis* e) {
terminalRegisterCommand(F("KELVIN"), [](Embedis* e) {
if (e->argc > 1) { if (e->argc > 1) {
String color = String("K") + String(e->argv[1]); String color = String("K") + String(e->argv[1]);
lightColor(color.c_str()); lightColor(color.c_str());
lightUpdate(true, true); lightUpdate(true, true);
} }
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
settingsRegisterCommand(F("MIRED"), [](Embedis* e) {
terminalRegisterCommand(F("MIRED"), [](Embedis* e) {
if (e->argc > 1) { if (e->argc > 1) {
String color = String("M") + String(e->argv[1]); String color = String("M") + String(e->argv[1]);
lightColor(color.c_str()); lightColor(color.c_str());
lightUpdate(true, true); lightUpdate(true, true);
} }
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
} }


+ 2
- 2
code/espurna/lightfox.ino View File

@ -74,12 +74,12 @@ void _lightfoxWebSocketOnAction(uint32_t client_id, const char * action, JsonObj
void _lightfoxInitCommands() { void _lightfoxInitCommands() {
settingsRegisterCommand(F("LIGHTFOX.LEARN"), [](Embedis* e) {
terminalRegisterCommand(F("LIGHTFOX.LEARN"), [](Embedis* e) {
lightfoxLearn(); lightfoxLearn();
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
settingsRegisterCommand(F("LIGHTFOX.CLEAR"), [](Embedis* e) {
terminalRegisterCommand(F("LIGHTFOX.CLEAR"), [](Embedis* e) {
lightfoxClear(); lightfoxClear();
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });


+ 24
- 1
code/espurna/migrate.ino View File

@ -257,6 +257,17 @@ void migrate() {
setSetting("myDCKIGPIO", 15); setSetting("myDCKIGPIO", 15);
setSetting("relays", 1); setSetting("relays", 1);
#elif defined(LYASI_LIGHT)
setSetting("board", 20);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_MY92XX);
setSetting("myModel", MY92XX_MODEL_MY9291);
setSetting("myChips", 1);
setSetting("myDIGPIO", 4);
setSetting("myDCKIGPIO", 5);
setSetting("relays", 1);
#elif defined(MAGICHOME_LED_CONTROLLER) #elif defined(MAGICHOME_LED_CONTROLLER)
setSetting("board", 21); setSetting("board", 21);
@ -1245,9 +1256,21 @@ void migrate() {
setSetting("relayGPIO", 0, 4); setSetting("relayGPIO", 0, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL); setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(OUKITEL_P1)
setSetting("board", 94);
setSetting("ledGPIO", 0, 0); // Blue LED
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12); // Right outlet
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayGPIO", 1, 15); // Left outlet
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(FOXEL_LIGHTFOX_DUAL) #elif defined(FOXEL_LIGHTFOX_DUAL)
setSetting("board", 92);
setSetting("board", 95);
setSetting("btnRelay", 0, 0); setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1); setSetting("btnRelay", 1, 1);
setSetting("btnRelay", 2, 1); setSetting("btnRelay", 2, 1);


+ 13
- 14
code/espurna/mqtt.ino View File

@ -36,6 +36,8 @@ WiFiClientSecure _mqtt_client_secure;
bool _mqtt_enabled = MQTT_ENABLED; bool _mqtt_enabled = MQTT_ENABLED;
bool _mqtt_use_json = false; bool _mqtt_use_json = false;
unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN; unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
unsigned long _mqtt_last_connection = 0;
bool _mqtt_connecting = false;
unsigned char _mqtt_qos = MQTT_QOS; unsigned char _mqtt_qos = MQTT_QOS;
bool _mqtt_retain = MQTT_RETAIN; bool _mqtt_retain = MQTT_RETAIN;
unsigned long _mqtt_keepalive = MQTT_KEEPALIVE; unsigned long _mqtt_keepalive = MQTT_KEEPALIVE;
@ -48,10 +50,6 @@ char *_mqtt_user = 0;
char *_mqtt_pass = 0; char *_mqtt_pass = 0;
char *_mqtt_will; char *_mqtt_will;
char *_mqtt_clientid; char *_mqtt_clientid;
#if MQTT_SKIP_RETAINED
unsigned long _mqtt_connected_at = 0;
#endif
unsigned long _mqtt_disconnected_at = 0;
std::vector<mqtt_callback_f> _mqtt_callbacks; std::vector<mqtt_callback_f> _mqtt_callbacks;
@ -72,11 +70,11 @@ void _mqttConnect() {
// Do not connect if disabled // Do not connect if disabled
if (!_mqtt_enabled) return; if (!_mqtt_enabled) return;
// Do not connect if already connected
if (_mqtt.connected()) return;
// Do not connect if already connected or still trying to connect
if (_mqtt.connected() || _mqtt_connecting) return;
// Check reconnect interval // Check reconnect interval
if (millis() - _mqtt_disconnected_at < _mqtt_reconnect_delay) return;
if (millis() - _mqtt_last_connection < _mqtt_reconnect_delay) return;
// Increase the reconnect delay // Increase the reconnect delay
_mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP; _mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP;
@ -109,6 +107,7 @@ void _mqttConnect() {
DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d\n"), host, port); DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d\n"), host, port);
#if MQTT_USE_ASYNC #if MQTT_USE_ASYNC
_mqtt_connecting = true;
_mqtt.setServer(host, port); _mqtt.setServer(host, port);
_mqtt.setClientId(_mqtt_clientid); _mqtt.setClientId(_mqtt_clientid);
@ -350,10 +349,10 @@ void _mqttWebSocketOnSend(JsonObject& root) {
void _mqttInitCommands() { void _mqttInitCommands() {
settingsRegisterCommand(F("MQTT.RESET"), [](Embedis* e) {
terminalRegisterCommand(F("MQTT.RESET"), [](Embedis* e) {
_mqttConfigure(); _mqttConfigure();
mqttDisconnect(); mqttDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
} }
@ -397,9 +396,7 @@ void _mqttOnConnect() {
DEBUG_MSG_P(PSTR("[MQTT] Connected!\n")); DEBUG_MSG_P(PSTR("[MQTT] Connected!\n"));
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN; _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
#if MQTT_SKIP_RETAINED
_mqtt_connected_at = millis();
#endif
_mqtt_last_connection = millis();
// Clean subscriptions // Clean subscriptions
mqttUnsubscribeRaw("#"); mqttUnsubscribeRaw("#");
@ -413,7 +410,9 @@ void _mqttOnConnect() {
void _mqttOnDisconnect() { void _mqttOnDisconnect() {
_mqtt_disconnected_at = millis();
// Reset reconnection delay
_mqtt_last_connection = millis();
_mqtt_connecting = false;
DEBUG_MSG_P(PSTR("[MQTT] Disconnected!\n")); DEBUG_MSG_P(PSTR("[MQTT] Disconnected!\n"));
@ -432,7 +431,7 @@ void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
strlcpy(message, (char *) payload, len + 1); strlcpy(message, (char *) payload, len + 1);
#if MQTT_SKIP_RETAINED #if MQTT_SKIP_RETAINED
if (millis() - _mqtt_connected_at < MQTT_SKIP_TIME) {
if (millis() - _mqtt_last_connection < MQTT_SKIP_TIME) {
DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s - SKIPPED\n"), topic, message); DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s - SKIPPED\n"), topic, message);
return; return;
} }


+ 2
- 2
code/espurna/nofuss.ino View File

@ -74,8 +74,8 @@ void _nofussConfigure() {
void _nofussInitCommands() { void _nofussInitCommands() {
settingsRegisterCommand(F("NOFUSS"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
terminalRegisterCommand(F("NOFUSS"), [](Embedis* e) {
terminalOK();
nofussRun(); nofussRun();
}); });


+ 1
- 2
code/espurna/ntp.ino View File

@ -34,7 +34,6 @@ void _ntpWebSocketOnSend(JsonObject& root) {
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;
root["ntpRegion"] = getSetting("ntpRegion", NTP_DST_REGION).toInt(); root["ntpRegion"] = getSetting("ntpRegion", NTP_DST_REGION).toInt();
if (ntpSynced()) root["now"] = now();
} }
#endif #endif
@ -108,7 +107,7 @@ void _ntpLoop() {
static unsigned char last_minute = 60; static unsigned char last_minute = 60;
if (ntpSynced() && (minute() != last_minute)) { if (ntpSynced() && (minute() != last_minute)) {
last_minute = minute(); last_minute = minute();
brokerPublish(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
brokerPublish(BROKER_MSG_TYPE_DATETIME, MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
} }
#endif #endif


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

@ -28,7 +28,7 @@ void _otaLoop() {
// Terminal OTA // Terminal OTA
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#if TERMINAL_SUPPORT
#if TERMINAL_SUPPORT || OTA_MQTT_SUPPORT
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
AsyncClient * _ota_client; AsyncClient * _ota_client;
@ -186,13 +186,18 @@ void _otaFrom(String url) {
} }
#endif // TERMINAL_SUPPORT || OTA_MQTT_SUPPORT
#if TERMINAL_SUPPORT
void _otaInitCommands() { void _otaInitCommands() {
settingsRegisterCommand(F("OTA"), [](Embedis* e) {
terminalRegisterCommand(F("OTA"), [](Embedis* e) {
if (e->argc < 2) { if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
} else { } else {
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
String url = String(e->argv[1]); String url = String(e->argv[1]);
_otaFrom(url); _otaFrom(url);
} }


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

@ -85,6 +85,11 @@ void _relayProviderStatus(unsigned char id, bool status) {
Serial.write(id + 1); Serial.write(id + 1);
Serial.write(status); Serial.write(status);
Serial.write(0xA1 + status + id); Serial.write(0xA1 + status + id);
// The serial init are not full recognized by relais board.
// References: https://github.com/xoseperez/espurna/issues/1519 , https://github.com/xoseperez/espurna/issues/1130
delay(100);
Serial.flush(); Serial.flush();
#endif #endif
@ -179,7 +184,7 @@ void _relayProcess(bool mode) {
// Send to Broker // Send to Broker
#if BROKER_SUPPORT #if BROKER_SUPPORT
brokerPublish(MQTT_TOPIC_RELAY, id, target ? "1" : "0");
brokerPublish(BROKER_MSG_TYPE_STATUS, MQTT_TOPIC_RELAY, id, target ? "1" : "0");
#endif #endif
// Send MQTT // Send MQTT
@ -554,6 +559,9 @@ void _relayBoot() {
void _relayConfigure() { void _relayConfigure() {
for (unsigned int i=0; i<_relays.size(); i++) { for (unsigned int i=0; i<_relays.size(); i++) {
_relays[i].pulse = getSetting("relayPulse", i, RELAY_PULSE_MODE).toInt();
_relays[i].pulse_ms = 1000 * getSetting("relayTime", i, RELAY_PULSE_MODE).toFloat();
if (GPIO_NONE == _relays[i].pin) continue; if (GPIO_NONE == _relays[i].pin) continue;
pinMode(_relays[i].pin, OUTPUT); pinMode(_relays[i].pin, OUTPUT);
@ -564,8 +572,6 @@ void _relayConfigure() {
//set to high to block short opening of relay //set to high to block short opening of relay
digitalWrite(_relays[i].pin, HIGH); digitalWrite(_relays[i].pin, HIGH);
} }
_relays[i].pulse = getSetting("relayPulse", i, RELAY_PULSE_MODE).toInt();
_relays[i].pulse_ms = 1000 * getSetting("relayTime", i, RELAY_PULSE_MODE).toFloat();
} }
} }
@ -582,65 +588,78 @@ bool _relayWebSocketOnReceive(const char * key, JsonVariant& value) {
void _relayWebSocketUpdate(JsonObject& root) { void _relayWebSocketUpdate(JsonObject& root) {
JsonArray& relay = root.createNestedArray("relayStatus"); JsonArray& relay = root.createNestedArray("relayStatus");
for (unsigned char i=0; i<relayCount(); i++) { for (unsigned char i=0; i<relayCount(); i++) {
relay.add(_relays[i].target_status);
relay.add<uint8_t>(_relays[i].target_status);
} }
} }
void _relayWebSocketSendRelay(unsigned char i) {
String _relayFriendlyName(unsigned char i) {
String res = String("GPIO") + String(_relays[i].pin);
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
JsonArray& config = root.createNestedArray("relayConfig");
JsonObject& line = config.createNestedObject();
line["id"] = i;
if (GPIO_NONE == _relays[i].pin) { if (GPIO_NONE == _relays[i].pin) {
#if (RELAY_PROVIDER == RELAY_PROVIDER_LIGHT) #if (RELAY_PROVIDER == RELAY_PROVIDER_LIGHT)
uint8_t physical = _relays.size() - DUMMY_RELAY_COUNT; uint8_t physical = _relays.size() - DUMMY_RELAY_COUNT;
if (i >= physical) { if (i >= physical) {
if (DUMMY_RELAY_COUNT == lightChannels()) { if (DUMMY_RELAY_COUNT == lightChannels()) {
line["gpio"] = String("CH") + String(i-physical);
res = String("CH") + String(i-physical);
} else if (DUMMY_RELAY_COUNT == (lightChannels() + 1u)) { } else if (DUMMY_RELAY_COUNT == (lightChannels() + 1u)) {
if (physical == i) { if (physical == i) {
line["gpio"] = String("Light");
res = String("Light");
} else { } else {
line["gpio"] = String("CH") + String(i-1-physical);
res = String("CH") + String(i-1-physical);
} }
} else { } else {
line["gpio"] = String("Light");
res = String("Light");
} }
} else { } else {
line["gpio"] = String("?");
res = String("?");
} }
#else #else
line["gpio"] = String("SW") + String(i);
res = String("SW") + String(i);
#endif #endif
} else {
line["gpio"] = String("GPIO") + String(_relays[i].pin);
} }
line["type"] = _relays[i].type;
line["reset"] = _relays[i].reset_pin;
line["boot"] = getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt();
line["pulse"] = _relays[i].pulse;
line["pulse_ms"] = _relays[i].pulse_ms / 1000.0;
#if MQTT_SUPPORT
line["group"] = getSetting("mqttGroup", i, "");
line["group_inv"] = getSetting("mqttGroupInv", i, 0).toInt();
line["on_disc"] = getSetting("relayOnDisc", i, 0).toInt();
#endif
String output;
root.printTo(output);
jsonBuffer.clear();
wsSend((char *) output.c_str());
return res;
} }
void _relayWebSocketSendRelays() { void _relayWebSocketSendRelays() {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
JsonObject& relays = root.createNestedObject("relayConfig");
relays["size"] = relayCount();
relays["start"] = 0;
JsonArray& gpio = relays.createNestedArray("gpio");
JsonArray& type = relays.createNestedArray("type");
JsonArray& reset = relays.createNestedArray("reset");
JsonArray& boot = relays.createNestedArray("boot");
JsonArray& pulse = relays.createNestedArray("pulse");
JsonArray& pulse_time = relays.createNestedArray("pulse_time");
#if MQTT_SUPPORT
JsonArray& group = relays.createNestedArray("group");
JsonArray& group_inverse = relays.createNestedArray("group_inv");
JsonArray& on_disconnect = relays.createNestedArray("on_disc");
#endif
for (unsigned char i=0; i<relayCount(); i++) { for (unsigned char i=0; i<relayCount(); i++) {
_relayWebSocketSendRelay(i);
gpio.add(_relayFriendlyName(i));
type.add(_relays[i].type);
reset.add(_relays[i].reset_pin);
boot.add(getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt());
pulse.add(_relays[i].pulse);
pulse_time.add(_relays[i].pulse_ms / 1000.0);
#if MQTT_SUPPORT
group.add(getSetting("mqttGroup", i, ""));
group_inverse.add(getSetting("mqttGroupInv", i, 0).toInt() == 1);
on_disconnect.add(getSetting("relayOnDisc", i, 0).toInt());
#endif
} }
wsSend(root);
} }
void _relayWebSocketOnStart(JsonObject& root) { void _relayWebSocketOnStart(JsonObject& root) {
@ -985,9 +1004,9 @@ void relaySetupMQTT() {
void _relayInitCommands() { void _relayInitCommands() {
settingsRegisterCommand(F("RELAY"), [](Embedis* e) {
terminalRegisterCommand(F("RELAY"), [](Embedis* e) {
if (e->argc < 2) { if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
return; return;
} }
int id = String(e->argv[1]).toInt(); int id = String(e->argv[1]).toInt();
@ -1010,7 +1029,7 @@ void _relayInitCommands() {
DEBUG_MSG_P(PSTR("Pulse time: %d\n"), _relays[id].pulse_ms); DEBUG_MSG_P(PSTR("Pulse time: %d\n"), _relays[id].pulse_ms);
} }
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
} }


+ 33
- 18
code/espurna/rfbridge.ino View File

@ -88,17 +88,31 @@ static bool _rfbToChar(byte * in, char * out, int n = RF_MESSAGE_SIZE) {
#if WEB_SUPPORT #if WEB_SUPPORT
void _rfbWebSocketSendCode(unsigned char id, bool status, const char * code) {
char wsb[192]; // (32 * 5): 46 bytes for json , 116 bytes raw code, reserve
snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%s\"}]}"), id, status ? 1 : 0, code);
wsSend(wsb);
void _rfbWebSocketSendCodeArray(unsigned char start, unsigned char size) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
JsonObject& rfb = root.createNestedObject("rfb");
rfb["size"] = size;
rfb["start"] = start;
JsonArray& on = rfb.createNestedArray("on");
JsonArray& off = rfb.createNestedArray("off");
for (byte id=start; id<start+size; id++) {
on.add(rfbRetrieve(id, true));
off.add(rfbRetrieve(id, false));
}
wsSend(rfb);
}
void _rfbWebSocketSendCode(unsigned char id) {
_rfbWebSocketSendCodeArray(id, 1);
} }
void _rfbWebSocketSendCodes() { void _rfbWebSocketSendCodes() {
for (unsigned char id=0; id<relayCount(); id++) {
_rfbWebSocketSendCode(id, true, rfbRetrieve(id, true).c_str());
_rfbWebSocketSendCode(id, false, rfbRetrieve(id, false).c_str());
}
_rfbWebSocketSendCodeArray(0, relayCount());
} }
void _rfbWebSocketOnSend(JsonObject& root) { void _rfbWebSocketOnSend(JsonObject& root) {
@ -152,9 +166,6 @@ void _rfbLearn() {
} }
#if not RF_SUPPORT
/* /*
From an hexa char array ("A220EE...") to a byte array (half the size) From an hexa char array ("A220EE...") to a byte array (half the size)
*/ */
@ -170,6 +181,8 @@ static int _rfbToArray(const char * in, byte * out, int length = RF_MESSAGE_SIZE
return n; return n;
} }
#if not RF_SUPPORT
void _rfbSendRaw(const byte *message, const unsigned char n = RF_MESSAGE_SIZE) { void _rfbSendRaw(const byte *message, const unsigned char n = RF_MESSAGE_SIZE) {
for (unsigned char j=0; j<n; j++) { for (unsigned char j=0; j<n; j++) {
Serial.write(message[j]); Serial.write(message[j]);
@ -338,7 +351,7 @@ void _rfbDecode() {
// Websocket update // Websocket update
#if WEB_SUPPORT #if WEB_SUPPORT
_rfbWebSocketSendCode(_learnId, _learnStatus, buffer);
_rfbWebSocketSendCode(_learnId);
#endif #endif
} }
@ -556,6 +569,7 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
void _rfbAPISetup() { void _rfbAPISetup() {
#if not RF_SUPPORT
apiRegister(MQTT_TOPIC_RFOUT, apiRegister(MQTT_TOPIC_RFOUT,
[](char * buffer, size_t len) { [](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("OK")); snprintf_P(buffer, len, PSTR("OK"));
@ -564,6 +578,7 @@ void _rfbAPISetup() {
_rfbParseCode((char *) payload); _rfbParseCode((char *) payload);
} }
); );
#endif // RF_SUPPORT
apiRegister(MQTT_TOPIC_RFLEARN, apiRegister(MQTT_TOPIC_RFLEARN,
[](char * buffer, size_t len) { [](char * buffer, size_t len) {
@ -605,10 +620,10 @@ void _rfbAPISetup() {
void _rfbInitCommands() { void _rfbInitCommands() {
settingsRegisterCommand(F("LEARN"), [](Embedis* e) {
terminalRegisterCommand(F("LEARN"), [](Embedis* e) {
if (e->argc < 3) { if (e->argc < 3) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
return; return;
} }
@ -622,14 +637,14 @@ void _rfbInitCommands() {
rfbLearn(id, status == 1); rfbLearn(id, status == 1);
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
settingsRegisterCommand(F("FORGET"), [](Embedis* e) {
terminalRegisterCommand(F("FORGET"), [](Embedis* e) {
if (e->argc < 3) { if (e->argc < 3) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
return; return;
} }
@ -643,7 +658,7 @@ void _rfbInitCommands() {
rfbForget(id, status == 1); rfbForget(id, status == 1);
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });


+ 33
- 18
code/espurna/scheduler.ino View File

@ -21,26 +21,41 @@ bool _schWebSocketOnReceive(const char * key, JsonVariant& value) {
void _schWebSocketOnSend(JsonObject &root){ void _schWebSocketOnSend(JsonObject &root){
if (relayCount() > 0) {
root["schVisible"] = 1;
root["maxSchedules"] = SCHEDULER_MAX_SCHEDULES;
JsonArray &sch = root.createNestedArray("schedule");
for (byte i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
if (!hasSetting("schSwitch", i)) break;
JsonObject &scheduler = sch.createNestedObject();
scheduler["schEnabled"] = getSetting("schEnabled", i, 1).toInt() == 1;
scheduler["schSwitch"] = getSetting("schSwitch", i, 0).toInt();
scheduler["schAction"] = getSetting("schAction", i, 0).toInt();
scheduler["schType"] = getSetting("schType", i, 0).toInt();
scheduler["schHour"] = getSetting("schHour", i, 0).toInt();
scheduler["schMinute"] = getSetting("schMinute", i, 0).toInt();
scheduler["schUTC"] = getSetting("schUTC", i, 0).toInt() == 1;
scheduler["schWDs"] = getSetting("schWDs", i, "");
}
if (!relayCount()) return;
root["schVisible"] = 1;
root["maxSchedules"] = SCHEDULER_MAX_SCHEDULES;
JsonObject &schedules = root.createNestedObject("schedules");
uint8_t size = 0;
JsonArray& enabled = schedules.createNestedArray("schEnabled");
JsonArray& switch_ = schedules.createNestedArray("schSwitch");
JsonArray& action = schedules.createNestedArray("schAction");
JsonArray& type = schedules.createNestedArray("schType");
JsonArray& hour = schedules.createNestedArray("schHour");
JsonArray& minute = schedules.createNestedArray("schMinute");
JsonArray& utc = schedules.createNestedArray("schUTC");
JsonArray& weekdays = schedules.createNestedArray("schWDs");
for (byte i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
if (!hasSetting("schSwitch", i)) break;
++size;
enabled.add<uint8_t>(getSetting("schEnabled", i, 1).toInt() == 1);
utc.add<uint8_t>(getSetting("schUTC", i, 0).toInt() == 1);
switch_.add(getSetting("schSwitch", i, 0).toInt());
action.add(getSetting("schAction", i, 0).toInt());
type.add(getSetting("schType", i, 0).toInt());
hour.add(getSetting("schHour", i, 0).toInt());
minute.add(getSetting("schMinute", i, 0).toInt());
weekdays.add(getSetting("schWDs", i, ""));
} }
schedules["size"] = size;
schedules["start"] = 0;
} }
#endif // WEB_SUPPORT #endif // WEB_SUPPORT


+ 62
- 33
code/espurna/sensor.ino View File

@ -102,6 +102,32 @@ double _magnitudeProcess(unsigned char type, double value) {
#if WEB_SUPPORT #if WEB_SUPPORT
template<typename T>
void _sensorWebSocketMagnitudes(JsonObject& root, T prefix) {
// ws produces flat list <prefix>Magnitudes
String ws_name = String(prefix);
ws_name.concat("Magnitudes");
// config uses <prefix>Magnitude<index> (cut 's')
String conf_name = ws_name.substring(0, ws_name.length() - 1);
JsonObject& list = root.createNestedObject(ws_name);
list["size"] = magnitudeCount();
JsonArray& name = list.createNestedArray("name");
JsonArray& type = list.createNestedArray("type");
JsonArray& index = list.createNestedArray("index");
JsonArray& idx = list.createNestedArray("idx");
for (unsigned char i=0; i<magnitudeCount(); ++i) {
name.add(magnitudeName(i));
type.add(magnitudeType(i));
index.add(magnitudeIndex(i));
idx.add(getSetting(conf_name, i, 0).toInt());
}
}
bool _sensorWebSocketOnReceive(const char * key, JsonVariant& value) { bool _sensorWebSocketOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, "pwr", 3) == 0) return true; if (strncmp(key, "pwr", 3) == 0) return true;
if (strncmp(key, "sns", 3) == 0) return true; if (strncmp(key, "sns", 3) == 0) return true;
@ -118,27 +144,36 @@ void _sensorWebSocketSendData(JsonObject& root) {
bool hasHumidity = false; bool hasHumidity = false;
bool hasMICS = false; bool hasMICS = false;
JsonArray& list = root.createNestedArray("magnitudes");
for (unsigned char i=0; i<_magnitudes.size(); i++) {
JsonObject& magnitudes = root.createNestedObject("magnitudes");
uint8_t size = 0;
JsonArray& index = magnitudes.createNestedArray("index");
JsonArray& type = magnitudes.createNestedArray("type");
JsonArray& value = magnitudes.createNestedArray("value");
JsonArray& units = magnitudes.createNestedArray("units");
JsonArray& error = magnitudes.createNestedArray("error");
JsonArray& description = magnitudes.createNestedArray("description");
for (unsigned char i=0; i<magnitudeCount(); i++) {
sensor_magnitude_t magnitude = _magnitudes[i]; sensor_magnitude_t magnitude = _magnitudes[i];
if (magnitude.type == MAGNITUDE_EVENT) continue; if (magnitude.type == MAGNITUDE_EVENT) continue;
++size;
unsigned char decimals = _magnitudeDecimals(magnitude.type); unsigned char decimals = _magnitudeDecimals(magnitude.type);
dtostrf(magnitude.current, 1-sizeof(buffer), decimals, buffer); dtostrf(magnitude.current, 1-sizeof(buffer), decimals, buffer);
JsonObject& element = list.createNestedObject();
element["index"] = int(magnitude.global);
element["type"] = int(magnitude.type);
element["value"] = String(buffer);
element["units"] = magnitudeUnits(magnitude.type);
element["error"] = magnitude.sensor->error();
index.add<uint8_t>(magnitude.global);
type.add<uint8_t>(magnitude.type);
value.add(buffer);
units.add(magnitudeUnits(magnitude.type));
error.add(magnitude.sensor->error());
if (magnitude.type == MAGNITUDE_ENERGY) { if (magnitude.type == MAGNITUDE_ENERGY) {
if (_sensor_energy_reset_ts.length() == 0) _sensorResetTS(); if (_sensor_energy_reset_ts.length() == 0) _sensorResetTS();
element["description"] = magnitude.sensor->slot(magnitude.local) + String(" (since ") + _sensor_energy_reset_ts + String(")");
description.add(magnitude.sensor->slot(magnitude.local) + String(" (since ") + _sensor_energy_reset_ts + String(")"));
} else { } else {
element["description"] = magnitude.sensor->slot(magnitude.local);
description.add(magnitude.sensor->slot(magnitude.local));
} }
if (magnitude.type == MAGNITUDE_TEMPERATURE) hasTemperature = true; if (magnitude.type == MAGNITUDE_TEMPERATURE) hasTemperature = true;
@ -148,6 +183,8 @@ void _sensorWebSocketSendData(JsonObject& root) {
#endif #endif
} }
magnitudes["size"] = size;
if (hasTemperature) root["temperatureVisible"] = 1; if (hasTemperature) root["temperatureVisible"] = 1;
if (hasHumidity) root["humidityVisible"] = 1; if (hasHumidity) root["humidityVisible"] = 1;
if (hasMICS) root["micsVisible"] = 1; if (hasMICS) root["micsVisible"] = 1;
@ -210,7 +247,7 @@ void _sensorWebSocketStart(JsonObject& root) {
} }
if (_magnitudes.size() > 0) {
if (magnitudeCount()) {
root["snsVisible"] = 1; root["snsVisible"] = 1;
//root["apiRealTime"] = _sensor_realtime; //root["apiRealTime"] = _sensor_realtime;
root["pwrUnits"] = _sensor_power_units; root["pwrUnits"] = _sensor_power_units;
@ -271,7 +308,7 @@ void _sensorAPISetup() {
#if TERMINAL_SUPPORT #if TERMINAL_SUPPORT
void _sensorInitCommands() { void _sensorInitCommands() {
settingsRegisterCommand(F("MAGNITUDES"), [](Embedis* e) {
terminalRegisterCommand(F("MAGNITUDES"), [](Embedis* e) {
for (unsigned char i=0; i<_magnitudes.size(); i++) { for (unsigned char i=0; i<_magnitudes.size(); i++) {
sensor_magnitude_t magnitude = _magnitudes[i]; sensor_magnitude_t magnitude = _magnitudes[i];
DEBUG_MSG_P(PSTR("[SENSOR] * %2d: %s @ %s (%s/%d)\n"), DEBUG_MSG_P(PSTR("[SENSOR] * %2d: %s @ %s (%s/%d)\n"),
@ -282,33 +319,33 @@ void _sensorInitCommands() {
magnitude.global magnitude.global
); );
} }
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
#if PZEM004T_SUPPORT #if PZEM004T_SUPPORT
settingsRegisterCommand(F("PZ.ADDRESS"), [](Embedis* e) {
terminalRegisterCommand(F("PZ.ADDRESS"), [](Embedis* e) {
if (e->argc == 1) { if (e->argc == 1) {
DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n")); DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
unsigned char dev_count = pzem004t_sensor->getAddressesCount(); unsigned char dev_count = pzem004t_sensor->getAddressesCount();
for(unsigned char dev = 0; dev < dev_count; dev++) { for(unsigned char dev = 0; dev < dev_count; dev++) {
DEBUG_MSG_P(PSTR("Device %d/%s\n"), dev, pzem004t_sensor->getAddress(dev).c_str()); DEBUG_MSG_P(PSTR("Device %d/%s\n"), dev, pzem004t_sensor->getAddress(dev).c_str());
} }
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
} else if(e->argc == 2) { } else if(e->argc == 2) {
IPAddress addr; IPAddress addr;
if (addr.fromString(String(e->argv[1]))) { if (addr.fromString(String(e->argv[1]))) {
if(pzem004t_sensor->setDeviceAddress(&addr)) { if(pzem004t_sensor->setDeviceAddress(&addr)) {
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
} }
} else { } else {
DEBUG_MSG_P(PSTR("-ERROR: Invalid address argument\n"));
terminalError(F("Invalid address argument"));
} }
} else { } else {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
} }
}); });
settingsRegisterCommand(F("PZ.RESET"), [](Embedis* e) {
terminalRegisterCommand(F("PZ.RESET"), [](Embedis* e) {
if(e->argc > 2) { if(e->argc > 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
} else { } else {
unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0; unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0;
unsigned char limit = e->argc == 2 ? init +1 : pzem004t_sensor->getAddressesCount(); unsigned char limit = e->argc == 2 ? init +1 : pzem004t_sensor->getAddressesCount();
@ -318,12 +355,12 @@ void _sensorInitCommands() {
setSetting("pzEneTotal", dev, offset); setSetting("pzEneTotal", dev, offset);
DEBUG_MSG_P(PSTR("Device %d/%s - Offset: %s\n"), dev, pzem004t_sensor->getAddress(dev).c_str(), String(offset).c_str()); DEBUG_MSG_P(PSTR("Device %d/%s - Offset: %s\n"), dev, pzem004t_sensor->getAddress(dev).c_str(), String(offset).c_str());
} }
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
} }
}); });
settingsRegisterCommand(F("PZ.VALUE"), [](Embedis* e) {
terminalRegisterCommand(F("PZ.VALUE"), [](Embedis* e) {
if(e->argc > 2) { if(e->argc > 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
} else { } else {
unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0; unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0;
unsigned char limit = e->argc == 2 ? init +1 : pzem004t_sensor->getAddressesCount(); unsigned char limit = e->argc == 2 ? init +1 : pzem004t_sensor->getAddressesCount();
@ -337,7 +374,7 @@ void _sensorInitCommands() {
String(pzem004t_sensor->value(dev * PZ_MAGNITUDE_POWER_ACTIVE_INDEX)).c_str(), String(pzem004t_sensor->value(dev * PZ_MAGNITUDE_POWER_ACTIVE_INDEX)).c_str(),
String(pzem004t_sensor->value(dev * PZ_MAGNITUDE_ENERGY_INDEX)).c_str()); String(pzem004t_sensor->value(dev * PZ_MAGNITUDE_ENERGY_INDEX)).c_str());
} }
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
} }
}); });
#endif #endif
@ -1172,7 +1209,7 @@ void _sensorReport(unsigned char index, double value) {
dtostrf(value, 1-sizeof(buffer), decimals, buffer); dtostrf(value, 1-sizeof(buffer), decimals, buffer);
#if BROKER_SUPPORT #if BROKER_SUPPORT
brokerPublish(magnitudeTopic(magnitude.type).c_str(), magnitude.local, buffer);
brokerPublish(BROKER_MSG_TYPE_SENSOR ,magnitudeTopic(magnitude.type).c_str(), magnitude.local, buffer);
#endif #endif
#if MQTT_SUPPORT #if MQTT_SUPPORT
@ -1191,14 +1228,6 @@ void _sensorReport(unsigned char index, double value) {
#endif // MQTT_SUPPORT #endif // MQTT_SUPPORT
#if INFLUXDB_SUPPORT
if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) {
idbSend(magnitudeTopic(magnitude.type).c_str(), magnitude.global, buffer);
} else {
idbSend(magnitudeTopic(magnitude.type).c_str(), buffer);
}
#endif // INFLUXDB_SUPPORT
#if THINGSPEAK_SUPPORT #if THINGSPEAK_SUPPORT
tspkEnqueueMeasurement(index, buffer); tspkEnqueueMeasurement(index, buffer);
#endif #endif


+ 7
- 1
code/espurna/sensors/PMSX003Sensor.h View File

@ -22,6 +22,7 @@
#define PMS_TYPE_X003_9 1 #define PMS_TYPE_X003_9 1
#define PMS_TYPE_5003T 2 #define PMS_TYPE_5003T 2
#define PMS_TYPE_5003ST 3 #define PMS_TYPE_5003ST 3
#define PMS_TYPE_5003S 4
// Sensor type specified data // Sensor type specified data
#define PMS_SLOT_MAX 4 #define PMS_SLOT_MAX 4
@ -35,7 +36,8 @@ const static struct {
{"PMSX003", 13, 3, {MAGNITUDE_PM1dot0, MAGNITUDE_PM2dot5, MAGNITUDE_PM10}}, {"PMSX003", 13, 3, {MAGNITUDE_PM1dot0, MAGNITUDE_PM2dot5, MAGNITUDE_PM10}},
{"PMSX003_9", 9, 3, {MAGNITUDE_PM1dot0, MAGNITUDE_PM2dot5, MAGNITUDE_PM10}}, {"PMSX003_9", 9, 3, {MAGNITUDE_PM1dot0, MAGNITUDE_PM2dot5, MAGNITUDE_PM10}},
{"PMS5003T", 13, 3, {MAGNITUDE_PM2dot5, MAGNITUDE_TEMPERATURE, MAGNITUDE_HUMIDITY}}, {"PMS5003T", 13, 3, {MAGNITUDE_PM2dot5, MAGNITUDE_TEMPERATURE, MAGNITUDE_HUMIDITY}},
{"PMS5003ST", 17, 4, {MAGNITUDE_PM2dot5, MAGNITUDE_TEMPERATURE, MAGNITUDE_HUMIDITY, MAGNITUDE_HCHO}}
{"PMS5003ST", 17, 4, {MAGNITUDE_PM2dot5, MAGNITUDE_TEMPERATURE, MAGNITUDE_HUMIDITY, MAGNITUDE_HCHO}},
{"PMS5003S", 13, 3, {MAGNITUDE_PM2dot5, MAGNITUDE_PM10, MAGNITUDE_HCHO}},
}; };
// [MAGIC][LEN][DATA9|13|17][SUM] // [MAGIC][LEN][DATA9|13|17][SUM]
@ -306,6 +308,10 @@ class PMSX003Sensor : public BaseSensor, PMSX003 {
_slot_values[1] = (double)data[13] / 10; _slot_values[1] = (double)data[13] / 10;
_slot_values[2] = (double)data[14] / 10; _slot_values[2] = (double)data[14] / 10;
_slot_values[3] = (double)data[12] / 1000; _slot_values[3] = (double)data[12] / 1000;
} else if (_type == PMS_TYPE_5003S) {
_slot_values[0] = data[4];
_slot_values[1] = data[5];
_slot_values[2] = (double)data[12] / 1000;
} else if (_type == PMS_TYPE_5003T) { } else if (_type == PMS_TYPE_5003T) {
_slot_values[0] = data[4]; _slot_values[0] = data[4];
_slot_values[1] = (double)data[10] / 10; _slot_values[1] = (double)data[10] / 10;


+ 7
- 286
code/espurna/settings.ino View File

@ -2,25 +2,12 @@
SETTINGS MODULE SETTINGS MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/ */
#include <EEPROM_Rotate.h>
#include <vector> #include <vector>
#include "libs/EmbedisWrap.h" #include "libs/EmbedisWrap.h"
#include <Stream.h>
#include "libs/StreamInjector.h"
StreamInjector _serial = StreamInjector(TERMINAL_BUFFER_SIZE);
EmbedisWrap embedis(_serial, TERMINAL_BUFFER_SIZE);
#if TERMINAL_SUPPORT
#if SERIAL_RX_ENABLED
char _serial_rx_buffer[TERMINAL_BUFFER_SIZE];
static unsigned char _serial_rx_pointer = 0;
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Reverse engineering EEPROM storage format // Reverse engineering EEPROM storage format
@ -106,214 +93,6 @@ std::vector<String> _settingsKeys() {
return keys; return keys;
} }
// -----------------------------------------------------------------------------
// Commands
// -----------------------------------------------------------------------------
void _settingsHelpCommand() {
// Get sorted list of commands
std::vector<String> commands;
unsigned char size = embedis.getCommandCount();
for (unsigned int i=0; i<size; i++) {
String command = embedis.getCommandName(i);
bool inserted = false;
for (unsigned char j=0; j<commands.size(); j++) {
// Check if we have to insert it before the current element
if (commands[j].compareTo(command) > 0) {
commands.insert(commands.begin() + j, command);
inserted = true;
break;
}
}
// If we could not insert it, just push it at the end
if (!inserted) commands.push_back(command);
}
// Output the list
DEBUG_MSG_P(PSTR("Available commands:\n"));
for (unsigned char i=0; i<commands.size(); i++) {
DEBUG_MSG_P(PSTR("> %s\n"), (commands[i]).c_str());
}
}
void _settingsKeysCommand() {
// Get sorted list of keys
std::vector<String> keys = _settingsKeys();
// Write key-values
DEBUG_MSG_P(PSTR("Current settings:\n"));
for (unsigned int i=0; i<keys.size(); i++) {
String value = getSetting(keys[i]);
DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), (keys[i]).c_str(), value.c_str());
}
unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize();
DEBUG_MSG_P(PSTR("Number of keys: %d\n"), keys.size());
DEBUG_MSG_P(PSTR("Current EEPROM sector: %u\n"), EEPROMr.current());
DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
}
void _settingsFactoryResetCommand() {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROMr.write(i, 0xFF);
}
EEPROMr.commit();
}
void _settingsInitCommands() {
#if DEBUG_SUPPORT
settingsRegisterCommand(F("CRASH"), [](Embedis* e) {
crashDump();
crashClear();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("COMMANDS"), [](Embedis* e) {
_settingsHelpCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
resetReason(CUSTOM_RESET_TERMINAL);
_eepromCommit();
ESP.eraseConfig();
*((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494
});
#if I2C_SUPPORT
settingsRegisterCommand(F("I2C.SCAN"), [](Embedis* e) {
i2cScan();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("I2C.CLEAR"), [](Embedis* e) {
i2cClearBus();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("FACTORY.RESET"), [](Embedis* e) {
_settingsFactoryResetCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("GPIO"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
int pin = String(e->argv[1]).toInt();
//if (!gpioValid(pin)) {
// DEBUG_MSG_P(PSTR("-ERROR: Invalid GPIO\n"));
// return;
//}
if (e->argc > 2) {
bool state = String(e->argv[2]).toInt() == 1;
digitalWrite(pin, state);
}
DEBUG_MSG_P(PSTR("GPIO %d is %s\n"), pin, digitalRead(pin) == HIGH ? "HIGH" : "LOW");
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HEAP"), [](Embedis* e) {
infoMemory("Heap", getInitialFreeHeap(), getFreeHeap());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("STACK"), [](Embedis* e) {
infoMemory("Stack", 4096, getFreeStack());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HELP"), [](Embedis* e) {
_settingsHelpCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("INFO"), [](Embedis* e) {
info();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("KEYS"), [](Embedis* e) {
_settingsKeysCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("GET"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
for (unsigned char i = 1; i < e->argc; i++) {
String key = String(e->argv[i]);
String value;
if (!Embedis::get(key, value)) {
DEBUG_MSG_P(PSTR("> %s =>\n"), key.c_str());
continue;
}
DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), key.c_str(), value.c_str());
}
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("RELOAD"), [](Embedis* e) {
espurnaReload();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("RESET"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
settingsRegisterCommand(F("RESET.SAFE"), [](Embedis* e) {
EEPROMr.write(EEPROM_CRASH_COUNTER, SYSTEM_CHECK_MAX);
DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
settingsRegisterCommand(F("UPTIME"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("CONFIG"), [](Embedis* e) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
settingsGetJson(root);
String output;
root.printTo(output);
DEBUG_MSG(output.c_str());
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
#if not SETTINGS_AUTOSAVE
settingsRegisterCommand(F("SAVE"), [](Embedis* e) {
eepromCommit();
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
#endif
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Key-value API // Key-value API
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -369,21 +148,16 @@ void saveSettings() {
} }
void resetSettings() { void resetSettings() {
_settingsFactoryResetCommand();
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROMr.write(i, 0xFF);
}
EEPROMr.commit();
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Settings
// API
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void settingsInject(void *data, size_t len) {
_serial.inject((char *) data, len);
}
Stream & settingsSerial() {
return (Stream &) _serial;
}
size_t settingsMaxSize() { size_t settingsMaxSize() {
size_t size = EEPROM_SIZE; size_t size = EEPROM_SIZE;
if (size > SPI_FLASH_SEC_SIZE) size = SPI_FLASH_SEC_SIZE; if (size > SPI_FLASH_SEC_SIZE) size = SPI_FLASH_SEC_SIZE;
@ -437,25 +211,12 @@ void settingsGetJson(JsonObject& root) {
} }
void settingsRegisterCommand(const String& name, void (*call)(Embedis*)) {
Embedis::command(name, call);
};
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Initialization // Initialization
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void settingsSetup() { void settingsSetup() {
_serial.callback([](uint8_t ch) {
#if TELNET_SUPPORT
telnetWrite(ch);
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.write(ch);
#endif
});
Embedis::dictionary( F("EEPROM"), Embedis::dictionary( F("EEPROM"),
SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE,
[](size_t pos) -> char { return EEPROMr.read(pos); }, [](size_t pos) -> char { return EEPROMr.read(pos); },
@ -467,44 +228,4 @@ void settingsSetup() {
#endif #endif
); );
_settingsInitCommands();
#if TERMINAL_SUPPORT
#if SERIAL_RX_ENABLED
SERIAL_RX_PORT.begin(SERIAL_RX_BAUDRATE);
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
// Register loop
espurnaRegisterLoop(settingsLoop);
}
void settingsLoop() {
#if TERMINAL_SUPPORT
#if DEBUG_SERIAL_SUPPORT
while (DEBUG_PORT.available()) {
_serial.inject(DEBUG_PORT.read());
}
#endif
embedis.process();
#if SERIAL_RX_ENABLED
while (SERIAL_RX_PORT.available() > 0) {
char rc = Serial.read();
_serial_rx_buffer[_serial_rx_pointer++] = rc;
if ((_serial_rx_pointer == TERMINAL_BUFFER_SIZE) || (rc == 10)) {
settingsInject(_serial_rx_buffer, (size_t) _serial_rx_pointer);
_serial_rx_pointer = 0;
}
}
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
}
}

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


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


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


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


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


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


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


+ 35
- 9
code/espurna/system.ino View File

@ -12,6 +12,8 @@ Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
unsigned long _loop_delay = 0; unsigned long _loop_delay = 0;
bool _system_send_heartbeat = false; bool _system_send_heartbeat = false;
unsigned char _heartbeat_mode = HEARTBEAT_MODE;
unsigned long _heartbeat_interval = HEARTBEAT_INTERVAL;
// Calculated load average 0 to 100; // Calculated load average 0 to 100;
unsigned short int _load_average = 100; unsigned short int _load_average = 100;
@ -66,6 +68,10 @@ void systemSendHeartbeat() {
_system_send_heartbeat = true; _system_send_heartbeat = true;
} }
bool systemGetHeartbeat() {
return _system_send_heartbeat;
}
unsigned long systemLoopDelay() { unsigned long systemLoopDelay() {
return _loop_delay; return _loop_delay;
} }
@ -75,6 +81,17 @@ unsigned long systemLoadAverage() {
return _load_average; return _load_average;
} }
void _systemSetupHeartbeat() {
_heartbeat_mode = getSetting("hbMode", HEARTBEAT_MODE).toInt();
_heartbeat_interval = getSetting("hbInterval", HEARTBEAT_INTERVAL).toInt();
}
#if WEB_SUPPORT
bool _systemWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "hb", 2) == 0);
}
#endif
void systemLoop() { void systemLoop() {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -97,19 +114,21 @@ void systemLoop() {
// Heartbeat // Heartbeat
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
#if HEARTBEAT_MODE == HEARTBEAT_ONCE
if (_system_send_heartbeat) {
_system_send_heartbeat = false;
heartbeat();
}
#elif HEARTBEAT_MODE == HEARTBEAT_REPEAT
if (_system_send_heartbeat && _heartbeat_mode == HEARTBEAT_ONCE) {
heartbeat();
_system_send_heartbeat = false;
} else if (_heartbeat_mode == HEARTBEAT_REPEAT || _heartbeat_mode == HEARTBEAT_REPEAT_STATUS) {
static unsigned long last_hbeat = 0; static unsigned long last_hbeat = 0;
if (_system_send_heartbeat || (last_hbeat == 0) || (millis() - last_hbeat > HEARTBEAT_INTERVAL)) {
_system_send_heartbeat = false;
#if NTP_SUPPORT
if ((_system_send_heartbeat && ntpSynced()) || (millis() - last_hbeat > _heartbeat_interval * 1000)) {
#else
if (_system_send_heartbeat || (millis() - last_hbeat > _heartbeat_interval * 1000)) {
#endif
last_hbeat = millis(); last_hbeat = millis();
heartbeat(); heartbeat();
_system_send_heartbeat = false;
} }
#endif // HEARTBEAT_MODE == HEARTBEAT_REPEAT
}
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Load Average calculation // Load Average calculation
@ -169,6 +188,10 @@ void systemSetup() {
systemCheck(false); systemCheck(false);
#endif #endif
#if WEB_SUPPORT
wsOnReceiveRegister(_systemWebSocketOnReceive);
#endif
// Init device-specific hardware // Init device-specific hardware
_systemSetupSpecificHardware(); _systemSetupSpecificHardware();
@ -179,4 +202,7 @@ void systemSetup() {
// Register Loop // Register Loop
espurnaRegisterLoop(systemLoop); espurnaRegisterLoop(systemLoop);
// Cache Heartbeat values
_systemSetupHeartbeat();
} }

+ 11
- 5
code/espurna/telnet.ino View File

@ -45,14 +45,14 @@ void _telnetDisconnect(unsigned char clientId) {
DEBUG_MSG_P(PSTR("[TELNET] Client #%d disconnected\n"), clientId); DEBUG_MSG_P(PSTR("[TELNET] Client #%d disconnected\n"), clientId);
} }
bool _telnetWrite(unsigned char clientId, void *data, size_t len) {
bool _telnetWrite(unsigned char clientId, const char *data, size_t len) {
if (_telnetClients[clientId] && _telnetClients[clientId]->connected()) { if (_telnetClients[clientId] && _telnetClients[clientId]->connected()) {
return (_telnetClients[clientId]->write((const char*) data, len) > 0);
return (_telnetClients[clientId]->write(data, len) > 0);
} }
return false; return false;
} }
unsigned char _telnetWrite(void *data, size_t len) {
unsigned char _telnetWrite(const char *data, size_t len) {
unsigned char count = 0; unsigned char count = 0;
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) { for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
// Do not send broadcast messages to unauthenticated clients // Do not send broadcast messages to unauthenticated clients
@ -65,8 +65,12 @@ unsigned char _telnetWrite(void *data, size_t len) {
return count; return count;
} }
unsigned char _telnetWrite(const char *data) {
return _telnetWrite(data, strlen(data));
}
bool _telnetWrite(unsigned char clientId, const char * message) { bool _telnetWrite(unsigned char clientId, const char * message) {
return _telnetWrite(clientId, (void *) message, strlen(message));
return _telnetWrite(clientId, message, strlen(message));
} }
void _telnetData(unsigned char clientId, void *data, size_t len) { void _telnetData(unsigned char clientId, void *data, size_t len) {
@ -113,7 +117,9 @@ void _telnetData(unsigned char clientId, void *data, size_t len) {
} }
// Inject command // Inject command
settingsInject(data, len);
#if TERMINAL_SUPPORT
terminalInject(data, len);
#endif
} }


+ 283
- 0
code/espurna/terminal.ino View File

@ -0,0 +1,283 @@
/*
TERMINAL MODULE
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if TERMINAL_SUPPORT
#include <vector>
#include "libs/EmbedisWrap.h"
#include <Stream.h>
#include "libs/StreamInjector.h"
StreamInjector _serial = StreamInjector(TERMINAL_BUFFER_SIZE);
EmbedisWrap embedis(_serial, TERMINAL_BUFFER_SIZE);
#if SERIAL_RX_ENABLED
char _serial_rx_buffer[TERMINAL_BUFFER_SIZE];
static unsigned char _serial_rx_pointer = 0;
#endif // SERIAL_RX_ENABLED
// -----------------------------------------------------------------------------
// Commands
// -----------------------------------------------------------------------------
void _terminalHelpCommand() {
// Get sorted list of commands
std::vector<String> commands;
unsigned char size = embedis.getCommandCount();
for (unsigned int i=0; i<size; i++) {
String command = embedis.getCommandName(i);
bool inserted = false;
for (unsigned char j=0; j<commands.size(); j++) {
// Check if we have to insert it before the current element
if (commands[j].compareTo(command) > 0) {
commands.insert(commands.begin() + j, command);
inserted = true;
break;
}
}
// If we could not insert it, just push it at the end
if (!inserted) commands.push_back(command);
}
// Output the list
DEBUG_MSG_P(PSTR("Available commands:\n"));
for (unsigned char i=0; i<commands.size(); i++) {
DEBUG_MSG_P(PSTR("> %s\n"), (commands[i]).c_str());
}
}
void _terminalKeysCommand() {
// Get sorted list of keys
std::vector<String> keys = _settingsKeys();
// Write key-values
DEBUG_MSG_P(PSTR("Current settings:\n"));
for (unsigned int i=0; i<keys.size(); i++) {
String value = getSetting(keys[i]);
DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), (keys[i]).c_str(), value.c_str());
}
unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize();
DEBUG_MSG_P(PSTR("Number of keys: %d\n"), keys.size());
DEBUG_MSG_P(PSTR("Current EEPROM sector: %u\n"), EEPROMr.current());
DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
}
void _terminalInitCommand() {
#if DEBUG_SUPPORT
terminalRegisterCommand(F("CRASH"), [](Embedis* e) {
crashDump();
crashClear();
terminalOK();
});
#endif
terminalRegisterCommand(F("COMMANDS"), [](Embedis* e) {
_terminalHelpCommand();
terminalOK();
});
terminalRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) {
terminalOK();
resetReason(CUSTOM_RESET_TERMINAL);
_eepromCommit();
ESP.eraseConfig();
*((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494
});
terminalRegisterCommand(F("FACTORY.RESET"), [](Embedis* e) {
resetSettings();
terminalOK();
});
terminalRegisterCommand(F("GPIO"), [](Embedis* e) {
if (e->argc < 2) {
terminalError(F("Wrong arguments"));
return;
}
int pin = String(e->argv[1]).toInt();
//if (!gpioValid(pin)) {
// terminalError(F("Invalid GPIO"));
// return;
//}
if (e->argc > 2) {
bool state = String(e->argv[2]).toInt() == 1;
digitalWrite(pin, state);
}
DEBUG_MSG_P(PSTR("GPIO %d is %s\n"), pin, digitalRead(pin) == HIGH ? "HIGH" : "LOW");
terminalOK();
});
terminalRegisterCommand(F("HEAP"), [](Embedis* e) {
infoMemory("Heap", getInitialFreeHeap(), getFreeHeap());
terminalOK();
});
terminalRegisterCommand(F("STACK"), [](Embedis* e) {
infoMemory("Stack", 4096, getFreeStack());
terminalOK();
});
terminalRegisterCommand(F("HELP"), [](Embedis* e) {
_terminalHelpCommand();
terminalOK();
});
terminalRegisterCommand(F("INFO"), [](Embedis* e) {
info();
terminalOK();
});
terminalRegisterCommand(F("KEYS"), [](Embedis* e) {
_terminalKeysCommand();
terminalOK();
});
terminalRegisterCommand(F("GET"), [](Embedis* e) {
if (e->argc < 2) {
terminalError(F("Wrong arguments"));
return;
}
for (unsigned char i = 1; i < e->argc; i++) {
String key = String(e->argv[i]);
String value;
if (!Embedis::get(key, value)) {
DEBUG_MSG_P(PSTR("> %s =>\n"), key.c_str());
continue;
}
DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), key.c_str(), value.c_str());
}
terminalOK();
});
terminalRegisterCommand(F("RELOAD"), [](Embedis* e) {
espurnaReload();
terminalOK();
});
terminalRegisterCommand(F("RESET"), [](Embedis* e) {
terminalOK();
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
terminalRegisterCommand(F("RESET.SAFE"), [](Embedis* e) {
EEPROMr.write(EEPROM_CRASH_COUNTER, SYSTEM_CHECK_MAX);
terminalOK();
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
terminalRegisterCommand(F("UPTIME"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime());
terminalOK();
});
terminalRegisterCommand(F("CONFIG"), [](Embedis* e) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
settingsGetJson(root);
String output;
root.printTo(output);
DEBUG_MSG(output.c_str());
});
#if not SETTINGS_AUTOSAVE
terminalRegisterCommand(F("SAVE"), [](Embedis* e) {
eepromCommit();
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
#endif
}
void _terminalLoop() {
#if DEBUG_SERIAL_SUPPORT
while (DEBUG_PORT.available()) {
_serial.inject(DEBUG_PORT.read());
}
#endif
embedis.process();
#if SERIAL_RX_ENABLED
while (SERIAL_RX_PORT.available() > 0) {
char rc = Serial.read();
_serial_rx_buffer[_serial_rx_pointer++] = rc;
if ((_serial_rx_pointer == TERMINAL_BUFFER_SIZE) || (rc == 10)) {
terminalInject(_serial_rx_buffer, (size_t) _serial_rx_pointer);
_serial_rx_pointer = 0;
}
}
#endif // SERIAL_RX_ENABLED
}
// -----------------------------------------------------------------------------
// Pubic API
// -----------------------------------------------------------------------------
void terminalInject(void *data, size_t len) {
_serial.inject((char *) data, len);
}
Stream & terminalSerial() {
return (Stream &) _serial;
}
void terminalRegisterCommand(const String& name, void (*call)(Embedis*)) {
Embedis::command(name, call);
};
void terminalOK() {
DEBUG_MSG_P(PSTR("+OK\n"));
}
void terminalError(const String& error) {
DEBUG_MSG_P(PSTR("-ERROR: %s\n"), error.c_str());
}
void terminalSetup() {
_serial.callback([](uint8_t ch) {
#if TELNET_SUPPORT
telnetWrite(ch);
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.write(ch);
#endif
});
_terminalInitCommand();
#if SERIAL_RX_ENABLED
SERIAL_RX_PORT.begin(SERIAL_RX_BAUDRATE);
#endif // SERIAL_RX_ENABLED
// Register loop
espurnaRegisterLoop(_terminalLoop);
}
#endif // TERMINAL_SUPPORT

+ 13
- 11
code/espurna/thinkspeak.ino View File

@ -36,11 +36,20 @@ unsigned char _tspk_tries = 0;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#if BROKER_SUPPORT #if BROKER_SUPPORT
void _tspkBrokerCallback(const char * topic, unsigned char id, const char * payload) {
if (strcmp(MQTT_TOPIC_RELAY, topic) == 0) {
void _tspkBrokerCallback(const unsigned char type, const char * topic, unsigned char id, const char * payload) {
// Process status messages
if (BROKER_MSG_TYPE_STATUS == type) {
tspkEnqueueRelay(id, (char *) payload); tspkEnqueueRelay(id, (char *) payload);
tspkFlush(); tspkFlush();
} }
// Porcess sensor messages
if (BROKER_MSG_TYPE_SENSOR == type) {
//tspkEnqueueMeasurement(id, (char *) payload);
//tspkFlush();
}
} }
#endif // BROKER_SUPPORT #endif // BROKER_SUPPORT
@ -66,15 +75,8 @@ void _tspkWebSocketOnSend(JsonObject& root) {
if (relayCount() > 0) visible = 1; if (relayCount() > 0) visible = 1;
#if SENSOR_SUPPORT #if SENSOR_SUPPORT
JsonArray& list = root.createNestedArray("tspkMagnitudes");
for (byte i=0; i<magnitudeCount(); i++) {
JsonObject& element = list.createNestedObject();
element["name"] = magnitudeName(i);
element["type"] = magnitudeType(i);
element["index"] = magnitudeIndex(i);
element["idx"] = getSetting("tspkMagnitude", i, 0).toInt();
}
if (magnitudeCount() > 0) visible = 1;
_sensorWebSocketMagnitudes(root, "tspk");
visible = visible || (magnitudeCount() > 0);
#endif #endif
root["tspkVisible"] = visible; root["tspkVisible"] = visible;


+ 23
- 7
code/espurna/utils.ino View File

@ -58,6 +58,14 @@ String getCoreRevision() {
#endif #endif
} }
unsigned char getHeartbeatMode() {
return getSetting("hbMode", HEARTBEAT_MODE).toInt();
}
unsigned char getHeartbeatInterval() {
return getSetting("hbInterval", HEARTBEAT_INTERVAL).toInt();
}
// WTF // WTF
// Calling ESP.getFreeHeap() is making the system crash on a specific // Calling ESP.getFreeHeap() is making the system crash on a specific
// AiLight bulb, but anywhere else... // AiLight bulb, but anywhere else...
@ -135,8 +143,6 @@ unsigned long getUptime() {
} }
#if HEARTBEAT_MODE != HEARTBEAT_NONE
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Heartbeat helper // Heartbeat helper
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -158,7 +164,8 @@ namespace Heartbeat {
Version = 1 << 14, Version = 1 << 14,
Board = 1 << 15, Board = 1 << 15,
Loadavg = 1 << 16, Loadavg = 1 << 16,
Interval = 1 << 17
Interval = 1 << 17,
Description = 1 << 18
}; };
constexpr uint32_t defaultValue() { constexpr uint32_t defaultValue() {
@ -174,6 +181,7 @@ namespace Heartbeat {
(Relay * (HEARTBEAT_REPORT_RELAY)) | \ (Relay * (HEARTBEAT_REPORT_RELAY)) | \
(Light * (HEARTBEAT_REPORT_LIGHT)) | \ (Light * (HEARTBEAT_REPORT_LIGHT)) | \
(Hostname * (HEARTBEAT_REPORT_HOSTNAME)) | \ (Hostname * (HEARTBEAT_REPORT_HOSTNAME)) | \
(Description * (HEARTBEAT_REPORT_DESCRIPTION)) | \
(App * (HEARTBEAT_REPORT_APP)) | \ (App * (HEARTBEAT_REPORT_APP)) | \
(Version * (HEARTBEAT_REPORT_VERSION)) | \ (Version * (HEARTBEAT_REPORT_VERSION)) | \
(Board * (HEARTBEAT_REPORT_BOARD)) | \ (Board * (HEARTBEAT_REPORT_BOARD)) | \
@ -196,6 +204,7 @@ void heartbeat() {
unsigned int free_heap = getFreeHeap(); unsigned int free_heap = getFreeHeap();
#if MQTT_SUPPORT #if MQTT_SUPPORT
unsigned char _heartbeat_mode = getHeartbeatMode();
bool serial = !mqttConnected(); bool serial = !mqttConnected();
#else #else
bool serial = true; bool serial = true;
@ -224,9 +233,9 @@ void heartbeat() {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
#if MQTT_SUPPORT #if MQTT_SUPPORT
if (!serial) {
if (!serial && (_heartbeat_mode == HEARTBEAT_REPEAT || systemGetHeartbeat())) {
if (hb_cfg & Heartbeat::Interval) if (hb_cfg & Heartbeat::Interval)
mqttSend(MQTT_TOPIC_INTERVAL, String(HEARTBEAT_INTERVAL / 1000).c_str());
mqttSend(MQTT_TOPIC_INTERVAL, String(getHeartbeatInterval() / 1000).c_str());
if (hb_cfg & Heartbeat::App) if (hb_cfg & Heartbeat::App)
mqttSend(MQTT_TOPIC_APP, APP_NAME); mqttSend(MQTT_TOPIC_APP, APP_NAME);
@ -240,6 +249,12 @@ void heartbeat() {
if (hb_cfg & Heartbeat::Hostname) if (hb_cfg & Heartbeat::Hostname)
mqttSend(MQTT_TOPIC_HOSTNAME, getSetting("hostname", getIdentifier()).c_str()); mqttSend(MQTT_TOPIC_HOSTNAME, getSetting("hostname", getIdentifier()).c_str());
if (hb_cfg & Heartbeat::Description) {
if (hasSetting("desc")) {
mqttSend(MQTT_TOPIC_DESCRIPTION, getSetting("desc").c_str());
}
}
if (hb_cfg & Heartbeat::Ssid) if (hb_cfg & Heartbeat::Ssid)
mqttSend(MQTT_TOPIC_SSID, WiFi.SSID().c_str()); mqttSend(MQTT_TOPIC_SSID, WiFi.SSID().c_str());
@ -280,7 +295,10 @@ void heartbeat() {
if (hb_cfg & Heartbeat::Loadavg) if (hb_cfg & Heartbeat::Loadavg)
mqttSend(MQTT_TOPIC_LOADAVG, String(systemLoadAverage()).c_str()); mqttSend(MQTT_TOPIC_LOADAVG, String(systemLoadAverage()).c_str());
} else if (!serial && _heartbeat_mode == HEARTBEAT_REPEAT_STATUS) {
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
} }
#endif #endif
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -300,8 +318,6 @@ void heartbeat() {
} }
#endif /// HEARTBEAT_MODE != HEARTBEAT_NONE
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// INFO // INFO
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


+ 12
- 12
code/espurna/wifi.ino View File

@ -385,39 +385,39 @@ void _wifiDebugCallback(justwifi_messages_t code, char * parameter) {
void _wifiInitCommands() { void _wifiInitCommands() {
settingsRegisterCommand(F("WIFI"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI"), [](Embedis* e) {
wifiDebug(); wifiDebug();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
settingsRegisterCommand(F("WIFI.RESET"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.RESET"), [](Embedis* e) {
_wifiConfigure(); _wifiConfigure();
wifiDisconnect(); wifiDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
settingsRegisterCommand(F("WIFI.AP"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.AP"), [](Embedis* e) {
wifiStartAP(); wifiStartAP();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
#if defined(JUSTWIFI_ENABLE_WPS) #if defined(JUSTWIFI_ENABLE_WPS)
settingsRegisterCommand(F("WIFI.WPS"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.WPS"), [](Embedis* e) {
wifiStartWPS(); wifiStartWPS();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
#endif // defined(JUSTWIFI_ENABLE_WPS) #endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG) #if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
settingsRegisterCommand(F("WIFI.SMARTCONFIG"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.SMARTCONFIG"), [](Embedis* e) {
wifiStartSmartConfig(); wifiStartSmartConfig();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG) #endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
settingsRegisterCommand(F("WIFI.SCAN"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.SCAN"), [](Embedis* e) {
_wifiScan(); _wifiScan();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}); });
} }


+ 131
- 62
code/espurna/ws.ino View File

@ -73,6 +73,27 @@ bool _wsAuth(AsyncWebSocketClient * client) {
} }
#if DEBUG_WEB_SUPPORT
bool wsDebugSend(const char* prefix, const char* message) {
if (!wsConnected()) return false;
if (getFreeHeap() < (strlen(message) * 3)) return false;
DynamicJsonBuffer jsonBuffer;
JsonObject &root = jsonBuffer.createObject();
JsonObject &weblog = root.createNestedObject("weblog");
weblog.set("message", message);
if (prefix && (prefix[0] != '\0')) {
weblog.set("prefix", prefix);
}
wsSend(root);
return true;
}
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#if MQTT_SUPPORT #if MQTT_SUPPORT
@ -289,76 +310,119 @@ void _wsUpdate(JsonObject& root) {
#endif #endif
} }
void _wsDoUpdate(bool reset = false) {
static unsigned long last = 0;
if (reset) {
last = 0;
return;
}
if (millis() - last > WS_UPDATE_INTERVAL) {
last = millis();
wsSend(_wsUpdate);
}
}
bool _wsOnReceive(const char * key, JsonVariant& value) { bool _wsOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, "ws", 2) == 0) return true; if (strncmp(key, "ws", 2) == 0) return true;
if (strncmp(key, "admin", 5) == 0) return true; if (strncmp(key, "admin", 5) == 0) return true;
if (strncmp(key, "hostname", 8) == 0) return true; if (strncmp(key, "hostname", 8) == 0) return true;
if (strncmp(key, "desc", 4) == 0) return true;
if (strncmp(key, "webPort", 7) == 0) return true; if (strncmp(key, "webPort", 7) == 0) return true;
return false; return false;
} }
void _wsOnStart(JsonObject& root) { void _wsOnStart(JsonObject& root) {
char chipid[7];
snprintf_P(chipid, sizeof(chipid), PSTR("%06X"), ESP.getChipId());
uint8_t * bssid = WiFi.BSSID();
char bssid_str[20];
snprintf_P(bssid_str, sizeof(bssid_str),
PSTR("%02X:%02X:%02X:%02X:%02X:%02X"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]
);
root["webMode"] = WEB_MODE_NORMAL;
root["app_name"] = APP_NAME;
root["app_version"] = APP_VERSION;
root["app_build"] = buildTime();
#if defined(APP_REVISION)
root["app_revision"] = APP_REVISION;
#endif
root["manufacturer"] = MANUFACTURER;
root["chipid"] = String(chipid);
root["mac"] = WiFi.macAddress();
root["bssid"] = String(bssid_str);
root["channel"] = WiFi.channel();
root["device"] = DEVICE;
root["hostname"] = getSetting("hostname");
root["desc"] = getSetting("desc");
root["network"] = getNetwork();
root["deviceip"] = getIP();
root["sketch_size"] = ESP.getSketchSize();
root["free_size"] = ESP.getFreeSketchSpace();
root["sdk"] = ESP.getSdkVersion();
root["core"] = getCoreVersion();
root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt();
root["webPort"] = getSetting("webPort", WEB_PORT).toInt();
root["wsAuth"] = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1;
#if TERMINAL_SUPPORT
root["cmdVisible"] = 1;
#endif
root["hbMode"] = getSetting("hbMode", HEARTBEAT_MODE).toInt();
root["hbInterval"] = getSetting("hbInterval", HEARTBEAT_INTERVAL).toInt();
_wsDoUpdate(true);
}
void wsSend(JsonObject& root) {
size_t len = root.measureLength();
AsyncWebSocketMessageBuffer* buffer = _ws.makeBuffer(len);
if (buffer) {
root.printTo(reinterpret_cast<char*>(buffer->get()), len + 1);
_ws.textAll(buffer);
}
}
void wsSend(uint32_t client_id, JsonObject& root) {
AsyncWebSocketClient* client = _ws.client(client_id);
if (client == nullptr) return;
size_t len = root.measureLength();
AsyncWebSocketMessageBuffer* buffer = _ws.makeBuffer(len);
if (buffer) {
root.printTo(reinterpret_cast<char*>(buffer->get()), len + 1);
client->text(buffer);
}
}
void _wsStart(uint32_t client_id) {
#if USE_PASSWORD && WEB_FORCE_PASS_CHANGE #if USE_PASSWORD && WEB_FORCE_PASS_CHANGE
bool changePassword = getAdminPass().equals(ADMIN_PASS); bool changePassword = getAdminPass().equals(ADMIN_PASS);
#else #else
bool changePassword = false; bool changePassword = false;
#endif #endif
if (changePassword) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
if (changePassword) {
root["webMode"] = WEB_MODE_PASSWORD; root["webMode"] = WEB_MODE_PASSWORD;
} else {
char chipid[7];
snprintf_P(chipid, sizeof(chipid), PSTR("%06X"), ESP.getChipId());
uint8_t * bssid = WiFi.BSSID();
char bssid_str[20];
snprintf_P(bssid_str, sizeof(bssid_str),
PSTR("%02X:%02X:%02X:%02X:%02X:%02X"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]
);
root["webMode"] = WEB_MODE_NORMAL;
root["app_name"] = APP_NAME;
root["app_version"] = APP_VERSION;
root["app_build"] = buildTime();
#if defined(APP_REVISION)
root["app_revision"] = APP_REVISION;
#endif
root["manufacturer"] = MANUFACTURER;
root["chipid"] = String(chipid);
root["mac"] = WiFi.macAddress();
root["bssid"] = String(bssid_str);
root["channel"] = WiFi.channel();
root["device"] = DEVICE;
root["hostname"] = getSetting("hostname");
root["network"] = getNetwork();
root["deviceip"] = getIP();
root["sketch_size"] = ESP.getSketchSize();
root["free_size"] = ESP.getFreeSketchSpace();
root["sdk"] = ESP.getSdkVersion();
root["core"] = getCoreVersion();
_wsUpdate(root);
root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt();
root["webPort"] = getSetting("webPort", WEB_PORT).toInt();
root["wsAuth"] = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1;
#if TERMINAL_SUPPORT
root["cmdVisible"] = 1;
#endif
wsSend(root);
return;
} }
}
void _wsStart(uint32_t client_id) {
for (unsigned char i = 0; i < _ws_on_send_callbacks.size(); i++) {
wsSend(client_id, _ws_on_send_callbacks[i]);
for (auto& callback : _ws_on_send_callbacks) {
callback(root);
} }
wsSend(client_id, root);
} }
void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
@ -406,12 +470,8 @@ void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTy
} }
void _wsLoop() { void _wsLoop() {
static unsigned long last = 0;
if (!wsConnected()) return; if (!wsConnected()) return;
if (millis() - last > WS_UPDATE_INTERVAL) {
last = millis();
wsSend(_wsUpdate);
}
_wsDoUpdate();
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -422,6 +482,10 @@ bool wsConnected() {
return (_ws.count() > 0); return (_ws.count() > 0);
} }
bool wsConnected(uint32_t client_id) {
return _ws.hasClient(client_id);
}
void wsOnSendRegister(ws_on_send_callback_f callback) { void wsOnSendRegister(ws_on_send_callback_f callback) {
_ws_on_send_callbacks.push_back(callback); _ws_on_send_callbacks.push_back(callback);
} }
@ -439,10 +503,8 @@ void wsSend(ws_on_send_callback_f callback) {
DynamicJsonBuffer jsonBuffer; DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject(); JsonObject& root = jsonBuffer.createObject();
callback(root); callback(root);
String output;
root.printTo(output);
jsonBuffer.clear();
_ws.textAll((char *) output.c_str());
wsSend(root);
} }
} }
@ -461,13 +523,20 @@ void wsSend_P(PGM_P payload) {
} }
void wsSend(uint32_t client_id, ws_on_send_callback_f callback) { void wsSend(uint32_t client_id, ws_on_send_callback_f callback) {
AsyncWebSocketClient* client = _ws.client(client_id);
if (client == nullptr) return;
DynamicJsonBuffer jsonBuffer; DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject(); JsonObject& root = jsonBuffer.createObject();
callback(root); callback(root);
String output;
root.printTo(output);
jsonBuffer.clear();
_ws.text(client_id, (char *) output.c_str());
size_t len = root.measureLength();
AsyncWebSocketMessageBuffer* buffer = _ws.makeBuffer(len);
if (buffer) {
root.printTo(reinterpret_cast<char*>(buffer->get()), len + 1);
client->text(buffer);
}
} }
void wsSend(uint32_t client_id, const char * payload) { void wsSend(uint32_t client_id, const char * payload) {


+ 75
- 42
code/html/custom.js View File

@ -657,7 +657,10 @@ function doScan() {
} }
function doHAConfig() { function doHAConfig() {
$("#haConfig").html("");
$("#haConfig")
.text("")
.height(0)
.show();
sendAction("haconfig", {}); sendAction("haconfig", {});
return false; return false;
} }
@ -767,12 +770,13 @@ function createMagnitudeList(data, container, template_name) {
if (current > 0) { return; } if (current > 0) { return; }
var template = $("#" + template_name + " .pure-g")[0]; var template = $("#" + template_name + " .pure-g")[0];
for (var i in data) {
var magnitude = data[i];
var size = data.size;
for (var i=0; i<size; ++i) {
var line = $(template).clone(); var line = $(template).clone();
$("label", line).html(magnitudeType(magnitude.type) + " #" + parseInt(magnitude.index, 10));
$("div.hint", line).html(magnitude.name);
$("input", line).attr("tabindex", 40 + i).val(magnitude.idx);
$("label", line).html(magnitudeType(data.type[i]) + " #" + parseInt(data.index[i], 10));
$("div.hint", line).html(data.name[i]);
$("input", line).attr("tabindex", 40 + i).val(data.idx[i]);
line.appendTo("#" + container); line.appendTo("#" + container);
} }
@ -934,24 +938,34 @@ function createCheckboxes() {
function initRelayConfig(data) { function initRelayConfig(data) {
var current = $("#relayConfig > div").length / 6; // there are 6 divs per each relay
var current = $("#relayConfig > legend").length; // there is a legend per relay
if (current > 0) { return; }
var size = data.size;
var start = data.start;
var template = $("#relayConfigTemplate").children(); var template = $("#relayConfigTemplate").children();
for (var i in data) {
var relay = data[i];
if (current > relay.id) continue;
for (var i=start; i<size; ++i) {
var line = $(template).clone(); var line = $(template).clone();
$("span.gpio", line).html(relay.gpio);
$("span.id", line).html(relay.id);
$("select[name='relayBoot']", line).val(relay.boot);
$("select[name='relayPulse']", line).val(relay.pulse);
$("input[name='relayTime']", line).val(relay.pulse_ms);
$("input[name='mqttGroup']", line).val(relay.group);
$("select[name='mqttGroupInv']", line).val(relay.group_inv);
$("select[name='relayOnDisc']", line).val(relay.on_disc);
line.appendTo("#relayConfig");
$("span.id", line).html(i);
$("span.gpio", line).html(data.gpio[i]);
$("select[name='relayBoot']", line).val(data.boot[i]);
$("select[name='relayPulse']", line).val(data.pulse[i]);
$("input[name='relayTime']", line).val(data.pulse_time[i]);
if ("group" in data) {
$("input[name='mqttGroup']", line).val(data.group[i]);
}
if ("group_inv" in data) {
$("input[name='mqttGroupInv']", line).val(data.group_inv[i]);
}
if ("on_disc" in data) {
$("input[name='relayOnDisc']", line).val(data.on_disc[i]);
}
line.appendTo("#relayConfig");
} }
} }
@ -963,17 +977,19 @@ function initRelayConfig(data) {
<!-- removeIf(!sensor)--> <!-- removeIf(!sensor)-->
function initMagnitudes(data) { function initMagnitudes(data) {
// check if already initialized
// check if already initialized (each magnitude is inside div.pure-g)
var done = $("#magnitudes > div").length; var done = $("#magnitudes > div").length;
if (done > 0) { return; } if (done > 0) { return; }
var size = data.size;
// add templates // add templates
var template = $("#magnitudeTemplate").children(); var template = $("#magnitudeTemplate").children();
for (var i in data) {
var magnitude = data[i];
for (var i=0; i<size; ++i) {
var line = $(template).clone(); var line = $(template).clone();
$("label", line).html(magnitudeType(magnitude.type) + " #" + parseInt(magnitude.index, 10));
$("div.hint", line).html(magnitude.description);
$("label", line).html(magnitudeType(data.type[i]) + " #" + parseInt(data.index[i], 10));
$("div.hint", line).html(data.description[i]);
$("input", line).attr("data", i); $("input", line).attr("data", i);
line.appendTo("#magnitudes"); line.appendTo("#magnitudes");
} }
@ -1244,11 +1260,18 @@ function processData(data) {
} }
if ("rfb" === key) { if ("rfb" === key) {
var nodes = data.rfb;
for (i in nodes) {
var node = nodes[i];
$("input[name='rfbcode'][data-id='" + node.id + "'][data-status='" + node.status + "']").val(node.data);
var rfb = data.rfb;
var size = data.size;
var start = data.start;
var on = rfb["on"];
var off = rfb["off"];
for (var i=start; i<start+size; ++i) {
$("input[name='rfbcode'][data-id='" + i + "'][data-status='1']").val(on[i]);
$("input[name='rfbcode'][data-id='" + i + "'][data-status='0']").val(off[i]);
} }
return; return;
} }
<!-- endRemoveIf(!rfbridge)--> <!-- endRemoveIf(!rfbridge)-->
@ -1376,15 +1399,13 @@ function processData(data) {
if ("magnitudes" === key) { if ("magnitudes" === key) {
initMagnitudes(value); initMagnitudes(value);
for (i in value) {
var magnitude = value[i];
var error = magnitude.error || 0;
for (var i=0; i<value.size; ++i) {
var error = value.error[i] || 0;
var text = (0 === error) ? var text = (0 === error) ?
magnitude.value + magnitude.units :
value.value[i] + value.units[i] :
magnitudeError(error); magnitudeError(error);
var element = $("input[name='magnitude'][data='" + i + "']"); var element = $("input[name='magnitude'][data='" + i + "']");
element.val(text); element.val(text);
$("div.hint", element.parent().parent()).html(magnitude.description);
} }
return; return;
} }
@ -1421,7 +1442,11 @@ function processData(data) {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
if ("haConfig" === key) { if ("haConfig" === key) {
$("#haConfig").show();
websock.send("{}");
$("#haConfig")
.append(new Text(value))
.height($("#haConfig")[0].scrollHeight);
return;
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -1433,13 +1458,13 @@ function processData(data) {
return; return;
} }
if ("schedule" === key) {
for (i in value) {
var schedule = value[i];
var sch_line = addSchedule({ data: {schType: schedule["schType"] }});
if ("schedules" === key) {
for (var i=0; i<value.size; ++i) {
var sch_line = addSchedule({ data: {schType: value.schType[i] }});
Object.keys(schedule).forEach(function(key) {
var sch_value = schedule[key];
Object.keys(value).forEach(function(key) {
if ("size" == key) return;
var sch_value = value[key][i];
$("input[name='" + key + "']", sch_line).val(sch_value); $("input[name='" + key + "']", sch_line).val(sch_value);
$("select[name='" + key + "']", sch_line).prop("value", sch_value); $("select[name='" + key + "']", sch_line).prop("value", sch_value);
$("input[type='checkbox'][name='" + key + "']", sch_line).prop("checked", sch_value); $("input[type='checkbox'][name='" + key + "']", sch_line).prop("checked", sch_value);
@ -1514,7 +1539,13 @@ function processData(data) {
// Web log // Web log
if ("weblog" === key) { if ("weblog" === key) {
$("#weblog").append(new Text(value));
websock.send("{}");
if (value.prefix) {
$("#weblog").append(new Text(value.prefix));
}
$("#weblog").append(new Text(value.message));
$("#weblog").scrollTop($("#weblog")[0].scrollHeight - $("#weblog").height()); $("#weblog").scrollTop($("#weblog")[0].scrollHeight - $("#weblog").height());
return; return;
} }
@ -1767,6 +1798,8 @@ $(function() {
$(document).on("change", "input", hasChanged); $(document).on("change", "input", hasChanged);
$(document).on("change", "select", hasChanged); $(document).on("change", "select", hasChanged);
$("textarea").on("dblclick", function() { this.select(); });
// don't autoconnect when opening from filesystem // don't autoconnect when opening from filesystem
if (window.location.protocol === "file:") { return; } if (window.location.protocol === "file:") { return; }


+ 42
- 2
code/html/index.html View File

@ -84,6 +84,7 @@
<span class="pure-menu-heading" name="hostname">HOSTNAME</span> <span class="pure-menu-heading" name="hostname">HOSTNAME</span>
<span class="pure-menu-heading small" name="title">ESPurna 0.0.0</span> <span class="pure-menu-heading small" name="title">ESPurna 0.0.0</span>
<span class="pure-menu-heading small" name="desc"></span>
<ul class="pure-menu-list"> <ul class="pure-menu-list">
@ -348,6 +349,16 @@
</div> </div>
</div> </div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Description</label>
<input name="desc" class="pure-u-1 pure-u-lg-3-4" maxlength="64" type="text" tabindex="2" />
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
Human-friendly name for your device. Will be reported with the heartbeat.<br />
You can use this to specify the location or some other identification information.
</div>
</div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Double click delay</label> <label class="pure-u-1 pure-u-lg-1-4">Double click delay</label>
<input name="btnDelay" class="pure-u-1 pure-u-lg-1-4" type="number" action="reboot" min="0" step="100" max="1000" tabindex="6" /> <input name="btnDelay" class="pure-u-1 pure-u-lg-1-4" type="number" action="reboot" min="0" step="100" max="1000" tabindex="6" />
@ -626,6 +637,30 @@
<div class="pure-u-1 pure-u-lg-3-4 hint">Request password when starting telnet session</div> <div class="pure-u-1 pure-u-lg-3-4 hint">Request password when starting telnet session</div>
</div> </div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Heartbeat message</label>
<select class="pure-u-1 pure-u-lg-3-4" name="hbMode" tabindex="15" >
<option value="0">Disabled</option>
<option value="1">On device startup</option>
<option value="2">Repeat after defined interval</option>
<option value="3">Repeat only device status</option>
</select>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
Define when heartbeat message will be sent.
</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Heartbeat interval</label>
<input name="hbInterval" class="pure-u-1 pure-u-lg-1-4" type="number" tabindex="16" />
<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-3-4 hint">
This is the interval in <strong>seconds</strong> how often to send the heartbeat message.
</div>
</div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Upgrade</label> <label class="pure-u-1 pure-u-lg-1-4">Upgrade</label>
<input class="pure-u-1-2 pure-u-lg-1-2" name="filename" type="text" readonly /> <input class="pure-u-1-2 pure-u-lg-1-2" name="filename" type="text" readonly />
@ -635,7 +670,7 @@
<div class="pure-u-1 pure-u-lg-3-4 hint">The device has <span name="free_size"></span> bytes available for OTA updates. If your image is larger than this consider doing a <a href="https://github.com/xoseperez/espurna/wiki/TwoStepUpdates" rel="noopener" target="_blank"><strong>two-step update</strong></a>.</div> <div class="pure-u-1 pure-u-lg-3-4 hint">The device has <span name="free_size"></span> bytes available for OTA updates. If your image is larger than this consider doing a <a href="https://github.com/xoseperez/espurna/wiki/TwoStepUpdates" rel="noopener" target="_blank"><strong>two-step update</strong></a>.</div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4"><progress id="upgrade-progress"></progress></div> <div class="pure-u-1 pure-u-lg-3-4"><progress id="upgrade-progress"></progress></div>
<input name="upgrade" type="file" tabindex="16" />
<input name="upgrade" type="file" tabindex="17" />
</div> </div>
</fieldset> </fieldset>
@ -657,6 +692,11 @@
<legend>General</legend> <legend>General</legend>
<div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Device hostname can be configured on the GENERAL tab.</div>
</div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Scan networks</label> <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>
@ -1085,7 +1125,7 @@
</div> </div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
<span class="pure-u-1 terminal" id="haConfig" name="haConfig"></span>
<textarea class="pure-u-1 terminal" id="haConfig" name="haConfig" wrap="off" readonly></textarea>
</div> </div>


+ 27
- 25
code/memanalyzer.py View File

@ -19,15 +19,16 @@ import argparse
import os import os
import re import re
import shlex import shlex
import subprocess
import sys import sys
from collections import OrderedDict from collections import OrderedDict
from sortedcontainers import SortedDict from sortedcontainers import SortedDict
import subprocess
if (sys.version_info > (3, 0)):
from subprocess import getstatusoutput as getstatusoutput
if sys.version_info > (3, 0):
from subprocess import getstatusoutput
else: else:
from commands import getstatusoutput as getstatusoutput
from commands import getstatusoutput
# ------------------------------------------------------------------------------- # -------------------------------------------------------------------------------
@ -55,7 +56,7 @@ def file_size(file):
def analyse_memory(elf_file): def analyse_memory(elf_file):
command = "%s -t '%s'" % (objdump_binary, elf_file)
command = "{} -t '{}'".format(objdump_binary, elf_file)
response = subprocess.check_output(shlex.split(command)) response = subprocess.check_output(shlex.split(command))
if isinstance(response, bytes): if isinstance(response, bytes):
response = response.decode('utf-8') response = response.decode('utf-8')
@ -66,8 +67,8 @@ def analyse_memory(elf_file):
ret = {} ret = {}
for (id_, _) in list(sections.items()): for (id_, _) in list(sections.items()):
section_start_token = " _%s_start" % id_
section_end_token = " _%s_end" % id_
section_start_token = " _{}_start".format(id_)
section_end_token = " _{}_end".format(id_)
section_start = -1 section_start = -1
section_end = -1 section_end = -1
for line in lines: for line in lines:
@ -92,24 +93,26 @@ def analyse_memory(elf_file):
# print("{0: >10}|{1: >30}|{2:12X}|{3:12X}|{4:8}".format(id_, descr, section_start, section_end, section_length)) # print("{0: >10}|{1: >30}|{2:12X}|{3:12X}|{4:8}".format(id_, descr, section_start, section_end, section_length))
# i += 1 # i += 1
# print("Total Used RAM : %d" % usedRAM)
# print("Free RAM : %d" % (TOTAL_DRAM - usedRAM))
# print("Free IRam : %d" % usedIRAM)
# print("Total Used RAM : {:d}".format(usedRAM))
# print("Free RAM : {:d}".format(TOTAL_DRAM - usedRAM))
# print("Free IRam : {:d}".format(usedIRAM))
return ret return ret
def run(env_, modules_): def run(env_, modules_):
flags = "" flags = ""
for item in modules_.items():
flags += "-D%s_SUPPORT=%d " % item
command = "ESPURNA_BOARD=\"WEMOS_D1_MINI_RELAYSHIELD\" ESPURNA_FLAGS=\"%s\" platformio run --silent --environment %s 2>/dev/null" % (flags, env_)
for k, v in modules_.items():
flags += "-D{}_SUPPORT={:d} ".format(k, v)
command = "ESPURNA_BOARD=\"WEMOS_D1_MINI_RELAYSHIELD\" ESPURNA_FLAGS=\"{}\" platformio run --silent --environment {} 2>/dev/null".format(flags, env_)
subprocess.check_call(command, shell=True) subprocess.check_call(command, shell=True)
def calc_free(module): def calc_free(module):
free = 80 * 1024 - module['data'] - module['rodata'] - module['bss'] free = 80 * 1024 - module['data'] - module['rodata'] - module['bss']
free = free + (16 - free % 16) free = free + (16 - free % 16)
module['free'] = free module['free'] = free
def modules_get(): def modules_get():
modules_ = SortedDict() modules_ = SortedDict()
for line in open("espurna/config/arduino.h"): for line in open("espurna/config/arduino.h"):
@ -120,7 +123,8 @@ def modules_get():
del modules_['NETBIOS'] del modules_['NETBIOS']
return modules_ return modules_
try:
if __name__ == '__main__':
# Parse command line options # Parse command line options
parser = argparse.ArgumentParser(description=description) parser = argparse.ArgumentParser(description=description)
@ -160,7 +164,7 @@ try:
# Check test modules exist # Check test modules exist
for module in test_modules: for module in test_modules:
if module not in available_modules: if module not in available_modules:
print("Module %s not found" % module)
print("Module {} not found".format(module))
sys.exit(2) sys.exit(2)
# Define base configuration # Define base configuration
@ -173,9 +177,9 @@ try:
# Show init message # Show init message
if len(test_modules) > 0: if len(test_modules) > 0:
print("Analyzing module(s) %s on top of %s configuration\n" % (", ".join(test_modules), "CORE" if args.core > 0 else "DEFAULT"))
print("Analyzing module(s) {} on top of {} configuration\n".format(", ".join(test_modules), "CORE" if args.core > 0 else "DEFAULT"))
else: else:
print("Analyzing %s configuration\n" % ("CORE" if args.core > 0 else "DEFAULT"))
print("Analyzing {} configuration\n".format("CORE" if args.core > 0 else "DEFAULT"))
output_format = "{:<20}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}" output_format = "{:<20}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}"
print(output_format.format( print(output_format.format(
@ -211,8 +215,8 @@ try:
# Build the core without modules to get base memory usage # Build the core without modules to get base memory usage
run(env, modules) run(env, modules)
base = analyse_memory(".pioenvs/%s/firmware.elf" % env)
base['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
base = analyse_memory(".pioenvs/{}/firmware.elf".format(env))
base['size'] = file_size(".pioenvs/{}/firmware.bin".format(env))
calc_free(base) calc_free(base)
print(output_format.format( print(output_format.format(
"CORE" if args.core == 1 else "DEFAULT", "CORE" if args.core == 1 else "DEFAULT",
@ -231,8 +235,8 @@ try:
modules[module] = 1 modules[module] = 1
run(env, modules) run(env, modules)
results[module] = analyse_memory(".pioenvs/%s/firmware.elf" % env)
results[module]['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
results[module] = analyse_memory(".pioenvs/{}/firmware.elf".format(env))
results[module]['size'] = file_size(".pioenvs/{}/firmware.bin".format(env))
calc_free(results[module]) calc_free(results[module])
modules[module] = 0 modules[module] = 0
@ -253,8 +257,8 @@ try:
for module in test_modules: for module in test_modules:
modules[module] = 1 modules[module] = 1
run(env, modules) run(env, modules)
total = analyse_memory(".pioenvs/%s/firmware.elf" % env)
total['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
total = analyse_memory(".pioenvs/{}/firmware.elf".format(env))
total['size'] = file_size(".pioenvs/{}/firmware.bin".format(env))
calc_free(total) calc_free(total)
if len(test_modules) > 1: if len(test_modules) > 1:
@ -279,7 +283,5 @@ try:
total['size'], total['size'],
)) ))
except:
raise
print("\n") print("\n")

+ 56
- 45
code/ota.py View File

@ -8,14 +8,14 @@
# ------------------------------------------------------------------------------- # -------------------------------------------------------------------------------
from __future__ import print_function from __future__ import print_function
import shutil
import argparse import argparse
import os
import re import re
import shutil
import socket import socket
import subprocess import subprocess
import sys import sys
import time import time
import os
from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf
@ -33,6 +33,7 @@ description = "ESPurna OTA Manager v0.3"
devices = [] devices = []
discover_last = 0 discover_last = 0
# ------------------------------------------------------------------------------- # -------------------------------------------------------------------------------
def on_service_state_change(zeroconf, service_type, name, state_change): def on_service_state_change(zeroconf, service_type, name, state_change):
@ -61,7 +62,7 @@ def on_service_state_change(zeroconf, service_type, name, state_change):
} }
for key, item in info.properties.items(): for key, item in info.properties.items():
device[key.decode('UTF-8')] = item.decode('UTF-8');
device[key.decode('UTF-8')] = item.decode('UTF-8')
# rename fields (needed for sorting by name) # rename fields (needed for sorting by name)
device['app'] = device['app_name'] device['app'] = device['app_name']
@ -75,35 +76,35 @@ def list_devices():
""" """
Shows the list of discovered devices Shows the list of discovered devices
""" """
output_format="{:>3} {:<14} {:<15} {:<17} {:<12} {:<12} {:<25} {:<8} {:<8} {:<10}"
output_format = "{:>3} {:<14} {:<15} {:<17} {:<12} {:<12} {:<25} {:<8} {:<8} {:<10}"
print(output_format.format( print(output_format.format(
"#",
"HOSTNAME",
"IP",
"MAC",
"APP",
"VERSION",
"DEVICE",
"MEM_SIZE",
"SDK_SIZE",
"FREE_SPACE"
"#",
"HOSTNAME",
"IP",
"MAC",
"APP",
"VERSION",
"DEVICE",
"MEM_SIZE",
"SDK_SIZE",
"FREE_SPACE"
)) ))
print("-" * 139) print("-" * 139)
index = 0 index = 0
for device in devices: for device in devices:
index = index + 1
index += 1
print(output_format.format( print(output_format.format(
index,
device.get('hostname', ''),
device.get('ip', ''),
device.get('mac', ''),
device.get('app_name', ''),
device.get('app_version', ''),
device.get('target_board', ''),
device.get('mem_size', 0),
device.get('sdk_size', 0),
device.get('free_space', 0),
index,
device.get('hostname', ''),
device.get('ip', ''),
device.get('mac', ''),
device.get('app_name', ''),
device.get('app_version', ''),
device.get('target_board', ''),
device.get('mem_size', 0),
device.get('sdk_size', 0),
device.get('free_space', 0),
)) ))
print() print()
@ -120,11 +121,13 @@ def get_boards():
boards.append(m.group(1)) boards.append(m.group(1))
return sorted(boards) return sorted(boards)
def get_device_size(device): def get_device_size(device):
if device.get('mem_size', 0) == device.get('sdk_size', 0): if device.get('mem_size', 0) == device.get('sdk_size', 0):
return int(device.get('mem_size', 0)) / 1024 return int(device.get('mem_size', 0)) / 1024
return 0 return 0
def get_empty_board(): def get_empty_board():
""" """
Returns the empty structure of a board to flash Returns the empty structure of a board to flash
@ -132,12 +135,13 @@ def get_empty_board():
board = {'board': '', 'ip': '', 'size': 0, 'auth': '', 'flags': ''} board = {'board': '', 'ip': '', 'size': 0, 'auth': '', 'flags': ''}
return board return board
def get_board_by_index(index): def get_board_by_index(index):
""" """
Returns the required data to flash a given board Returns the required data to flash a given board
""" """
board = {} board = {}
if 1 <= index and index <= len(devices):
if 1 <= index <= len(devices):
device = devices[index - 1] device = devices[index - 1]
board['hostname'] = device.get('hostname') board['hostname'] = device.get('hostname')
board['board'] = device.get('target_board', '') board['board'] = device.get('target_board', '')
@ -145,6 +149,7 @@ def get_board_by_index(index):
board['size'] = get_device_size(device) board['size'] = get_device_size(device)
return board return board
def get_board_by_mac(mac): def get_board_by_mac(mac):
""" """
Returns the required data to flash a given board Returns the required data to flash a given board
@ -161,6 +166,7 @@ def get_board_by_mac(mac):
return board return board
return None return None
def get_board_by_hostname(hostname): def get_board_by_hostname(hostname):
""" """
Returns the required data to flash a given board Returns the required data to flash a given board
@ -178,6 +184,7 @@ def get_board_by_hostname(hostname):
return board return board
return None return None
def input_board(): def input_board():
""" """
Grabs info from the user about what device to flash Grabs info from the user about what device to flash
@ -189,10 +196,10 @@ def input_board():
except ValueError: except ValueError:
index = 0 index = 0
if index < 0 or len(devices) < index: if index < 0 or len(devices) < index:
print("Board number must be between 1 and %s\n" % str(len(devices)))
print("Board number must be between 1 and {}\n".format(str(len(devices))))
return None return None
board = get_board_by_index(index);
board = get_board_by_index(index)
# Choose board type if none before # Choose board type if none before
if len(board.get('board', '')) == 0: if len(board.get('board', '')) == 0:
@ -201,15 +208,15 @@ def input_board():
count = 1 count = 1
boards = get_boards() boards = get_boards()
for name in boards: for name in boards:
print("%3d\t%s" % (count, name))
count = count + 1
print("{:3d}\t{}".format(count, name))
count += 1
print() print()
try: try:
index = int(input("Choose the board type you want to flash: ")) index = int(input("Choose the board type you want to flash: "))
except ValueError: except ValueError:
index = 0 index = 0
if index < 1 or len(boards) < index: if index < 1 or len(boards) < index:
print("Board number must be between 1 and %s\n" % str(len(boards)))
print("Board number must be between 1 and {}\n".format(str(len(boards))))
return None return None
board['board'] = boards[index - 1] board['board'] = boards[index - 1]
@ -227,12 +234,14 @@ def input_board():
return board return board
def boardname(board): def boardname(board):
return board.get('hostname', board['ip']) return board.get('hostname', board['ip'])
def store(device, env): def store(device, env):
source = ".pioenvs/%s/firmware.elf" % env
destination = ".pioenvs/elfs/%s.elf" % boardname(device).lower()
source = ".pioenvs/{}/firmware.elf".format(env)
destination = ".pioenvs/elfs/{}.elf".format(boardname(device).lower())
dst_dir = os.path.dirname(destination) dst_dir = os.path.dirname(destination)
if not os.path.exists(dst_dir): if not os.path.exists(dst_dir):
@ -240,6 +249,7 @@ def store(device, env):
shutil.move(source, destination) shutil.move(source, destination)
def run(device, env): def run(device, env):
print("Building and flashing image over-the-air...") print("Building and flashing image over-the-air...")
environ = os.environ.copy() environ = os.environ.copy()
@ -253,6 +263,7 @@ def run(device, env):
store(device, env) store(device, env)
# ------------------------------------------------------------------------------- # -------------------------------------------------------------------------------
if __name__ == '__main__': if __name__ == '__main__':
@ -272,13 +283,13 @@ if __name__ == '__main__':
print(description) print(description)
print() print()
# Look for sevices
# Look for services
zeroconf = Zeroconf() zeroconf = Zeroconf()
browser = ServiceBrowser(zeroconf, "_arduino._tcp.local.", handlers=[on_service_state_change]) browser = ServiceBrowser(zeroconf, "_arduino._tcp.local.", handlers=[on_service_state_change])
discover_last = time.time() discover_last = time.time()
while time.time() < discover_last + DISCOVER_TIMEOUT: while time.time() < discover_last + DISCOVER_TIMEOUT:
None
#zeroconf.close()
pass
# zeroconf.close()
if len(devices) == 0: if len(devices) == 0:
print("Nothing found!\n") print("Nothing found!\n")
@ -287,7 +298,7 @@ if __name__ == '__main__':
# Sort list # Sort list
field = args.sort.lower() field = args.sort.lower()
if field not in devices[0]: if field not in devices[0]:
print("Unknown field '%s'\n" % field)
print("Unknown field '{}'\n".format(field))
sys.exit(1) sys.exit(1)
devices = sorted(devices, key=lambda device: device.get(field, '')) devices = sorted(devices, key=lambda device: device.get(field, ''))
@ -322,23 +333,23 @@ if __name__ == '__main__':
queue = sorted(queue, key=lambda device: device.get('board', '')) queue = sorted(queue, key=lambda device: device.get('board', ''))
# Flash eash board
# Flash each board
for board in queue: for board in queue:
# Flash core version? # Flash core version?
if args.core > 0: if args.core > 0:
board['flags'] = "-DESPURNA_CORE " + board['flags'] board['flags'] = "-DESPURNA_CORE " + board['flags']
env = "esp8266-%dm-ota" % board['size']
env = "esp8266-{:d}m-ota".format(board['size'])
# Summary # Summary
print() print()
print("HOST = %s" % boardname(board))
print("IP = %s" % board['ip'])
print("BOARD = %s" % board['board'])
print("AUTH = %s" % board['auth'])
print("FLAGS = %s" % board['flags'])
print("ENV = %s" % env)
print("HOST = {}".format(boardname(board)))
print("IP = {}".format(board['ip']))
print("BOARD = {}".format(board['board']))
print("AUTH = {}".format(board['auth']))
print("FLAGS = {}".format(board['flags']))
print("ENV = {}".format(env))
response = True response = True
if args.yes == 0: if args.yes == 0:


+ 131
- 10
code/platformio.ini View File

@ -77,7 +77,7 @@ lib_deps =
ArduinoJson ArduinoJson
https://github.com/marvinroger/async-mqtt-client#v0.8.1 https://github.com/marvinroger/async-mqtt-client#v0.8.1
Brzo I2C Brzo I2C
https://github.com/xoseperez/debounceevent.git#2.0.4
https://github.com/xoseperez/debounceevent.git#2.0.5
https://github.com/xoseperez/eeprom_rotate#0.9.2 https://github.com/xoseperez/eeprom_rotate#0.9.2
Embedis Embedis
Encoder Encoder
@ -458,6 +458,17 @@ build_flags = ${common.build_flags_1m0m} -DITEAD_SONOFF_BASIC -DDHT_SUPPORT=1
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:itead-sonoff-basic-r2-dht]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DITEAD_SONOFF_BASIC -DDHT_SUPPORT=1 -DDHT_PIN=2
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:itead-sonoff-basic-dht-ota] [env:itead-sonoff-basic-dht-ota]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
@ -483,6 +494,17 @@ build_flags = ${common.build_flags_1m0m} -DITEAD_SONOFF_BASIC -DDALLAS_SUPPORT=1
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:itead-sonoff-basic-r2-dallas]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DITEAD_SONOFF_BASIC -DDALLAS_SUPPORT=1 -DDALLAS_PIN=2
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:itead-sonoff-rf] [env:itead-sonoff-rf]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
@ -1230,6 +1252,31 @@ upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:lyasi-rgb-light]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DLYASI_LIGHT
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:lyasi-rgb-light-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DLYASI_LIGHT
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:magichome-led-controller] [env:magichome-led-controller]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
@ -2184,6 +2231,30 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:oukitel-p1]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DOUKITEL_P1
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:oukitel-p1-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DOUKITEL_P1
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:tonbux-xsssa01] [env:tonbux-xsssa01]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
@ -2493,6 +2564,17 @@ build_flags = ${common.build_flags_1m0m} -DGENERIC_ESP01S_RELAY_V40
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:generic-esp01s-relay-40-inv]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGENERIC_ESP01S_RELAY_V40 -DRELAY1_TYPE=1
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:generic-esp01s-relay-40-ota] [env:generic-esp01s-relay-40-ota]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
@ -2506,6 +2588,19 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:generic-esp01s-relay-40-inv-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGENERIC_ESP01S_RELAY_V40 -DRELAY1_TYPE=1
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:generic-esp01s-rgbled-10] [env:generic-esp01s-rgbled-10]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
@ -2700,52 +2795,52 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:blitzwolf-bwshp2]
[env:blitzwolf-bwshpx]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
board = ${common.board_1m} board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode} board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHP2
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHPX
upload_speed = ${common.upload_speed} upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:blitzwolf-bwshp2-ota]
[env:blitzwolf-bwshpx-ota]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
board = ${common.board_1m} board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode} board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHP2
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHPX
upload_speed = ${common.upload_speed} upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port} upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:blitzwolf-bwshp2-v23]
[env:blitzwolf-bwshpx-v23]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
board = ${common.board_1m} board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode} board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHP2_V23
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHPX_V23
upload_speed = ${common.upload_speed} upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:blitzwolf-bwshp2-v23-ota]
[env:blitzwolf-bwshpx-v23-ota]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
board = ${common.board_1m} board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode} board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHP2_V23
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHPX_V23
upload_speed = ${common.upload_speed} upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port} upload_port = ${common.upload_port}
@ -2778,6 +2873,32 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:gosund-ws1]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGOSUND_WS1
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:gosund-ws1-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGOSUND_WS1
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:gosund-sp1-v23] [env:gosund-sp1-v23]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
@ -3079,4 +3200,4 @@ upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port} upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
extra_scripts = ${common.extra_scripts}

+ 11
- 7
pre-commit View File

@ -15,17 +15,18 @@ Copy this file to .git/hooks/
""" """
import os import os
import sys
import string
import re import re
import string
import sys
from subprocess import call, check_output from subprocess import call, check_output
try: try:
from urllib.parse import urlparse from urllib.parse import urlparse
except ImportError: except ImportError:
from urlparse import urlparse from urlparse import urlparse
from fileinput import FileInput from fileinput import FileInput
# https://github.com/python/cpython/commit/6cb7b659#diff-78790b53ff259619377058acd4f74672 # https://github.com/python/cpython/commit/6cb7b659#diff-78790b53ff259619377058acd4f74672
if sys.version_info[0] < 3: if sys.version_info[0] < 3:
class FileInputCtx(FileInput): class FileInputCtx(FileInput):
@ -35,6 +36,7 @@ if sys.version_info[0] < 3:
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
self.close() self.close()
FileInput = FileInputCtx FileInput = FileInputCtx
@ -91,15 +93,15 @@ def espurna_get_version(base, version_h="code/espurna/config/version.h"):
TEMPLATES = { TEMPLATES = {
"![travis]": "[![travis](https://travis-ci.org/{USER}/{REPO}.svg?branch={BRANCH})]" "![travis]": "[![travis](https://travis-ci.org/{USER}/{REPO}.svg?branch={BRANCH})]"
"(https://travis-ci.org/{USER}/{REPO})\n",
"![version]": "[![version](https://img.shields.io/badge/version-{VERSION:escape_hyphen}-brightgreen.svg)](CHANGELOG.md)\n",
"(https://travis-ci.org/{USER}/{REPO})\n",
"![version]": "[![version](https://img.shields.io/badge/version-{VERSION:escape_hyphen}-brightgreen.svg)]"
"(CHANGELOG.md)\n",
"![branch]": "[![branch](https://img.shields.io/badge/branch-{BRANCH:escape_hyphen}-orange.svg)]" "![branch]": "[![branch](https://img.shields.io/badge/branch-{BRANCH:escape_hyphen}-orange.svg)]"
"(https://github.com/{USER}/{REPO}/tree/{BRANCH}/)\n",
"(https://github.com/{USER}/{REPO}/tree/{BRANCH}/)\n",
} }
README = "README.md" README = "README.md"
if __name__ == "__main__": if __name__ == "__main__":
base = os.getcwd() base = os.getcwd()
@ -117,6 +119,7 @@ if __name__ == "__main__":
for k, tmpl in TEMPLATES.items() for k, tmpl in TEMPLATES.items()
] ]
def fmt_line(line): def fmt_line(line):
for match, tmpl in templates: for match, tmpl in templates:
if match in line: if match in line:
@ -124,6 +127,7 @@ if __name__ == "__main__":
return line return line
path = os.path.join(base, README) path = os.path.join(base, README)
with FileInput(path, inplace=True) as readme: with FileInput(path, inplace=True) as readme:


Loading…
Cancel
Save