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());
}
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
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);
if (strcmp(MQTT_TOPIC_CHANNEL, topic) == 0) {
@ -76,7 +81,7 @@ void alexaSetup() {
moveSetting("fauxmoEnabled", "alexaEnabled");
// Basic fauxmoESP configuration
alexa.createServer(false);
alexa.createServer(!WEB_SUPPORT);
alexa.setPort(80);
// Uses hostname as base name for all devices
@ -113,6 +118,8 @@ void alexaSetup() {
// Websockets
#if WEB_SUPPORT
webBodyRegister(_alexaBodyCallback);
webRequestRegister(_alexaRequestCallback);
wsOnSendRegister(_alexaWebSocketOnSend);
wsOnReceiveRegister(_alexaWebSocketOnReceive);
#endif
@ -134,8 +141,6 @@ void alexaSetup() {
});
// Register main callbacks
webBodyRegister(_alexaBodyCallback);
webRequestRegister(_alexaRequestCallback);
#if BROKER_SUPPORT
brokerRegister(_alexaBrokerCallback);
#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>
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);
}
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);
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

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

@ -105,37 +105,61 @@ void buttonEvent(unsigned int id, unsigned char event) {
}
#endif
if (action == BUTTON_MODE_TOGGLE) {
if (BUTTON_MODE_TOGGLE == action) {
if (_buttons[id].relayID > 0) {
relayToggle(_buttons[id].relayID - 1);
}
}
if (action == BUTTON_MODE_ON) {
if (BUTTON_MODE_ON == action) {
if (_buttons[id].relayID > 0) {
relayStatus(_buttons[id].relayID - 1, true);
}
}
if (action == BUTTON_MODE_OFF) {
if (BUTTON_MODE_OFF == action) {
if (_buttons[id].relayID > 0) {
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);
}
if (action == BUTTON_MODE_FACTORY) {
if (BUTTON_MODE_FACTORY == action) {
DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
resetSettings();
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() {


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

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


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

@ -41,6 +41,7 @@
//#define ELECTRODRAGON_WIFI_IOT
//#define WORKCHOICE_ECOPLUG
//#define AITHINKER_AI_LIGHT
//#define LYASI_LIGHT
//#define MAGICHOME_LED_CONTROLLER
//#define MAGICHOME_LED_CONTROLLER_20
//#define HUACANXING_H801
@ -72,6 +73,7 @@
//#define LINGAN_SWA1
//#define HEYGO_HY02
//#define MAXCIO_WUS002S
//#define OUKITEL_P1
//#define YIDIAN_XSSSA05
//#define TONBUX_XSSSA06
//#define TONBUX_XSSSA01
@ -90,7 +92,7 @@
//#define NEO_COOLCAM_NAS_WR01W
//#define ESTINK_WIFI_POWER_STRIP
//#define PILOTAK_ESP_DIN_V1
//#define BLITZWOLF_BWSHP2
//#define BLITZWOLF_BWSHPX
//#define BH_ONOFRE
//#define ITEAD_SONOFF_IFAN02
//#define GENERIC_AG_L4
@ -111,7 +113,9 @@
//#define GBLIFE_RGBW_SOCKET
//#define SMARTLIFE_MINI_SMART_SOCKET
//#define GOSUND_SP1_V23
//#define GOSUND_WS1
//#define ARILUX_AL_LC02_V14
//#define BLITZWOLF_BWSHPX_V23
//#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_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_STATUS 3 // Send it upon MQTT connection and every HEARTBEAT_INTERVAL only STATUS report
// Backwards compatibility check
#if defined(HEARTBEAT_ENABLED) && (HEARTBEAT_ENABLED == 0)
@ -180,7 +181,7 @@
#endif
#ifndef HEARTBEAT_INTERVAL
#define HEARTBEAT_INTERVAL 300000 // Interval between heartbeat messages (in ms)
#define HEARTBEAT_INTERVAL 300 // Interval between heartbeat messages (in sec)
#endif
#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value
@ -234,6 +235,10 @@
#define HEARTBEAT_REPORT_HOSTNAME 1
#endif
#ifndef HEARTBEAT_REPORT_DESCRIPTION
#define HEARTBEAT_REPORT_DESCRIPTION 1
#endif
#ifndef HEARTBEAT_REPORT_APP
#define HEARTBEAT_REPORT_APP 1
#endif
@ -801,6 +806,7 @@
#define MQTT_TOPIC_APP "app"
#define MQTT_TOPIC_INTERVAL "interval"
#define MQTT_TOPIC_HOSTNAME "host"
#define MQTT_TOPIC_DESCRIPTION "desc"
#define MQTT_TOPIC_TIME "time"
#define MQTT_TOPIC_RFOUT "rfout"
#define MQTT_TOPIC_RFIN "rfin"
@ -881,6 +887,10 @@
#define LIGHT_SAVE_ENABLED 1 // Light channel values saved by default after each change
#endif
#ifndef LIGHT_COMMS_DELAY
#define LIGHT_COMMS_DELAY 100 // Delay communication after light update (in ms)
#endif
#ifndef LIGHT_SAVE_DELAY
#define LIGHT_SAVE_DELAY 5 // Persist color after 5 seconds to avoid wearing out
#endif


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

@ -112,7 +112,7 @@
#define MANUFACTURER "WEMOS"
#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
#define BUTTON1_PIN 0 // Connect a pushbutton between D3 and GND,
// it's the same as using a Wemos one button shield
@ -1151,6 +1151,28 @@
#define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT
#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
// -----------------------------------------------------------------------------
@ -2079,6 +2101,38 @@
#define LED1_PIN 13
#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
// -----------------------------------------------------------------------------
@ -2277,7 +2331,9 @@
// Relays
#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
#define LED1_PIN 2
@ -2667,7 +2723,8 @@
#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)
// * HomeCube (https://www.amazon.de/Steckdose-Homecube-intelligente-Verbrauchsanzeige-funktioniert/dp/B076Q2LKHG)
// * 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)
// -----------------------------------------------------------------------------
#elif defined(BLITZWOLF_BWSHP2)
#elif defined(BLITZWOLF_BWSHPX)
// Info
#define MANUFACTURER "BLITZWOLF"
#define DEVICE "BWSHP2"
#define DEVICE "BWSHPX"
// Buttons
#define BUTTON1_PIN 13
@ -2714,13 +2771,15 @@
// -----------------------------------------------------------------------------
// 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
#define MANUFACTURER "BLITZWOLF"
#define DEVICE "BWSHP2V2.3"
#define DEVICE "BWSHPX_V23"
// Buttons
#define BUTTON1_PIN 3
@ -2798,6 +2857,26 @@
// 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)
// Info
@ -3357,6 +3436,7 @@
#define PMSX003_SUPPORT 1
#define SENSEAIR_SUPPORT 1
#define VL53L1X_SUPPORT 1
#define MAX6675_SUPPORT 1
// A bit of lights - pin 5
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
@ -3392,6 +3472,14 @@
#define MICS2710_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)
// Relay provider dual
@ -3498,6 +3586,7 @@
#define LLMNR_SUPPORT 1
#define NETBIOS_SUPPORT 1
#define SSDP_SUPPORT 1
#define RF_SUPPORT 1
#endif


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

@ -24,7 +24,7 @@ extern "C" {
// Broker
// -----------------------------------------------------------------------------
#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
// -----------------------------------------------------------------------------
@ -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);
void settingsGetJson(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
@ -178,6 +184,8 @@ void webRequestRegister(web_request_callback_f callback);
#if WEB_SUPPORT
typedef std::function<void(JsonObject&)> ws_on_send_callback_f;
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);
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;
void wsOnReceiveRegister(ws_on_receive_callback_f callback);
bool wsConnected();
bool wsConnected(uint32_t);
bool wsDebugSend(const char*, const char*);
#else
#define ws_on_send_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
//------------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// 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
// -----------------------------------------------------------------------------
@ -39,6 +48,9 @@
#define BUTTON_MODE_FACTORY 7
#define BUTTON_MODE_WPS 8
#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 (?)
#ifndef BUTTON_PUSHBUTTON
@ -284,6 +296,7 @@
#define SENSOR_VL53L1X_ID 32
#define SENSOR_EZOPH_ID 33
#define SENSOR_BMP180_ID 34
#define SENSOR_MAX6675_ID 35
//--------------------------------------------------------------------------------
// 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
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;
char timestamp[10] = {0};
#if DEBUG_ADD_TIMESTAMP
static bool add_timestamp = true;
char timestamp[10] = {0};
if (add_timestamp) snprintf_P(timestamp, sizeof(timestamp), PSTR("[%06lu] "), millis() % 1000000);
add_timestamp = (message[strlen(message)-1] == 10) || (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
#if DEBUG_SERIAL_SUPPORT
#if DEBUG_ADD_TIMESTAMP
DEBUG_PORT.printf(timestamp);
#endif
DEBUG_PORT.printf(message);
_debugSendSerial(timestamp, message);
#endif
#if DEBUG_UDP_SUPPORT
@ -51,31 +72,13 @@ void _debugSend(char * message) {
#endif
#if DEBUG_TELNET_SUPPORT
#if DEBUG_ADD_TIMESTAMP
_telnetWrite(timestamp, strlen(timestamp));
#endif
_telnetWrite(message, strlen(message));
_debugSendTelnet(timestamp, message);
pause = true;
#endif
#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
if (pause) optimistic_yield(100);
@ -128,12 +131,16 @@ void debugWebSetup() {
});
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


+ 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>
bool _dcz_enabled = false;
std::vector<bool> _dcz_relay_state;
//------------------------------------------------------------------------------
// 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) {
if (!_dcz_enabled) return;
@ -70,6 +80,7 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
String stype = root["stype"];
if (
(stype.equals("RGB") || stype.equals("RGBW") || stype.equals("RGBWW"))
&& domoticzIdx(0) == idx
) {
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (lightHasColor()) {
@ -127,7 +138,7 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
if (relayID >= 0) {
unsigned char value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx);
relayStatus(relayID, value > 0);
_domoticzStatus(relayID, value > 0);
}
}
#else
@ -138,7 +149,7 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
if (relayID >= 0) {
unsigned char value = root["nvalue"];
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
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) {
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
@ -165,7 +183,7 @@ bool _domoticzWebSocketOnReceive(const char * key, JsonVariant& value) {
void _domoticzWebSocketOnSend(JsonObject& root) {
root["dczVisible"] = 1;
unsigned char visible = 0;
root["dczEnabled"] = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC);
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
@ -174,18 +192,15 @@ void _domoticzWebSocketOnSend(JsonObject& root) {
for (unsigned char i=0; i<relayCount(); i++) {
relays.add(domoticzIdx(i));
}
visible = (relayCount() > 0);
#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
root["dczVisible"] = visible;
}
#endif // WEB_SUPPORT
@ -193,6 +208,12 @@ void _domoticzWebSocketOnSend(JsonObject& root) {
void _domoticzConfigure() {
bool enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
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;
}


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

@ -58,43 +58,43 @@ void eepromCommit() {
void _eepromInitCommands() {
settingsRegisterCommand(F("EEPROM"), [](Embedis* e) {
terminalRegisterCommand(F("EEPROM"), [](Embedis* e) {
infoMemory("EEPROM", SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE - settingsSize());
eepromSectorsDebug();
if (_eeprom_commit_count > 0) {
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("+OK\n"));
terminalOK();
});
settingsRegisterCommand(F("EEPROM.COMMIT"), [](Embedis* e) {
terminalRegisterCommand(F("EEPROM.COMMIT"), [](Embedis* e) {
const bool res = _eepromCommit();
if (res) {
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
} else {
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) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
return;
}
uint32_t sector = String(e->argv[1]).toInt();
uint32_t max = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE;
if (sector >= max) {
DEBUG_MSG_P(PSTR("-ERROR: Sector out of range\n"));
terminalError(F("Sector out of range"));
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
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);
} else {


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

@ -64,11 +64,16 @@ void setup() {
// Init EEPROM
eepromSetup();
// Init persistance
settingsSetup();
// Init Serial, SPIFFS and system check
systemSetup();
// Init persistance and terminal features
settingsSetup();
// Init terminal features
#if TERMINAL_SUPPORT
terminalSetup();
#endif
// Hostname & board name initialization
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
#include <ArduinoJson.h>
#include <queue>
bool _haEnabled = 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))
String type = String("light");
@ -163,8 +162,16 @@ String _haGetConfig() {
JsonObject& config = jsonBuffer.createObject();
_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;
for (auto kv : config) {
if (first) {
output += " - ";
@ -172,11 +179,21 @@ String _haGetConfig() {
} else {
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();
printer(output);
}
#if SENSOR_SUPPORT
@ -187,8 +204,16 @@ String _haGetConfig() {
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
output += "\nsensor:\n";
String output;
output.reserve(config.measureLength() + 32);
if (wrapJson) {
output += "{\"haConfig\": \"";
}
output += "\n\nsensor:\n";
bool first = true;
for (auto kv : config) {
if (first) {
output += " - ";
@ -198,18 +223,25 @@ String _haGetConfig() {
}
String value = kv.value.as<String>();
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();
printer(output);
}
#endif
return output;
}
void _haSend() {
@ -241,6 +273,8 @@ void _haConfigure() {
#if WEB_SUPPORT
std::queue<uint32_t> _ha_send_config;
bool _haWebSocketOnReceive(const char * key, JsonVariant& value) {
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) {
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() {
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");
_haConfigure();
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
#endif
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
terminalRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
setSetting("haEnabled", "0");
_haConfigure();
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
#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() {
_haConfigure();
@ -304,6 +354,7 @@ void haSetup() {
wsOnSendRegister(_haWebSocketOnSend);
wsOnActionRegister(_haWebSocketOnAction);
wsOnReceiveRegister(_haWebSocketOnReceive);
espurnaRegisterLoop(_haLoop);
#endif
#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"));
}
void i2cCommands() {
terminalRegisterCommand(F("I2C.SCAN"), [](Embedis* e) {
i2cScan();
terminalOK();
});
terminalRegisterCommand(F("I2C.CLEAR"), [](Embedis* e) {
i2cClearBus();
terminalOK();
});
}
void i2cSetup() {
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);
#if TERMINAL_SUPPORT
i2cCommands();
#endif
#if I2C_CLEAR_BUS
i2cClearBus();
#endif


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

@ -39,10 +39,13 @@ void _idbConfigure() {
}
#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);
}
}
#endif // BROKER_SUPPORT


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

@ -74,10 +74,15 @@ void _ledWebSocketOnSend(JsonObject& root) {
#endif
#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) {
ledUpdate(true);
}
}
#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_transition_ticker;
@ -640,7 +641,7 @@ void lightBroker() {
char buffer[10];
for (unsigned int i=0; i < _light_channel.size(); i++) {
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;
}
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) {
_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_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
// 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;
}
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_use_cct) {
root["useCCT"] = _light_use_cct;
root["mireds"] = _light_mireds;
}
if (useRGB) {
if (getSetting("useRGB", LIGHT_USE_RGB).toInt() == 1) {
root["rgb"] = lightColor(true);
} else {
root["hsv"] = lightColor(false);
@ -839,7 +840,20 @@ void _lightWebSocketOnSend(JsonObject& root) {
for (unsigned char id=0; id < _light_channel.size(); 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) {
@ -972,18 +986,18 @@ void _lightAPISetup() {
void _lightInitCommands() {
settingsRegisterCommand(F("BRIGHTNESS"), [](Embedis* e) {
terminalRegisterCommand(F("BRIGHTNESS"), [](Embedis* e) {
if (e->argc > 1) {
lightBrightness(String(e->argv[1]).toInt());
lightUpdate(true, true);
}
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) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
}
int id = String(e->argv[1]).toInt();
if (e->argc > 2) {
@ -992,37 +1006,37 @@ void _lightInitCommands() {
lightUpdate(true, true);
}
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) {
String color = String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
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) {
String color = String("K") + String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
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) {
String color = String("M") + String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
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() {
settingsRegisterCommand(F("LIGHTFOX.LEARN"), [](Embedis* e) {
terminalRegisterCommand(F("LIGHTFOX.LEARN"), [](Embedis* e) {
lightfoxLearn();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("LIGHTFOX.CLEAR"), [](Embedis* e) {
terminalRegisterCommand(F("LIGHTFOX.CLEAR"), [](Embedis* e) {
lightfoxClear();
DEBUG_MSG_P(PSTR("+OK\n"));
});


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

@ -257,6 +257,17 @@ void migrate() {
setSetting("myDCKIGPIO", 15);
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)
setSetting("board", 21);
@ -1245,9 +1256,21 @@ void migrate() {
setSetting("relayGPIO", 0, 4);
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)
setSetting("board", 92);
setSetting("board", 95);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 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_use_json = false;
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;
bool _mqtt_retain = MQTT_RETAIN;
unsigned long _mqtt_keepalive = MQTT_KEEPALIVE;
@ -48,10 +50,6 @@ char *_mqtt_user = 0;
char *_mqtt_pass = 0;
char *_mqtt_will;
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;
@ -72,11 +70,11 @@ void _mqttConnect() {
// Do not connect if disabled
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
if (millis() - _mqtt_disconnected_at < _mqtt_reconnect_delay) return;
if (millis() - _mqtt_last_connection < _mqtt_reconnect_delay) return;
// Increase the reconnect delay
_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);
#if MQTT_USE_ASYNC
_mqtt_connecting = true;
_mqtt.setServer(host, port);
_mqtt.setClientId(_mqtt_clientid);
@ -350,10 +349,10 @@ void _mqttWebSocketOnSend(JsonObject& root) {
void _mqttInitCommands() {
settingsRegisterCommand(F("MQTT.RESET"), [](Embedis* e) {
terminalRegisterCommand(F("MQTT.RESET"), [](Embedis* e) {
_mqttConfigure();
mqttDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
}
@ -397,9 +396,7 @@ void _mqttOnConnect() {
DEBUG_MSG_P(PSTR("[MQTT] Connected!\n"));
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
#if MQTT_SKIP_RETAINED
_mqtt_connected_at = millis();
#endif
_mqtt_last_connection = millis();
// Clean subscriptions
mqttUnsubscribeRaw("#");
@ -413,7 +410,9 @@ void _mqttOnConnect() {
void _mqttOnDisconnect() {
_mqtt_disconnected_at = millis();
// Reset reconnection delay
_mqtt_last_connection = millis();
_mqtt_connecting = false;
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);
#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);
return;
}


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

@ -74,8 +74,8 @@ void _nofussConfigure() {
void _nofussInitCommands() {
settingsRegisterCommand(F("NOFUSS"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
terminalRegisterCommand(F("NOFUSS"), [](Embedis* e) {
terminalOK();
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["ntpDST"] = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1;
root["ntpRegion"] = getSetting("ntpRegion", NTP_DST_REGION).toInt();
if (ntpSynced()) root["now"] = now();
}
#endif
@ -108,7 +107,7 @@ void _ntpLoop() {
static unsigned char last_minute = 60;
if (ntpSynced() && (minute() != last_minute)) {
last_minute = minute();
brokerPublish(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
brokerPublish(BROKER_MSG_TYPE_DATETIME, MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
}
#endif


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

@ -28,7 +28,7 @@ void _otaLoop() {
// Terminal OTA
// -----------------------------------------------------------------------------
#if TERMINAL_SUPPORT
#if TERMINAL_SUPPORT || OTA_MQTT_SUPPORT
#include <ESPAsyncTCP.h>
AsyncClient * _ota_client;
@ -186,13 +186,18 @@ void _otaFrom(String url) {
}
#endif // TERMINAL_SUPPORT || OTA_MQTT_SUPPORT
#if TERMINAL_SUPPORT
void _otaInitCommands() {
settingsRegisterCommand(F("OTA"), [](Embedis* e) {
terminalRegisterCommand(F("OTA"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
} else {
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
String url = String(e->argv[1]);
_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(status);
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();
#endif
@ -179,7 +184,7 @@ void _relayProcess(bool mode) {
// Send to Broker
#if BROKER_SUPPORT
brokerPublish(MQTT_TOPIC_RELAY, id, target ? "1" : "0");
brokerPublish(BROKER_MSG_TYPE_STATUS, MQTT_TOPIC_RELAY, id, target ? "1" : "0");
#endif
// Send MQTT
@ -554,6 +559,9 @@ void _relayBoot() {
void _relayConfigure() {
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;
pinMode(_relays[i].pin, OUTPUT);
@ -564,8 +572,6 @@ void _relayConfigure() {
//set to high to block short opening of relay
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) {
JsonArray& relay = root.createNestedArray("relayStatus");
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 (RELAY_PROVIDER == RELAY_PROVIDER_LIGHT)
uint8_t physical = _relays.size() - DUMMY_RELAY_COUNT;
if (i >= physical) {
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)) {
if (physical == i) {
line["gpio"] = String("Light");
res = String("Light");
} else {
line["gpio"] = String("CH") + String(i-1-physical);
res = String("CH") + String(i-1-physical);
}
} else {
line["gpio"] = String("Light");
res = String("Light");
}
} else {
line["gpio"] = String("?");
res = String("?");
}
#else
line["gpio"] = String("SW") + String(i);
res = String("SW") + String(i);
#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() {
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++) {
_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) {
@ -985,9 +1004,9 @@ void relaySetupMQTT() {
void _relayInitCommands() {
settingsRegisterCommand(F("RELAY"), [](Embedis* e) {
terminalRegisterCommand(F("RELAY"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
return;
}
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("+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
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() {
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) {
@ -152,9 +166,6 @@ void _rfbLearn() {
}
#if not RF_SUPPORT
/*
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;
}
#if not RF_SUPPORT
void _rfbSendRaw(const byte *message, const unsigned char n = RF_MESSAGE_SIZE) {
for (unsigned char j=0; j<n; j++) {
Serial.write(message[j]);
@ -338,7 +351,7 @@ void _rfbDecode() {
// Websocket update
#if WEB_SUPPORT
_rfbWebSocketSendCode(_learnId, _learnStatus, buffer);
_rfbWebSocketSendCode(_learnId);
#endif
}
@ -556,6 +569,7 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
void _rfbAPISetup() {
#if not RF_SUPPORT
apiRegister(MQTT_TOPIC_RFOUT,
[](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("OK"));
@ -564,6 +578,7 @@ void _rfbAPISetup() {
_rfbParseCode((char *) payload);
}
);
#endif // RF_SUPPORT
apiRegister(MQTT_TOPIC_RFLEARN,
[](char * buffer, size_t len) {
@ -605,10 +620,10 @@ void _rfbAPISetup() {
void _rfbInitCommands() {
settingsRegisterCommand(F("LEARN"), [](Embedis* e) {
terminalRegisterCommand(F("LEARN"), [](Embedis* e) {
if (e->argc < 3) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
return;
}
@ -622,14 +637,14 @@ void _rfbInitCommands() {
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) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
return;
}
@ -643,7 +658,7 @@ void _rfbInitCommands() {
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){
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


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

@ -102,6 +102,32 @@ double _magnitudeProcess(unsigned char type, double value) {
#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) {
if (strncmp(key, "pwr", 3) == 0) return true;
if (strncmp(key, "sns", 3) == 0) return true;
@ -118,27 +144,36 @@ void _sensorWebSocketSendData(JsonObject& root) {
bool hasHumidity = 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];
if (magnitude.type == MAGNITUDE_EVENT) continue;
++size;
unsigned char decimals = _magnitudeDecimals(magnitude.type);
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 (_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 {
element["description"] = magnitude.sensor->slot(magnitude.local);
description.add(magnitude.sensor->slot(magnitude.local));
}
if (magnitude.type == MAGNITUDE_TEMPERATURE) hasTemperature = true;
@ -148,6 +183,8 @@ void _sensorWebSocketSendData(JsonObject& root) {
#endif
}
magnitudes["size"] = size;
if (hasTemperature) root["temperatureVisible"] = 1;
if (hasHumidity) root["humidityVisible"] = 1;
if (hasMICS) root["micsVisible"] = 1;
@ -210,7 +247,7 @@ void _sensorWebSocketStart(JsonObject& root) {
}
if (_magnitudes.size() > 0) {
if (magnitudeCount()) {
root["snsVisible"] = 1;
//root["apiRealTime"] = _sensor_realtime;
root["pwrUnits"] = _sensor_power_units;
@ -271,7 +308,7 @@ void _sensorAPISetup() {
#if TERMINAL_SUPPORT
void _sensorInitCommands() {
settingsRegisterCommand(F("MAGNITUDES"), [](Embedis* e) {
terminalRegisterCommand(F("MAGNITUDES"), [](Embedis* e) {
for (unsigned char i=0; i<_magnitudes.size(); i++) {
sensor_magnitude_t magnitude = _magnitudes[i];
DEBUG_MSG_P(PSTR("[SENSOR] * %2d: %s @ %s (%s/%d)\n"),
@ -282,33 +319,33 @@ void _sensorInitCommands() {
magnitude.global
);
}
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
#if PZEM004T_SUPPORT
settingsRegisterCommand(F("PZ.ADDRESS"), [](Embedis* e) {
terminalRegisterCommand(F("PZ.ADDRESS"), [](Embedis* e) {
if (e->argc == 1) {
DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
unsigned char dev_count = pzem004t_sensor->getAddressesCount();
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("+OK\n"));
terminalOK();
} else if(e->argc == 2) {
IPAddress addr;
if (addr.fromString(String(e->argv[1]))) {
if(pzem004t_sensor->setDeviceAddress(&addr)) {
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}
} else {
DEBUG_MSG_P(PSTR("-ERROR: Invalid address argument\n"));
terminalError(F("Invalid address argument"));
}
} 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) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
} else {
unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0;
unsigned char limit = e->argc == 2 ? init +1 : pzem004t_sensor->getAddressesCount();
@ -318,12 +355,12 @@ void _sensorInitCommands() {
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("+OK\n"));
terminalOK();
}
});
settingsRegisterCommand(F("PZ.VALUE"), [](Embedis* e) {
terminalRegisterCommand(F("PZ.VALUE"), [](Embedis* e) {
if(e->argc > 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
} else {
unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0;
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_ENERGY_INDEX)).c_str());
}
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
}
});
#endif
@ -1172,7 +1209,7 @@ void _sensorReport(unsigned char index, double value) {
dtostrf(value, 1-sizeof(buffer), decimals, buffer);
#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
#if MQTT_SUPPORT
@ -1191,14 +1228,6 @@ void _sensorReport(unsigned char index, double value) {
#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
tspkEnqueueMeasurement(index, buffer);
#endif


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

@ -22,6 +22,7 @@
#define PMS_TYPE_X003_9 1
#define PMS_TYPE_5003T 2
#define PMS_TYPE_5003ST 3
#define PMS_TYPE_5003S 4
// Sensor type specified data
#define PMS_SLOT_MAX 4
@ -35,7 +36,8 @@ const static struct {
{"PMSX003", 13, 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}},
{"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]
@ -306,6 +308,10 @@ class PMSX003Sensor : public BaseSensor, PMSX003 {
_slot_values[1] = (double)data[13] / 10;
_slot_values[2] = (double)data[14] / 10;
_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) {
_slot_values[0] = data[4];
_slot_values[1] = (double)data[10] / 10;


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

@ -2,25 +2,12 @@
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 "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
@ -106,214 +93,6 @@ std::vector<String> _settingsKeys() {
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
// -----------------------------------------------------------------------------
@ -369,21 +148,16 @@ void saveSettings() {
}
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 size = EEPROM_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
// -----------------------------------------------------------------------------
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"),
SPI_FLASH_SEC_SIZE,
[](size_t pos) -> char { return EEPROMr.read(pos); },
@ -467,44 +228,4 @@ void settingsSetup() {
#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;
bool _system_send_heartbeat = false;
unsigned char _heartbeat_mode = HEARTBEAT_MODE;
unsigned long _heartbeat_interval = HEARTBEAT_INTERVAL;
// Calculated load average 0 to 100;
unsigned short int _load_average = 100;
@ -66,6 +68,10 @@ void systemSendHeartbeat() {
_system_send_heartbeat = true;
}
bool systemGetHeartbeat() {
return _system_send_heartbeat;
}
unsigned long systemLoopDelay() {
return _loop_delay;
}
@ -75,6 +81,17 @@ unsigned long systemLoadAverage() {
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() {
// -------------------------------------------------------------------------
@ -97,19 +114,21 @@ void systemLoop() {
// 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;
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();
heartbeat();
_system_send_heartbeat = false;
}
#endif // HEARTBEAT_MODE == HEARTBEAT_REPEAT
}
// -------------------------------------------------------------------------
// Load Average calculation
@ -169,6 +188,10 @@ void systemSetup() {
systemCheck(false);
#endif
#if WEB_SUPPORT
wsOnReceiveRegister(_systemWebSocketOnReceive);
#endif
// Init device-specific hardware
_systemSetupSpecificHardware();
@ -179,4 +202,7 @@ void systemSetup() {
// Register Loop
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);
}
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()) {
return (_telnetClients[clientId]->write((const char*) data, len) > 0);
return (_telnetClients[clientId]->write(data, len) > 0);
}
return false;
}
unsigned char _telnetWrite(void *data, size_t len) {
unsigned char _telnetWrite(const char *data, size_t len) {
unsigned char count = 0;
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
// Do not send broadcast messages to unauthenticated clients
@ -65,8 +65,12 @@ unsigned char _telnetWrite(void *data, size_t len) {
return count;
}
unsigned char _telnetWrite(const char *data) {
return _telnetWrite(data, strlen(data));
}
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) {
@ -113,7 +117,9 @@ void _telnetData(unsigned char clientId, void *data, size_t len) {
}
// 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
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);
tspkFlush();
}
// Porcess sensor messages
if (BROKER_MSG_TYPE_SENSOR == type) {
//tspkEnqueueMeasurement(id, (char *) payload);
//tspkFlush();
}
}
#endif // BROKER_SUPPORT
@ -66,15 +75,8 @@ void _tspkWebSocketOnSend(JsonObject& root) {
if (relayCount() > 0) visible = 1;
#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
root["tspkVisible"] = visible;


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

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


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

@ -385,39 +385,39 @@ void _wifiDebugCallback(justwifi_messages_t code, char * parameter) {
void _wifiInitCommands() {
settingsRegisterCommand(F("WIFI"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI"), [](Embedis* e) {
wifiDebug();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
settingsRegisterCommand(F("WIFI.RESET"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.RESET"), [](Embedis* e) {
_wifiConfigure();
wifiDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
settingsRegisterCommand(F("WIFI.AP"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.AP"), [](Embedis* e) {
wifiStartAP();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
#if defined(JUSTWIFI_ENABLE_WPS)
settingsRegisterCommand(F("WIFI.WPS"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.WPS"), [](Embedis* e) {
wifiStartWPS();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
#endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
settingsRegisterCommand(F("WIFI.SMARTCONFIG"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.SMARTCONFIG"), [](Embedis* e) {
wifiStartSmartConfig();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
settingsRegisterCommand(F("WIFI.SCAN"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.SCAN"), [](Embedis* e) {
_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
@ -289,76 +310,119 @@ void _wsUpdate(JsonObject& root) {
#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) {
if (strncmp(key, "ws", 2) == 0) return true;
if (strncmp(key, "admin", 5) == 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;
return false;
}
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
bool changePassword = getAdminPass().equals(ADMIN_PASS);
#else
bool changePassword = false;
#endif
if (changePassword) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
if (changePassword) {
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){
@ -406,12 +470,8 @@ void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTy
}
void _wsLoop() {
static unsigned long last = 0;
if (!wsConnected()) return;
if (millis() - last > WS_UPDATE_INTERVAL) {
last = millis();
wsSend(_wsUpdate);
}
_wsDoUpdate();
}
// -----------------------------------------------------------------------------
@ -422,6 +482,10 @@ bool wsConnected() {
return (_ws.count() > 0);
}
bool wsConnected(uint32_t client_id) {
return _ws.hasClient(client_id);
}
void wsOnSendRegister(ws_on_send_callback_f callback) {
_ws_on_send_callbacks.push_back(callback);
}
@ -439,10 +503,8 @@ void wsSend(ws_on_send_callback_f callback) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
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) {
AsyncWebSocketClient* client = _ws.client(client_id);
if (client == nullptr) return;
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
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) {


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

@ -657,7 +657,10 @@ function doScan() {
}
function doHAConfig() {
$("#haConfig").html("");
$("#haConfig")
.text("")
.height(0)
.show();
sendAction("haconfig", {});
return false;
}
@ -767,12 +770,13 @@ function createMagnitudeList(data, container, template_name) {
if (current > 0) { return; }
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();
$("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);
}
@ -934,24 +938,34 @@ function createCheckboxes() {
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();
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();
$("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)-->
function initMagnitudes(data) {
// check if already initialized
// check if already initialized (each magnitude is inside div.pure-g)
var done = $("#magnitudes > div").length;
if (done > 0) { return; }
var size = data.size;
// add templates
var template = $("#magnitudeTemplate").children();
for (var i in data) {
var magnitude = data[i];
for (var i=0; i<size; ++i) {
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);
line.appendTo("#magnitudes");
}
@ -1244,11 +1260,18 @@ function processData(data) {
}
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;
}
<!-- endRemoveIf(!rfbridge)-->
@ -1376,15 +1399,13 @@ function processData(data) {
if ("magnitudes" === key) {
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) ?
magnitude.value + magnitude.units :
value.value[i] + value.units[i] :
magnitudeError(error);
var element = $("input[name='magnitude'][data='" + i + "']");
element.val(text);
$("div.hint", element.parent().parent()).html(magnitude.description);
}
return;
}
@ -1421,7 +1442,11 @@ function processData(data) {
// -----------------------------------------------------------------------------
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;
}
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);
$("select[name='" + key + "']", sch_line).prop("value", sch_value);
$("input[type='checkbox'][name='" + key + "']", sch_line).prop("checked", sch_value);
@ -1514,7 +1539,13 @@ function processData(data) {
// Web log
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());
return;
}
@ -1767,6 +1798,8 @@ $(function() {
$(document).on("change", "input", hasChanged);
$(document).on("change", "select", hasChanged);
$("textarea").on("dblclick", function() { this.select(); });
// don't autoconnect when opening from filesystem
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 small" name="title">ESPurna 0.0.0</span>
<span class="pure-menu-heading small" name="desc"></span>
<ul class="pure-menu-list">
@ -348,6 +349,16 @@
</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">
<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" />
@ -626,6 +637,30 @@
<div class="pure-u-1 pure-u-lg-3-4 hint">Request password when starting telnet session</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">
<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 />
@ -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-0 pure-u-lg-1-4"></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>
</fieldset>
@ -657,6 +692,11 @@
<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">
<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>
@ -1085,7 +1125,7 @@
</div>
</div>
<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>


+ 27
- 25
code/memanalyzer.py View File

@ -19,15 +19,16 @@ import argparse
import os
import re
import shlex
import subprocess
import sys
from collections import OrderedDict
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:
from commands import getstatusoutput as getstatusoutput
from commands import getstatusoutput
# -------------------------------------------------------------------------------
@ -55,7 +56,7 @@ def file_size(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))
if isinstance(response, bytes):
response = response.decode('utf-8')
@ -66,8 +67,8 @@ def analyse_memory(elf_file):
ret = {}
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_end = -1
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))
# 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
def run(env_, modules_):
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)
def calc_free(module):
free = 80 * 1024 - module['data'] - module['rodata'] - module['bss']
free = free + (16 - free % 16)
module['free'] = free
def modules_get():
modules_ = SortedDict()
for line in open("espurna/config/arduino.h"):
@ -120,7 +123,8 @@ def modules_get():
del modules_['NETBIOS']
return modules_
try:
if __name__ == '__main__':
# Parse command line options
parser = argparse.ArgumentParser(description=description)
@ -160,7 +164,7 @@ try:
# Check test modules exist
for module in test_modules:
if module not in available_modules:
print("Module %s not found" % module)
print("Module {} not found".format(module))
sys.exit(2)
# Define base configuration
@ -173,9 +177,9 @@ try:
# Show init message
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:
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}"
print(output_format.format(
@ -211,8 +215,8 @@ try:
# Build the core without modules to get base memory usage
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)
print(output_format.format(
"CORE" if args.core == 1 else "DEFAULT",
@ -231,8 +235,8 @@ try:
modules[module] = 1
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])
modules[module] = 0
@ -253,8 +257,8 @@ try:
for module in test_modules:
modules[module] = 1
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)
if len(test_modules) > 1:
@ -279,7 +283,5 @@ try:
total['size'],
))
except:
raise
print("\n")

+ 56
- 45
code/ota.py View File

@ -8,14 +8,14 @@
# -------------------------------------------------------------------------------
from __future__ import print_function
import shutil
import argparse
import os
import re
import shutil
import socket
import subprocess
import sys
import time
import os
from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf
@ -33,6 +33,7 @@ description = "ESPurna OTA Manager v0.3"
devices = []
discover_last = 0
# -------------------------------------------------------------------------------
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():
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)
device['app'] = device['app_name']
@ -75,35 +76,35 @@ def list_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(
"#",
"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)
index = 0
for device in devices:
index = index + 1
index += 1
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()
@ -120,11 +121,13 @@ def get_boards():
boards.append(m.group(1))
return sorted(boards)
def get_device_size(device):
if device.get('mem_size', 0) == device.get('sdk_size', 0):
return int(device.get('mem_size', 0)) / 1024
return 0
def get_empty_board():
"""
Returns the empty structure of a board to flash
@ -132,12 +135,13 @@ def get_empty_board():
board = {'board': '', 'ip': '', 'size': 0, 'auth': '', 'flags': ''}
return board
def get_board_by_index(index):
"""
Returns the required data to flash a given board
"""
board = {}
if 1 <= index and index <= len(devices):
if 1 <= index <= len(devices):
device = devices[index - 1]
board['hostname'] = device.get('hostname')
board['board'] = device.get('target_board', '')
@ -145,6 +149,7 @@ def get_board_by_index(index):
board['size'] = get_device_size(device)
return board
def get_board_by_mac(mac):
"""
Returns the required data to flash a given board
@ -161,6 +166,7 @@ def get_board_by_mac(mac):
return board
return None
def get_board_by_hostname(hostname):
"""
Returns the required data to flash a given board
@ -178,6 +184,7 @@ def get_board_by_hostname(hostname):
return board
return None
def input_board():
"""
Grabs info from the user about what device to flash
@ -189,10 +196,10 @@ def input_board():
except ValueError:
index = 0
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
board = get_board_by_index(index);
board = get_board_by_index(index)
# Choose board type if none before
if len(board.get('board', '')) == 0:
@ -201,15 +208,15 @@ def input_board():
count = 1
boards = get_boards()
for name in boards:
print("%3d\t%s" % (count, name))
count = count + 1
print("{:3d}\t{}".format(count, name))
count += 1
print()
try:
index = int(input("Choose the board type you want to flash: "))
except ValueError:
index = 0
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
board['board'] = boards[index - 1]
@ -227,12 +234,14 @@ def input_board():
return board
def boardname(board):
return board.get('hostname', board['ip'])
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)
if not os.path.exists(dst_dir):
@ -240,6 +249,7 @@ def store(device, env):
shutil.move(source, destination)
def run(device, env):
print("Building and flashing image over-the-air...")
environ = os.environ.copy()
@ -253,6 +263,7 @@ def run(device, env):
store(device, env)
# -------------------------------------------------------------------------------
if __name__ == '__main__':
@ -272,13 +283,13 @@ if __name__ == '__main__':
print(description)
print()
# Look for sevices
# Look for services
zeroconf = Zeroconf()
browser = ServiceBrowser(zeroconf, "_arduino._tcp.local.", handlers=[on_service_state_change])
discover_last = time.time()
while time.time() < discover_last + DISCOVER_TIMEOUT:
None
#zeroconf.close()
pass
# zeroconf.close()
if len(devices) == 0:
print("Nothing found!\n")
@ -287,7 +298,7 @@ if __name__ == '__main__':
# Sort list
field = args.sort.lower()
if field not in devices[0]:
print("Unknown field '%s'\n" % field)
print("Unknown field '{}'\n".format(field))
sys.exit(1)
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', ''))
# Flash eash board
# Flash each board
for board in queue:
# Flash core version?
if args.core > 0:
board['flags'] = "-DESPURNA_CORE " + board['flags']
env = "esp8266-%dm-ota" % board['size']
env = "esp8266-{:d}m-ota".format(board['size'])
# Summary
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
if args.yes == 0:


+ 131
- 10
code/platformio.ini View File

@ -77,7 +77,7 @@ lib_deps =
ArduinoJson
https://github.com/marvinroger/async-mqtt-client#v0.8.1
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
Embedis
Encoder
@ -458,6 +458,17 @@ build_flags = ${common.build_flags_1m0m} -DITEAD_SONOFF_BASIC -DDHT_SUPPORT=1
monitor_speed = ${common.monitor_speed}
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]
platform = ${common.platform}
framework = ${common.framework}
@ -483,6 +494,17 @@ build_flags = ${common.build_flags_1m0m} -DITEAD_SONOFF_BASIC -DDALLAS_SUPPORT=1
monitor_speed = ${common.monitor_speed}
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]
platform = ${common.platform}
framework = ${common.framework}
@ -1230,6 +1252,31 @@ upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
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]
platform = ${common.platform}
framework = ${common.framework}
@ -2184,6 +2231,30 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
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]
platform = ${common.platform}
framework = ${common.framework}
@ -2493,6 +2564,17 @@ build_flags = ${common.build_flags_1m0m} -DGENERIC_ESP01S_RELAY_V40
monitor_speed = ${common.monitor_speed}
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]
platform = ${common.platform}
framework = ${common.framework}
@ -2506,6 +2588,19 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
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]
platform = ${common.platform}
framework = ${common.framework}
@ -2700,52 +2795,52 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:blitzwolf-bwshp2]
[env:blitzwolf-bwshpx]
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} -DBLITZWOLF_BWSHP2
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHPX
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:blitzwolf-bwshp2-ota]
[env:blitzwolf-bwshpx-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} -DBLITZWOLF_BWSHP2
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHPX
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:blitzwolf-bwshp2-v23]
[env:blitzwolf-bwshpx-v23]
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} -DBLITZWOLF_BWSHP2_V23
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHPX_V23
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:blitzwolf-bwshp2-v23-ota]
[env:blitzwolf-bwshpx-v23-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} -DBLITZWOLF_BWSHP2_V23
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHPX_V23
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
@ -2778,6 +2873,32 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
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]
platform = ${common.platform}
framework = ${common.framework}
@ -3079,4 +3200,4 @@ 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}
extra_scripts = ${common.extra_scripts}

+ 11
- 7
pre-commit View File

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


Loading…
Cancel
Save