Browse Source

Merge branch 'dev' into feature/gizwits_witty_cloud

rfm69
Xose Pérez 6 years ago
committed by GitHub
parent
commit
fb3c595b68
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 5300 additions and 4171 deletions
  1. +0
    -1
      .gitignore
  2. +4
    -4
      README.md
  3. +1
    -1
      code/build.sh
  4. +10
    -12
      code/espurna/button.ino
  5. +18
    -5
      code/espurna/config/all.h
  6. +3
    -3
      code/espurna/config/arduino.h
  7. +5
    -0
      code/espurna/config/build.h
  8. +90
    -9
      code/espurna/config/defaults.h
  9. +81
    -38
      code/espurna/config/general.h
  10. +75
    -33
      code/espurna/config/hardware.h
  11. +1
    -1
      code/espurna/config/prototypes.h
  12. +27
    -4
      code/espurna/config/sensors.h
  13. +1
    -1
      code/espurna/config/version.h
  14. BIN
      code/espurna/data/index.html.gz
  15. +1
    -0
      code/espurna/debug.ino
  16. +11
    -11
      code/espurna/domoticz.ino
  17. +4
    -0
      code/espurna/espurna.ino
  18. +292
    -0
      code/espurna/homeassistant.ino
  19. +0
    -181
      code/espurna/homeassitant.ino
  20. +33
    -11
      code/espurna/led.ino
  21. +1
    -1
      code/espurna/light.ino
  22. +3
    -0
      code/espurna/mdns.ino
  23. +10
    -12
      code/espurna/migrate.ino
  24. +39
    -15
      code/espurna/mqtt.ino
  25. +1
    -1
      code/espurna/nofuss.ino
  26. +1
    -1
      code/espurna/ntp.ino
  27. +186
    -9
      code/espurna/ota.ino
  28. +100
    -74
      code/espurna/relay.ino
  29. +145
    -61
      code/espurna/rf.ino
  30. +19
    -9
      code/espurna/rfbridge.ino
  31. +1
    -0
      code/espurna/scheduler.ino
  32. +67
    -1
      code/espurna/sensor.ino
  33. +1
    -1
      code/espurna/sensors/AnalogSensor.h
  34. +1
    -1
      code/espurna/sensors/BMX280Sensor.h
  35. +6
    -2
      code/espurna/sensors/BaseSensor.h
  36. +58
    -47
      code/espurna/settings.ino
  37. +3209
    -3177
      code/espurna/static/index.html.gz.h
  38. +17
    -1
      code/espurna/telnet.ino
  39. +103
    -0
      code/espurna/uartmqtt.ino
  40. +8
    -1
      code/espurna/utils.ino
  41. +5
    -3
      code/espurna/web.ino
  42. +21
    -41
      code/espurna/wifi.ino
  43. +25
    -6
      code/espurna/ws.ino
  44. +37
    -2
      code/extra_scripts.py
  45. +28
    -24
      code/gulpfile.js
  46. +9
    -1
      code/html/custom.css
  47. +352
    -311
      code/html/custom.js
  48. +105
    -35
      code/html/index.html
  49. +2
    -2
      code/memanalyzer.py
  50. +4
    -8
      code/ota.py
  51. +3
    -4
      code/package.json
  52. +66
    -5
      code/platformio.ini
  53. +10
    -0
      pre-commit

+ 0
- 1
.gitignore View File

@ -8,7 +8,6 @@ firmware*
.gcc-flags.json .gcc-flags.json
.sconsign.dblite .sconsign.dblite
credentials.h credentials.h
.build
node_modules node_modules
code/utils code/utils
custom.h custom.h


+ 4
- 4
README.md View File

@ -3,10 +3,10 @@
ESPurna ("spark" in Catalan) is a custom firmware for ESP8266 based smart switches and sensors. ESPurna ("spark" in Catalan) is a custom firmware for ESP8266 based smart switches and sensors.
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries. It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
[![version](https://img.shields.io/badge/version-1.12.3-brightgreen.svg)](CHANGELOG.md)
![branch](https://img.shields.io/badge/branch-master-orange.svg)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=master)](https://travis-ci.org/xoseperez/espurna)
[![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE)
[![version](https://img.shields.io/badge/version-1.12.4a-brightgreen.svg)](CHANGELOG.md)
![branch](https://img.shields.io/badge/branch-dev-orange.svg)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=dev)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/dev.svg)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=xose%2eperez%40gmail%2ecom&lc=US&no_note=0&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHostedGuest) [![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=xose%2eperez%40gmail%2ecom&lc=US&no_note=0&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHostedGuest)
[![twitter](https://img.shields.io/twitter/follow/xoseperez.svg?style=social)](https://twitter.com/intent/follow?screen_name=xoseperez) [![twitter](https://img.shields.io/twitter/follow/xoseperez.svg?style=social)](https://twitter.com/intent/follow?screen_name=xoseperez)


+ 1
- 1
code/build.sh View File

@ -39,7 +39,7 @@ echo "--------------------------------------------------------------"
echo "Building web interface..." echo "Building web interface..."
node node_modules/gulp/bin/gulp.js || exit node node_modules/gulp/bin/gulp.js || exit
# Build all the required firmwares
# Build all the required firmware images
echo "--------------------------------------------------------------" echo "--------------------------------------------------------------"
echo "Building firmware images..." echo "Building firmware images..."
mkdir -p ../firmware/espurna-$version mkdir -p ../firmware/espurna-$version


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

@ -22,14 +22,14 @@ typedef struct {
std::vector<button_t> _buttons; std::vector<button_t> _buttons;
#if MQTT_SUPPORT #if MQTT_SUPPORT
#ifdef MQTT_TOPIC_BUTTON
void buttonMQTT(unsigned char id, uint8_t event) { void buttonMQTT(unsigned char id, uint8_t event) {
if (id >= _buttons.size()) return; if (id >= _buttons.size()) return;
char payload[2]; char payload[2];
itoa(event, payload, 10); itoa(event, payload, 10);
mqttSend(MQTT_TOPIC_BUTTON, id, payload); mqttSend(MQTT_TOPIC_BUTTON, id, payload);
} }
#endif
#endif #endif
int buttonFromRelay(unsigned int relayID) { int buttonFromRelay(unsigned int relayID) {
@ -84,10 +84,8 @@ void buttonEvent(unsigned int id, unsigned char event) {
if (event == 0) return; if (event == 0) return;
#if MQTT_SUPPORT #if MQTT_SUPPORT
#ifdef MQTT_TOPIC_BUTTON
buttonMQTT(id, event); buttonMQTT(id, event);
#endif #endif
#endif
unsigned char action = buttonAction(id, event); unsigned char action = buttonAction(id, event);
@ -131,49 +129,49 @@ void buttonSetup() {
unsigned long btnDelay = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt(); unsigned long btnDelay = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt();
#ifdef BUTTON1_PIN
#if BUTTON1_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON1_PRESS, BUTTON1_CLICK, BUTTON1_DBLCLICK, BUTTON1_LNGCLICK, BUTTON1_LNGLNGCLICK); unsigned int actions = buttonStore(BUTTON1_PRESS, BUTTON1_CLICK, BUTTON1_DBLCLICK, BUTTON1_LNGCLICK, BUTTON1_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON1_PIN, BUTTON1_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON1_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON1_PIN, BUTTON1_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON1_RELAY});
} }
#endif #endif
#ifdef BUTTON2_PIN
#if BUTTON2_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON2_PRESS, BUTTON2_CLICK, BUTTON2_DBLCLICK, BUTTON2_LNGCLICK, BUTTON2_LNGLNGCLICK); unsigned int actions = buttonStore(BUTTON2_PRESS, BUTTON2_CLICK, BUTTON2_DBLCLICK, BUTTON2_LNGCLICK, BUTTON2_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON2_PIN, BUTTON2_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON2_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON2_PIN, BUTTON2_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON2_RELAY});
} }
#endif #endif
#ifdef BUTTON3_PIN
#if BUTTON3_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON3_PRESS, BUTTON3_CLICK, BUTTON3_DBLCLICK, BUTTON3_LNGCLICK, BUTTON3_LNGLNGCLICK); unsigned int actions = buttonStore(BUTTON3_PRESS, BUTTON3_CLICK, BUTTON3_DBLCLICK, BUTTON3_LNGCLICK, BUTTON3_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON3_PIN, BUTTON3_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON3_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON3_PIN, BUTTON3_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON3_RELAY});
} }
#endif #endif
#ifdef BUTTON4_PIN
#if BUTTON4_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON4_PRESS, BUTTON4_CLICK, BUTTON4_DBLCLICK, BUTTON4_LNGCLICK, BUTTON4_LNGLNGCLICK); unsigned int actions = buttonStore(BUTTON4_PRESS, BUTTON4_CLICK, BUTTON4_DBLCLICK, BUTTON4_LNGCLICK, BUTTON4_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON4_PIN, BUTTON4_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON4_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON4_PIN, BUTTON4_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON4_RELAY});
} }
#endif #endif
#ifdef BUTTON5_PIN
#if BUTTON5_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON5_PRESS, BUTTON5_CLICK, BUTTON5_DBLCLICK, BUTTON5_LNGCLICK, BUTTON5_LNGLNGCLICK); unsigned int actions = buttonStore(BUTTON5_PRESS, BUTTON5_CLICK, BUTTON5_DBLCLICK, BUTTON5_LNGCLICK, BUTTON5_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON5_PIN, BUTTON5_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON5_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON5_PIN, BUTTON5_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON5_RELAY});
} }
#endif #endif
#ifdef BUTTON6_PIN
#if BUTTON6_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON6_PRESS, BUTTON6_CLICK, BUTTON6_DBLCLICK, BUTTON6_LNGCLICK, BUTTON6_LNGLNGCLICK); unsigned int actions = buttonStore(BUTTON6_PRESS, BUTTON6_CLICK, BUTTON6_DBLCLICK, BUTTON6_LNGCLICK, BUTTON6_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON6_PIN, BUTTON6_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON6_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON6_PIN, BUTTON6_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON6_RELAY});
} }
#endif #endif
#ifdef BUTTON7_PIN
#if BUTTON7_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON7_PRESS, BUTTON7_CLICK, BUTTON7_DBLCLICK, BUTTON7_LNGCLICK, BUTTON7_LNGLNGCLICK); unsigned int actions = buttonStore(BUTTON7_PRESS, BUTTON7_CLICK, BUTTON7_DBLCLICK, BUTTON7_LNGCLICK, BUTTON7_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON7_PIN, BUTTON7_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON7_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON7_PIN, BUTTON7_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON7_RELAY});
} }
#endif #endif
#ifdef BUTTON8_PIN
#if BUTTON8_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON8_PRESS, BUTTON8_CLICK, BUTTON8_DBLCLICK, BUTTON8_LNGCLICK, BUTTON8_LNGLNGCLICK); unsigned int actions = buttonStore(BUTTON8_PRESS, BUTTON8_CLICK, BUTTON8_DBLCLICK, BUTTON8_LNGCLICK, BUTTON8_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON8_PIN, BUTTON8_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON8_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON8_PIN, BUTTON8_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON8_RELAY});


+ 18
- 5
code/espurna/config/all.h View File

@ -1,13 +1,24 @@
/* /*
If you want to modify the stock configuration but you don't want to touch If you want to modify the stock configuration but you don't want to touch
the repo files you can either define USE_CUSTOM_H or remove the
"#ifdef USE_CUSTOM_H" & "#endif" lines and add a "custom.h"
file to this same folder.
the repo files you can define USE_CUSTOM_H in your build settings.
Arduino IDE:
define it in your boards.txt for the board of your choice.
For instance, for the "Generic ESP8266 Module" with prefix "generic" just add:
generic.build.extra_flags=-DESP8266 -DUSE_CUSTOM_H
PlatformIO:
add the setting to your environment or just define global PLATFORMIO_BUILD_FLAGS
export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'"
Check https://github.com/xoseperez/espurna/issues/104 Check https://github.com/xoseperez/espurna/issues/104
for an example on how to use this file. for an example on how to use this file.
(Define USE_CUSTOM_H on commandline for platformio:
export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'" )
*/ */
#ifdef USE_CUSTOM_H #ifdef USE_CUSTOM_H
#include "custom.h" #include "custom.h"
#endif #endif
@ -23,3 +34,5 @@
#ifdef USE_CORE_VERSION_H #ifdef USE_CORE_VERSION_H
#include "core_version.h" #include "core_version.h"
#endif #endif
#include "build.h"

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

@ -54,12 +54,12 @@
//#define ARILUX_E27 //#define ARILUX_E27
//#define XENON_SM_PW702U //#define XENON_SM_PW702U
//#define AUTHOMETION_LYT8266 //#define AUTHOMETION_LYT8266
//#define KMC_70011
//#define GENERIC_8CH //#define GENERIC_8CH
//#define ARILUX_AL_LC01 //#define ARILUX_AL_LC01
//#define ARILUX_AL_LC11 //#define ARILUX_AL_LC11
//#define ARILUX_AL_LC02 //#define ARILUX_AL_LC02
//#define WEMOS_D1_TARPUNA_SHIELD //#define WEMOS_D1_TARPUNA_SHIELD
//#define MAGICHOME_LED_CONTROLLER_23
//#define GIZWITS_WITTY_CLOUD //#define GIZWITS_WITTY_CLOUD
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
@ -67,6 +67,7 @@
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
//#define ALEXA_SUPPORT 0 //#define ALEXA_SUPPORT 0
//#define BROKER_SUPPORT 0
//#define DEBUG_SERIAL_SUPPORT 0 //#define DEBUG_SERIAL_SUPPORT 0
//#define DEBUG_TELNET_SUPPORT 0 //#define DEBUG_TELNET_SUPPORT 0
//#define DEBUG_UDP_SUPPORT 1 //#define DEBUG_UDP_SUPPORT 1
@ -78,7 +79,6 @@
//#define LLMNR_SUPPORT 1 // Only with Arduino Core 2.4.0 //#define LLMNR_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define MDNS_SERVER_SUPPORT 0 //#define MDNS_SERVER_SUPPORT 0
//#define MDNS_CLIENT_SUPPORT 1 //#define MDNS_CLIENT_SUPPORT 1
//#define BROKER_SUPPORT 0
//#define MQTT_SUPPORT 0 //#define MQTT_SUPPORT 0
//#define NETBIOS_SUPPORT 1 // Only with Arduino Core 2.4.0 //#define NETBIOS_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define NOFUSS_SUPPORT 1 //#define NOFUSS_SUPPORT 1
@ -90,6 +90,7 @@
//#define TELNET_SUPPORT 0 //#define TELNET_SUPPORT 0
//#define TERMINAL_SUPPORT 0 //#define TERMINAL_SUPPORT 0
//#define THINGSPEAK_SUPPORT 0 //#define THINGSPEAK_SUPPORT 0
//#define UART_MQTT_SUPPORT 1
//#define WEB_SUPPORT 0 //#define WEB_SUPPORT 0
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
@ -97,7 +98,6 @@
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
//#define ANALOG_SUPPORT 1 //#define ANALOG_SUPPORT 1
//#define GL55XX_SUPPORT 1
//#define BH1750_SUPPORT 1 //#define BH1750_SUPPORT 1
//#define BMX280_SUPPORT 1 //#define BMX280_SUPPORT 1
//#define DALLAS_SUPPORT 1 //#define DALLAS_SUPPORT 1


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

@ -0,0 +1,5 @@
// DO NOT EDIT THIS FILE MANUALLY
// This file is modified by PlatformIO
// This file should not be pushed when modified, untrack changes with:
// git update-index --assume-unchanged code/espurna/config/build.h
#define APP_BUILD_FLAGS ""

+ 90
- 9
code/espurna/config/defaults.h View File

@ -2,10 +2,37 @@
// Hardware default values // Hardware default values
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#define GPIO_NONE 0x99
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Buttons // Buttons
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#ifndef BUTTON1_PIN
#define BUTTON1_PIN GPIO_NONE
#endif
#ifndef BUTTON2_PIN
#define BUTTON2_PIN GPIO_NONE
#endif
#ifndef BUTTON3_PIN
#define BUTTON3_PIN GPIO_NONE
#endif
#ifndef BUTTON4_PIN
#define BUTTON4_PIN GPIO_NONE
#endif
#ifndef BUTTON5_PIN
#define BUTTON5_PIN GPIO_NONE
#endif
#ifndef BUTTON6_PIN
#define BUTTON6_PIN GPIO_NONE
#endif
#ifndef BUTTON7_PIN
#define BUTTON7_PIN GPIO_NONE
#endif
#ifndef BUTTON8_PIN
#define BUTTON8_PIN GPIO_NONE
#endif
#ifndef BUTTON1_PRESS #ifndef BUTTON1_PRESS
#define BUTTON1_PRESS BUTTON_MODE_NONE #define BUTTON1_PRESS BUTTON_MODE_NONE
#endif #endif
@ -160,6 +187,35 @@
// Relays // Relays
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#ifndef DUMMY_RELAY_COUNT
#define DUMMY_RELAY_COUNT 0
#endif
#ifndef RELAY1_PIN
#define RELAY1_PIN GPIO_NONE
#endif
#ifndef RELAY2_PIN
#define RELAY2_PIN GPIO_NONE
#endif
#ifndef RELAY3_PIN
#define RELAY3_PIN GPIO_NONE
#endif
#ifndef RELAY4_PIN
#define RELAY4_PIN GPIO_NONE
#endif
#ifndef RELAY5_PIN
#define RELAY5_PIN GPIO_NONE
#endif
#ifndef RELAY6_PIN
#define RELAY6_PIN GPIO_NONE
#endif
#ifndef RELAY7_PIN
#define RELAY7_PIN GPIO_NONE
#endif
#ifndef RELAY8_PIN
#define RELAY8_PIN GPIO_NONE
#endif
#ifndef RELAY1_TYPE #ifndef RELAY1_TYPE
#define RELAY1_TYPE RELAY_TYPE_NORMAL #define RELAY1_TYPE RELAY_TYPE_NORMAL
#endif #endif
@ -175,7 +231,7 @@
#ifndef RELAY5_TYPE #ifndef RELAY5_TYPE
#define RELAY5_TYPE RELAY_TYPE_NORMAL #define RELAY5_TYPE RELAY_TYPE_NORMAL
#endif #endif
#ifndef RELAY6_TYPE
#ifndef RELAY6_TYPE
#define RELAY6_TYPE RELAY_TYPE_NORMAL #define RELAY6_TYPE RELAY_TYPE_NORMAL
#endif #endif
#ifndef RELAY7_TYPE #ifndef RELAY7_TYPE
@ -186,28 +242,28 @@
#endif #endif
#ifndef RELAY1_RESET_PIN #ifndef RELAY1_RESET_PIN
#define RELAY1_RESET_PIN 0
#define RELAY1_RESET_PIN GPIO_NONE
#endif #endif
#ifndef RELAY2_RESET_PIN #ifndef RELAY2_RESET_PIN
#define RELAY2_RESET_PIN 0
#define RELAY2_RESET_PIN GPIO_NONE
#endif #endif
#ifndef RELAY3_RESET_PIN #ifndef RELAY3_RESET_PIN
#define RELAY3_RESET_PIN 0
#define RELAY3_RESET_PIN GPIO_NONE
#endif #endif
#ifndef RELAY4_RESET_PIN #ifndef RELAY4_RESET_PIN
#define RELAY4_RESET_PIN 0
#define RELAY4_RESET_PIN GPIO_NONE
#endif #endif
#ifndef RELAY5_RESET_PIN #ifndef RELAY5_RESET_PIN
#define RELAY5_RESET_PIN 0
#define RELAY5_RESET_PIN GPIO_NONE
#endif #endif
#ifndef RELAY6_RESET_PIN #ifndef RELAY6_RESET_PIN
#define RELAY6_RESET_PIN 0
#define RELAY6_RESET_PIN GPIO_NONE
#endif #endif
#ifndef RELAY7_RESET_PIN #ifndef RELAY7_RESET_PIN
#define RELAY7_RESET_PIN 0
#define RELAY7_RESET_PIN GPIO_NONE
#endif #endif
#ifndef RELAY8_RESET_PIN #ifndef RELAY8_RESET_PIN
#define RELAY8_RESET_PIN 0
#define RELAY8_RESET_PIN GPIO_NONE
#endif #endif
#ifndef RELAY1_DELAY_ON #ifndef RELAY1_DELAY_ON
@ -264,6 +320,31 @@
// LEDs // LEDs
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#ifndef LED1_PIN
#define LED1_PIN GPIO_NONE
#endif
#ifndef LED2_PIN
#define LED2_PIN GPIO_NONE
#endif
#ifndef LED3_PIN
#define LED3_PIN GPIO_NONE
#endif
#ifndef LED4_PIN
#define LED4_PIN GPIO_NONE
#endif
#ifndef LED5_PIN
#define LED5_PIN GPIO_NONE
#endif
#ifndef LED6_PIN
#define LED6_PIN GPIO_NONE
#endif
#ifndef LED7_PIN
#define LED7_PIN GPIO_NONE
#endif
#ifndef LED8_PIN
#define LED8_PIN GPIO_NONE
#endif
#ifndef LED1_MODE #ifndef LED1_MODE
#define LED1_MODE LED_MODE_WIFI #define LED1_MODE LED_MODE_WIFI
#endif #endif


+ 81
- 38
code/espurna/config/general.h View File

@ -15,22 +15,6 @@
#define ARRAYINIT(type, name, ...) \ #define ARRAYINIT(type, name, ...) \
type name[] = {__VA_ARGS__}; type name[] = {__VA_ARGS__};
//------------------------------------------------------------------------------
// ESPURNA CORE
//------------------------------------------------------------------------------
#ifdef ESPURNA_CORE
#define ALEXA_SUPPORT 0
#define SCHEDULER_SUPPORT 0
#define DOMOTICZ_SUPPORT 0
#define HOMEASSISTANT_SUPPORT 0
#define MQTT_SUPPORT 0
#define NTP_SUPPORT 0
#define WEB_SUPPORT 0
#define SENSOR_SUPPORT 0
#define I2C_SUPPORT 0
#endif
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// TELNET // TELNET
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -71,9 +55,15 @@
// Second serial port (used for RX) // Second serial port (used for RX)
//#define SERIAL_RX_PORT Serial // This setting is usually defined
#ifndef SERIAL_RX_ENABLED
#define SERIAL_RX_ENABLED 0 // Secondary serial port for RX
#endif
#ifndef SERIAL_RX_PORT
#define SERIAL_RX_PORT Serial // This setting is usually defined
// in the hardware.h file for those // in the hardware.h file for those
// boards that require it // boards that require it
#endif
#ifndef SERIAL_RX_BAUDRATE #ifndef SERIAL_RX_BAUDRATE
#define SERIAL_RX_BAUDRATE 115200 // Default baudrate #define SERIAL_RX_BAUDRATE 115200 // Default baudrate
@ -275,6 +265,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define RELAY_PROVIDER_DUAL 1 #define RELAY_PROVIDER_DUAL 1
#define RELAY_PROVIDER_LIGHT 2 #define RELAY_PROVIDER_LIGHT 2
#define RELAY_PROVIDER_RFBRIDGE 3 #define RELAY_PROVIDER_RFBRIDGE 3
#define RELAY_PROVIDER_STM 4
// Default boot mode: 0 means OFF, 1 ON and 2 whatever was before // Default boot mode: 0 means OFF, 1 ON and 2 whatever was before
#define RELAY_BOOT_MODE RELAY_BOOT_OFF #define RELAY_BOOT_MODE RELAY_BOOT_OFF
@ -300,13 +291,6 @@ PROGMEM const char* const custom_reset_string[] = {
// Do not save relay state after these many milliseconds // Do not save relay state after these many milliseconds
#define RELAY_SAVE_DELAY 1000 #define RELAY_SAVE_DELAY 1000
//------------------------------------------------------------------------------
// I18N
//------------------------------------------------------------------------------
#define TMP_CELSIUS 0
#define TMP_FAHRENHEIT 1
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// LED // LED
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -316,10 +300,11 @@ PROGMEM const char* const custom_reset_string[] = {
#define LED_MODE_FOLLOW 2 // LED will follow state of linked relay (check RELAY#_LED) #define LED_MODE_FOLLOW 2 // LED will follow state of linked relay (check RELAY#_LED)
#define LED_MODE_FOLLOW_INVERSE 3 // LED will follow the opposite state of linked relay (check RELAY#_LED) #define LED_MODE_FOLLOW_INVERSE 3 // LED will follow the opposite state of linked relay (check RELAY#_LED)
#define LED_MODE_FINDME 4 // LED will be ON if all relays are OFF #define LED_MODE_FINDME 4 // LED will be ON if all relays are OFF
#define LED_MODE_MIXED 5 // A mixed between WIFI and FINDME
#define LED_MODE_FINDME_WIFI 5 // A mixture between WIFI and FINDME
#define LED_MODE_ON 6 // LED always ON #define LED_MODE_ON 6 // LED always ON
#define LED_MODE_OFF 7 // LED always OFF #define LED_MODE_OFF 7 // LED always OFF
#define LED_MODE_STATUS 8 // If any relay is ON, LED will be ON, otherwise OFF
#define LED_MODE_RELAY 8 // If any relay is ON, LED will be ON, otherwise OFF
#define LED_MODE_RELAY_WIFI 9 // A mixture between WIFI and RELAY, the reverse of MIXED
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// WIFI // WIFI
@ -332,15 +317,43 @@ PROGMEM const char* const custom_reset_string[] = {
#define WIFI_SLEEP_ENABLED 1 // Enable WiFi light sleep #define WIFI_SLEEP_ENABLED 1 // Enable WiFi light sleep
#define WIFI_SCAN_NETWORKS 1 // Perform a network scan before connecting #define WIFI_SCAN_NETWORKS 1 // Perform a network scan before connecting
// Optional hardcoded configuration (up to 2 different networks)
//#define WIFI1_SSID "..."
//#define WIFI1_PASS "..."
//#define WIFI1_IP "192.168.1.201"
//#define WIFI1_GW "192.168.1.1"
//#define WIFI1_MASK "255.255.255.0"
//#define WIFI1_DNS "8.8.8.8"
//#define WIFI2_SSID "..."
//#define WIFI2_PASS "..."
// Optional hardcoded configuration (up to 2 networks)
#ifndef WIFI1_SSID
#define WIFI1_SSID ""
#endif
#ifndef WIFI1_PASS
#define WIFI1_PASS ""
#endif
#ifndef WIFI1_IP
#define WIFI1_IP ""
#endif
#ifndef WIFI1_GW
#define WIFI1_GW ""
#endif
#ifndef WIFI1_MASK
#define WIFI1_MASK ""
#endif
#ifndef WIFI1_DNS
#define WIFI1_DNS ""
#endif
#ifndef WIFI2_SSID
#define WIFI2_SSID ""
#endif
#ifndef WIFI2_PASS
#define WIFI2_PASS ""
#endif
#ifndef WIFI2_IP
#define WIFI2_IP ""
#endif
#ifndef WIFI2_GW
#define WIFI2_GW ""
#endif
#ifndef WIFI2_MASK
#define WIFI2_MASK ""
#endif
#ifndef WIFI2_DNS
#define WIFI2_DNS ""
#endif
#define WIFI_RSSI_1M -30 // Calibrate it with your router reading the RSSI at 1m #define WIFI_RSSI_1M -30 // Calibrate it with your router reading the RSSI at 1m
#define WIFI_PROPAGATION_CONST 4 // This is typically something between 2.7 to 4.3 (free space is 2) #define WIFI_PROPAGATION_CONST 4 // This is typically something between 2.7 to 4.3 (free space is 2)
@ -374,6 +387,7 @@ PROGMEM const char* const custom_reset_string[] = {
// This will only be enabled if WEB_SUPPORT is 1 (this is the default value) // This will only be enabled if WEB_SUPPORT is 1 (this is the default value)
#define WS_AUTHENTICATION 1 // WS authentication ON by default (see #507)
#define WS_BUFFER_SIZE 5 // Max number of secured websocket connections #define WS_BUFFER_SIZE 5 // Max number of secured websocket connections
#define WS_TIMEOUT 1800000 // Timeout for secured websocket #define WS_TIMEOUT 1800000 // Timeout for secured websocket
#define WS_UPDATE_INTERVAL 30000 // Update clients every 30 seconds #define WS_UPDATE_INTERVAL 30000 // Update clients every 30 seconds
@ -444,6 +458,7 @@ PROGMEM const char* const custom_reset_string[] = {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#define OTA_PORT 8266 // OTA port #define OTA_PORT 8266 // OTA port
#define OTA_GITHUB_FP "D7:9F:07:61:10:B3:92:93:E3:49:AC:89:84:5B:03:80:C1:9E:2F:8B"
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// NOFUSS // NOFUSS
@ -457,6 +472,29 @@ PROGMEM const char* const custom_reset_string[] = {
#define NOFUSS_SERVER "" // Default NoFuss Server #define NOFUSS_SERVER "" // Default NoFuss Server
#define NOFUSS_INTERVAL 3600000 // Check for updates every hour #define NOFUSS_INTERVAL 3600000 // Check for updates every hour
// -----------------------------------------------------------------------------
// UART <-> MQTT
// -----------------------------------------------------------------------------
#ifndef UART_MQTT_SUPPORT
#define UART_MQTT_SUPPORT 0 // No support by default
#endif
#define UART_MQTT_USE_SOFT 0 // Use SoftwareSerial
#define UART_MQTT_HW_PORT Serial // Hardware serial port (if UART_MQTT_USE_SOFT == 0)
#define UART_MQTT_RX_PIN 4 // RX PIN (if UART_MQTT_USE_SOFT == 1)
#define UART_MQTT_TX_PIN 5 // TX PIN (if UART_MQTT_USE_SOFT == 1)
#define UART_MQTT_BAUDRATE 115200 // Serial speed
#define UART_MQTT_BUFFER_SIZE 100 // UART buffer size
#if UART_MQTT_SUPPORT
#define MQTT_SUPPORT 1
#undef TERMINAL_SUPPORT
#define TERMINAL_SUPPORT 0
#undef DEBUG_SERIAL_SUPPORT
#define DEBUG_SERIAL_SUPPORT 0
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// MQTT // MQTT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -511,7 +549,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_USE_JSON 0 // Group messages in a JSON body #define MQTT_USE_JSON 0 // Group messages in a JSON body
#define MQTT_USE_JSON_DELAY 100 // Wait this many ms before grouping messages #define MQTT_USE_JSON_DELAY 100 // Wait this many ms before grouping messages
#define MQTT_QUEUE_MAX_SIZE 10 // Size of the MQTT queue when MQTT_USE_JSON is enabled
#define MQTT_QUEUE_MAX_SIZE 20 // Size of the MQTT queue when MQTT_USE_JSON is enabled
// These are the properties that will be sent when useJson is true // These are the properties that will be sent when useJson is true
#ifndef MQTT_ENQUEUE_IP #ifndef MQTT_ENQUEUE_IP
@ -554,6 +592,8 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_TOPIC_RFIN "rfin" #define MQTT_TOPIC_RFIN "rfin"
#define MQTT_TOPIC_RFLEARN "rflearn" #define MQTT_TOPIC_RFLEARN "rflearn"
#define MQTT_TOPIC_RFRAW "rfraw" #define MQTT_TOPIC_RFRAW "rfraw"
#define MQTT_TOPIC_UARTIN "uartin"
#define MQTT_TOPIC_UARTOUT "uartout"
// Light module // Light module
#define MQTT_TOPIC_CHANNEL "channel" #define MQTT_TOPIC_CHANNEL "channel"
@ -772,6 +812,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define NTP_SERVER "pool.ntp.org" // Default NTP server #define NTP_SERVER "pool.ntp.org" // Default NTP server
#define NTP_TIME_OFFSET 1 // Default timezone offset (GMT+1) #define NTP_TIME_OFFSET 1 // Default timezone offset (GMT+1)
#define NTP_DAY_LIGHT true // Enable daylight time saving by default #define NTP_DAY_LIGHT true // Enable daylight time saving by default
#define NTP_SYNC_INTERVAL 60 // NTP initial check every minute
#define NTP_UPDATE_INTERVAL 1800 // NTP check every 30 minutes #define NTP_UPDATE_INTERVAL 1800 // NTP check every 30 minutes
#define NTP_START_DELAY 1000 // Delay NTP start 1 second #define NTP_START_DELAY 1000 // Delay NTP start 1 second
@ -791,6 +832,7 @@ PROGMEM const char* const custom_reset_string[] = {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// RFBRIDGE // RFBRIDGE
// This module is not compatible with RF_SUPPORT=1
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#define RF_SEND_TIMES 4 // How many times to send the message #define RF_SEND_TIMES 4 // How many times to send the message
@ -952,6 +994,7 @@ PROGMEM const char* const custom_reset_string[] = {
// Custom RF module // Custom RF module
// Check http://tinkerman.cat/adding-rf-to-a-non-rf-itead-sonoff/ // Check http://tinkerman.cat/adding-rf-to-a-non-rf-itead-sonoff/
// Enable support by passing RF_SUPPORT=1 build flag // Enable support by passing RF_SUPPORT=1 build flag
// This module is not compatible with RFBRIDGE
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
#ifndef RF_SUPPORT #ifndef RF_SUPPORT
@ -962,5 +1005,5 @@ PROGMEM const char* const custom_reset_string[] = {
#define RF_PIN 14 #define RF_PIN 14
#endif #endif
#define RF_CHANNEL 31
#define RF_DEVICE 1
#define RF_DEBOUNCE 500
#define RF_LEARN_TIMEOUT 60000

+ 75
- 33
code/espurna/config/hardware.h View File

@ -20,11 +20,38 @@
// //
// Besides, other hardware specific information should be stated here // Besides, other hardware specific information should be stated here
// -----------------------------------------------------------------------------
// ESPurna Core
// -----------------------------------------------------------------------------
#if defined(ESPURNA_CORE)
// This is a special device targeted to generate a light-weight binary image
// meant to be able to do two-step-updates:
// https://github.com/xoseperez/espurna/wiki/TwoStepUpdates
// Info
#define MANUFACTURER "ESPRESSIF"
#define DEVICE "ESPURNA_CORE"
// Disable non-core modules
#define ALEXA_SUPPORT 0
#define BROKER_SUPPORT 0
#define DOMOTICZ_SUPPORT 0
#define HOMEASSISTANT_SUPPORT 0
#define I2C_SUPPORT 0
#define MQTT_SUPPORT 0
#define NTP_SUPPORT 0
#define SCHEDULER_SUPPORT 0
#define SENSOR_SUPPORT 0
#define THINGSPEAK_SUPPORT 0
#define WEB_SUPPORT 0
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Development boards // Development boards
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#if defined(NODEMCU_LOLIN)
#elif defined(NODEMCU_LOLIN)
// Info // Info
#define MANUFACTURER "NODEMCU" #define MANUFACTURER "NODEMCU"
@ -914,35 +941,6 @@
#define IR_PIN 4 #define IR_PIN 4
#define IR_BUTTON_SET 1 #define IR_BUTTON_SET 1
#elif defined(MAGICHOME_LED_CONTROLLER_23)
// Info
#define MANUFACTURER "MAGICHOME"
#define DEVICE "LED_CONTROLLER_23"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
// LEDs
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
// Light
#define LIGHT_CHANNELS 4
#define LIGHT_CH1_PIN 12 // RED
#define LIGHT_CH2_PIN 5 // GREEN
#define LIGHT_CH3_PIN 13 // BLUE
#define LIGHT_CH4_PIN 15 // WHITE
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
// IR
#define IR_SUPPORT 1
#define IR_PIN 4
#define IR_BUTTON_SET 1
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// HUACANXING H801 & H802 // HUACANXING H801 & H802
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -956,7 +954,7 @@
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1 #define DUMMY_RELAY_COUNT 1
#define DEBUG_PORT Serial1 #define DEBUG_PORT Serial1
#define SERIAL_RX_PORT Serial
#define SERIAL_RX_ENABLED 1
// LEDs // LEDs
#define LED1_PIN 5 #define LED1_PIN 5
@ -984,7 +982,7 @@
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1 #define DUMMY_RELAY_COUNT 1
#define DEBUG_PORT Serial1 #define DEBUG_PORT Serial1
#define SERIAL_RX_PORT Serial
#define SERIAL_RX_ENABLED 1
// Light // Light
#define LIGHT_CHANNELS 4 #define LIGHT_CHANNELS 4
@ -1426,6 +1424,38 @@
#define LIGHT_CH2_INVERSE 0 #define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0 #define LIGHT_CH3_INVERSE 0
// -----------------------------------------------------------------------------
// KMC 70011
// https://www.amazon.com/KMC-Monitoring-Required-Control-Compatible/dp/B07313TH7B
// -----------------------------------------------------------------------------
#elif defined(KMC_70011)
// Info
#define MANUFACTURER "KMC"
#define DEVICE "70011"
// 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 13
#define LED1_PIN_INVERSE 0
// HLW8012
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 1
#endif
#define HLW8012_SEL_PIN 12
#define HLW8012_CF1_PIN 5
#define HLW8012_CF_PIN 4
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Generic 8CH // Generic 8CH
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -1454,7 +1484,19 @@
#define RELAY8_PIN 15 #define RELAY8_PIN 15
#define RELAY8_TYPE RELAY_TYPE_NORMAL #define RELAY8_TYPE RELAY_TYPE_NORMAL
// -----------------------------------------------------------------------------
#elif defined(STM_RELAY)
// Info
#define MANUFACTURER "STM_RELAY"
#define DEVICE "2CH"
// Relays
#define DUMMY_RELAY_COUNT 2
#define RELAY_PROVIDER RELAY_PROVIDER_STM
// Remove UART noise on serial line
#define TERMINAL_SUPPORT 0
#define DEBUG_SERIAL_SUPPORT 0
#endif #endif


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

@ -45,7 +45,7 @@ void wifiRegister(wifi_callback_f callback);
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f; typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
void mqttRegister(mqtt_callback_f callback); void mqttRegister(mqtt_callback_f callback);
String mqttTopicKey(char * topic);
String mqttMagnitude(char * topic);
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Broker // Broker


+ 27
- 4
code/espurna/config/sensors.h View File

@ -15,10 +15,6 @@
#define SENSOR_USE_INDEX 0 // Use the index in topic (i.e. temperature/0) #define SENSOR_USE_INDEX 0 // Use the index in topic (i.e. temperature/0)
// even if just one sensor (0 for backwards compatibility) // even if just one sensor (0 for backwards compatibility)
#ifndef SENSOR_TEMPERATURE_UNITS
#define SENSOR_TEMPERATURE_UNITS TMP_CELSIUS // Temperature units (TMP_CELSIUS | TMP_FAHRENHEIT)
#endif
#ifndef SENSOR_TEMPERATURE_CORRECTION #ifndef SENSOR_TEMPERATURE_CORRECTION
#define SENSOR_TEMPERATURE_CORRECTION 0.0 // Offset correction #define SENSOR_TEMPERATURE_CORRECTION 0.0 // Offset correction
#endif #endif
@ -39,6 +35,31 @@
#define SENSOR_PUBLISH_ADDRESSES 0 // Publish sensor addresses #define SENSOR_PUBLISH_ADDRESSES 0 // Publish sensor addresses
#define SENSOR_ADDRESS_TOPIC "address" // Topic to publish sensor addresses #define SENSOR_ADDRESS_TOPIC "address" // Topic to publish sensor addresses
//------------------------------------------------------------------------------
// UNITS
//------------------------------------------------------------------------------
#define POWER_WATTS 0
#define POWER_KILOWATTS 1
#define ENERGY_JOULES 0
#define ENERGY_KWH 1
#define TMP_CELSIUS 0
#define TMP_FAHRENHEIT 1
#ifndef SENSOR_TEMPERATURE_UNITS
#define SENSOR_TEMPERATURE_UNITS TMP_CELSIUS // Temperature units (TMP_CELSIUS | TMP_FAHRENHEIT)
#endif
#ifndef SENSOR_ENERGY_UNITS
#define SENSOR_ENERGY_UNITS ENERGY_JOULES // Energy units (ENERGY_JOULES | ENERGY_KWH)
#endif
#ifndef SENSOR_POWER_UNITS
#define SENSOR_POWER_UNITS POWER_WATTS // Power units (POWER_WATTS | POWER_KILOWATTS)
#endif
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Sensor ID // Sensor ID
// These should remain over time, do not modify them, only add new ones at the end // These should remain over time, do not modify them, only add new ones at the end
@ -549,7 +570,9 @@ PROGMEM const char magnitude_hectopascals[] = "hPa";
PROGMEM const char magnitude_amperes[] = "A"; PROGMEM const char magnitude_amperes[] = "A";
PROGMEM const char magnitude_volts[] = "V"; PROGMEM const char magnitude_volts[] = "V";
PROGMEM const char magnitude_watts[] = "W"; PROGMEM const char magnitude_watts[] = "W";
PROGMEM const char magnitude_kw[] = "kW";
PROGMEM const char magnitude_joules[] = "J"; PROGMEM const char magnitude_joules[] = "J";
PROGMEM const char magnitude_kwh[] = "kWh";
PROGMEM const char magnitude_ugm3[] = "µg/m3"; PROGMEM const char magnitude_ugm3[] = "µg/m3";
PROGMEM const char magnitude_ppm[] = "ppm"; PROGMEM const char magnitude_ppm[] = "ppm";
PROGMEM const char magnitude_lux[] = "lux"; PROGMEM const char magnitude_lux[] = "lux";


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

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

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


+ 1
- 0
code/espurna/debug.ino View File

@ -47,6 +47,7 @@ void _debugSend(char * message) {
#endif #endif
_udp_debug.write(message); _udp_debug.write(message);
_udp_debug.endPacket(); _udp_debug.endPacket();
delay(1); // https://github.com/xoseperez/espurna/issues/438
#if SYSTEM_CHECK_ENABLED #if SYSTEM_CHECK_ENABLED
} }
#endif #endif


+ 11
- 11
code/espurna/domoticz.ino View File

@ -16,8 +16,8 @@ bool _dcz_enabled = false;
// Private methods // Private methods
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
int _domoticzRelay(unsigned int idx) {
for (int relayID=0; relayID<relayCount(); relayID++) {
unsigned char _domoticzRelay(unsigned int idx) {
for (unsigned char relayID=0; relayID<relayCount(); relayID++) {
if (domoticzIdx(relayID) == idx) { if (domoticzIdx(relayID) == idx) {
return relayID; return relayID;
} }
@ -60,11 +60,11 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
} }
// IDX // IDX
unsigned long idx = root["idx"];
int relayID = _domoticzRelay(idx);
unsigned int idx = root["idx"];
unsigned char relayID = _domoticzRelay(idx);
if (relayID >= 0) { if (relayID >= 0) {
unsigned long value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %lu for IDX %lu\n"), value, idx);
unsigned char value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx);
relayStatus(relayID, value == 1); relayStatus(relayID, value == 1);
} }
@ -84,7 +84,7 @@ void _domoticzWebSocketOnSend(JsonObject& root) {
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC); root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
JsonArray& relays = root.createNestedArray("dczRelays"); JsonArray& relays = root.createNestedArray("dczRelays");
for (byte i=0; i<relayCount(); i++) {
for (unsigned char i=0; i<relayCount(); i++) {
relays.add(domoticzIdx(i)); relays.add(domoticzIdx(i));
} }
@ -127,16 +127,16 @@ template<typename T> void domoticzSend(const char * key, T nvalue) {
domoticzSend(key, nvalue, ""); domoticzSend(key, nvalue, "");
} }
void domoticzSendRelay(unsigned int relayID) {
void domoticzSendRelay(unsigned char relayID) {
if (!_dcz_enabled) return; if (!_dcz_enabled) return;
char buffer[15]; char buffer[15];
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%lu"), relayID);
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%u"), relayID);
domoticzSend(buffer, relayStatus(relayID) ? "1" : "0"); domoticzSend(buffer, relayStatus(relayID) ? "1" : "0");
} }
int domoticzIdx(unsigned int relayID) {
unsigned int domoticzIdx(unsigned char relayID) {
char buffer[15]; char buffer[15];
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%lu"), relayID);
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%u"), relayID);
return getSetting(buffer).toInt(); return getSetting(buffer).toInt();
} }


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

@ -147,6 +147,10 @@ void setup() {
#if SCHEDULER_SUPPORT #if SCHEDULER_SUPPORT
schSetup(); schSetup();
#endif #endif
#if UART_MQTT_SUPPORT
uartmqttSetup();
#endif
// 3rd party code hook // 3rd party code hook
#if USE_EXTRA #if USE_EXTRA


+ 292
- 0
code/espurna/homeassistant.ino View File

@ -0,0 +1,292 @@
/*
HOME ASSISTANT MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if HOMEASSISTANT_SUPPORT
#include <ArduinoJson.h>
bool _haEnabled = false;
bool _haSendFlag = false;
// -----------------------------------------------------------------------------
// SENSORS
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
void _haSendMagnitude(unsigned char i, JsonObject& config) {
unsigned char type = magnitudeType(i);
config["name"] = getSetting("hostname") + String(" ") + magnitudeTopic(type);
config["platform"] = "mqtt";
config["device_class"] = "sensor";
config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
config["unit_of_measurement"] = magnitudeUnits(type);
}
void _haSendMagnitudes() {
for (unsigned char i=0; i<magnitudeCount(); i++) {
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/sensor/" +
getSetting("hostname") + "_" + String(i) +
"/config";
String output;
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
config.printTo(output);
}
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
}
#endif // SENSOR_SUPPORT
// -----------------------------------------------------------------------------
// SWITCHES & LIGHTS
// -----------------------------------------------------------------------------
void _haSendSwitch(unsigned char i, JsonObject& config) {
String name = getSetting("hostname");
if (relayCount() > 1) {
name += String(" #") + String(i);
}
config["name"] = name;
config["platform"] = "mqtt";
if (relayCount()) {
config["state_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, false);
config["command_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, true);
config["payload_on"] = String("1");
config["payload_off"] = String("0");
config["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false);
config["payload_available"] = String("1");
config["payload_not_available"] = String("0");
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (i == 0) {
if (lightHasColor()) {
config["brightness_state_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, false);
config["brightness_command_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, true);
config["rgb_state_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, false);
config["rgb_command_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, true);
config["color_temp_command_topic"] = mqttTopic(MQTT_TOPIC_MIRED, true);
}
if (lightChannels() > 3) {
config["white_value_state_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, false);
config["white_value_command_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, true);
}
}
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
}
void _haSendSwitches() {
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light");
#else
String type = String("switch");
#endif
for (unsigned char i=0; i<relayCount(); i++) {
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/" + type +
"/" + getSetting("hostname") + "_" + String(i) +
"/config";
String output;
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
config.printTo(output);
}
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
}
// -----------------------------------------------------------------------------
String _haGetConfig() {
String output;
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light");
#else
String type = String("switch");
#endif
for (unsigned char i=0; i<relayCount(); i++) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
output += type + ":\n";
bool first = true;
for (auto kv : config) {
if (first) {
output += " - ";
first = false;
} else {
output += " ";
}
output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
}
output += "\n";
}
#if SENSOR_SUPPORT
for (unsigned char i=0; i<magnitudeCount(); i++) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
output += "sensor:\n";
bool first = true;
for (auto kv : config) {
if (first) {
output += " - ";
first = false;
} else {
output += " ";
}
output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
}
output += "\n";
}
#endif
return output;
}
void _haSend() {
// Pending message to send?
if (!_haSendFlag) return;
// Are we connected?
if (!mqttConnected()) return;
DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n"));
// Send messages
_haSendSwitches();
#if SENSOR_SUPPORT
_haSendMagnitudes();
#endif
_haSendFlag = false;
}
void _haConfigure() {
bool enabled = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
_haSendFlag = (enabled != _haEnabled);
_haEnabled = enabled;
_haSend();
}
#if WEB_SUPPORT
void _haWebSocketOnSend(JsonObject& root) {
root["haVisible"] = 1;
root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX);
root["haEnabled"] = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
}
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());
}
}
#endif
#if TERMINAL_SUPPORT
void _haInitCommands() {
settingsRegisterCommand(F("HA.CONFIG"), [](Embedis* e) {
DEBUG_MSG(_haGetConfig().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) {
setSetting("haEnabled", "1");
_haConfigure();
wsSend(_haWebSocketOnSend);
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
setSetting("haEnabled", "0");
_haConfigure();
wsSend(_haWebSocketOnSend);
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
#endif
// -----------------------------------------------------------------------------
void haSetup() {
_haConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_haWebSocketOnSend);
wsOnAfterParseRegister(_haConfigure);
wsOnActionRegister(_haWebSocketOnAction);
#endif
// On MQTT connect check if we have something to send
mqttRegister([](unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) _haSend();
});
#if TERMINAL_SUPPORT
_haInitCommands();
#endif
}
#endif // HOMEASSISTANT_SUPPORT

+ 0
- 181
code/espurna/homeassitant.ino View File

@ -1,181 +0,0 @@
/*
HOME ASSISTANT MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if HOMEASSISTANT_SUPPORT
#include <ArduinoJson.h>
bool _haEnabled = false;
bool _haSendFlag = false;
// -----------------------------------------------------------------------------
void _haWebSocketOnSend(JsonObject& root) {
root["haVisible"] = 1;
root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX);
}
#if SENSOR_SUPPORT
void _haSendMagnitude(unsigned char i) {
String output;
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
unsigned char type = magnitudeType(i);
root["device_class"] = "sensor";
root["name"] = getSetting("hostname") + String(" ") + magnitudeTopic(type);
root["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
root["unit_of_measurement"] = magnitudeUnits(type);
root.printTo(output);
}
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/sensor/" +
getSetting("hostname") + "_" + String(i) +
"/config";
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
void _haSendMagnitudes() {
for (unsigned char i=0; i<magnitudeCount(); i++) {
_haSendMagnitude(i);
}
}
#endif
void _haSendSwitch(unsigned char i) {
String output;
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
String name = getSetting("hostname");
if (relayCount() > 1) {
name += String(" switch #") + String(i);
}
root["name"] = name;
root["platform"] = "mqtt";
if (relayCount()) {
root["state_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, false);
root["command_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, true);
root["payload_on"] = String("1");
root["payload_off"] = String("0");
root["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false);
root["payload_available"] = String("1");
root["payload_not_available"] = String("0");
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (i == 0) {
if (lightHasColor()) {
root["brightness_state_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, false);
root["brightness_command_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, true);
root["rgb_state_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, false);
root["rgb_command_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, true);
root["color_temp_command_topic"] = mqttTopic(MQTT_TOPIC_MIRED, true);
}
if (lightChannels() > 3) {
root["white_value_state_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, false);
root["white_value_command_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, true);
}
}
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
root.printTo(output);
}
#if LIGHT_PROVIDER == LIGHT_PROVIDER_NONE
String component = String("switch");
#else
String component = String("light");
#endif
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/" + component +
"/" + getSetting("hostname") + "_" + String(i) +
"/config";
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
void _haSendSwitches() {
for (unsigned char i=0; i<relayCount(); i++) {
_haSendSwitch(i);
}
}
void _haSend() {
// Pending message to send?
if (!_haSendFlag) return;
// Are we connected?
if (!mqttConnected()) return;
DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n"));
// Send messages
_haSendSwitches();
#if SENSOR_SUPPORT
_haSendMagnitudes();
#endif
_haSendFlag = false;
}
void _haConfigure() {
bool enabled = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
_haSendFlag = (enabled != _haEnabled);
_haEnabled = enabled;
_haSend();
}
// -----------------------------------------------------------------------------
void haSetup() {
_haConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_haWebSocketOnSend);
wsOnAfterParseRegister(_haConfigure);
#endif
// On MQTT connect check if we have something to send
mqttRegister([](unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) _haSend();
});
}
#endif // HOMEASSISTANT_SUPPORT

+ 33
- 11
code/espurna/led.ino View File

@ -77,7 +77,7 @@ void _ledMQTTCallback(unsigned int type, const char * topic, const char * payloa
if (type == MQTT_MESSAGE_EVENT) { if (type == MQTT_MESSAGE_EVENT) {
// Match topic // Match topic
String t = mqttTopicKey((char *) topic);
String t = mqttMagnitude((char *) topic);
if (!t.startsWith(MQTT_TOPIC_LED)) return; if (!t.startsWith(MQTT_TOPIC_LED)) return;
// Get led ID // Get led ID
@ -124,28 +124,28 @@ void ledUpdate(bool value) {
void ledSetup() { void ledSetup() {
#ifdef LED1_PIN
#if LED1_PIN != GPIO_NONE
_leds.push_back((led_t) { LED1_PIN, LED1_PIN_INVERSE, LED1_MODE, LED1_RELAY }); _leds.push_back((led_t) { LED1_PIN, LED1_PIN_INVERSE, LED1_MODE, LED1_RELAY });
#endif #endif
#ifdef LED2_PIN
#if LED2_PIN != GPIO_NONE
_leds.push_back((led_t) { LED2_PIN, LED2_PIN_INVERSE, LED2_MODE, LED2_RELAY }); _leds.push_back((led_t) { LED2_PIN, LED2_PIN_INVERSE, LED2_MODE, LED2_RELAY });
#endif #endif
#ifdef LED3_PIN
#if LED3_PIN != GPIO_NONE
_leds.push_back((led_t) { LED3_PIN, LED3_PIN_INVERSE, LED3_MODE, LED3_RELAY }); _leds.push_back((led_t) { LED3_PIN, LED3_PIN_INVERSE, LED3_MODE, LED3_RELAY });
#endif #endif
#ifdef LED4_PIN
#if LED4_PIN != GPIO_NONE
_leds.push_back((led_t) { LED4_PIN, LED4_PIN_INVERSE, LED4_MODE, LED4_RELAY }); _leds.push_back((led_t) { LED4_PIN, LED4_PIN_INVERSE, LED4_MODE, LED4_RELAY });
#endif #endif
#ifdef LED5_PIN
#if LED5_PIN != GPIO_NONE
_leds.push_back((led_t) { LED5_PIN, LED5_PIN_INVERSE, LED5_MODE, LED5_RELAY }); _leds.push_back((led_t) { LED5_PIN, LED5_PIN_INVERSE, LED5_MODE, LED5_RELAY });
#endif #endif
#ifdef LED6_PIN
#if LED6_PIN != GPIO_NONE
_leds.push_back((led_t) { LED6_PIN, LED6_PIN_INVERSE, LED6_MODE, LED6_RELAY }); _leds.push_back((led_t) { LED6_PIN, LED6_PIN_INVERSE, LED6_MODE, LED6_RELAY });
#endif #endif
#ifdef LED7_PIN
#if LED7_PIN != GPIO_NONE
_leds.push_back((led_t) { LED7_PIN, LED7_PIN_INVERSE, LED7_MODE, LED7_RELAY }); _leds.push_back((led_t) { LED7_PIN, LED7_PIN_INVERSE, LED7_MODE, LED7_RELAY });
#endif #endif
#ifdef LED8_PIN
#if LED8_PIN != GPIO_NONE
_leds.push_back((led_t) { LED8_PIN, LED8_PIN_INVERSE, LED8_MODE, LED8_RELAY }); _leds.push_back((led_t) { LED8_PIN, LED8_PIN_INVERSE, LED8_MODE, LED8_RELAY });
#endif #endif
@ -191,7 +191,7 @@ void ledLoop() {
} }
if (_ledMode(i) == LED_MODE_MIXED) {
if (_ledMode(i) == LED_MODE_FINDME_WIFI) {
if (wifiConnected()) { if (wifiConnected()) {
if (relayStatus(_leds[i].relay-1)) { if (relayStatus(_leds[i].relay-1)) {
@ -213,6 +213,28 @@ void ledLoop() {
} }
if (_ledMode(i) == LED_MODE_RELAY_WIFI) {
if (wifiConnected()) {
if (relayStatus(_leds[i].relay-1)) {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 100, 900);
} else {
_ledBlink(i, 100, 4900);
}
} else {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 4900, 100);
}
}
} else {
_ledBlink(i, 500, 500);
}
}
// Relay-based modes, update only if relays have been updated // Relay-based modes, update only if relays have been updated
if (!_led_update) continue; if (!_led_update) continue;
@ -235,7 +257,7 @@ void ledLoop() {
_ledStatus(i, status); _ledStatus(i, status);
} }
if (_ledMode(i) == LED_MODE_STATUS) {
if (_ledMode(i) == LED_MODE_RELAY) {
bool status = false; bool status = false;
for (unsigned char k=0; k<relayCount(); k++) { for (unsigned char k=0; k<relayCount(); k++) {
if (relayStatus(k)) { if (relayStatus(k)) {


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

@ -496,7 +496,7 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl
} }
// Match topic // Match topic
String t = mqttTopicKey((char *) topic);
String t = mqttMagnitude((char *) topic);
// Color temperature in mireds // Color temperature in mireds
if (t.equals(MQTT_TOPIC_MIRED)) { if (t.equals(MQTT_TOPIC_MIRED)) {


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

@ -67,6 +67,9 @@ void mdnsServerSetup() {
itoa(ESP.getFreeSketchSpace(), buffer, 10); itoa(ESP.getFreeSketchSpace(), buffer, 10);
MDNS.addServiceTxt("arduino", "tcp", "free_space", (const char *) buffer); MDNS.addServiceTxt("arduino", "tcp", "free_space", (const char *) buffer);
} }
#ifdef APP_BUILD_FLAGS
//MDNS.addServiceTxt("arduino", "tcp", "build_flags", APP_BUILD_FLAGS);
#endif
wifiRegister([](justwifi_messages_t code, char * parameter) { wifiRegister([](justwifi_messages_t code, char * parameter) {


+ 10
- 12
code/espurna/migrate.ino View File

@ -698,20 +698,18 @@ void migrate() {
setSetting("chLogic", 3, 0); setSetting("chLogic", 3, 0);
setSetting("relays", 1); setSetting("relays", 1);
#elif defined(MAGICHOME_LED_CONTROLLER_23)
#elif defined(KMC_70011)
setSetting("board", 53); setSetting("board", 53);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 12);
setSetting("chGPIO", 1, 5);
setSetting("chGPIO", 2, 13);
setSetting("chGPIO", 3, 15);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("relays", 1);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 14);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 12);
setSetting("cf1GPIO", 5);
setSetting("cfGPIO", 4);
#else #else


+ 39
- 15
code/espurna/mqtt.ino View File

@ -38,7 +38,7 @@ bool _mqtt_use_json = false;
unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN; unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
unsigned char _mqtt_qos = MQTT_QOS; unsigned char _mqtt_qos = MQTT_QOS;
bool _mqtt_retain = MQTT_RETAIN; bool _mqtt_retain = MQTT_RETAIN;
unsigned char _mqtt_keepalive = MQTT_KEEPALIVE;
unsigned long _mqtt_keepalive = MQTT_KEEPALIVE;
String _mqtt_topic; String _mqtt_topic;
String _mqtt_topic_json; String _mqtt_topic_json;
String _mqtt_setter; String _mqtt_setter;
@ -215,11 +215,12 @@ void _mqttConfigure() {
// Get base topic // Get base topic
_mqtt_topic = getSetting("mqttTopic", MQTT_TOPIC); _mqtt_topic = getSetting("mqttTopic", MQTT_TOPIC);
if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1); if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1);
if (_mqtt_topic.indexOf("#") == -1) _mqtt_topic = _mqtt_topic + "/#";
// Placeholders // Placeholders
_mqtt_topic.replace("{identifier}", getSetting("hostname")); _mqtt_topic.replace("{identifier}", getSetting("hostname"));
_mqtt_topic.replace("{hostname}", getSetting("hostname")); _mqtt_topic.replace("{hostname}", getSetting("hostname"));
_mqtt_topic.replace("{magnitude}", "#");
if (_mqtt_topic.indexOf("#") == -1) _mqtt_topic = _mqtt_topic + "/#";
String mac = WiFi.macAddress(); String mac = WiFi.macAddress();
mac.replace(":", ""); mac.replace(":", "");
_mqtt_topic.replace("{mac}", mac); _mqtt_topic.replace("{mac}", mac);
@ -356,7 +357,7 @@ void _mqttCallback(unsigned int type, const char * topic, const char * payload)
if (type == MQTT_MESSAGE_EVENT) { if (type == MQTT_MESSAGE_EVENT) {
// Match topic // Match topic
String t = mqttTopicKey((char *) topic);
String t = mqttMagnitude((char *) topic);
// Actions // Actions
if (t.equals(MQTT_TOPIC_ACTION)) { if (t.equals(MQTT_TOPIC_ACTION)) {
@ -425,7 +426,13 @@ void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
// Public API // Public API
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
String mqttTopicKey(char * topic) {
/**
Returns the magnitude part of a topic
@param topic the full MQTT topic
@return String object with the magnitude part.
*/
String mqttMagnitude(char * topic) {
String pattern = _mqtt_topic + _mqtt_setter; String pattern = _mqtt_topic + _mqtt_setter;
int position = pattern.indexOf("#"); int position = pattern.indexOf("#");
@ -433,28 +440,45 @@ String mqttTopicKey(char * topic) {
String start = pattern.substring(0, position); String start = pattern.substring(0, position);
String end = pattern.substring(position + 1); String end = pattern.substring(position + 1);
String response = String(topic);
if (response.startsWith(start) && response.endsWith(end)) {
response.replace(start, "");
response.replace(end, "");
String magnitude = String(topic);
if (magnitude.startsWith(start) && magnitude.endsWith(end)) {
magnitude.replace(start, "");
magnitude.replace(end, "");
} else { } else {
response = String();
magnitude = String();
} }
return response;
return magnitude;
} }
String mqttTopic(const char * topic, bool is_set) {
/**
Returns a full MQTT topic from the magnitude
@param magnitude the magnitude part of the topic.
@param is_set whether to build a command topic (true)
or a state topic (false).
@return String full MQTT topic.
*/
String mqttTopic(const char * magnitude, bool is_set) {
String output = _mqtt_topic; String output = _mqtt_topic;
output.replace("#", topic);
output.replace("#", magnitude);
output += is_set ? _mqtt_setter : _mqtt_getter; output += is_set ? _mqtt_setter : _mqtt_getter;
return output; return output;
} }
String mqttTopic(const char * topic, unsigned int index, bool is_set) {
char buffer[strlen(topic)+5];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), topic, index);
/**
Returns a full MQTT topic from the magnitude
@param magnitude the magnitude part of the topic.
@param index index of the magnitude when more than one such magnitudes.
@param is_set whether to build a command topic (true)
or a state topic (false).
@return String full MQTT topic.
*/
String mqttTopic(const char * magnitude, unsigned int index, bool is_set) {
char buffer[strlen(magnitude)+5];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), magnitude, index);
return mqttTopic(buffer, is_set); return mqttTopic(buffer, is_set);
} }


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

@ -74,7 +74,7 @@ void _nofussInitCommands() {
} }
#endif // TERMINAL_SUPPORT #endif // TERMINAL_SUPPORT
#
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void nofussRun() { void nofussRun() {


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

@ -35,7 +35,7 @@ void _ntpStart() {
_ntp_start = 0; _ntp_start = 0;
NTP.begin(getSetting("ntpServer", NTP_SERVER)); NTP.begin(getSetting("ntpServer", NTP_SERVER));
NTP.setInterval(NTP_UPDATE_INTERVAL);
NTP.setInterval(NTP_SYNC_INTERVAL, NTP_UPDATE_INTERVAL);
_ntpConfigure(); _ntpConfigure();
} }


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

@ -9,7 +9,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include "ArduinoOTA.h" #include "ArduinoOTA.h"
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// OTA
// Arduino OTA
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void _otaConfigure() { void _otaConfigure() {
@ -20,15 +20,199 @@ void _otaConfigure() {
#endif #endif
} }
void _otaLoop() {
ArduinoOTA.handle();
}
// -----------------------------------------------------------------------------
// Terminal OTA
// -----------------------------------------------------------------------------
#if TERMINAL_SUPPORT
#include <ESPAsyncTCP.h>
AsyncClient * _ota_client;
char * _ota_host;
char * _ota_url;
unsigned int _ota_port = 80;
unsigned long _ota_size = 0;
const char OTA_REQUEST_TEMPLATE[] PROGMEM =
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n"
"User-Agent: ESPurna\r\n"
"Connection: close\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"Content-Length: 0\r\n\r\n\r\n";
void _otaFrom(const char * host, unsigned int port, const char * url) {
if (_ota_host) free(_ota_host);
if (_ota_url) free(_ota_url);
_ota_host = strdup(host);
_ota_url = strdup(url);
_ota_port = port;
_ota_size = 0;
if (_ota_client == NULL) {
_ota_client = new AsyncClient();
}
_ota_client->onDisconnect([](void *s, AsyncClient *c) {
DEBUG_MSG_P(PSTR("\n"));
if (Update.end(true)){
DEBUG_MSG_P(PSTR("[OTA] Success: %u bytes\n"), _ota_size);
deferredReset(100, CUSTOM_RESET_OTA);
} else {
#ifdef DEBUG_PORT
Update.printError(DEBUG_PORT);
#endif
}
DEBUG_MSG_P(PSTR("[OTA] Disconnected\n"));
_ota_client->free();
delete _ota_client;
_ota_client = NULL;
free(_ota_host);
_ota_host = NULL;
free(_ota_url);
_ota_url = NULL;
}, 0);
_ota_client->onTimeout([](void *s, AsyncClient *c, uint32_t time) {
_ota_client->close(true);
}, 0);
_ota_client->onData([](void * arg, AsyncClient * c, void * data, size_t len) {
char * p = (char *) data;
if (_ota_size == 0) {
Update.runAsync(true);
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
#ifdef DEBUG_PORT
Update.printError(DEBUG_PORT);
#endif
}
p = strstr((char *)data, "\r\n\r\n") + 4;
len = len - (p - (char *) data);
}
if (!Update.hasError()) {
if (Update.write((uint8_t *) p, len) != len) {
#ifdef DEBUG_PORT
Update.printError(DEBUG_PORT);
#endif
}
}
_ota_size += len;
DEBUG_MSG_P(PSTR("[OTA] Progress: %u bytes\r"), _ota_size);
}, NULL);
_ota_client->onConnect([](void * arg, AsyncClient * client) {
#if ASYNC_TCP_SSL_ENABLED
if (443 == _ota_port) {
uint8_t fp[20] = {0};
sslFingerPrintArray(getSetting("otafp", OTA_GITHUB_FP).c_str(), fp);
SSL * ssl = _ota_client->getSSL();
if (ssl_match_fingerprint(ssl, fp) != SSL_OK) {
DEBUG_MSG_P(PSTR("[OTA] Warning: certificate doesn't match\n"));
}
}
#endif
DEBUG_MSG_P(PSTR("[OTA] Downloading %s\n"), _ota_url);
char buffer[strlen_P(OTA_REQUEST_TEMPLATE) + strlen(_ota_url) + strlen(_ota_host)];
snprintf_P(buffer, sizeof(buffer), OTA_REQUEST_TEMPLATE, _ota_url, _ota_host);
client->write(buffer);
}, NULL);
#if ASYNC_TCP_SSL_ENABLED
bool connected = _ota_client->connect(host, port, 443 == port);
#else
bool connected = _ota_client->connect(host, port);
#endif
if (!connected) {
DEBUG_MSG_P(PSTR("[OTA] Connection failed\n"));
_ota_client->close(true);
}
}
void _otaFrom(String url) {
// Port from protocol
unsigned int port = 80;
if (url.startsWith("https://")) port = 443;
url = url.substring(url.indexOf("/") + 2);
// Get host
String host = url.substring(0, url.indexOf("/"));
// Explicit port
int p = host.indexOf(":");
if (p > 0) {
port = host.substring(p + 1).toInt();
host = host.substring(0, p);
}
// Get URL
String uri = url.substring(url.indexOf("/"));
_otaFrom(host.c_str(), port, uri.c_str());
}
void _otaInitCommands() {
settingsRegisterCommand(F("OTA"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
} else {
DEBUG_MSG_P(PSTR("+OK\n"));
String url = String(e->argv[1]);
_otaFrom(url);
}
});
}
#endif // TERMINAL_SUPPORT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void otaSetup() { void otaSetup() {
_otaConfigure(); _otaConfigure();
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnAfterParseRegister(_otaConfigure); wsOnAfterParseRegister(_otaConfigure);
#endif #endif
#if TERMINAL_SUPPORT
_otaInitCommands();
#endif
// Register loop
espurnaRegisterLoop(_otaLoop);
// -------------------------------------------------------------------------
ArduinoOTA.onStart([]() { ArduinoOTA.onStart([]() {
DEBUG_MSG_P(PSTR("[OTA] Start\n")); DEBUG_MSG_P(PSTR("[OTA] Start\n"));
#if WEB_SUPPORT #if WEB_SUPPORT
@ -38,7 +222,7 @@ void otaSetup() {
ArduinoOTA.onEnd([]() { ArduinoOTA.onEnd([]() {
DEBUG_MSG_P(PSTR("\n")); DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[OTA] End\n"));
DEBUG_MSG_P(PSTR("[OTA] Done, restarting...\n"));
#if WEB_SUPPORT #if WEB_SUPPORT
wsSend_P(PSTR("{\"action\": \"reload\"}")); wsSend_P(PSTR("{\"action\": \"reload\"}"));
#endif #endif
@ -62,11 +246,4 @@ void otaSetup() {
ArduinoOTA.begin(); ArduinoOTA.begin();
// Register loop
espurnaRegisterLoop(otaLoop);
}
void otaLoop() {
ArduinoOTA.handle();
} }

+ 100
- 74
code/espurna/relay.ino View File

@ -77,6 +77,15 @@ void _relayProviderStatus(unsigned char id, bool status) {
#endif #endif
#if RELAY_PROVIDER == RELAY_PROVIDER_STM
Serial.flush();
Serial.write(0xA0);
Serial.write(id + 1);
Serial.write(status);
Serial.write(0xA1 + status + id);
Serial.flush();
#endif
#if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT #if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
// If the number of relays matches the number of light channels // If the number of relays matches the number of light channels
@ -125,6 +134,74 @@ void _relayProviderStatus(unsigned char id, bool status) {
} }
/**
* Walks the relay vector processing only those relays
* that have to change to the requested mode
* @bool mode Requested mode
*/
void _relayProcess(bool mode) {
unsigned long current_time = millis();
for (unsigned char id = 0; id < _relays.size(); id++) {
bool target = _relays[id].target_status;
// Only process the relays we have to change
if (target == _relays[id].current_status) continue;
// Only process the relays we have change to the requested mode
if (target != mode) continue;
// Only process if the change_time has arrived
if (current_time < _relays[id].change_time) continue;
DEBUG_MSG_P(PSTR("[RELAY] #%d set to %s\n"), id, target ? "ON" : "OFF");
// Call the provider to perform the action
_relayProviderStatus(id, target);
// Send to Broker
#if BROKER_SUPPORT
brokerPublish(MQTT_TOPIC_RELAY, id, target ? "1" : "0");
#endif
// Send MQTT
#if MQTT_SUPPORT
relayMQTT(id);
#endif
if (!_relayRecursive) {
relayPulse(id);
_relaySaveTicker.once_ms(RELAY_SAVE_DELAY, relaySave);
#if WEB_SUPPORT
wsSend(_relayWebSocketUpdate);
#endif
}
#if DOMOTICZ_SUPPORT
domoticzSendRelay(id);
#endif
#if INFLUXDB_SUPPORT
relayInfluxDB(id);
#endif
#if THINGSPEAK_SUPPORT
tspkEnqueueRelay(id, target);
tspkFlush();
#endif
// Flag relay-based LEDs to update status
ledUpdate(true);
_relays[id].report = false;
_relays[id].group_report = false;
}
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// RELAY // RELAY
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -385,7 +462,11 @@ void _relayBoot() {
} }
_relays[i].current_status = !status; _relays[i].current_status = !status;
_relays[i].target_status = status; _relays[i].target_status = status;
_relays[i].change_time = millis();
#if RELAY_PROVIDER == RELAY_PROVIDER_STM
_relays[i].change_time = millis() + 3000 + 1000 * i;
#else
_relays[i].change_time = millis();
#endif
bit <<= 1; bit <<= 1;
} }
@ -512,7 +593,7 @@ void relaySetupAPI() {
apiRegister(key, apiRegister(key,
[relayID](char * buffer, size_t len) { [relayID](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), relayStatus(relayID) ? 1 : 0);
snprintf_P(buffer, len, PSTR("%d"), _relays[relayID].target_status ? 1 : 0);
}, },
[relayID](const char * payload) { [relayID](const char * payload) {
@ -611,7 +692,7 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
if (type == MQTT_MESSAGE_EVENT) { if (type == MQTT_MESSAGE_EVENT) {
// Check relay topic // Check relay topic
String t = mqttTopicKey((char *) topic);
String t = mqttMagnitude((char *) topic);
if (t.startsWith(MQTT_TOPIC_RELAY)) { if (t.startsWith(MQTT_TOPIC_RELAY)) {
// Get value // Get value
@ -709,11 +790,16 @@ void _relayInitCommands() {
// Setup // Setup
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void _relayLoop() {
_relayProcess(false);
_relayProcess(true);
}
void relaySetup() { void relaySetup() {
// Dummy relays for AI Light, Magic Home LED Controller, H801, // Dummy relays for AI Light, Magic Home LED Controller, H801,
// Sonoff Dual and Sonoff RF Bridge // Sonoff Dual and Sonoff RF Bridge
#ifdef DUMMY_RELAY_COUNT
#if DUMMY_RELAY_COUNT > 0
for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) { for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL}); _relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL});
@ -721,28 +807,28 @@ void relaySetup() {
#else #else
#ifdef RELAY1_PIN
#if RELAY1_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY1_PIN, RELAY1_TYPE, RELAY1_RESET_PIN, RELAY1_DELAY_ON, RELAY1_DELAY_OFF }); _relays.push_back((relay_t) { RELAY1_PIN, RELAY1_TYPE, RELAY1_RESET_PIN, RELAY1_DELAY_ON, RELAY1_DELAY_OFF });
#endif #endif
#ifdef RELAY2_PIN
#if RELAY2_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY2_PIN, RELAY2_TYPE, RELAY2_RESET_PIN, RELAY2_DELAY_ON, RELAY2_DELAY_OFF }); _relays.push_back((relay_t) { RELAY2_PIN, RELAY2_TYPE, RELAY2_RESET_PIN, RELAY2_DELAY_ON, RELAY2_DELAY_OFF });
#endif #endif
#ifdef RELAY3_PIN
#if RELAY3_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY3_PIN, RELAY3_TYPE, RELAY3_RESET_PIN, RELAY3_DELAY_ON, RELAY3_DELAY_OFF }); _relays.push_back((relay_t) { RELAY3_PIN, RELAY3_TYPE, RELAY3_RESET_PIN, RELAY3_DELAY_ON, RELAY3_DELAY_OFF });
#endif #endif
#ifdef RELAY4_PIN
#if RELAY4_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY4_PIN, RELAY4_TYPE, RELAY4_RESET_PIN, RELAY4_DELAY_ON, RELAY4_DELAY_OFF }); _relays.push_back((relay_t) { RELAY4_PIN, RELAY4_TYPE, RELAY4_RESET_PIN, RELAY4_DELAY_ON, RELAY4_DELAY_OFF });
#endif #endif
#ifdef RELAY5_PIN
#if RELAY5_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY5_PIN, RELAY5_TYPE, RELAY5_RESET_PIN, RELAY5_DELAY_ON, RELAY5_DELAY_OFF }); _relays.push_back((relay_t) { RELAY5_PIN, RELAY5_TYPE, RELAY5_RESET_PIN, RELAY5_DELAY_ON, RELAY5_DELAY_OFF });
#endif #endif
#ifdef RELAY6_PIN
#if RELAY6_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY6_PIN, RELAY6_TYPE, RELAY6_RESET_PIN, RELAY6_DELAY_ON, RELAY6_DELAY_OFF }); _relays.push_back((relay_t) { RELAY6_PIN, RELAY6_TYPE, RELAY6_RESET_PIN, RELAY6_DELAY_ON, RELAY6_DELAY_OFF });
#endif #endif
#ifdef RELAY7_PIN
#if RELAY7_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY7_PIN, RELAY7_TYPE, RELAY7_RESET_PIN, RELAY7_DELAY_ON, RELAY7_DELAY_OFF }); _relays.push_back((relay_t) { RELAY7_PIN, RELAY7_TYPE, RELAY7_RESET_PIN, RELAY7_DELAY_ON, RELAY7_DELAY_OFF });
#endif #endif
#ifdef RELAY8_PIN
#if RELAY8_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY8_PIN, RELAY8_TYPE, RELAY8_RESET_PIN, RELAY8_DELAY_ON, RELAY8_DELAY_OFF }); _relays.push_back((relay_t) { RELAY8_PIN, RELAY8_TYPE, RELAY8_RESET_PIN, RELAY8_DELAY_ON, RELAY8_DELAY_OFF });
#endif #endif
@ -751,9 +837,9 @@ void relaySetup() {
_relayBackwards(); _relayBackwards();
_relayConfigure(); _relayConfigure();
_relayBoot(); _relayBoot();
relayLoop();
_relayLoop();
espurnaRegisterLoop(relayLoop);
espurnaRegisterLoop(_relayLoop);
#if WEB_SUPPORT #if WEB_SUPPORT
relaySetupAPI(); relaySetupAPI();
@ -769,63 +855,3 @@ void relaySetup() {
DEBUG_MSG_P(PSTR("[RELAY] Number of relays: %d\n"), _relays.size()); DEBUG_MSG_P(PSTR("[RELAY] Number of relays: %d\n"), _relays.size());
} }
void relayLoop(void) {
unsigned char id;
for (id = 0; id < _relays.size(); id++) {
unsigned int current_time = millis();
bool status = _relays[id].target_status;
if ((_relays[id].current_status != status)
&& (current_time >= _relays[id].change_time)) {
DEBUG_MSG_P(PSTR("[RELAY] #%d set to %s\n"), id, status ? "ON" : "OFF");
// Call the provider to perform the action
_relayProviderStatus(id, status);
// Send to Broker
#if BROKER_SUPPORT
brokerPublish(MQTT_TOPIC_RELAY, id, status ? "1" : "0");
#endif
// Send MQTT
#if MQTT_SUPPORT
relayMQTT(id);
#endif
if (!_relayRecursive) {
relayPulse(id);
_relaySaveTicker.once_ms(RELAY_SAVE_DELAY, relaySave);
#if WEB_SUPPORT
wsSend(_relayWebSocketUpdate);
#endif
}
#if DOMOTICZ_SUPPORT
domoticzSendRelay(id);
#endif
#if INFLUXDB_SUPPORT
relayInfluxDB(id);
#endif
#if THINGSPEAK_SUPPORT
tspkEnqueueRelay(id, status);
tspkFlush();
#endif
// Flag relay-based LEDs to update status
ledUpdate(true);
_relays[id].report = false;
_relays[id].group_report = false;
}
}
}

+ 145
- 61
code/espurna/rf.ino View File

@ -8,97 +8,181 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#if RF_SUPPORT #if RF_SUPPORT
#include <RemoteReceiver.h>
#include <RCSwitch.h>
unsigned long rfCode = 0;
unsigned long rfCodeON = 0;
unsigned long rfCodeOFF = 0;
RCSwitch * _rfModem;
unsigned long _rf_learn_start = 0;
unsigned char _rf_learn_id = 0;
bool _rf_learn_status = true;
bool _rf_learn_active = false;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// RF // RF
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void _rfWebSocketOnSend(JsonObject& root) {
root["rfVisible"] = 1;
root["rfChannel"] = getSetting("rfChannel", RF_CHANNEL);
root["rfDevice"] = getSetting("rfDevice", RF_DEVICE);
unsigned long _rfRetrieve(unsigned char id, bool status) {
String code = getSetting(status ? "rfbON" : "rfbOFF", id, "0");
return strtoul(code.c_str(), 0, 16);
} }
void _rfBuildCodes() {
void _rfStore(unsigned char id, bool status, unsigned long code) {
DEBUG_MSG_P(PSTR("[RF] Storing %d-%s => %X\n"), id, status ? "ON" : "OFF", code);
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%X"), code);
setSetting(status ? "rfbON" : "rfbOFF", id, buffer);
}
unsigned long code = 0;
void _rfLearn(unsigned char id, bool status) {
_rf_learn_start = millis();
_rf_learn_id = id;
_rf_learn_status = status;
_rf_learn_active = true;
}
// channel
unsigned int channel = getSetting("rfChannel", RF_CHANNEL).toInt();
for (byte i = 0; i < 5; i++) {
code *= 3;
if (channel & 1) code += 1;
channel >>= 1;
}
void _rfForget(unsigned char id, bool status) {
delSetting(status ? "rfbON" : "rfbOFF", id);
// Websocket update
#if WEB_SUPPORT
char wsb[100];
snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"\"}]}"), id, status ? 1 : 0);
wsSend(wsb);
#endif
}
// device
unsigned int device = getSetting("rfDevice", RF_DEVICE).toInt();
for (byte i = 0; i < 5; i++) {
code *= 3;
if (device != i) code += 2;
}
// status
code *= 9;
rfCodeOFF = code + 2;
rfCodeON = code + 6;
bool _rfMatch(unsigned long code, unsigned char& relayID, unsigned char& value) {
DEBUG_MSG_P(PSTR("[RF] Code ON : %lu\n"), rfCodeON);
DEBUG_MSG_P(PSTR("[RF] Code OFF: %lu\n"), rfCodeOFF);
bool found = false;
DEBUG_MSG_P(PSTR("[RF] Trying to match code %X\n"), code);
for (unsigned char i=0; i<relayCount(); i++) {
unsigned long code_on = _rfRetrieve(i, true);
unsigned long code_off = _rfRetrieve(i, false);
if (code == code_on) {
DEBUG_MSG_P(PSTR("[RF] Match ON code for relay %d\n"), i);
value = 1;
found = true;
}
if (code == code_off) {
DEBUG_MSG_P(PSTR("[RF] Match OFF code for relay %d\n"), i);
if (found) value = 2;
found = true;
}
if (found) {
relayID = i;
return true;
}
}
return false;
} }
// -----------------------------------------------------------------------------
// WEB
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void rfLoop() {
return;
if (rfCode == 0) return;
DEBUG_MSG_P(PSTR("[RF] Received code: %lu\n"), rfCode);
if (rfCode == rfCodeON) relayStatus(0, true);
if (rfCode == rfCodeOFF) relayStatus(0, false);
rfCode = 0;
void _rfWebSocketOnSend(JsonObject& root) {
char buffer[20];
root["rfbVisible"] = 1;
root["rfbCount"] = relayCount();
JsonArray& rfb = root.createNestedArray("rfb");
for (byte id=0; id<relayCount(); id++) {
for (byte status=0; status<2; status++) {
JsonObject& node = rfb.createNestedObject();
snprintf_P(buffer, sizeof(buffer), PSTR("%X"), _rfRetrieve(id, status == 1));
node["id"] = id;
node["status"] = status;
node["data"] = String(buffer);
}
}
} }
void rfCallback(unsigned long code, unsigned int period) {
rfCode = code;
void _rfWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "rfblearn") == 0) _rfLearn(data["id"], data["status"]);
if (strcmp(action, "rfbforget") == 0) _rfForget(data["id"], data["status"]);
if (strcmp(action, "rfbsend") == 0) _rfStore(data["id"], data["status"], data["data"].as<long>());
} }
void rfSetup() {
// -----------------------------------------------------------------------------
void rfLoop() {
static unsigned long last = 0;
if (_rfModem->available()) {
if (millis() - last > RF_DEBOUNCE) {
last = millis();
if (_rfModem->getReceivedValue() > 0) {
unsigned long rf_code = _rfModem->getReceivedValue();
DEBUG_MSG_P(PSTR("[RF] Received code: %X\n"), rf_code);
if (_rf_learn_active) {
_rf_learn_active = false;
pinMode(RF_PIN, INPUT_PULLUP);
_rfBuildCodes();
RemoteReceiver::init(RF_PIN, 3, rfCallback);
RemoteReceiver::disable();
DEBUG_MSG_P(PSTR("[RF] Disabled\n"));
_rfStore(_rf_learn_id, _rf_learn_status, rf_code);
static WiFiEventHandler e1 = WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected& event) {
RemoteReceiver::disable();
DEBUG_MSG_P(PSTR("[RF] Disabled\n"));
});
// Websocket update
#if WEB_SUPPORT
char wsb[100];
snprintf_P(
wsb, sizeof(wsb),
PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%X\"}]}"),
_rf_learn_id, _rf_learn_status ? 1 : 0, rf_code);
wsSend(wsb);
#endif
static WiFiEventHandler e2 = WiFi.onSoftAPModeStationDisconnected([](const WiFiEventSoftAPModeStationDisconnected& event) {
RemoteReceiver::disable();
DEBUG_MSG_P(PSTR("[RF] Disabled\n"));
});
} else {
static WiFiEventHandler e3 = WiFi.onStationModeConnected([](const WiFiEventStationModeConnected& event) {
RemoteReceiver::enable();
DEBUG_MSG_P(PSTR("[RF] Enabled\n"));
});
unsigned char id;
unsigned char value;
if (_rfMatch(rf_code, id, value)) {
if (2 == value) {
relayToggle(id);
} else {
relayStatus(id, 1 == value);
}
}
}
}
}
_rfModem->resetAvailable();
}
if (_rf_learn_active && (millis() - _rf_learn_start > RF_LEARN_TIMEOUT)) {
_rf_learn_active = false;
}
}
void rfSetup() {
static WiFiEventHandler e4 = WiFi.onSoftAPModeStationConnected([](const WiFiEventSoftAPModeStationConnected& event) {
RemoteReceiver::enable();
DEBUG_MSG_P(PSTR("[RF] Enabled\n"));
});
_rfModem = new RCSwitch();
_rfModem->enableReceive(RF_PIN);
DEBUG_MSG_P(PSTR("[RF] RF receiver on GPIO %u\n"), RF_PIN);
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_rfWebSocketOnSend); wsOnSendRegister(_rfWebSocketOnSend);
wsOnAfterParseRegister(_rfBuildCodes);
wsOnActionRegister(_rfWebSocketOnAction);
#endif #endif
// Register loop // Register loop


+ 19
- 9
code/espurna/rfbridge.ino View File

@ -354,7 +354,7 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
if (type == MQTT_MESSAGE_EVENT) { if (type == MQTT_MESSAGE_EVENT) {
// Match topic // Match topic
String t = mqttTopicKey((char *) topic);
String t = mqttMagnitude((char *) topic);
// Check if should go into learn mode // Check if should go into learn mode
if (t.startsWith(MQTT_TOPIC_RFLEARN)) { if (t.startsWith(MQTT_TOPIC_RFLEARN)) {
@ -443,37 +443,47 @@ String rfbRetrieve(unsigned char id, bool status) {
} }
void rfbStatus(unsigned char id, bool status) { void rfbStatus(unsigned char id, bool status) {
String value = rfbRetrieve(id, status); String value = rfbRetrieve(id, status);
if (value.length() > 0) { if (value.length() > 0) {
bool same = _rfbSameOnOff(id); bool same = _rfbSameOnOff(id);
#if RF_RAW_SUPPORT #if RF_RAW_SUPPORT
byte message[RF_MAX_MESSAGE_SIZE]; byte message[RF_MAX_MESSAGE_SIZE];
int len = _rfbToArray(value.c_str(), message, 0); int len = _rfbToArray(value.c_str(), message, 0);
if (len == RF_MESSAGE_SIZE && // probably a standard msg if (len == RF_MESSAGE_SIZE && // probably a standard msg
(message[0] != RF_CODE_START || // raw would start with 0xAA (message[0] != RF_CODE_START || // raw would start with 0xAA
message[1] != RF_CODE_RFOUT_BUCKET || // followed by 0xB0, message[1] != RF_CODE_RFOUT_BUCKET || // followed by 0xB0,
message[2] + 4 != len || // needs a valid length, message[2] + 4 != len || // needs a valid length,
message[len-1] != RF_CODE_STOP)) { // and finish with 0x55 message[len-1] != RF_CODE_STOP)) { // and finish with 0x55
unsigned char times = RF_SEND_TIMES;
if (same) times = _rfbin ? 0 : 1;
_rfbSend(message, times);
if (!_rfbin) {
unsigned char times = same ? 1 : RF_SEND_TIMES;
_rfbSend(message, times);
}
} else { } else {
_rfbSendRawOnce(message, len); // send a raw message _rfbSendRawOnce(message, len); // send a raw message
} }
#else // RF_RAW_SUPPORT #else // RF_RAW_SUPPORT
byte message[RF_MESSAGE_SIZE];
_rfbToArray(value.c_str(), message);
unsigned char times = RF_SEND_TIMES;
if (same) times = _rfbin ? 0 : 1;
_rfbSend(message, times);
if (!_rfbin) {
byte message[RF_MESSAGE_SIZE];
_rfbToArray(value.c_str(), message);
unsigned char times = same ? 1 : RF_SEND_TIMES;
_rfbSend(message, times);
}
#endif // RF_RAW_SUPPORT #endif // RF_RAW_SUPPORT
} }
_rfbin = false;
} }
void rfbLearn(unsigned char id, bool status) { void rfbLearn(unsigned char id, bool status) {


+ 1
- 0
code/espurna/scheduler.ino View File

@ -17,6 +17,7 @@ Adapted by Xose Pérez <xose dot perez at gmail dot com>
void _schWebSocketOnSend(JsonObject &root){ void _schWebSocketOnSend(JsonObject &root){
root["schVisible"] = 1;
root["maxScheduled"] = SCHEDULER_MAX_SCHEDULES; root["maxScheduled"] = SCHEDULER_MAX_SCHEDULES;
JsonArray &sch = root.createNestedArray("schedule"); JsonArray &sch = root.createNestedArray("schedule");
for (byte i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) { for (byte i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {


+ 67
- 1
code/espurna/sensor.ino View File

@ -33,6 +33,8 @@ unsigned char _counts[MAGNITUDE_MAX];
bool _sensor_realtime = API_REAL_TIME_VALUES; bool _sensor_realtime = API_REAL_TIME_VALUES;
unsigned long _sensor_read_interval = 1000 * SENSOR_READ_INTERVAL; unsigned long _sensor_read_interval = 1000 * SENSOR_READ_INTERVAL;
unsigned char _sensor_report_every = SENSOR_REPORT_EVERY; unsigned char _sensor_report_every = SENSOR_REPORT_EVERY;
unsigned char _sensor_power_units = SENSOR_POWER_UNITS;
unsigned char _sensor_energy_units = SENSOR_ENERGY_UNITS;
unsigned char _sensor_temperature_units = SENSOR_TEMPERATURE_UNITS; unsigned char _sensor_temperature_units = SENSOR_TEMPERATURE_UNITS;
double _sensor_temperature_correction = SENSOR_TEMPERATURE_CORRECTION; double _sensor_temperature_correction = SENSOR_TEMPERATURE_CORRECTION;
@ -41,16 +43,43 @@ double _sensor_temperature_correction = SENSOR_TEMPERATURE_CORRECTION;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
unsigned char _magnitudeDecimals(unsigned char type) { unsigned char _magnitudeDecimals(unsigned char type) {
// Hardcoded decimals (these should be linked to the unit, instead of the magnitude)
if (type == MAGNITUDE_ENERGY ||
type == MAGNITUDE_ENERGY_DELTA) {
if (_sensor_energy_units == ENERGY_KWH) return 3;
}
if (type == MAGNITUDE_POWER_ACTIVE ||
type == MAGNITUDE_POWER_APPARENT ||
type == MAGNITUDE_POWER_REACTIVE) {
if (_sensor_power_units == POWER_KILOWATTS) return 3;
}
if (type < MAGNITUDE_MAX) return pgm_read_byte(magnitude_decimals + type); if (type < MAGNITUDE_MAX) return pgm_read_byte(magnitude_decimals + type);
return 0; return 0;
} }
double _magnitudeProcess(unsigned char type, double value) { double _magnitudeProcess(unsigned char type, double value) {
// Hardcoded conversions (these should be linked to the unit, instead of the magnitude)
if (type == MAGNITUDE_TEMPERATURE) { if (type == MAGNITUDE_TEMPERATURE) {
if (_sensor_temperature_units == TMP_FAHRENHEIT) value = value * 1.8 + 32; if (_sensor_temperature_units == TMP_FAHRENHEIT) value = value * 1.8 + 32;
value = value + _sensor_temperature_correction; value = value + _sensor_temperature_correction;
} }
if (type == MAGNITUDE_ENERGY ||
type == MAGNITUDE_ENERGY_DELTA) {
if (_sensor_energy_units == ENERGY_KWH) value = value / 3600000;
}
if (type == MAGNITUDE_POWER_ACTIVE ||
type == MAGNITUDE_POWER_APPARENT ||
type == MAGNITUDE_POWER_REACTIVE) {
if (_sensor_power_units == POWER_KILOWATTS) value = value / 1000;
}
return roundTo(value, _magnitudeDecimals(type)); return roundTo(value, _magnitudeDecimals(type));
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -94,6 +123,7 @@ void _sensorWebSocketStart(JsonObject& root) {
#if EMON_ANALOG_SUPPORT #if EMON_ANALOG_SUPPORT
if (sensor->getID() == SENSOR_EMON_ANALOG_ID) { if (sensor->getID() == SENSOR_EMON_ANALOG_ID) {
root["emonVisible"] = 1; root["emonVisible"] = 1;
root["pwrVisible"] = 1;
root["pwrVoltage"] = ((EmonAnalogSensor *) sensor)->getVoltage(); root["pwrVoltage"] = ((EmonAnalogSensor *) sensor)->getVoltage();
} }
#endif #endif
@ -101,6 +131,19 @@ void _sensorWebSocketStart(JsonObject& root) {
#if HLW8012_SUPPORT #if HLW8012_SUPPORT
if (sensor->getID() == SENSOR_HLW8012_ID) { if (sensor->getID() == SENSOR_HLW8012_ID) {
root["hlwVisible"] = 1; root["hlwVisible"] = 1;
root["pwrVisible"] = 1;
}
#endif
#if V9261F_SUPPORT
if (sensor->getID() == SENSOR_V9261F_ID) {
root["pwrVisible"] = 1;
}
#endif
#if ECH1560_SUPPORT
if (sensor->getID() == SENSOR_ECH1560_ID) {
root["pwrVisible"] = 1;
} }
#endif #endif
@ -109,6 +152,8 @@ void _sensorWebSocketStart(JsonObject& root) {
if (_magnitudes.size() > 0) { if (_magnitudes.size() > 0) {
root["sensorsVisible"] = 1; root["sensorsVisible"] = 1;
//root["apiRealTime"] = _sensor_realtime; //root["apiRealTime"] = _sensor_realtime;
root["powerUnits"] = _sensor_power_units;
root["energyUnits"] = _sensor_energy_units;
root["tmpUnits"] = _sensor_temperature_units; root["tmpUnits"] = _sensor_temperature_units;
root["tmpCorrection"] = _sensor_temperature_correction; root["tmpCorrection"] = _sensor_temperature_correction;
root["snsRead"] = _sensor_read_interval / 1000; root["snsRead"] = _sensor_read_interval / 1000;
@ -396,6 +441,12 @@ void _sensorInit() {
} }
void _sensorCallback(unsigned char i, unsigned char type, const char * payload) {
DEBUG_MSG_P(PSTR("[SENSOR] Sensor #%u callback, type %u, payload: '%s'\n"), i, type, payload);
}
void _sensorConfigure() { void _sensorConfigure() {
for (unsigned char i=0; i<_sensors.size(); i++) { for (unsigned char i=0; i<_sensors.size(); i++) {
@ -407,7 +458,7 @@ void _sensorConfigure() {
double value; double value;
EmonAnalogSensor * sensor = (EmonAnalogSensor *) _sensors[i]; EmonAnalogSensor * sensor = (EmonAnalogSensor *) _sensors[i];
if (value = getSetting("pwrExpectedP", 0).toInt() == 0) {
if (value = (getSetting("pwrExpectedP", 0).toInt() == 0)) {
value = getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat(); value = getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat();
if (value > 0) sensor->setCurrentRatio(0, value); if (value > 0) sensor->setCurrentRatio(0, value);
} else { } else {
@ -430,6 +481,11 @@ void _sensorConfigure() {
// Force sensor to reload config // Force sensor to reload config
_sensors[i]->begin(); _sensors[i]->begin();
// Hook callback
_sensors[i]->onEvent([i](unsigned char type, const char * payload) {
_sensorCallback(i, type, payload);
});
#if HLW8012_SUPPORT #if HLW8012_SUPPORT
@ -479,6 +535,8 @@ void _sensorConfigure() {
_sensor_read_interval = 1000 * constrain(getSetting("snsRead", SENSOR_READ_INTERVAL).toInt(), SENSOR_READ_MIN_INTERVAL, SENSOR_READ_MAX_INTERVAL); _sensor_read_interval = 1000 * constrain(getSetting("snsRead", SENSOR_READ_INTERVAL).toInt(), SENSOR_READ_MIN_INTERVAL, SENSOR_READ_MAX_INTERVAL);
_sensor_report_every = constrain(getSetting("snsReport", SENSOR_REPORT_EVERY).toInt(), SENSOR_REPORT_MIN_EVERY, SENSOR_REPORT_MAX_EVERY); _sensor_report_every = constrain(getSetting("snsReport", SENSOR_REPORT_EVERY).toInt(), SENSOR_REPORT_MIN_EVERY, SENSOR_REPORT_MAX_EVERY);
_sensor_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1; _sensor_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
_sensor_power_units = getSetting("powerUnits", SENSOR_POWER_UNITS).toInt();
_sensor_energy_units = getSetting("energyUnits", SENSOR_ENERGY_UNITS).toInt();
_sensor_temperature_units = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt(); _sensor_temperature_units = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt();
_sensor_temperature_correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat(); _sensor_temperature_correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat();
@ -597,6 +655,14 @@ String magnitudeUnits(unsigned char type) {
if (type < MAGNITUDE_MAX) { if (type < MAGNITUDE_MAX) {
if ((type == MAGNITUDE_TEMPERATURE) && (_sensor_temperature_units == TMP_FAHRENHEIT)) { if ((type == MAGNITUDE_TEMPERATURE) && (_sensor_temperature_units == TMP_FAHRENHEIT)) {
strncpy_P(buffer, magnitude_fahrenheit, sizeof(buffer)); strncpy_P(buffer, magnitude_fahrenheit, sizeof(buffer));
} else if (
(type == MAGNITUDE_ENERGY || type == MAGNITUDE_ENERGY_DELTA) &&
(_sensor_energy_units == ENERGY_KWH)) {
strncpy_P(buffer, magnitude_kwh, sizeof(buffer));
} else if (
(type == MAGNITUDE_POWER_ACTIVE || type == MAGNITUDE_POWER_APPARENT || type == MAGNITUDE_POWER_REACTIVE) &&
(_sensor_power_units == POWER_KILOWATTS)) {
strncpy_P(buffer, magnitude_kw, sizeof(buffer));
} else { } else {
strncpy_P(buffer, magnitude_units[type], sizeof(buffer)); strncpy_P(buffer, magnitude_units[type], sizeof(buffer));
} }


+ 1
- 1
code/espurna/sensors/AnalogSensor.h View File

@ -34,7 +34,7 @@ class AnalogSensor : public BaseSensor {
// Descriptive name of the sensor // Descriptive name of the sensor
String description() { String description() {
return String("ANALOG @ GPIO0");
return String("ANALOG @ TOUT");
} }
// Descriptive name of the slot # index // Descriptive name of the slot # index


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

@ -341,7 +341,7 @@ class BMX280Sensor : public I2CSensor {
var1 = ((var1 * var1 * (int64_t)_bmx280_calib.dig_P3)>>8) + var1 = ((var1 * var1 * (int64_t)_bmx280_calib.dig_P3)>>8) +
((var1 * (int64_t)_bmx280_calib.dig_P2)<<12); ((var1 * (int64_t)_bmx280_calib.dig_P2)<<12);
var1 = (((((int64_t)1)<<47)+var1))*((int64_t)_bmx280_calib.dig_P1)>>33; var1 = (((((int64_t)1)<<47)+var1))*((int64_t)_bmx280_calib.dig_P1)>>33;
if (var1 == 0) return; // avoid exception caused by division by zero
if (var1 == 0) return SENSOR_ERROR_I2C; // avoid exception caused by division by zero
p = 1048576 - adc_P; p = 1048576 - adc_P;
p = (((p<<31) - var2)*3125) / var1; p = (((p<<31) - var2)*3125) / var1;


+ 6
- 2
code/espurna/sensors/BaseSensor.h View File

@ -10,8 +10,6 @@
#include <Arduino.h> #include <Arduino.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#define GPIO_NONE 0x99
#define SENSOR_ERROR_OK 0 // No error #define SENSOR_ERROR_OK 0 // No error
#define SENSOR_ERROR_OUT_OF_RANGE 1 // Result out of sensor range #define SENSOR_ERROR_OUT_OF_RANGE 1 // Result out of sensor range
#define SENSOR_ERROR_WARM_UP 2 // Sensor is warming-up #define SENSOR_ERROR_WARM_UP 2 // Sensor is warming-up
@ -21,6 +19,8 @@
#define SENSOR_ERROR_I2C 6 // Wrong or locked I2C address #define SENSOR_ERROR_I2C 6 // Wrong or locked I2C address
#define SENSOR_ERROR_GPIO_USED 7 // The GPIO is already in use #define SENSOR_ERROR_GPIO_USED 7 // The GPIO is already in use
typedef std::function<void(unsigned char, const char *)> TSensorCallback;
class BaseSensor { class BaseSensor {
public: public:
@ -79,8 +79,12 @@ class BaseSensor {
// Number of available slots // Number of available slots
unsigned char count() { return _count; } unsigned char count() { return _count; }
// Hook for event callback
void onEvent(TSensorCallback fn) { _callback = fn; };
protected: protected:
TSensorCallback _callback = NULL;
unsigned char _sensor_id = 0x00; unsigned char _sensor_id = 0x00;
int _error = 0; int _error = 0;
bool _dirty = true; bool _dirty = true;


+ 58
- 47
code/espurna/settings.ino View File

@ -27,11 +27,11 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
EmbedisWrap embedis(EMBEDIS_PORT, TERMINAL_BUFFER_SIZE); EmbedisWrap embedis(EMBEDIS_PORT, TERMINAL_BUFFER_SIZE);
#if TERMINAL_SUPPORT #if TERMINAL_SUPPORT
#ifdef SERIAL_RX_PORT
#if SERIAL_RX_ENABLED
char _serial_rx_buffer[TERMINAL_BUFFER_SIZE]; char _serial_rx_buffer[TERMINAL_BUFFER_SIZE];
static unsigned char _serial_rx_pointer = 0; static unsigned char _serial_rx_pointer = 0;
#endif
#endif
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
bool _settings_save = false; bool _settings_save = false;
@ -85,11 +85,42 @@ String _settingsKeyName(unsigned int index) {
} }
std::vector<String> _settingsKeys() {
// Get sorted list of keys
std::vector<String> keys;
//unsigned int size = settingsKeyCount();
unsigned int size = _settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
//String key = settingsKeyName(i);
String key = _settingsKeyName(i);
bool inserted = false;
for (unsigned char j=0; j<keys.size(); j++) {
// Check if we have to insert it before the current element
if (keys[j].compareTo(key) > 0) {
keys.insert(keys.begin() + j, key);
inserted = true;
break;
}
}
// If we could not insert it, just push it at the end
if (!inserted) keys.push_back(key);
}
return keys;
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Commands // Commands
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void _settingsHelp() {
void _settingsHelpCommand() {
// Get sorted list of commands // Get sorted list of commands
std::vector<String> commands; std::vector<String> commands;
@ -122,32 +153,10 @@ void _settingsHelp() {
} }
void _settingsKeys() {
void _settingsKeysCommand() {
// Get sorted list of keys // Get sorted list of keys
std::vector<String> keys;
//unsigned int size = settingsKeyCount();
unsigned int size = _settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
//String key = settingsKeyName(i);
String key = _settingsKeyName(i);
bool inserted = false;
for (unsigned char j=0; j<keys.size(); j++) {
// Check if we have to insert it before the current element
if (keys[j].compareTo(key) > 0) {
keys.insert(keys.begin() + j, key);
inserted = true;
break;
}
}
// If we could not insert it, just push it at the end
if (!inserted) keys.push_back(key);
}
std::vector<String> keys = _settingsKeys();
// Write key-values // Write key-values
DEBUG_MSG_P(PSTR("Current settings:\n")); DEBUG_MSG_P(PSTR("Current settings:\n"));
@ -162,14 +171,14 @@ void _settingsKeys() {
} }
void _settingsFactoryReset() {
void _settingsFactoryResetCommand() {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) { for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF); EEPROM.write(i, 0xFF);
} }
EEPROM.commit(); EEPROM.commit();
} }
void _settingsDump(bool ascii) {
void _settingsDumpCommand(bool ascii) {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) { for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
if (i % 16 == 0) DEBUG_MSG_P(PSTR("\n[%04X] "), i); if (i % 16 == 0) DEBUG_MSG_P(PSTR("\n[%04X] "), i);
byte c = EEPROM.read(i); byte c = EEPROM.read(i);
@ -191,14 +200,14 @@ void _settingsInitCommands() {
#endif #endif
settingsRegisterCommand(F("COMMANDS"), [](Embedis* e) { settingsRegisterCommand(F("COMMANDS"), [](Embedis* e) {
_settingsHelp();
_settingsHelpCommand();
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) { settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) {
bool ascii = false; bool ascii = false;
if (e->argc == 2) ascii = String(e->argv[1]).toInt() == 1; if (e->argc == 2) ascii = String(e->argv[1]).toInt() == 1;
_settingsDump(ascii);
_settingsDumpCommand(ascii);
DEBUG_MSG_P(PSTR("\n+OK\n")); DEBUG_MSG_P(PSTR("\n+OK\n"));
}); });
@ -210,7 +219,7 @@ void _settingsInitCommands() {
}); });
settingsRegisterCommand(F("FACTORY.RESET"), [](Embedis* e) { settingsRegisterCommand(F("FACTORY.RESET"), [](Embedis* e) {
_settingsFactoryReset();
_settingsFactoryResetCommand();
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
@ -238,7 +247,7 @@ void _settingsInitCommands() {
}); });
settingsRegisterCommand(F("HELP"), [](Embedis* e) { settingsRegisterCommand(F("HELP"), [](Embedis* e) {
_settingsHelp();
_settingsHelpCommand();
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
@ -252,7 +261,7 @@ void _settingsInitCommands() {
}); });
settingsRegisterCommand(F("KEYS"), [](Embedis* e) { settingsRegisterCommand(F("KEYS"), [](Embedis* e) {
_settingsKeys();
_settingsKeysCommand();
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
@ -323,7 +332,7 @@ void saveSettings() {
} }
void resetSettings() { void resetSettings() {
_settingsFactoryReset();
_settingsFactoryResetCommand();
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -367,11 +376,13 @@ bool settingsRestoreJson(JsonObject& data) {
bool settingsGetJson(JsonObject& root) { bool settingsGetJson(JsonObject& root) {
unsigned int size = _settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
String key = _settingsKeyName(i);
String value = getSetting(key);
root[key] = value;
// Get sorted list of keys
std::vector<String> keys = _settingsKeys();
// Add the key-values to the json object
for (unsigned int i=0; i<keys.size(); i++) {
String value = getSetting(keys[i]);
root[keys[i]] = value;
} }
} }
@ -408,10 +419,10 @@ void settingsSetup() {
_settingsInitCommands(); _settingsInitCommands();
#if TERMINAL_SUPPORT #if TERMINAL_SUPPORT
#ifdef SERIAL_RX_PORT
SERIAL_RX_PORT.begin(SERIAL_RX_BAUDRATE);
#endif
#endif
#if SERIAL_RX_ENABLED
SERIAL_RX_PORT.begin(SERIAL_RX_BAUDRATE);
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
// Register loop // Register loop
espurnaRegisterLoop(settingsLoop); espurnaRegisterLoop(settingsLoop);
@ -429,7 +440,7 @@ void settingsLoop() {
embedis.process(); embedis.process();
#ifdef SERIAL_RX_PORT
#if SERIAL_RX_ENABLED
while (SERIAL_RX_PORT.available() > 0) { while (SERIAL_RX_PORT.available() > 0) {
char rc = Serial.read(); char rc = Serial.read();
@ -440,7 +451,7 @@ void settingsLoop() {
} }
} }
#endif // SERIAL_RX_PORT
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT #endif // TERMINAL_SUPPORT


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


+ 17
- 1
code/espurna/telnet.ino View File

@ -71,7 +71,14 @@ void _telnetData(unsigned char clientId, void *data, size_t len) {
void _telnetNewClient(AsyncClient *client) { void _telnetNewClient(AsyncClient *client) {
if (client->localIP() != WiFi.softAPIP()) { if (client->localIP() != WiFi.softAPIP()) {
bool telnetSTA = getSetting("telnetSTA", TELNET_STA).toInt() == 1;
// Telnet is always available for the ESPurna Core image
#ifdef ESPURNA_CORE
bool telnetSTA = true;
#else
bool telnetSTA = getSetting("telnetSTA", TELNET_STA).toInt() == 1;
#endif
if (!telnetSTA) { if (!telnetSTA) {
DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Only local connections\n")); DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Only local connections\n"));
client->onDisconnect([](void *s, AsyncClient *c) { client->onDisconnect([](void *s, AsyncClient *c) {
@ -81,6 +88,7 @@ void _telnetNewClient(AsyncClient *client) {
client->close(true); client->close(true);
return; return;
} }
} }
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) { for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
@ -109,6 +117,14 @@ void _telnetNewClient(AsyncClient *client) {
}, 0); }, 0);
DEBUG_MSG_P(PSTR("[TELNET] Client #%u connected\n"), i); DEBUG_MSG_P(PSTR("[TELNET] Client #%u connected\n"), i);
// If there is no terminal support automatically dump info and crash data
#if TERMINAL_SUPPORT == 0
info();
wifiStatus();
debugDumpCrashInfo();
#endif
_telnetFirst = true; _telnetFirst = true;
wifiReconnectCheck(); wifiReconnectCheck();
return; return;


+ 103
- 0
code/espurna/uartmqtt.ino View File

@ -0,0 +1,103 @@
/*
UART_MQTT MODULE
Copyright (C) 2018 by Albert Weterings
Adapted by Xose Pérez <xose dot perez at gmail dot com>
*/
#if UART_MQTT_SUPPORT
char _uartmqttBuffer[UART_MQTT_BUFFER_SIZE];
bool _uartmqttNewData = false;
#if UART_MQTT_USE_SOFT
#include <SoftwareSerial.h>
SoftwareSerial _uart_mqtt_serial(UART_MQTT_RX_PIN, UART_MQTT_TX_PIN, false, UART_MQTT_BUFFER_SIZE);
#define UART_MQTT_PORT _uart_mqtt_serial
#else
#define UART_MQTT_PORT UART_MQTT_HW_PORT
#endif
// -----------------------------------------------------------------------------
// Private
// -----------------------------------------------------------------------------
void _uartmqttReceiveUART() {
static unsigned char ndx = 0;
while (UART_MQTT_PORT.available() > 0 && _uartmqttNewData == false) {
char rc = UART_MQTT_PORT.read();
if (rc != '\n') {
_uartmqttBuffer[ndx] = rc;
if (ndx < UART_MQTT_BUFFER_SIZE - 1) ndx++;
} else {
_uartmqttBuffer[ndx] = '\0';
_uartmqttNewData = true;
ndx = 0;
}
}
}
void _uartmqttSendMQTT() {
if (_uartmqttNewData == true && MQTT_SUPPORT) {
DEBUG_MSG_P(PSTR("[UART_MQTT] Send data over MQTT: %s\n"), _uartmqttBuffer);
mqttSend(MQTT_TOPIC_UARTIN, _uartmqttBuffer);
_uartmqttNewData = false;
}
}
void _uartmqttSendUART(const char * message) {
DEBUG_MSG_P(PSTR("[UART_MQTT] Send data over UART: %s\n"), message);
UART_MQTT_PORT.println(message);
}
void _uartmqttMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribe(MQTT_TOPIC_UARTOUT);
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttMagnitude((char *) topic);
if (t.equals(MQTT_TOPIC_UARTOUT)) {
_uartmqttSendUART(payload);
}
}
}
// -----------------------------------------------------------------------------
// SETUP & LOOP
// -----------------------------------------------------------------------------
void _uartmqttLoop() {
_uartmqttReceiveUART();
_uartmqttSendMQTT();
}
void uartmqttSetup() {
// Init port
UART_MQTT_PORT.begin(UART_MQTT_BAUDRATE);
// Register MQTT callbackj
mqttRegister(_uartmqttMQTTCallback);
// Register loop
espurnaRegisterLoop(_uartmqttLoop);
}
#endif // UART_MQTT_SUPPORT

+ 8
- 1
code/espurna/utils.ino View File

@ -252,6 +252,9 @@ void info() {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
#ifdef APP_BUILD_FLAGS
DEBUG_MSG_P(PSTR("[INIT] BUILD_FLAGS: %s\n"), APP_BUILD_FLAGS);
#endif
DEBUG_MSG_P(PSTR("[INIT] BOARD: %s\n"), getBoardName().c_str()); DEBUG_MSG_P(PSTR("[INIT] BOARD: %s\n"), getBoardName().c_str());
DEBUG_MSG_P(PSTR("[INIT] SUPPORT:")); DEBUG_MSG_P(PSTR("[INIT] SUPPORT:"));
@ -324,13 +327,17 @@ void info() {
#if THINGSPEAK_SUPPORT #if THINGSPEAK_SUPPORT
DEBUG_MSG_P(PSTR(" THINGSPEAK")); DEBUG_MSG_P(PSTR(" THINGSPEAK"));
#endif #endif
#if UART_MQTT_SUPPORT
DEBUG_MSG_P(PSTR(" UART_MQTT"));
#endif
#if WEB_SUPPORT #if WEB_SUPPORT
DEBUG_MSG_P(PSTR(" WEB")); DEBUG_MSG_P(PSTR(" WEB"));
#endif #endif
#if SENSOR_SUPPORT #if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n[INIT] SENSORS:"));
DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[INIT] SENSORS:"));
#if ANALOG_SUPPORT #if ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" ANALOG")); DEBUG_MSG_P(PSTR(" ANALOG"));


+ 5
- 3
code/espurna/web.ino View File

@ -45,17 +45,19 @@ void _onGetConfig(AsyncWebServerRequest *request) {
webLog(request); webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str()); if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
AsyncJsonResponse * response = new AsyncJsonResponse();
JsonObject& root = response->getRoot();
AsyncResponseStream *response = request->beginResponseStream("text/json");
DynamicJsonBuffer jsonBuffer;
JsonObject &root = jsonBuffer.createObject();
root["app"] = APP_NAME; root["app"] = APP_NAME;
root["version"] = APP_VERSION; root["version"] = APP_VERSION;
settingsGetJson(root); settingsGetJson(root);
root.prettyPrintTo(*response);
char buffer[100]; char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("attachment; filename=\"%s-backup.json\""), (char *) getSetting("hostname").c_str()); snprintf_P(buffer, sizeof(buffer), PSTR("attachment; filename=\"%s-backup.json\""), (char *) getSetting("hostname").c_str());
response->addHeader("Content-Disposition", buffer); response->addHeader("Content-Disposition", buffer);
response->setLength();
request->send(response); request->send(response);
} }


+ 21
- 41
code/espurna/wifi.ino View File

@ -171,44 +171,29 @@ bool _wifiClean(unsigned char num) {
// Inject hardcoded networks // Inject hardcoded networks
void _wifiInject() { void _wifiInject() {
#ifdef WIFI1_SSID
if (getSetting("ssid", 0, "").length() == 0) setSetting("ssid", 0, WIFI1_SSID);
#endif
#ifdef WIFI1_PASS
if (getSetting("pass", 0, "").length() == 0) setSetting("pass", 0, WIFI1_PASS);
#endif
#ifdef WIFI1_IP
if (getSetting("ip", 0, "").length() == 0) setSetting("ip", 0, WIFI1_IP);
#endif
#ifdef WIFI1_GW
if (getSetting("gw", 0, "").length() == 0) setSetting("gw", 0, WIFI1_GW);
#endif
#ifdef WIFI1_MASK
if (getSetting("mask", 0, "").length() == 0) setSetting("mask", 0, WIFI1_MASK);
#endif
#ifdef WIFI1_DNS
if (getSetting("dns", 0, "").length() == 0) setSetting("dns", 0, WIFI1_DNS);
#endif
if (strlen(WIFI1_SSID)) {
if (!hasSetting("ssid", 0)) {
setSetting("ssid", 0, WIFI1_SSID);
setSetting("pass", 0, WIFI1_PASS);
setSetting("ip", 0, WIFI1_IP);
setSetting("gw", 0, WIFI1_GW);
setSetting("mask", 0, WIFI1_MASK);
setSetting("dns", 0, WIFI1_DNS);
}
#ifdef WIFI2_SSID
if (getSetting("ssid", 1, "").length() == 0) setSetting("ssid", 1, WIFI2_SSID);
#endif
#ifdef WIFI2_PASS
if (getSetting("pass", 1, "").length() == 0) setSetting("pass", 1, WIFI2_PASS);
#endif
#ifdef WIFI2_IP
if (getSetting("ip", 1, "").length() == 0) setSetting("ip", 1, WIFI2_IP);
#endif
#ifdef WIFI2_GW
if (getSetting("gw", 1, "").length() == 0) setSetting("gw", 1, WIFI2_GW);
#endif
#ifdef WIFI2_MASK
if (getSetting("mask", 1, "").length() == 0) setSetting("mask", 1, WIFI2_MASK);
#endif
#ifdef WIFI2_DNS
if (getSetting("dns", 1, "").length() == 0) setSetting("dns", 1, WIFI2_DNS);
#endif
if (strlen(WIFI2_SSID)) {
if (!hasSetting("ssid", 1)) {
setSetting("ssid", 1, WIFI2_SSID);
setSetting("pass", 1, WIFI2_PASS);
setSetting("ip", 1, WIFI2_IP);
setSetting("gw", 1, WIFI2_GW);
setSetting("mask", 1, WIFI2_MASK);
setSetting("dns", 1, WIFI2_DNS);
}
}
}
} }
#if DEBUG_SUPPORT #if DEBUG_SUPPORT
@ -345,11 +330,6 @@ String getNetwork() {
return WiFi.SSID(); return WiFi.SSID();
} }
double wifiDistance(int rssi) {
double exponent = (double) (WIFI_RSSI_1M - rssi) / WIFI_PROPAGATION_CONST / 10.0;
return round(pow(10, exponent));
}
bool wifiConnected() { bool wifiConnected() {
return jw.connected(); return jw.connected();
} }


+ 25
- 6
code/espurna/ws.ino View File

@ -93,8 +93,22 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] Requested action: %s\n"), action); DEBUG_MSG_P(PSTR("[WEBSOCKET] Requested action: %s\n"), action);
if (strcmp(action, "reboot") == 0) deferredReset(100, CUSTOM_RESET_WEB);
if (strcmp(action, "reconnect") == 0) _web_defer.once_ms(100, wifiDisconnect);
if (strcmp(action, "reboot") == 0) {
deferredReset(100, CUSTOM_RESET_WEB);
return;
}
if (strcmp(action, "reconnect") == 0) {
_web_defer.once_ms(100, wifiDisconnect);
return;
}
if (strcmp(action, "factory_reset") == 0) {
DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
resetSettings();
deferredReset(100, CUSTOM_RESET_FACTORY);
return;
}
JsonObject& data = root["data"]; JsonObject& data = root["data"];
if (data.success()) { if (data.success()) {
@ -113,6 +127,8 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
} }
} }
return;
} }
}; };
@ -204,7 +220,6 @@ void _wsUpdate(JsonObject& root) {
root["heap"] = getFreeHeap(); root["heap"] = getFreeHeap();
root["uptime"] = getUptime(); root["uptime"] = getUptime();
root["rssi"] = WiFi.RSSI(); root["rssi"] = WiFi.RSSI();
root["distance"] = wifiDistance(WiFi.RSSI());
#if NTP_SUPPORT #if NTP_SUPPORT
if (ntpSynced()) root["now"] = now(); if (ntpSynced()) root["now"] = now();
#endif #endif
@ -255,8 +270,7 @@ void _wsOnStart(JsonObject& root) {
root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt(); root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt();
root["webPort"] = getSetting("webPort", WEB_PORT).toInt(); root["webPort"] = getSetting("webPort", WEB_PORT).toInt();
root["tmpUnits"] = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt();
root["tmpCorrection"] = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat();
root["wsAuth"] = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1;
} }
@ -375,7 +389,12 @@ void wsSend_P(uint32_t client_id, PGM_P payload) {
void wsConfigure() { void wsConfigure() {
#if USE_PASSWORD #if USE_PASSWORD
_ws.setAuthentication(WEB_USERNAME, (const char *) getSetting("adminPass", ADMIN_PASS).c_str());
bool auth = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1;
if (auth) {
_ws.setAuthentication(WEB_USERNAME, (const char *) getSetting("adminPass", ADMIN_PASS).c_str());
} else {
_ws.setAuthentication("", "");
}
#endif #endif
} }


+ 37
- 2
code/extra_scripts.py View File

@ -1,12 +1,15 @@
#!/usr/bin/env python #!/usr/bin/env python
from subprocess import call
import os
import time import time
Import("env") Import("env")
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Utils # Utils
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class Color:
class Color(object):
BLACK = '\x1b[1;30m' BLACK = '\x1b[1;30m'
RED = '\x1b[1;31m' RED = '\x1b[1;31m'
GREEN = '\x1b[1;32m' GREEN = '\x1b[1;32m'
@ -31,16 +34,48 @@ def clr(color, text):
# Callbacks # Callbacks
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
def remove_float_support():
flags = " ".join(env['LINKFLAGS'])
flags = flags.replace("-u _printf_float", "")
flags = flags.replace("-u _scanf_float", "")
newflags = flags.split()
env.Replace(
LINKFLAGS = newflags
)
def cpp_check(source, target, env):
print("Started cppcheck...\n")
call(["cppcheck", os.getcwd()+"/espurna", "--force", "--enable=all"])
print("Finished cppcheck...\n")
def check_size(source, target, env): def check_size(source, target, env):
time.sleep(1)
time.sleep(2)
size = target[0].get_size() size = target[0].get_size()
print clr(Color.LIGHT_BLUE, "Binary size: %s bytes" % size) print clr(Color.LIGHT_BLUE, "Binary size: %s bytes" % size)
#if size > 512000: #if size > 512000:
# print clr(Color.LIGHT_RED, "File too large for OTA!") # print clr(Color.LIGHT_RED, "File too large for OTA!")
# Exit(1) # Exit(1)
def add_build_flags(source, target, env):
build_h = "espurna/config/build.h"
build_flags = env['BUILD_FLAGS'][0]
lines = open(build_h).readlines()
with open(build_h, "w") as fh:
for line in lines:
if "APP_BUILD_FLAGS" in line:
fh.write("#define APP_BUILD_FLAGS \"%s\"" % build_flags)
else:
fh.write(line)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Hooks # Hooks
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
remove_float_support()
#env.AddPreAction("buildprog", cpp_check)
env.AddPreAction("$BUILD_DIR/src/espurna.ino.o", add_build_flags)
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", check_size) env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", check_size)

+ 28
- 24
code/gulpfile.js View File

@ -36,22 +36,17 @@ const inline = require('gulp-inline');
const inlineImages = require('gulp-css-base64'); const inlineImages = require('gulp-css-base64');
const favicon = require('gulp-base64-favicon'); const favicon = require('gulp-base64-favicon');
const htmllint = require('gulp-htmllint'); const htmllint = require('gulp-htmllint');
const gutil = require('gulp-util');
const log = require('fancy-log');
const csslint = require('gulp-csslint'); const csslint = require('gulp-csslint');
const dataFolder = 'espurna/data/'; const dataFolder = 'espurna/data/';
const staticFolder = 'espurna/static/'; const staticFolder = 'espurna/static/';
String.prototype.replaceAll = function(search, replacement) {
var target = this;
return target.split(search).join(replacement);
};
var toHeader = function(filename) { var toHeader = function(filename) {
var source = dataFolder + filename; var source = dataFolder + filename;
var destination = staticFolder + filename + '.h'; var destination = staticFolder + filename + '.h';
var safename = filename.replaceAll('.', '_');
var safename = filename.split('.').join('_');
var wstream = fs.createWriteStream(destination); var wstream = fs.createWriteStream(destination);
wstream.on('error', function (err) { wstream.on('error', function (err) {
@ -64,9 +59,11 @@ var toHeader = function(filename) {
wstream.write('const uint8_t ' + safename + '[] PROGMEM = {'); wstream.write('const uint8_t ' + safename + '[] PROGMEM = {');
for (var i=0; i<data.length; i++) { for (var i=0; i<data.length; i++) {
if (i % 20 == 0) wstream.write('\n');
if (0 === (i % 20)) {
wstream.write('\n');
}
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2)); wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i<data.length-1) {
if (i < (data.length - 1)) {
wstream.write(','); wstream.write(',');
} }
} }
@ -76,10 +73,17 @@ var toHeader = function(filename) {
}; };
function htmllintReporter(filepath, issues) {
var htmllintReporter = function(filepath, issues) {
if (issues.length > 0) { if (issues.length > 0) {
issues.forEach(function (issue) { issues.forEach(function (issue) {
gutil.log(gutil.colors.cyan('[gulp-htmllint] ') + gutil.colors.white(filepath + ' [' + issue.line + ',' + issue.column + ']: ') + gutil.colors.red('(' + issue.code + ') ' + issue.msg));
log.info(
'[gulp-htmllint] ' +
filepath + ' [' +
issue.line + ',' +
issue.column + ']: ' +
'(' + issue.code + ') ' +
issue.msg
);
}); });
process.exitCode = 1; process.exitCode = 1;
} }
@ -91,9 +95,9 @@ gulp.task('build_certs', function() {
}); });
gulp.task('csslint', function() { gulp.task('csslint', function() {
gulp.src('html/*.css')
.pipe(csslint({ids: false}))
.pipe(csslint.formatter());
gulp.src('html/*.css').
pipe(csslint({ids: false})).
pipe(csslint.formatter());
}); });
gulp.task('buildfs_embeded', ['buildfs_inline'], function() { gulp.task('buildfs_embeded', ['buildfs_inline'], function() {
@ -101,29 +105,29 @@ gulp.task('buildfs_embeded', ['buildfs_inline'], function() {
}); });
gulp.task('buildfs_inline', function() { gulp.task('buildfs_inline', function() {
return gulp.src('html/*.html')
.pipe(htmllint({
return gulp.src('html/*.html').
pipe(htmllint({
'failOnError': true, 'failOnError': true,
'rules': { 'rules': {
'id-class-style': false, 'id-class-style': false,
'label-req-for': false, 'label-req-for': false,
} }
}, htmllintReporter))
.pipe(favicon())
.pipe(inline({
}, htmllintReporter)).
pipe(favicon()).
pipe(inline({
base: 'html/', base: 'html/',
js: [uglify], js: [uglify],
css: [cleancss, inlineImages], css: [cleancss, inlineImages],
disabledTypes: ['svg', 'img'] disabledTypes: ['svg', 'img']
}))
.pipe(htmlmin({
})).
pipe(htmlmin({
collapseWhitespace: true, collapseWhitespace: true,
removeComments: true, removeComments: true,
minifyCSS: true, minifyCSS: true,
minifyJS: true minifyJS: true
}))
.pipe(gzip())
.pipe(gulp.dest(dataFolder));
})).
pipe(gzip()).
pipe(gulp.dest(dataFolder));
}); });


+ 9
- 1
code/html/custom.css View File

@ -5,6 +5,7 @@
#menu .pure-menu-heading { #menu .pure-menu-heading {
font-size: 100%; font-size: 100%;
padding: .5em .5em; padding: .5em .5em;
white-space: normal;
} }
.pure-g { .pure-g {
@ -55,6 +56,10 @@ h2 {
margin: -10px 0 10px 0; margin: -10px 0 10px 0;
} }
.hint a {
color:inherit;
}
legend.module, legend.module,
.module { .module {
display: none; display: none;
@ -143,7 +148,8 @@ div.state {
.button-rfb-forget, .button-rfb-forget,
.button-del-network, .button-del-network,
.button-del-schedule, .button-del-schedule,
.button-upgrade {
.button-upgrade,
.button-settings-factory {
background: rgb(192, 0, 0); /* redish */ background: rgb(192, 0, 0); /* redish */
} }
@ -154,6 +160,7 @@ div.state {
.button-rfb-learn, .button-rfb-learn,
.button-upgrade-browse, .button-upgrade-browse,
.button-ha-add, .button-ha-add,
.button-ha-config,
.button-settings-backup, .button-settings-backup,
.button-settings-restore, .button-settings-restore,
.button-apikey { .button-apikey {
@ -272,6 +279,7 @@ span.slider {
display: none; display: none;
} }
#haConfig,
#scanResult { #scanResult {
color: #888; color: #888;
font-family: 'Courier New', monospace; font-family: 'Courier New', monospace;


+ 352
- 311
code/html/custom.js
File diff suppressed because it is too large
View File


+ 105
- 35
code/html/index.html View File

@ -102,7 +102,7 @@
<a href="#" class="pure-menu-link" data="panel-relay">SWITCHES</a> <a href="#" class="pure-menu-link" data="panel-relay">SWITCHES</a>
</li> </li>
<li class="pure-menu-item module module-relay">
<li class="pure-menu-item module module-sch">
<a href="#" class="pure-menu-link" data="panel-schedule">SCHEDULE</a> <a href="#" class="pure-menu-link" data="panel-schedule">SCHEDULE</a>
</li> </li>
@ -118,6 +118,10 @@
<a href="#" class="pure-menu-link" data="panel-domoticz">DOMOTICZ</a> <a href="#" class="pure-menu-link" data="panel-domoticz">DOMOTICZ</a>
</li> </li>
<li class="pure-menu-item module module-ha">
<a href="#" class="pure-menu-link" data="panel-ha">HASS</a>
</li>
<li class="pure-menu-item module module-idb"> <li class="pure-menu-item module module-idb">
<a href="#" class="pure-menu-link" data="panel-idb">INFLUXDB</a> <a href="#" class="pure-menu-link" data="panel-idb">INFLUXDB</a>
</li> </li>
@ -127,7 +131,7 @@
</li> </li>
<li class="pure-menu-item module module-rfb"> <li class="pure-menu-item module module-rfb">
<a href="#" class="pure-menu-link" data="panel-rfb">RFBRIDGE</a>
<a href="#" class="pure-menu-link" data="panel-rfb">RF</a>
</li> </li>
<li class="pure-menu-item"> <li class="pure-menu-item">
@ -199,7 +203,7 @@
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="channel"></span></div> <div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="channel"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">RSSI</div> <div class="pure-u-1-2 pure-u-lg-1-4">RSSI</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="rssi"></span> (<span name="distance" post="m"></span>)</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="rssi"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">IP</div> <div class="pure-u-1-2 pure-u-lg-1-4">IP</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="deviceip"></span></div> <div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="deviceip"></span></div>
@ -264,7 +268,7 @@
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"> <div class="pure-u-1 pure-u-lg-3-4 hint">
This name will identify this device in your network (http://&lt;hostname&gt;.local). For this setting to take effect you should restart the wifi interface clicking the "Reconnect" button.
This name will identify this device in your network (http://&lt;hostname&gt;.local). For this setting to take effect you should restart the wifi interface by clicking the "Reconnect" button.
</div> </div>
</div> </div>
@ -286,10 +290,11 @@
<label class="pure-u-1 pure-u-lg-1-4">LED mode</label> <label class="pure-u-1 pure-u-lg-1-4">LED mode</label>
<select name="ledMode0" class="pure-u-1 pure-u-lg-1-4" tabindex="7"> <select name="ledMode0" class="pure-u-1 pure-u-lg-1-4" tabindex="7">
<option value="1">WiFi status</option> <option value="1">WiFi status</option>
<option value="8">Relay status</option>
<option value="0">MQTT managed</option> <option value="0">MQTT managed</option>
<option value="4">Find me</option> <option value="4">Find me</option>
<option value="8">Status</option>
<option value="5">Mixed</option>
<option value="9">Relay &amp; WiFi</option>
<option value="5">Find me &amp; WiFi</option>
<option value="6">Always ON</option> <option value="6">Always ON</option>
<option value="7">Always OFF</option> <option value="7">Always OFF</option>
</select> </select>
@ -297,11 +302,12 @@
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"> <div class="pure-u-1 pure-u-lg-3-4 hint">
This setting defines the behaviour of the main LED in the board.<br /> This setting defines the behaviour of the main LED in the board.<br />
When in "WiFi status" it will blink at 1Hz when trying to connecting. If successfully connected if will briefly lit every 5 seconds if in STA mode or every second if in AP mode.<br />
When in "WiFi status" it will blink at 1Hz when trying to connect. If successfully connected it will briefly blink every 5 seconds if in STA mode or every second if in AP mode.<br />
When in "Relay status" mode the LED will be ON whenever any relay is ON, and OFF otherwise. This is global status notification.<br />
When in "MQTT managed" mode you will be able to set the LED state sending a message to "&lt;base_topic&gt;/led/0/set" with a payload of 0, 1 or 2 (to toggle it).<br /> When in "MQTT managed" mode you will be able to set the LED state sending a message to "&lt;base_topic&gt;/led/0/set" with a payload of 0, 1 or 2 (to toggle it).<br />
When in "Find me" mode the LED will be ON when all relays are OFF. This is meant to locate switches at night.<br /> When in "Find me" mode the LED will be ON when all relays are OFF. This is meant to locate switches at night.<br />
When in "Status" mode the LED will be ON whenever any relay is ON, and OFF otherwise. This is global status notification.<br />
When in "Mixed" mode it will follow the WiFi status but will stay mostly on when relays are OFF, and mostly OFF when any of them is ON.<br />
When in "Relay &amp; WiFi" mode it will follow the WiFi status but will stay mostly off when relays are OFF, and mostly ON when any of them is ON.<br />
When in "Find me &amp; WiFi" mode is the opposite of the "Relay &amp; WiFi", it will follow the WiFi status but will stay mostly on when relays are OFF, and mostly OFF when any of them is ON.<br />
"Always ON" and "Always OFF" modes are self-explanatory. "Always ON" and "Always OFF" modes are self-explanatory.
</div> </div>
</div> </div>
@ -311,22 +317,6 @@
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="alexaEnabled" tabindex="13" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="alexaEnabled" tabindex="13" /></div>
</div> </div>
<div class="pure-g module module-ha">
<label class="pure-u-1 pure-u-lg-1-4">Home Assistant</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="haEnabled" tabindex="14" /></div>
<div class="pure-u-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">
Home Assistant auto-discovery feature. Enable and save to add the device to your HA console.
When using a colour light you might want to disable CSS style so Home Assistant can parse the color.
</div>
</div>
<div class="pure-g module module-ha">
<label class="pure-u-1 pure-u-lg-1-4">Home Assistant Prefix</label>
<input class="pure-u-1 pure-u-lg-1-4" name="haPrefix" type="text" tabindex="15" />
</div>
</fieldset> </fieldset>
</div> </div>
</div> </div>
@ -375,11 +365,11 @@
<fieldset> <fieldset>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Use colorpicker</label>
<label class="pure-u-1 pure-u-lg-1-4">Use color</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useColor" action="reload" tabindex="8" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useColor" action="reload" tabindex="8" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Use color picker for the first 3 channels as RGB.<br />Will only work if the device has at least 3 dimmable channels.<br />Reload the page to update the web interface.</div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Use the first three channels as RGB channels. This will also enable the color picker in the web UI. Will only work if the device has at least 3 dimmable channels.<br />Reload the page to update the web interface.</div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
@ -469,6 +459,11 @@
</div> </div>
</div> </div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Enable WS Auth</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="wsAuth" /></div>
</div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Enable HTTP API</label> <label class="pure-u-1 pure-u-lg-1-4">Enable HTTP API</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiEnabled" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiEnabled" /></div>
@ -521,7 +516,8 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Settings</label> <label class="pure-u-1 pure-u-lg-1-4">Settings</label>
<div class="pure-u-1-2 pure-u-lg-1-8"><button class="pure-button button-settings-backup pure-u-23-24">Backup</button></div> <div class="pure-u-1-2 pure-u-lg-1-8"><button class="pure-button button-settings-backup pure-u-23-24">Backup</button></div>
<div class="pure-u-1-2 pure-u-lg-1-8"><button class="pure-button button-settings-restore pure-u-1">Restore</button></div>
<div class="pure-u-1-2 pure-u-lg-1-8"><button class="pure-button button-settings-restore pure-u-23-24">Restore</button></div>
<div class="pure-u-1-4 pure-u-lg-1-4"><button class="pure-button button-settings-factory pure-u-1">Factory Reset</button></div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
@ -530,6 +526,8 @@
<div class=" pure-u-1-4 pure-u-lg-1-8"><button class="pure-button button-upgrade-browse pure-u-23-24">Browse</button></div> <div class=" pure-u-1-4 pure-u-lg-1-8"><button class="pure-button button-upgrade-browse pure-u-23-24">Browse</button></div>
<div class=" pure-u-1-4 pure-u-lg-1-8"><button class="pure-button button-upgrade pure-u-23-24">Upgrade</button></div> <div class=" pure-u-1-4 pure-u-lg-1-8"><button class="pure-button button-upgrade pure-u-23-24">Upgrade</button></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-0 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" 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> <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="16" />
</div> </div>
@ -569,7 +567,7 @@
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<span class="pure-u-1 pure-u-lg-3-4 terminal" id="scanResult" name="scanResult"></span>
<span class="pure-u-1 pure-u-lg-3-4" id="scanResult" name="scanResult"></span>
</div> </div>
<legend>Networks</legend> <legend>Networks</legend>
@ -710,7 +708,7 @@
All messages (except the device status) will be included in a JSON payload along with the timestamp and hostname All messages (except the device status) will be included in a JSON payload along with the timestamp and hostname
and sent under the <strong>&lt;root&gt;/data</strong> topic.<br /> and sent under the <strong>&lt;root&gt;/data</strong> topic.<br />
Messages will be queued and sent after 100ms, so different messages could be merged into a single payload.<br /> Messages will be queued and sent after 100ms, so different messages could be merged into a single payload.<br />
Subscribtions will still be done to single topics.
Subscriptions will still be done to single topics.
</div> </div>
</div> </div>
@ -800,6 +798,61 @@
</div> </div>
<div class="panel" id="panel-ha">
<div class="header">
<h1>HOME ASSISTANT</h1>
<h2>
Add this device to your Home Assistant.
</h2>
</div>
<div class="page">
<fieldset>
<legend>Discover</legend>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Discover</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="haEnabled" tabindex="14" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
Home Assistant auto-discovery feature. Enable and save to add the device to your HA console.
When using a colour light you might want to disable CSS style so Home Assistant can parse the color.
</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Prefix</label>
<input class="pure-u-1 pure-u-lg-1-4" name="haPrefix" type="text" tabindex="15" />
</div>
<legend>Configuration</legend>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Configuration</label>
<div class="pure-u-1-4 pure-u-lg-3-4"><button class="pure-button button-ha-config pure-u-1-3">Show</button></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
These are the settings you should copy to your Home Assistant "configuration.yaml" file.
If any of the sections below (switch, light, sensor) already exists, do not dupplicate it,
simply copy the contents of the section below the ones already present.
</div>
</div>
<div class="pure-g">
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4"><span id="haConfig" name="haConfig"></span></div>
</div>
</fieldset>
</div>
</div>
<div class="panel" id="panel-thingspeak"> <div class="panel" id="panel-thingspeak">
<div class="header"> <div class="header">
@ -930,6 +983,22 @@
</div> </div>
</div> </div>
<div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-lg-1-4">Power units</label>
<select name="powerUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
<option value="0">Watts (W)</option>
<option value="1">Kilowatts (kW)</option>
</select>
</div>
<div class="pure-g module module-hlw module-emon">
<label class="pure-u-1 pure-u-lg-1-4">Energy units</label>
<select name="energyUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
<option value="0">Joules (J)</option>
<option value="1">Kilowatts·hour (kWh)</option>
</select>
</div>
<div class="pure-g module module-temperature"> <div class="pure-g module module-temperature">
<label class="pure-u-1 pure-u-lg-1-4">Temperature units</label> <label class="pure-u-1 pure-u-lg-1-4">Temperature units</label>
<select name="tmpUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4"> <select name="tmpUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
@ -961,7 +1030,7 @@
<label class="pure-u-1 pure-u-lg-1-4">Expected Current</label> <label class="pure-u-1 pure-u-lg-1-4">Expected Current</label>
<input class="pure-u-1 pure-u-lg-3-4 pwrExpected" name="pwrExpectedC" type="text" tabindex="52" placeholder="0" /> <input class="pure-u-1 pure-u-lg-3-4 pwrExpected" name="pwrExpectedC" type="text" tabindex="52" placeholder="0" />
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">In Ampers (A). If you are using a pure resistive load like a bulb this will the ratio between the two previous values, i.e. power / voltage. You can also use a current clamp around one fo the power wires to get this value.</div>
<div class="pure-u-1 pure-u-lg-3-4 hint">In Amperes (A). If you are using a pure resistive load like a bulb, this will be the ratio between the two previous values, i.e. power / voltage. You can also use a current clamp around one of the power wires to get this value.</div>
</div> </div>
<div class="pure-g module module-hlw"> <div class="pure-g module module-hlw">
@ -994,11 +1063,12 @@
<div class="panel" id="panel-rfb"> <div class="panel" id="panel-rfb">
<div class="header"> <div class="header">
<h1>RFBRIDGE</h1>
<h1>RADIO FREQUENCY</h1>
<h2> <h2>
Sonoff 433 RF Bridge Configuration<br /><br />
To learn a new code click <strong>LEARN</strong>, the Sonoff RFBridge will beep, then press a button on the remote, the RFBridge will then double beep and the new code should show up. If the device double beeps but the code does not update it has not been properly learnt. Keep trying.<br /><br />
Modify or create new codes manually (18 characters) and then click <strong>SAVE</strong> to store them in the device memory. If your controlled device uses the same code to switch ON and OFF, learn the code with the ON button and copy paste it to the OFF input box, then click SAVE on the last one to store the value.<br /><br />
Sonoff 433 RF Bridge &amp; RF Link Configuration<br /><br />
This page allows you to configure the RF codes for the Sonoff RFBridge 433 and also for a basic RF receiver.<br /><br />
To learn a new code click <strong>LEARN</strong> (the Sonoff RFBridge will beep) then press a button on the remote, the new code should show up (and the RFBridge will double beep). If the device double beeps but the code does not update it has not been properly learnt. Keep trying.<br /><br />
Modify or create new codes manually and then click <strong>SAVE</strong> to store them in the device memory. If your controlled device uses the same code to switch ON and OFF, learn the code with the ON button and copy paste it to the OFF input box, then click SAVE on the last one to store the value.<br /><br />
Delete any code clicking the <strong>FORGET</strong> button. Delete any code clicking the <strong>FORGET</strong> button.
<span class="module module-rfbraw"><br /><br />You can also specify 116-chars long RAW codes. Raw codes require a <a target="_blank" href="https://github.com/rhx/RF-Bridge-EFM8BB1">specific firmware for for the EFM8BB1</a>.</span> <span class="module module-rfbraw"><br /><br />You can also specify 116-chars long RAW codes. Raw codes require a <a target="_blank" href="https://github.com/rhx/RF-Bridge-EFM8BB1">specific firmware for for the EFM8BB1</a>.</span>
</h2> </h2>


+ 2
- 2
code/memanalyzer.py View File

@ -50,7 +50,7 @@ description = "ESPurna Memory Analyzer v0.1"
def file_size(file): def file_size(file):
try: try:
return os.stat(file).st_size return os.stat(file).st_size
except:
except OSError:
return 0 return 0
@ -65,7 +65,7 @@ def analyse_memory(elf_file):
# print("------------------------------------------------------------------------------"); # print("------------------------------------------------------------------------------");
ret = {} ret = {}
for (id_, descr) in list(sections.items()):
for (id_, _) in list(sections.items()):
section_start_token = " _%s_start" % id_ section_start_token = " _%s_start" % id_
section_end_token = " _%s_end" % id_ section_end_token = " _%s_end" % id_
section_start = -1 section_start = -1


+ 4
- 8
code/ota.py View File

@ -155,7 +155,7 @@ def input_board():
# Choose the board # Choose the board
try: try:
index = int(input("Choose the board you want to flash (empty if none of these): ")) index = int(input("Choose the board you want to flash (empty if none of these): "))
except:
except ValueError:
index = 0 index = 0
if index < 0 or len(devices) < index: if index < 0 or len(devices) < index:
print("Board number must be between 1 and %s\n" % str(len(devices))) print("Board number must be between 1 and %s\n" % str(len(devices)))
@ -175,7 +175,7 @@ def input_board():
print() print()
try: try:
index = int(input("Choose the board type you want to flash: ")) index = int(input("Choose the board type you want to flash: "))
except:
except ValueError:
index = 0 index = 0
if index < 1 or len(boards) < index: if index < 1 or len(boards) < index:
print("Board number must be between 1 and %s\n" % str(len(boards))) print("Board number must be between 1 and %s\n" % str(len(boards)))
@ -186,17 +186,13 @@ def input_board():
if board.get('size', 0) == 0: if board.get('size', 0) == 0:
try: try:
board['size'] = int(input("Board memory size (1 for 1M, 4 for 4M): ")) board['size'] = int(input("Board memory size (1 for 1M, 4 for 4M): "))
except:
except ValueError:
print("Wrong memory size") print("Wrong memory size")
return None return None
# Choose IP of none before # Choose IP of none before
if len(board.get('ip', '')) == 0: if len(board.get('ip', '')) == 0:
try:
board['ip'] = input("IP of the device to flash (empty for 192.168.4.1): ") or "192.168.4.1"
except:
print("Wrong IP")
return None
board['ip'] = input("IP of the device to flash (empty for 192.168.4.1): ") or "192.168.4.1"
return board return board


+ 3
- 4
code/package.json View File

@ -7,6 +7,7 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"devDependencies": { "devDependencies": {
"del": "^2.2.1", "del": "^2.2.1",
"fancy-log": "^1.3.2",
"gulp": "^3.9.1", "gulp": "^3.9.1",
"gulp-base64-favicon": "^1.0.2", "gulp-base64-favicon": "^1.0.2",
"gulp-clean-css": "^3.4.2", "gulp-clean-css": "^3.4.2",
@ -16,8 +17,6 @@
"gulp-htmllint": "0.0.14", "gulp-htmllint": "0.0.14",
"gulp-htmlmin": "^2.0.0", "gulp-htmlmin": "^2.0.0",
"gulp-inline": "^0.1.1", "gulp-inline": "^0.1.1",
"gulp-uglify": "^1.5.3",
"gulp-util": "^3.0.8"
},
"dependencies": {}
"gulp-uglify": "^1.5.3"
}
} }

+ 66
- 5
code/platformio.ini View File

@ -17,8 +17,8 @@ lib_deps =
https://bitbucket.org/xoseperez/debounceevent.git#2.0.1 https://bitbucket.org/xoseperez/debounceevent.git#2.0.1
Embedis Embedis
https://github.com/krosk93/espsoftwareserial#a770677 https://github.com/krosk93/espsoftwareserial#a770677
https://github.com/me-no-dev/ESPAsyncTCP#a57560d
https://github.com/me-no-dev/ESPAsyncWebServer#313f337
https://github.com/me-no-dev/ESPAsyncTCP#55cd520
https://github.com/me-no-dev/ESPAsyncWebServer#232b87a
https://bitbucket.org/xoseperez/fauxmoesp.git#2.4.2 https://bitbucket.org/xoseperez/fauxmoesp.git#2.4.2
https://bitbucket.org/xoseperez/hlw8012.git#1.1.0 https://bitbucket.org/xoseperez/hlw8012.git#1.1.0
https://github.com/markszabo/IRremoteESP8266#v2.2.0 https://github.com/markszabo/IRremoteESP8266#v2.2.0
@ -30,21 +30,32 @@ lib_deps =
OneWire OneWire
PMS Library PMS Library
PubSubClient PubSubClient
https://github.com/xoseperez/RemoteSwitch-arduino-library.git
rc-switch
https://github.com/xoseperez/Time https://github.com/xoseperez/Time
lib_ignore = lib_ignore =
extra_scripts = extra_scripts.py extra_scripts = extra_scripts.py
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
[env:espurna-core]
[env:espurna-core-1MB]
platform = ${common.platform} platform = ${common.platform}
framework = arduino framework = arduino
board = esp01_1m board = esp01_1m
board_flash_mode = dout board_flash_mode = dout
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DITEAD_SONOFF_BASIC -DESPURNA_CORE
build_flags = ${common.build_flags_1m} -DESPURNA_CORE
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
[env:espurna-core-4MB]
platform = ${common.platform}
framework = arduino
board = d1_mini
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags} -DESPURNA_CORE
monitor_baud = 115200 monitor_baud = 115200
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
@ -1375,6 +1386,31 @@ upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200 monitor_baud = 115200
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:kmc-70011]
platform = ${common.platform}
framework = arduino
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DKMC_70011
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
[env:kmc-70011-ota]
platform = ${common.platform}
framework = arduino
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DKMC_70011
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
[env:yjzk-switch-2ch] [env:yjzk-switch-2ch]
platform = ${common.platform} platform = ${common.platform}
framework = arduino framework = arduino
@ -1435,6 +1471,31 @@ upload_port = "${env.ESPURNA_IP}"
upload_flags = --auth=${env.ESPURNA_AUTH} --port 8266 upload_flags = --auth=${env.ESPURNA_AUTH} --port 8266
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:stm-relay-ota]
platform = ${common.platform}
framework = arduino
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DSTM_RELAY
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
[env:stm-relay-ota]
platform = ${common.platform}
framework = arduino
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DSTM_RELAY
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# GENERIC OTA ENVIRONMENTS # GENERIC OTA ENVIRONMENTS
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------


+ 10
- 0
pre-commit View File

@ -52,6 +52,14 @@ travis = "[![travis](https://travis-ci.org/{USER}/{REPO}.svg?branch={BRANCH})]"
BRANCH = BRANCH BRANCH = BRANCH
) )
codacy = "[![codacy](https://img.shields.io/codacy/grade/{HASH}/{BRANCH}.svg)]" \
"(https://www.codacy.com/app/{USER}/{REPO}/dashboard)\n".format(
HASH = "c9496e25cf07434cba786b462cb15f49",
USER = USER,
REPO = REPO,
BRANCH = BRANCH
)
lines = open(README).readlines() lines = open(README).readlines()
with open(README, "w") as fh: with open(README, "w") as fh:
for line in lines: for line in lines:
@ -61,6 +69,8 @@ with open(README, "w") as fh:
fh.write(version) fh.write(version)
elif "![branch]" in line: elif "![branch]" in line:
fh.write(branch) fh.write(branch)
elif "![codacy]" in line:
fh.write(codacy)
else: else:
fh.write(line) fh.write(line)


Loading…
Cancel
Save