Browse Source

Merge branch 'master' into master

rfm69
ManuelW 6 years ago
committed by GitHub
parent
commit
fa91eca455
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 5875 additions and 1109 deletions
  1. +0
    -1
      .gitignore
  2. +51
    -0
      CHANGELOG.md
  3. +11
    -7
      README.md
  4. +1
    -1
      code/build.sh
  5. +10
    -12
      code/espurna/button.ino
  6. +18
    -5
      code/espurna/config/all.h
  7. +6
    -2
      code/espurna/config/arduino.h
  8. +5
    -0
      code/espurna/config/build.h
  9. +90
    -9
      code/espurna/config/defaults.h
  10. +81
    -38
      code/espurna/config/general.h
  11. +143
    -34
      code/espurna/config/hardware.h
  12. +4
    -3
      code/espurna/config/prototypes.h
  13. +60
    -4
      code/espurna/config/sensors.h
  14. +1
    -1
      code/espurna/config/version.h
  15. BIN
      code/espurna/data/index.html.gz
  16. +1
    -0
      code/espurna/debug.ino
  17. +11
    -11
      code/espurna/domoticz.ino
  18. +4
    -0
      code/espurna/espurna.ino
  19. +292
    -0
      code/espurna/homeassistant.ino
  20. +0
    -181
      code/espurna/homeassitant.ino
  21. +33
    -11
      code/espurna/led.ino
  22. +1
    -1
      code/espurna/light.ino
  23. +3
    -0
      code/espurna/mdns.ino
  24. +31
    -5
      code/espurna/migrate.ino
  25. +39
    -15
      code/espurna/mqtt.ino
  26. +1
    -1
      code/espurna/nofuss.ino
  27. +1
    -1
      code/espurna/ntp.ino
  28. +186
    -9
      code/espurna/ota.ino
  29. +100
    -74
      code/espurna/relay.ino
  30. +145
    -61
      code/espurna/rf.ino
  31. +19
    -9
      code/espurna/rfbridge.ino
  32. +1
    -0
      code/espurna/scheduler.ino
  33. +145
    -50
      code/espurna/sensor.ino
  34. +2
    -1
      code/espurna/sensors/AnalogSensor.h
  35. +2
    -1
      code/espurna/sensors/BH1750Sensor.h
  36. +19
    -11
      code/espurna/sensors/BMX280Sensor.h
  37. +12
    -4
      code/espurna/sensors/BaseSensor.h
  38. +1
    -0
      code/espurna/sensors/DHTSensor.h
  39. +2
    -1
      code/espurna/sensors/DallasSensor.h
  40. +1
    -0
      code/espurna/sensors/DigitalSensor.h
  41. +8
    -1
      code/espurna/sensors/ECH1560Sensor.h
  42. +0
    -1
      code/espurna/sensors/EmonADS1X15Sensor.h
  43. +4
    -0
      code/espurna/sensors/EmonSensor.h
  44. +1
    -0
      code/espurna/sensors/EventSensor.h
  45. +2
    -0
      code/espurna/sensors/HLW8012Sensor.h
  46. +18
    -14
      code/espurna/sensors/I2CSensor.h
  47. +4
    -1
      code/espurna/sensors/MHZ19Sensor.h
  48. +3
    -1
      code/espurna/sensors/PMSX003Sensor.h
  49. +136
    -0
      code/espurna/sensors/PZEM004TSensor.h
  50. +3
    -1
      code/espurna/sensors/SHT3XI2CSensor.h
  51. +17
    -11
      code/espurna/sensors/SI7021Sensor.h
  52. +4
    -1
      code/espurna/sensors/V9261FSensor.h
  53. +72
    -47
      code/espurna/settings.ino
  54. +3219
    -0
      code/espurna/static/index.html.gz.h
  55. +17
    -1
      code/espurna/telnet.ino
  56. +103
    -0
      code/espurna/uartmqtt.ino
  57. +11
    -1
      code/espurna/utils.ino
  58. +5
    -3
      code/espurna/web.ino
  59. +21
    -41
      code/espurna/wifi.ino
  60. +28
    -4
      code/espurna/ws.ino
  61. +37
    -2
      code/extra_scripts.py
  62. +28
    -24
      code/gulpfile.js
  63. +9
    -1
      code/html/custom.css
  64. +352
    -311
      code/html/custom.js
  65. +105
    -35
      code/html/index.html
  66. +2
    -2
      code/memanalyzer.py
  67. +4
    -8
      code/ota.py
  68. +3
    -4
      code/package.json
  69. +116
    -30
      code/platformio.ini
  70. +10
    -0
      pre-commit

+ 0
- 1
.gitignore View File

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


+ 51
- 0
CHANGELOG.md View File

@ -3,6 +3,57 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.12.4] 2018-03-05
### Fixed
- Adding a 1ms delay after UDP send to avoid loosing packets ([#438](https://github.com/xoseperez/espurna/issues/438))
- Fixed void return in BMX280 sensor ([#489](https://github.com/xoseperez/espurna/issues/489))
- Fix MQTT keep alive cannot be more than 255 seconds ([#515](https://github.com/xoseperez/espurna/issues/515))
- Do not show scheduler tab in Web UI if build without scheduler support ([#527](https://github.com/xoseperez/espurna/issues/527))
- Fix inline documentation for Sonoff 4CH Pro button modes ([#551](https://github.com/xoseperez/espurna/issues/551))
- Prevent resending messages from rfin in RF Bridge ([#561](https://github.com/xoseperez/espurna/issues/561))
- Fix AnalogSensor description ([#601](https://github.com/xoseperez/espurna/issues/601))
- Fixed missing setting in HASS WS callback (thanks to Maxim Prokhorov)
- ECH1560 call sync from tick method
- Fixed several issues reported by codacy
### Added
- UART to MQTT module (thanks to Albert Weterings, [#529](https://github.com/xoseperez/espurna/issues/529))
- Added option to show HASS configuration code in ESPurna web UI ([#616](https://github.com/xoseperez/espurna/issues/616))
- OTA upgrade via terminal (using 'ota' command, with SSL support)
- Added I2C scan and clear commands to terminal (only when I2C enabled)
- Added new relay & wifi led mode ([#604](https://github.com/xoseperez/espurna/issues/604))
- Option to enable/disable web auth from web UI
- Added "Reset to factory settings" in web UI (thanks to Teo Pavel, [#569](https://github.com/xoseperez/espurna/issues/569))
- Added {magnitude} placeholder to MQTT root topic
- Option to report energy in kWh and power in kW ([#523](https://github.com/xoseperez/espurna/issues/523))
- Check upgrade file size and signature in web UI
- Automatically dump info on telnet connection if TERMINAL_SUPPORT is disabled
- Two different ESPURNA_CORE images for 1MB and 4MB boards, freeing GPIOs ([#557](https://github.com/xoseperez/espurna/issues/557))
- Initial support for PZEM004T sensor (still beta)
- Support for STM_RELAY board (thanks to Maciej Czerniak)
- Support for KMC 70011 energy monitor (thanks to Wayne Manion, [#598](https://github.com/xoseperez/espurna/issues/598))
- Support for Wifi Stecker Shuko device (thanks to @Geitde, [#622](https://github.com/xoseperez/espurna/issues/622))
- Support for GizWits Witty Cloud device (thanks to Theonedemon)
### Changed
- BMX280 changes to allow for hot-plug ([#353](https://github.com/xoseperez/espurna/issues/353))
- Increase the initial check interval for NTP ([#452](https://github.com/xoseperez/espurna/issues/452))
- Force turning relays off before turning others on when synced ([#491](https://github.com/xoseperez/espurna/issues/491))
- Publish slampher as light to Home Assistant ([#494](https://github.com/xoseperez/espurna/issues/494))
- Force API to return the target status of the relay ([#548](https://github.com/xoseperez/espurna/issues/548))
- Increasing max number of messages in JSON payload to 20 ([#588](https://github.com/xoseperez/espurna/issues/588))
- Change copy from 'Use colorpicker' to 'Use color'. Better hint. ([#590](https://github.com/xoseperez/espurna/issues/590))
- Completely reworked the RF module to use the same web UI as the RFBridge module to learn new codes ([#594](https://github.com/xoseperez/espurna/issues/594))
- Several spelling and grammar changes by Lee Marlow
- Always enabled telnet access in ESPURNA_CORE image
- Updated ESPSoftwareSerial, ESPAsyncTCP and ESPAsyncWebServer libraries
### Removed
- Remove dependency from gulp-util ([#493](https://github.com/xoseperez/espurna/issues/493))
- Removed specific support for Magic Home LED Controller 2.3 ([#512](https://github.com/xoseperez/espurna/issues/512))
- Disabled floating point support when building against Arduino Core 2.4.0 with PIO
- Removed WiFi distance calculation
## [1.12.3] 2018-01-29
### Fixed
- Fix telnet crash due to local reference ([#487](https://github.com/xoseperez/espurna/issues/487))


+ 11
- 7
README.md View File

@ -3,10 +3,10 @@
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.
[![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.4-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)
[![twitter](https://img.shields.io/twitter/follow/xoseperez.svg?style=social)](https://twitter.com/intent/follow?screen_name=xoseperez)
@ -45,10 +45,11 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* **Alexa** integration using the [FauxmoESP Library](https://bitbucket.org/xoseperez/fauxmoesp)
* [**Google Assistant**](http://tinkerman.cat/using-google-assistant-control-your-esp8266-devices/) integration using IFTTT and Webhooks (Google Home, Allo)
* [**Domoticz**](https://domoticz.com/) integration via MQTT
* [**Home Assistant**](https://home-assistant.io/) integration via MQTT
* [**Home Assistant**](https://home-assistant.io/) integration
* Support for switches (on/off)
* Support for lights (color, brightness, on/off state)
* Supports MQTT auto-discover feature (switches, lights and sensors)
* Integration via MQTT Discover or copy-pasting configuration code
* [**InfluxDB**](https://www.influxdata.com/) integration via HTTP API
* [**Thingspeak**](https://thingspeak.com/) integration via HTTP API (HTTPS available for custom builds)
* **Sonoff RF Bridge** support
@ -72,7 +73,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* **V9261F** power monitor chip
* Raw analog and digital sensors
* Simple pulse counter
* All temperature sensors support Fahrenheit and Celsius
* Support for different units (Fahrenheit or Celsius, Watts or Kilowatts, Joules or kWh)
* Support for LED lights
* MY92XX-based light bulbs and PWM LED strips (dimmers) up to 5 channels (RGB, cold white and warm white, for instance)
* RGB and HSV color codes supported
@ -82,6 +83,8 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Soft color transitions
* Color synchronization between light using MQTT
* Option to have separate switches for each channel
* Support for simple 433MHz RF receivers
* Support for UART-to-MQTT bidirectional bridge
* Fast asynchronous **HTTP Server**
* Configurable port
* Basic authentication
@ -115,6 +118,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* **Unstable system check**
* Detects unstable system (crashes on boot continuously) and defaults to a stable system
* Only WiFi AP, OTA and Telnet available if system is flagged as unstable
* Configurable LED notifications based on WiFi status, relays status or MQTT messages.
* Button interface
* Click to toggle relays
* Double click to enter AP mode (only main button)
@ -193,7 +197,7 @@ Here is the list of supported hardware. For more information please refer to the
|![EXS Wifi Relay v3.1](images/devices/exs-wifi-relay-v31.jpg)|||
|**EXS Wifi Relay v3.1**|||
**Other supported boards:** Itead Sonoff LED, Itead Sonoff Dual R2, Huacanxing H802, WiOn 50055, ManCaveMade ESP-Live, InterMitTech QuinLED 2.6, Arilux AL-LC01, Arilux AL-LC02, Arilux AL-LC06, Arilux AL-LC11, Arilux E27 light bulb, Xenon SM-PW702U, Authometion LYT8266, YJZK 2-gang switch, Magic Home LED Controller 2.3.
**Other supported boards:** Itead Sonoff LED, Itead Sonoff Dual R2, Huacanxing H802, WiOn 50055, ManCaveMade ESP-Live, InterMitTech QuinLED 2.6, Arilux AL-LC01, Arilux AL-LC02, Arilux AL-LC06, Arilux AL-LC11, Arilux E27 light bulb, Xenon SM-PW702U, Authometion LYT8266, YJZK 2-gang switch, Magic Home LED Controller 2.3, STM_RELAY, KMC 70011 energy monitor, Wifi Stecker Shuko, GizWits Witty Cloud.
## License


+ 1
- 1
code/build.sh View File

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


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

@ -22,14 +22,14 @@ typedef struct {
std::vector<button_t> _buttons;
#if MQTT_SUPPORT
#ifdef MQTT_TOPIC_BUTTON
void buttonMQTT(unsigned char id, uint8_t event) {
if (id >= _buttons.size()) return;
char payload[2];
itoa(event, payload, 10);
mqttSend(MQTT_TOPIC_BUTTON, id, payload);
}
#endif
#endif
int buttonFromRelay(unsigned int relayID) {
@ -84,10 +84,8 @@ void buttonEvent(unsigned int id, unsigned char event) {
if (event == 0) return;
#if MQTT_SUPPORT
#ifdef MQTT_TOPIC_BUTTON
buttonMQTT(id, event);
#endif
#endif
unsigned char action = buttonAction(id, event);
@ -131,49 +129,49 @@ void buttonSetup() {
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);
_buttons.push_back({new DebounceEvent(BUTTON1_PIN, BUTTON1_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON1_RELAY});
}
#endif
#ifdef BUTTON2_PIN
#if BUTTON2_PIN != GPIO_NONE
{
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});
}
#endif
#ifdef BUTTON3_PIN
#if BUTTON3_PIN != GPIO_NONE
{
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});
}
#endif
#ifdef BUTTON4_PIN
#if BUTTON4_PIN != GPIO_NONE
{
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});
}
#endif
#ifdef BUTTON5_PIN
#if BUTTON5_PIN != GPIO_NONE
{
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});
}
#endif
#ifdef BUTTON6_PIN
#if BUTTON6_PIN != GPIO_NONE
{
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});
}
#endif
#ifdef BUTTON7_PIN
#if BUTTON7_PIN != GPIO_NONE
{
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});
}
#endif
#ifdef BUTTON8_PIN
#if BUTTON8_PIN != GPIO_NONE
{
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});


+ 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
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
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
#include "custom.h"
#endif
@ -23,3 +34,5 @@
#ifdef USE_CORE_VERSION_H
#include "core_version.h"
#endif
#include "build.h"

+ 6
- 2
code/espurna/config/arduino.h View File

@ -54,18 +54,21 @@
//#define ARILUX_E27
//#define XENON_SM_PW702U
//#define AUTHOMETION_LYT8266
//#define KMC_70011
//#define GENERIC_8CH
//#define ARILUX_AL_LC01
//#define ARILUX_AL_LC11
//#define ARILUX_AL_LC02
//#define WEMOS_D1_TARPUNA_SHIELD
//#define MAGICHOME_LED_CONTROLLER_23
//#define GIZWITS_WITTY_CLOUD
//#define EUROMATE_WIFI_STECKER_SCHUKO
//--------------------------------------------------------------------------------
// Features (values below are non-default values)
//--------------------------------------------------------------------------------
//#define ALEXA_SUPPORT 0
//#define BROKER_SUPPORT 0
//#define DEBUG_SERIAL_SUPPORT 0
//#define DEBUG_TELNET_SUPPORT 0
//#define DEBUG_UDP_SUPPORT 1
@ -77,7 +80,6 @@
//#define LLMNR_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define MDNS_SERVER_SUPPORT 0
//#define MDNS_CLIENT_SUPPORT 1
//#define BROKER_SUPPORT 0
//#define MQTT_SUPPORT 0
//#define NETBIOS_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define NOFUSS_SUPPORT 1
@ -89,6 +91,7 @@
//#define TELNET_SUPPORT 0
//#define TERMINAL_SUPPORT 0
//#define THINGSPEAK_SUPPORT 0
//#define UART_MQTT_SUPPORT 1
//#define WEB_SUPPORT 0
//--------------------------------------------------------------------------------
@ -109,6 +112,7 @@
//#define HLW8012_SUPPORT 1
//#define MHZ19_SUPPORT 1
//#define PMSX003_SUPPORT 1
//#define PZEM004T_SUPPORT 1
//#define SHT3X_I2C_SUPPORT 1
//#define SI7021_SUPPORT 1
//#define V9261F_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
// -----------------------------------------------------------------------------
#define GPIO_NONE 0x99
// -----------------------------------------------------------------------------
// 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
#define BUTTON1_PRESS BUTTON_MODE_NONE
#endif
@ -160,6 +187,35 @@
// 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
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#endif
@ -175,7 +231,7 @@
#ifndef RELAY5_TYPE
#define RELAY5_TYPE RELAY_TYPE_NORMAL
#endif
#ifndef RELAY6_TYPE
#ifndef RELAY6_TYPE
#define RELAY6_TYPE RELAY_TYPE_NORMAL
#endif
#ifndef RELAY7_TYPE
@ -186,28 +242,28 @@
#endif
#ifndef RELAY1_RESET_PIN
#define RELAY1_RESET_PIN 0
#define RELAY1_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY2_RESET_PIN
#define RELAY2_RESET_PIN 0
#define RELAY2_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY3_RESET_PIN
#define RELAY3_RESET_PIN 0
#define RELAY3_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY4_RESET_PIN
#define RELAY4_RESET_PIN 0
#define RELAY4_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY5_RESET_PIN
#define RELAY5_RESET_PIN 0
#define RELAY5_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY6_RESET_PIN
#define RELAY6_RESET_PIN 0
#define RELAY6_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY7_RESET_PIN
#define RELAY7_RESET_PIN 0
#define RELAY7_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY8_RESET_PIN
#define RELAY8_RESET_PIN 0
#define RELAY8_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY1_DELAY_ON
@ -264,6 +320,31 @@
// 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
#define LED1_MODE LED_MODE_WIFI
#endif


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

@ -15,22 +15,6 @@
#define ARRAYINIT(type, name, ...) \
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
//------------------------------------------------------------------------------
@ -71,9 +55,15 @@
// 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
// boards that require it
#endif
#ifndef SERIAL_RX_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_LIGHT 2
#define RELAY_PROVIDER_RFBRIDGE 3
#define RELAY_PROVIDER_STM 4
// Default boot mode: 0 means OFF, 1 ON and 2 whatever was before
#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
#define RELAY_SAVE_DELAY 1000
//------------------------------------------------------------------------------
// I18N
//------------------------------------------------------------------------------
#define TMP_CELSIUS 0
#define TMP_FAHRENHEIT 1
//------------------------------------------------------------------------------
// 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_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_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_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
@ -332,15 +317,43 @@ PROGMEM const char* const custom_reset_string[] = {
#define WIFI_SLEEP_ENABLED 1 // Enable WiFi light sleep
#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_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)
#define WS_AUTHENTICATION 1 // WS authentication ON by default (see #507)
#define WS_BUFFER_SIZE 5 // Max number of secured websocket connections
#define WS_TIMEOUT 1800000 // Timeout for secured websocket
#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_GITHUB_FP "D7:9F:07:61:10:B3:92:93:E3:49:AC:89:84:5B:03:80:C1:9E:2F:8B"
// -----------------------------------------------------------------------------
// NOFUSS
@ -457,6 +472,29 @@ PROGMEM const char* const custom_reset_string[] = {
#define NOFUSS_SERVER "" // Default NoFuss Server
#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
// -----------------------------------------------------------------------------
@ -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_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
#ifndef MQTT_ENQUEUE_IP
@ -554,6 +592,8 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_TOPIC_RFIN "rfin"
#define MQTT_TOPIC_RFLEARN "rflearn"
#define MQTT_TOPIC_RFRAW "rfraw"
#define MQTT_TOPIC_UARTIN "uartin"
#define MQTT_TOPIC_UARTOUT "uartout"
// Light module
#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_TIME_OFFSET 1 // Default timezone offset (GMT+1)
#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_START_DELAY 1000 // Delay NTP start 1 second
@ -791,6 +832,7 @@ PROGMEM const char* const custom_reset_string[] = {
// -----------------------------------------------------------------------------
// RFBRIDGE
// This module is not compatible with RF_SUPPORT=1
// -----------------------------------------------------------------------------
#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
// Check http://tinkerman.cat/adding-rf-to-a-non-rf-itead-sonoff/
// Enable support by passing RF_SUPPORT=1 build flag
// This module is not compatible with RFBRIDGE
//--------------------------------------------------------------------------------
#ifndef RF_SUPPORT
@ -962,5 +1005,5 @@ PROGMEM const char* const custom_reset_string[] = {
#define RF_PIN 14
#endif
#define RF_CHANNEL 31
#define RF_DEVICE 1
#define RF_DEBOUNCE 500
#define RF_LEARN_TIMEOUT 60000

+ 143
- 34
code/espurna/config/hardware.h View File

@ -20,11 +20,38 @@
//
// 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
// -----------------------------------------------------------------------------
#if defined(NODEMCU_LOLIN)
#elif defined(NODEMCU_LOLIN)
// Info
#define MANUFACTURER "NODEMCU"
@ -476,12 +503,14 @@
// and disables any possibly harmful combination with S6 set to 0.
// If you are sure you will only use S6 to 1 you can comment the
// BUTTON1_LNGCLICK and BUTTON1_LNGLNGCLICK options below to recover the
// AP mode and factory reset functionalities.
// reset mode and factory reset functionalities, or link other actions like
// AP mode in the commented line below.
#define BUTTON1_PRESS BUTTON_MODE_TOGGLE
#define BUTTON1_CLICK BUTTON_MODE_NONE
#define BUTTON1_DBLCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGCLICK BUTTON_MODE_NONE
//#define BUTTON1_LNGCLICK BUTTON_MODE_AP
#define BUTTON1_LNGLNGCLICK BUTTON_MODE_NONE
#define BUTTON2_PRESS BUTTON_MODE_TOGGLE
#define BUTTON2_CLICK BUTTON_MODE_NONE
@ -914,35 +943,6 @@
#define IR_PIN 4
#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
// -----------------------------------------------------------------------------
@ -956,7 +956,7 @@
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
#define DEBUG_PORT Serial1
#define SERIAL_RX_PORT Serial
#define SERIAL_RX_ENABLED 1
// LEDs
#define LED1_PIN 5
@ -984,7 +984,7 @@
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
#define DEBUG_PORT Serial1
#define SERIAL_RX_PORT Serial
#define SERIAL_RX_ENABLED 1
// Light
#define LIGHT_CHANNELS 4
@ -1393,6 +1393,103 @@
#define LIGHT_ENABLE_PIN 15
#elif defined(GIZWITS_WITTY_CLOUD)
// Info
#define MANUFACTURER "GIZWITS"
#define DEVICE "WITTY_CLOUD"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
// Buttons
#define BUTTON1_PIN 4
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_PRESS BUTTON_MODE_TOGGLE
#define BUTTON1_CLICK BUTTON_MODE_NONE
#define BUTTON1_DBLCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGLNGCLICK BUTTON_MODE_RESET
#define ANALOG_SUPPORT 1
// LEDs
#define LED1_PIN 2 // BLUE build-in
#define LED1_PIN_INVERSE 1
// Light
#define LIGHT_CHANNELS 3
#define LIGHT_CH1_PIN 15 // RED
#define LIGHT_CH2_PIN 12 // GREEN
#define LIGHT_CH3_PIN 13 // BLUE
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_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
// -----------------------------------------------------------------------------
// Euromate (?) Wifi Stecker Shuko
// https://www.obi.de/hausfunksteuerung/wifi-stecker-schuko/p/2291706
// Thanks to @Geitde
// -----------------------------------------------------------------------------
#elif defined(EUROMATE_WIFI_STECKER_SCHUKO)
// Info
#define MANUFACTURER "EUROMATE"
#define DEVICE "WIFI_STECKER_SCHUKO"
// Buttons
#define BUTTON1_PIN 14
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
// #define RELAY1_PIN 12
// #define RELAY1_TYPE RELAY_TYPE_LATCHED_INVERSE
// #define RELAY1_RESET_PIN 5
// Hack: drive GPIO12 low and use GPIO5 as normal relay pin:
#define RELAY1_PIN 5
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#define LED2_PIN 12 /* DUMMY: exploit default off state for GPIO12=low */
#define LED2_PIN_INVERSE 0
// LEDs
#define LED1_PIN 4
#define LED1_PIN_INVERSE 0
// -----------------------------------------------------------------------------
// Generic 8CH
// -----------------------------------------------------------------------------
@ -1421,7 +1518,19 @@
#define RELAY8_PIN 15
#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


+ 4
- 3
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;
void mqttRegister(mqtt_callback_f callback);
String mqttTopicKey(char * topic);
String mqttMagnitude(char * topic);
// -----------------------------------------------------------------------------
// Broker
@ -67,10 +67,11 @@ void settingsRegisterCommand(const String& name, void (*call)(Embedis*));
// -----------------------------------------------------------------------------
// I2C
// -----------------------------------------------------------------------------
unsigned char i2cFindAndLock(size_t size, unsigned char * addresses);
void i2cScan();
void i2cClearBus();
bool i2cGetLock(unsigned char address);
bool i2cReleaseLock(unsigned char address);
void i2cClearBus();
unsigned char i2cFindAndLock(size_t size, unsigned char * addresses);
void i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len);
void i2c_write_uint8(uint8_t address, uint8_t value);


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

@ -7,6 +7,7 @@
#define SENSOR_READ_INTERVAL 6 // Read data from sensors every 6 seconds
#define SENSOR_READ_MIN_INTERVAL 6 // Minimum read interval
#define SENSOR_READ_MAX_INTERVAL 3600 // Maximum read interval
#define SENSOR_INIT_INTERVAL 10000 // Try to re-init non-ready sensors every 10s
#define SENSOR_REPORT_EVERY 10 // Report every this many readings
#define SENSOR_REPORT_MIN_EVERY 1 // Minimum every value
@ -15,10 +16,6 @@
#define SENSOR_USE_INDEX 0 // Use the index in topic (i.e. temperature/0)
// 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
#define SENSOR_TEMPERATURE_CORRECTION 0.0 // Offset correction
#endif
@ -43,6 +40,31 @@
#define SENSOR_PUBLISH_ADDRESSES 0 // 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
// These should remain over time, do not modify them, only add new ones at the end
@ -65,6 +87,7 @@
#define SENSOR_SI7021_ID 0x15
#define SENSOR_SHT3X_I2C_ID 0x16
#define SENSOR_BH1750_ID 0x17
#define SENSOR_PZEM004T_ID 0x18
//--------------------------------------------------------------------------------
// Magnitudes
@ -383,6 +406,32 @@
#define PMS_RX_PIN 13
#define PMS_TX_PIN 15
//------------------------------------------------------------------------------
// PZEM004T based power monitor
// Enable support by passing PZEM004T_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef PZEM004T_SUPPORT
#define PZEM004T_SUPPORT 0
#endif
#ifndef PZEM004T_USE_SOFT
#define PZEM004T_USE_SOFT 1 // Use software serial
#endif
#ifndef PZEM004T_RX_PIN
#define PZEM004T_RX_PIN 13 // Software serial RX GPIO (if PZEM004T_USE_SOFT == 1)
#endif
#ifndef PZEM004T_TX_PIN
#define PZEM004T_TX_PIN 15 // Software serial TX GPIO (if PZEM004T_USE_SOFT == 1)
#endif
#ifndef PZEM004T_HW_PORT
#define PZEM004T_HW_PORT Serial1 // Hardware serial port (if PZEM004T_USE_SOFT == 0)
#endif
//------------------------------------------------------------------------------
// SHT3X I2C (Wemos) temperature & humidity sensor
// Enable support by passing SHT3X_SUPPORT=1 build flag
@ -553,7 +602,9 @@ PROGMEM const char magnitude_hectopascals[] = "hPa";
PROGMEM const char magnitude_amperes[] = "A";
PROGMEM const char magnitude_volts[] = "V";
PROGMEM const char magnitude_watts[] = "W";
PROGMEM const char magnitude_kw[] = "kW";
PROGMEM const char magnitude_joules[] = "J";
PROGMEM const char magnitude_kwh[] = "kWh";
PROGMEM const char magnitude_ugm3[] = "µg/m3";
PROGMEM const char magnitude_ppm[] = "ppm";
PROGMEM const char magnitude_lux[] = "lux";
@ -632,6 +683,11 @@ PROGMEM const char* const magnitude_units[] = {
#include "../sensors/PMSX003Sensor.h"
#endif
#if PZEM004T_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/PZEM004TSensor.h"
#endif
#if SI7021_SUPPORT
#include "../sensors/SI7021Sensor.h"
#endif


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

@ -1,5 +1,5 @@
#define APP_NAME "ESPURNA"
#define APP_VERSION "1.12.3"
#define APP_VERSION "1.12.4"
#define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat"
#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
_udp_debug.write(message);
_udp_debug.endPacket();
delay(1); // https://github.com/xoseperez/espurna/issues/438
#if SYSTEM_CHECK_ENABLED
}
#endif


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

@ -16,8 +16,8 @@ bool _dcz_enabled = false;
// 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) {
return relayID;
}
@ -60,11 +60,11 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
}
// IDX
unsigned long idx = root["idx"];
int relayID = _domoticzRelay(idx);
unsigned int idx = root["idx"];
unsigned char relayID = _domoticzRelay(idx);
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);
}
@ -84,7 +84,7 @@ void _domoticzWebSocketOnSend(JsonObject& root) {
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
JsonArray& relays = root.createNestedArray("dczRelays");
for (byte i=0; i<relayCount(); i++) {
for (unsigned char i=0; i<relayCount(); i++) {
relays.add(domoticzIdx(i));
}
@ -127,16 +127,16 @@ template<typename T> void domoticzSend(const char * key, T nvalue) {
domoticzSend(key, nvalue, "");
}
void domoticzSendRelay(unsigned int relayID) {
void domoticzSendRelay(unsigned char relayID) {
if (!_dcz_enabled) return;
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");
}
int domoticzIdx(unsigned int relayID) {
unsigned int domoticzIdx(unsigned char relayID) {
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();
}


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

@ -147,6 +147,10 @@ void setup() {
#if SCHEDULER_SUPPORT
schSetup();
#endif
#if UART_MQTT_SUPPORT
uartmqttSetup();
#endif
// 3rd party code hook
#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) {
// Match topic
String t = mqttTopicKey((char *) topic);
String t = mqttMagnitude((char *) topic);
if (!t.startsWith(MQTT_TOPIC_LED)) return;
// Get led ID
@ -124,28 +124,28 @@ void ledUpdate(bool value) {
void ledSetup() {
#ifdef LED1_PIN
#if LED1_PIN != GPIO_NONE
_leds.push_back((led_t) { LED1_PIN, LED1_PIN_INVERSE, LED1_MODE, LED1_RELAY });
#endif
#ifdef LED2_PIN
#if LED2_PIN != GPIO_NONE
_leds.push_back((led_t) { LED2_PIN, LED2_PIN_INVERSE, LED2_MODE, LED2_RELAY });
#endif
#ifdef LED3_PIN
#if LED3_PIN != GPIO_NONE
_leds.push_back((led_t) { LED3_PIN, LED3_PIN_INVERSE, LED3_MODE, LED3_RELAY });
#endif
#ifdef LED4_PIN
#if LED4_PIN != GPIO_NONE
_leds.push_back((led_t) { LED4_PIN, LED4_PIN_INVERSE, LED4_MODE, LED4_RELAY });
#endif
#ifdef LED5_PIN
#if LED5_PIN != GPIO_NONE
_leds.push_back((led_t) { LED5_PIN, LED5_PIN_INVERSE, LED5_MODE, LED5_RELAY });
#endif
#ifdef LED6_PIN
#if LED6_PIN != GPIO_NONE
_leds.push_back((led_t) { LED6_PIN, LED6_PIN_INVERSE, LED6_MODE, LED6_RELAY });
#endif
#ifdef LED7_PIN
#if LED7_PIN != GPIO_NONE
_leds.push_back((led_t) { LED7_PIN, LED7_PIN_INVERSE, LED7_MODE, LED7_RELAY });
#endif
#ifdef LED8_PIN
#if LED8_PIN != GPIO_NONE
_leds.push_back((led_t) { LED8_PIN, LED8_PIN_INVERSE, LED8_MODE, LED8_RELAY });
#endif
@ -191,7 +191,7 @@ void ledLoop() {
}
if (_ledMode(i) == LED_MODE_MIXED) {
if (_ledMode(i) == LED_MODE_FINDME_WIFI) {
if (wifiConnected()) {
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
if (!_led_update) continue;
@ -235,7 +257,7 @@ void ledLoop() {
_ledStatus(i, status);
}
if (_ledMode(i) == LED_MODE_STATUS) {
if (_ledMode(i) == LED_MODE_RELAY) {
bool status = false;
for (unsigned char k=0; k<relayCount(); 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
String t = mqttTopicKey((char *) topic);
String t = mqttMagnitude((char *) topic);
// Color temperature in mireds
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);
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) {


+ 31
- 5
code/espurna/migrate.ino View File

@ -698,21 +698,47 @@ void migrate() {
setSetting("chLogic", 3, 0);
setSetting("relays", 1);
#elif defined(MAGICHOME_LED_CONTROLLER_23)
#elif defined(KMC_70011)
setSetting("board", 53);
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);
#elif defined(GIZWITS_WITTY_CLOUD)
setSetting("board", 54);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 4);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 12);
setSetting("chGPIO", 1, 5);
setSetting("chGPIO", 0, 15);
setSetting("chGPIO", 1, 12);
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);
#elif defined(EUROMATE_WIFI_STECKER_SCHUKO)
setSetting("board", 55);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 0);
setSetting("ledGPIO", 1, 12);
setSetting("ledLogic", 1, 0);
setSetting("btnGPIO", 0, 14);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#else
// Allow users to define new settings without migration config


+ 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 char _mqtt_qos = MQTT_QOS;
bool _mqtt_retain = MQTT_RETAIN;
unsigned char _mqtt_keepalive = MQTT_KEEPALIVE;
unsigned long _mqtt_keepalive = MQTT_KEEPALIVE;
String _mqtt_topic;
String _mqtt_topic_json;
String _mqtt_setter;
@ -215,11 +215,12 @@ void _mqttConfigure() {
// Get base topic
_mqtt_topic = getSetting("mqttTopic", MQTT_TOPIC);
if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1);
if (_mqtt_topic.indexOf("#") == -1) _mqtt_topic = _mqtt_topic + "/#";
// Placeholders
_mqtt_topic.replace("{identifier}", 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();
mac.replace(":", "");
_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) {
// Match topic
String t = mqttTopicKey((char *) topic);
String t = mqttMagnitude((char *) topic);
// Actions
if (t.equals(MQTT_TOPIC_ACTION)) {
@ -425,7 +426,13 @@ void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
// 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;
int position = pattern.indexOf("#");
@ -433,28 +440,45 @@ String mqttTopicKey(char * topic) {
String start = pattern.substring(0, position);
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 {
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;
output.replace("#", topic);
output.replace("#", magnitude);
output += is_set ? _mqtt_setter : _mqtt_getter;
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);
}


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

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


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

@ -35,7 +35,7 @@ void _ntpStart() {
_ntp_start = 0;
NTP.begin(getSetting("ntpServer", NTP_SERVER));
NTP.setInterval(NTP_UPDATE_INTERVAL);
NTP.setInterval(NTP_SYNC_INTERVAL, NTP_UPDATE_INTERVAL);
_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"
// -----------------------------------------------------------------------------
// OTA
// Arduino OTA
// -----------------------------------------------------------------------------
void _otaConfigure() {
@ -20,15 +20,199 @@ void _otaConfigure() {
#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() {
_otaConfigure();
#if WEB_SUPPORT
wsOnAfterParseRegister(_otaConfigure);
#endif
#if TERMINAL_SUPPORT
_otaInitCommands();
#endif
// Register loop
espurnaRegisterLoop(_otaLoop);
// -------------------------------------------------------------------------
ArduinoOTA.onStart([]() {
DEBUG_MSG_P(PSTR("[OTA] Start\n"));
#if WEB_SUPPORT
@ -38,7 +222,7 @@ void otaSetup() {
ArduinoOTA.onEnd([]() {
DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[OTA] End\n"));
DEBUG_MSG_P(PSTR("[OTA] Done, restarting...\n"));
#if WEB_SUPPORT
wsSend_P(PSTR("{\"action\": \"reload\"}"));
#endif
@ -62,11 +246,4 @@ void otaSetup() {
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
#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 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
// -----------------------------------------------------------------------------
@ -385,7 +462,11 @@ void _relayBoot() {
}
_relays[i].current_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;
}
@ -512,7 +593,7 @@ void relaySetupAPI() {
apiRegister(key,
[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) {
@ -611,7 +692,7 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
if (type == MQTT_MESSAGE_EVENT) {
// Check relay topic
String t = mqttTopicKey((char *) topic);
String t = mqttMagnitude((char *) topic);
if (t.startsWith(MQTT_TOPIC_RELAY)) {
// Get value
@ -709,11 +790,16 @@ void _relayInitCommands() {
// Setup
//------------------------------------------------------------------------------
void _relayLoop() {
_relayProcess(false);
_relayProcess(true);
}
void relaySetup() {
// Dummy relays for AI Light, Magic Home LED Controller, H801,
// 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++) {
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL});
@ -721,28 +807,28 @@ void relaySetup() {
#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 });
#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 });
#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 });
#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 });
#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 });
#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 });
#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 });
#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 });
#endif
@ -751,9 +837,9 @@ void relaySetup() {
_relayBackwards();
_relayConfigure();
_relayBoot();
relayLoop();
_relayLoop();
espurnaRegisterLoop(relayLoop);
espurnaRegisterLoop(_relayLoop);
#if WEB_SUPPORT
relaySetupAPI();
@ -769,63 +855,3 @@ void relaySetup() {
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
#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
// -----------------------------------------------------------------------------
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
wsOnSendRegister(_rfWebSocketOnSend);
wsOnAfterParseRegister(_rfBuildCodes);
wsOnActionRegister(_rfWebSocketOnAction);
#endif
// 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) {
// Match topic
String t = mqttTopicKey((char *) topic);
String t = mqttMagnitude((char *) topic);
// Check if should go into learn mode
if (t.startsWith(MQTT_TOPIC_RFLEARN)) {
@ -443,37 +443,47 @@ String rfbRetrieve(unsigned char id, bool status) {
}
void rfbStatus(unsigned char id, bool status) {
String value = rfbRetrieve(id, status);
if (value.length() > 0) {
bool same = _rfbSameOnOff(id);
#if RF_RAW_SUPPORT
byte message[RF_MAX_MESSAGE_SIZE];
int len = _rfbToArray(value.c_str(), message, 0);
if (len == RF_MESSAGE_SIZE && // probably a standard msg
(message[0] != RF_CODE_START || // raw would start with 0xAA
message[1] != RF_CODE_RFOUT_BUCKET || // followed by 0xB0,
message[2] + 4 != len || // needs a valid length,
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 {
_rfbSendRawOnce(message, len); // send a raw message
}
#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
}
_rfbin = false;
}
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){
root["schVisible"] = 1;
root["maxScheduled"] = SCHEDULER_MAX_SCHEDULES;
JsonArray &sch = root.createNestedArray("schedule");
for (byte i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {


+ 145
- 50
code/espurna/sensor.ino View File

@ -28,11 +28,14 @@ typedef struct {
std::vector<BaseSensor *> _sensors;
std::vector<sensor_magnitude_t> _magnitudes;
bool _sensors_ready = false;
unsigned char _counts[MAGNITUDE_MAX];
bool _sensor_realtime = API_REAL_TIME_VALUES;
unsigned long _sensor_read_interval = 1000 * SENSOR_READ_INTERVAL;
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;
double _sensor_temperature_correction = SENSOR_TEMPERATURE_CORRECTION;
double _sensor_humidity_correction = SENSOR_HUMIDITY_CORRECTION;
@ -42,19 +45,48 @@ double _sensor_humidity_correction = SENSOR_HUMIDITY_CORRECTION;
// -----------------------------------------------------------------------------
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);
return 0;
}
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 (_sensor_temperature_units == TMP_FAHRENHEIT) value = value * 1.8 + 32;
value = value + _sensor_temperature_correction;
}
if (type == MAGNITUDE_HUMIDITY) {
value = value + _sensor_humidity_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));
}
// -----------------------------------------------------------------------------
@ -98,6 +130,7 @@ void _sensorWebSocketStart(JsonObject& root) {
#if EMON_ANALOG_SUPPORT
if (sensor->getID() == SENSOR_EMON_ANALOG_ID) {
root["emonVisible"] = 1;
root["pwrVisible"] = 1;
root["pwrVoltage"] = ((EmonAnalogSensor *) sensor)->getVoltage();
}
#endif
@ -105,6 +138,25 @@ void _sensorWebSocketStart(JsonObject& root) {
#if HLW8012_SUPPORT
if (sensor->getID() == SENSOR_HLW8012_ID) {
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
#if PZEM004T_SUPPORT
if (sensor->getID() == SENSOR_PZEM004T_ID) {
root["pwrVisible"] = 1;
}
#endif
@ -113,6 +165,8 @@ void _sensorWebSocketStart(JsonObject& root) {
if (_magnitudes.size() > 0) {
root["sensorsVisible"] = 1;
//root["apiRealTime"] = _sensor_realtime;
root["powerUnits"] = _sensor_power_units;
root["energyUnits"] = _sensor_energy_units;
root["tmpUnits"] = _sensor_temperature_units;
root["tmpCorrection"] = _sensor_temperature_correction;
root["humCorrection"] = _sensor_humidity_correction;
@ -208,7 +262,7 @@ void _sensorPost() {
// Sensor initialization
// -----------------------------------------------------------------------------
void _sensorInit() {
void _sensorLoad() {
/*
@ -374,6 +428,19 @@ void _sensorInit() {
}
#endif
#if PZEM004T_SUPPORT
{
PZEM004TSensor * sensor = new PZEM004TSensor();
#if PZEM004T_USE_SOFT
sensor->setRX(PZEM004T_RX_PIN);
sensor->setTX(PZEM004T_TX_PIN);
#else
sensor->setSerial(& PZEM004T_HW_PORT);
#endif
_sensors.push_back(sensor);
}
#endif
#if SHT3X_I2C_SUPPORT
{
SHT3XI2CSensor * sensor = new SHT3XI2CSensor();
@ -401,10 +468,20 @@ void _sensorInit() {
}
void _sensorConfigure() {
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 _sensorInit() {
_sensors_ready = true;
for (unsigned char i=0; i<_sensors.size(); i++) {
// Do not process and already initialized sensor
if (_sensors[i]->ready()) continue;
DEBUG_MSG_P(PSTR("[SENSOR] Initializing %s\n"), _sensors[i]->description().c_str());
#if EMON_ANALOG_SUPPORT
if (_sensors[i]->getID() == SENSOR_EMON_ANALOG_ID) {
@ -412,7 +489,7 @@ void _sensorConfigure() {
double value;
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();
if (value > 0) sensor->setCurrentRatio(0, value);
} else {
@ -434,6 +511,46 @@ void _sensorConfigure() {
// Force sensor to reload config
_sensors[i]->begin();
if (!_sensors[i]->ready()) {
if (_sensors[i]->error() != 0) DEBUG_MSG_P(PSTR("[SENSOR] -> ERROR %d\n"), _sensors[i]->error());
_sensors_ready = false;
continue;
}
// Initialize magnitudes
for (unsigned char k=0; k<_sensors[i]->count(); k++) {
unsigned char type = _sensors[i]->type(k);
sensor_magnitude_t new_magnitude;
new_magnitude.sensor = _sensors[i];
new_magnitude.local = k;
new_magnitude.type = type;
new_magnitude.global = _counts[type];
new_magnitude.current = 0;
new_magnitude.filtered = 0;
new_magnitude.reported = 0;
new_magnitude.min_change = 0;
if (type == MAGNITUDE_DIGITAL) {
new_magnitude.filter = new MaxFilter();
} else if (type == MAGNITUDE_EVENTS) {
new_magnitude.filter = new MovingAverageFilter();
} else {
new_magnitude.filter = new MedianFilter();
}
new_magnitude.filter->resize(_sensor_report_every);
_magnitudes.push_back(new_magnitude);
DEBUG_MSG_P(PSTR("[SENSOR] -> %s:%d\n"), magnitudeTopic(type).c_str(), _counts[type]);
_counts[type] = _counts[type] + 1;
}
// Hook callback
_sensors[i]->onEvent([i](unsigned char type, const char * payload) {
_sensorCallback(i, type, payload);
});
#if HLW8012_SUPPORT
@ -480,10 +597,16 @@ void _sensorConfigure() {
}
}
void _sensorConfigure() {
// General sensor settings
_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_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_correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat();
_sensor_humidity_correction = getSetting("humCorrection", SENSOR_HUMIDITY_CORRECTION).toFloat();
@ -502,48 +625,6 @@ void _sensorConfigure() {
}
void _magnitudesInit() {
for (unsigned char i=0; i<_sensors.size(); i++) {
BaseSensor * sensor = _sensors[i];
DEBUG_MSG_P(PSTR("[SENSOR] %s\n"), sensor->description().c_str());
if (sensor->error() != 0) DEBUG_MSG_P(PSTR("[SENSOR] -> ERROR %d\n"), sensor->error());
for (unsigned char k=0; k<sensor->count(); k++) {
unsigned char type = sensor->type(k);
sensor_magnitude_t new_magnitude;
new_magnitude.sensor = sensor;
new_magnitude.local = k;
new_magnitude.type = type;
new_magnitude.global = _counts[type];
new_magnitude.current = 0;
new_magnitude.filtered = 0;
new_magnitude.reported = 0;
new_magnitude.min_change = 0;
if (type == MAGNITUDE_DIGITAL) {
new_magnitude.filter = new MaxFilter();
} else if (type == MAGNITUDE_EVENTS) {
new_magnitude.filter = new MovingAverageFilter();
} else {
new_magnitude.filter = new MedianFilter();
}
new_magnitude.filter->resize(_sensor_report_every);
_magnitudes.push_back(new_magnitude);
DEBUG_MSG_P(PSTR("[SENSOR] -> %s:%d\n"), magnitudeTopic(type).c_str(), _counts[type]);
_counts[type] = _counts[type] + 1;
}
}
}
// -----------------------------------------------------------------------------
// Public
// -----------------------------------------------------------------------------
@ -603,6 +684,14 @@ String magnitudeUnits(unsigned char type) {
if (type < MAGNITUDE_MAX) {
if ((type == MAGNITUDE_TEMPERATURE) && (_sensor_temperature_units == TMP_FAHRENHEIT)) {
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 {
strncpy_P(buffer, magnitude_units[type], sizeof(buffer));
}
@ -615,14 +704,12 @@ String magnitudeUnits(unsigned char type) {
void sensorSetup() {
// Load sensors
_sensorLoad();
_sensorInit();
// Configure stored values
_sensorConfigure();
// Load magnitudes
_magnitudesInit();
#if WEB_SUPPORT
// Websockets
@ -646,8 +733,14 @@ void sensorSetup() {
void sensorLoop() {
static unsigned long last_update = 0;
static unsigned long report_count = 0;
// Check if we still have uninitialized sensors
static unsigned long last_init = 0;
if (!_sensors_ready) {
if (millis() - last_init > SENSOR_INIT_INTERVAL) {
last_init = millis();
_sensorInit();
}
}
if (_magnitudes.size() == 0) return;
@ -655,6 +748,8 @@ void sensorLoop() {
_sensorTick();
// Check if we should read new data
static unsigned long last_update = 0;
static unsigned long report_count = 0;
if (millis() - last_update > _sensor_read_interval) {
last_update = millis();


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

@ -30,11 +30,12 @@ class AnalogSensor : public BaseSensor {
// Initialization method, must be idempotent
void begin() {
pinMode(0, INPUT);
_ready = true;
}
// Descriptive name of the sensor
String description() {
return String("ANALOG @ GPIO0");
return String("ANALOG @ TOUT");
}
// Descriptive name of the slot # index


+ 2
- 1
code/espurna/sensors/BH1750Sensor.h View File

@ -55,7 +55,6 @@ class BH1750Sensor : public I2CSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
// I2C auto-discover
unsigned char addresses[] = {0x23, 0x5C};
@ -64,6 +63,8 @@ class BH1750Sensor : public I2CSensor {
// Run configuration on next update
_run_configure = true;
_ready = true;
_dirty = false;
}


+ 19
- 11
code/espurna/sensors/BMX280Sensor.h View File

@ -67,18 +67,9 @@ class BMX280Sensor : public I2CSensor {
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_dirty = false;
_chip = 0;
// I2C auto-discover
_address = _begin_i2c(_address, sizeof(BMX280Sensor::addresses), BMX280Sensor::addresses);
if (_address == 0) return;
// Init
_init();
_dirty = !_ready;
}
// Descriptive name of the sensor
@ -194,13 +185,29 @@ class BMX280Sensor : public I2CSensor {
// Make sure sensor had enough time to turn on. BMX280 requires 2ms to start up
delay(10);
// No chip ID by default
_chip = 0;
// I2C auto-discover
_address = _begin_i2c(_address, sizeof(BMX280Sensor::addresses), BMX280Sensor::addresses);
if (_address == 0) return;
// Check sensor correctly initialized
_chip = i2c_read_uint8(_address, BMX280_REGISTER_CHIPID);
if ((_chip != BMX280_CHIP_BME280) && (_chip != BMX280_CHIP_BMP280)) {
_chip = 0;
i2cReleaseLock(_address);
_previous_address = 0;
_error = SENSOR_ERROR_UNKNOWN_ID;
// Setting _address to 0 forces auto-discover
// This might be necessary at this stage if there is a
// different sensor in the hardcoded address
_address = 0;
return;
}
_count = 0;
@ -233,6 +240,7 @@ class BMX280Sensor : public I2CSensor {
_measurement_delay = _measurementTime();
_run_init = false;
_ready = true;
}
@ -341,7 +349,7 @@ class BMX280Sensor : public I2CSensor {
var1 = ((var1 * var1 * (int64_t)_bmx280_calib.dig_P3)>>8) +
((var1 * (int64_t)_bmx280_calib.dig_P2)<<12);
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 = (((p<<31) - var2)*3125) / var1;


+ 12
- 4
code/espurna/sensors/BaseSensor.h View File

@ -10,8 +10,6 @@
#include <Arduino.h>
#include <ArduinoJson.h>
#define GPIO_NONE 0x99
#define SENSOR_ERROR_OK 0 // No error
#define SENSOR_ERROR_OUT_OF_RANGE 1 // Result out of sensor range
#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_GPIO_USED 7 // The GPIO is already in use
typedef std::function<void(unsigned char, const char *)> TSensorCallback;
class BaseSensor {
public:
@ -70,8 +70,11 @@ class BaseSensor {
// Sensor ID
unsigned char getID() { return _sensor_id; };
// Return sensor status (true for ready)
bool status() { return _error == 0; }
// Return status (true if no errors)
bool status() { return 0 == _error; }
// Return ready status (true for ready)
bool ready() { return _ready; }
// Return sensor last internal error
int error() { return _error; }
@ -79,12 +82,17 @@ class BaseSensor {
// Number of available slots
unsigned char count() { return _count; }
// Hook for event callback
void onEvent(TSensorCallback fn) { _callback = fn; };
protected:
TSensorCallback _callback = NULL;
unsigned char _sensor_id = 0x00;
int _error = 0;
bool _dirty = true;
unsigned char _count = 0;
bool _ready = false;
};


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

@ -75,6 +75,7 @@ class DHTSensor : public BaseSensor {
_previous = _gpio;
_count = 2;
_ready = true;
}


+ 2
- 1
code/espurna/sensors/DallasSensor.h View File

@ -64,7 +64,6 @@ class DallasSensor : public BaseSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
// Manage GPIO lock
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
@ -93,6 +92,8 @@ class DallasSensor : public BaseSensor {
} else {
_previous = _gpio;
}
_ready = true;
_dirty = false;
}


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

@ -58,6 +58,7 @@ class DigitalSensor : public BaseSensor {
// Initialization method, must be idempotent
void begin() {
pinMode(_gpio, _mode);
_ready = true;
}
// Descriptive name of the sensor


+ 8
- 1
code/espurna/sensors/ECH1560Sensor.h View File

@ -67,12 +67,19 @@ class ECH1560Sensor : public BaseSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
pinMode(_clk, INPUT);
pinMode(_miso, INPUT);
_enableInterrupts(true);
_dirty = false;
_ready = true;
}
// Loop-like method, call it in your main loop
void tick() {
if (_dosync) _sync();
}
// Descriptive name of the sensor


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

@ -147,7 +147,6 @@ class EmonADS1X15Sensor : public EmonSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
// Discover
unsigned char addresses[] = {0x48, 0x49, 0x4A, 0x4B};


+ 4
- 0
code/espurna/sensors/EmonSensor.h View File

@ -109,6 +109,10 @@ class EmonSensor : public I2CSensor {
}
#endif
_ready = true;
_dirty = false;
}
protected:


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

@ -72,6 +72,7 @@ class EventSensor : public BaseSensor {
void begin() {
pinMode(_gpio, _mode);
_enableInterrupts(true);
_ready = true;
}
// Descriptive name of the sensor


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

@ -153,6 +153,8 @@ class HLW8012Sensor : public BaseSensor {
});
#endif
_ready = true;
}
// Descriptive name of the sensor


+ 18
- 14
code/espurna/sensors/I2CSensor.h View File

@ -40,28 +40,32 @@ class I2CSensor : public BaseSensor {
// Specific for I2C sensors
unsigned char _begin_i2c(unsigned char address, size_t size, unsigned char * addresses) {
// If we have already locked this address for this sensor quit
if ((address > 0) && (address == _previous_address)) {
return _previous_address;
}
// Check if we should release a previously locked address
if (_previous_address != address) {
if ((_previous_address > 0) && (_previous_address != address)) {
i2cReleaseLock(_previous_address);
_previous_address = 0;
}
// If we have already an address, check it is not locked
if (address && !i2cGetLock(address)) {
_error = SENSOR_ERROR_I2C;
// If we don't have an address...
} else {
// Trigger auto-discover
address = i2cFindAndLock(size, addresses);
// If requesting a specific address, try to ger a lock to it
if ((0 < address) && i2cGetLock(address)) {
_previous_address = address;
return _previous_address;
}
// If still nothing exit with error
if (address == 0) _error = SENSOR_ERROR_I2C;
// If everything else fails, perform an auto-discover
_previous_address = i2cFindAndLock(size, addresses);
// Flag error
if (0 == _previous_address) {
_error = SENSOR_ERROR_I2C;
}
_previous_address = address;
return address;
return _previous_address;
}


+ 4
- 1
code/espurna/sensors/MHZ19Sensor.h View File

@ -72,14 +72,17 @@ class MHZ19Sensor : public BaseSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
if (_serial) delete _serial;
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32);
_serial->enableIntTx(false);
_serial->begin(9600);
calibrateAuto(false);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor


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

@ -62,17 +62,19 @@ class PMSX003Sensor : public BaseSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
if (_serial) delete _serial;
if (_pms) delete _pms;
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32);
_serial->enableIntTx(false);
_serial->begin(9600);
_pms = new PMS(* _serial);
_pms->passiveMode();
_startTime = millis();
_ready = true;
_dirty = false;
}


+ 136
- 0
code/espurna/sensors/PZEM004TSensor.h View File

@ -0,0 +1,136 @@
// -----------------------------------------------------------------------------
// PZEM004T based power monitor
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && PZEM004T_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <PZEM004T.h>
class PZEM004TSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
PZEM004TSensor(): BaseSensor(), _data() {
_count = 4;
_sensor_id = SENSOR_PZEM004T_ID;
}
~PZEM004TSensor() {
if (_pzem) delete _pzem;
}
// ---------------------------------------------------------------------
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setTX(unsigned char pin_tx) {
if (_pin_tx == pin_tx) return;
_pin_tx = pin_tx;
_dirty = true;
}
void setSerial(Stream & serial) {
_serial = serial;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
unsigned char getTX() {
return _pin_tx;
}
Stream & getSerial() {
return _serial;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_pzem) delete _pzem;
if (_serial == NULL) {
_pzem = PZEM004T(_pin_rx, _pin_tx);
} else {
_pzem = PZEM004T(_serial);
}
_pzem->setAddress(_ip);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "PZEM004T @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return _ip.toString();
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
if (index == 3) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _pzem->current(_ip);
if (index == 1) return _pzem->voltage(_ip);
if (index == 2) return _pzem->power(_ip);
if (index == 3) return _pzem->energy(_ip);
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
unsigned int _pin_rx = PZEM004T_RX_PIN;
unsigned int _pin_tx = PZEM004T_TX_PIN;
Stream & _serial = NULL;
IPAddress _ip(192,168,1,1);
PZEM004T * _pzem = NULL;
};
#endif // SENSOR_SUPPORT && PZEM004T_SUPPORT

+ 3
- 1
code/espurna/sensors/SHT3XI2CSensor.h View File

@ -31,13 +31,15 @@ class SHT3XI2CSensor : public I2CSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
// I2C auto-discover
unsigned char addresses[] = {0x45};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor


+ 17
- 11
code/espurna/sensors/SI7021Sensor.h View File

@ -41,18 +41,9 @@ class SI7021Sensor : public I2CSensor {
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_dirty = false;
// I2C auto-discover
unsigned char addresses[] = {0x40};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Initialize sensor
_init();
_dirty = !_ready;
}
// Descriptive name of the sensor
@ -116,18 +107,33 @@ class SI7021Sensor : public I2CSensor {
void _init() {
// I2C auto-discover
unsigned char addresses[] = {0x40};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Check device
i2c_write_uint8(_address, 0xFC, 0xC9);
_chip = i2c_read_uint8(_address);
if ((_chip != SI7021_CHIP_SI7021) & (_chip != SI7021_CHIP_HTU21D)) {
_count = 0;
i2cReleaseLock(_address);
_previous_address = 0;
_error = SENSOR_ERROR_UNKNOWN_ID;
_count = 0;
// Setting _address to 0 forces auto-discover
// This might be necessary at this stage if there is a
// different sensor in the hardcoded address
_address = 0;
} else {
_count = 2;
}
_ready = true;
}
unsigned int _read(uint8_t command) {


+ 4
- 1
code/espurna/sensors/V9261FSensor.h View File

@ -61,13 +61,16 @@ class V9261FSensor : public BaseSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
if (_serial) delete _serial;
_serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32);
_serial->enableIntTx(false);
_serial->begin(V9261F_BAUDRATE);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor


+ 72
- 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);
#if TERMINAL_SUPPORT
#ifdef SERIAL_RX_PORT
#if SERIAL_RX_ENABLED
char _serial_rx_buffer[TERMINAL_BUFFER_SIZE];
static unsigned char _serial_rx_pointer = 0;
#endif
#endif
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
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
// -----------------------------------------------------------------------------
void _settingsHelp() {
void _settingsHelpCommand() {
// Get sorted list of commands
std::vector<String> commands;
@ -122,32 +153,10 @@ void _settingsHelp() {
}
void _settingsKeys() {
void _settingsKeysCommand() {
// 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
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++) {
EEPROM.write(i, 0xFF);
}
EEPROM.commit();
}
void _settingsDump(bool ascii) {
void _settingsDumpCommand(bool ascii) {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
if (i % 16 == 0) DEBUG_MSG_P(PSTR("\n[%04X] "), i);
byte c = EEPROM.read(i);
@ -191,14 +200,14 @@ void _settingsInitCommands() {
#endif
settingsRegisterCommand(F("COMMANDS"), [](Embedis* e) {
_settingsHelp();
_settingsHelpCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) {
bool ascii = false;
if (e->argc == 2) ascii = String(e->argv[1]).toInt() == 1;
_settingsDump(ascii);
_settingsDumpCommand(ascii);
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
@ -209,8 +218,22 @@ void _settingsInitCommands() {
*((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494
});
#if I2C_SUPPORT
settingsRegisterCommand(F("I2C.SCAN"), [](Embedis* e) {
_settingsI2CScanCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("I2C.CLEAR"), [](Embedis* e) {
i2cClearBus();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("FACTORY.RESET"), [](Embedis* e) {
_settingsFactoryReset();
_settingsFactoryResetCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
@ -238,7 +261,7 @@ void _settingsInitCommands() {
});
settingsRegisterCommand(F("HELP"), [](Embedis* e) {
_settingsHelp();
_settingsHelpCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
@ -252,7 +275,7 @@ void _settingsInitCommands() {
});
settingsRegisterCommand(F("KEYS"), [](Embedis* e) {
_settingsKeys();
_settingsKeysCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
@ -323,7 +346,7 @@ void saveSettings() {
}
void resetSettings() {
_settingsFactoryReset();
_settingsFactoryResetCommand();
}
// -----------------------------------------------------------------------------
@ -367,11 +390,13 @@ bool settingsRestoreJson(JsonObject& data) {
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 +433,10 @@ void settingsSetup() {
_settingsInitCommands();
#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
espurnaRegisterLoop(settingsLoop);
@ -429,7 +454,7 @@ void settingsLoop() {
embedis.process();
#ifdef SERIAL_RX_PORT
#if SERIAL_RX_ENABLED
while (SERIAL_RX_PORT.available() > 0) {
char rc = Serial.read();
@ -440,7 +465,7 @@ void settingsLoop() {
}
}
#endif // SERIAL_RX_PORT
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT


+ 3219
- 0
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) {
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) {
DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Only local connections\n"));
client->onDisconnect([](void *s, AsyncClient *c) {
@ -81,6 +88,7 @@ void _telnetNewClient(AsyncClient *client) {
client->close(true);
return;
}
}
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
@ -109,6 +117,14 @@ void _telnetNewClient(AsyncClient *client) {
}, 0);
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;
wifiReconnectCheck();
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

+ 11
- 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] SUPPORT:"));
@ -324,13 +327,17 @@ void info() {
#if THINGSPEAK_SUPPORT
DEBUG_MSG_P(PSTR(" THINGSPEAK"));
#endif
#if UART_MQTT_SUPPORT
DEBUG_MSG_P(PSTR(" UART_MQTT"));
#endif
#if WEB_SUPPORT
DEBUG_MSG_P(PSTR(" WEB"));
#endif
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n[INIT] SENSORS:"));
DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[INIT] SENSORS:"));
#if ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" ANALOG"));
@ -371,6 +378,9 @@ void info() {
#if PMSX003_SUPPORT
DEBUG_MSG_P(PSTR(" PMSX003"));
#endif
#if PZEM004T_SUPPORT
DEBUG_MSG_P(PSTR(" PZEM004T"));
#endif
#if SHT3X_I2C_SUPPORT
DEBUG_MSG_P(PSTR(" SHT3X_I2C"));
#endif


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

@ -45,17 +45,19 @@ void _onGetConfig(AsyncWebServerRequest *request) {
webLog(request);
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["version"] = APP_VERSION;
settingsGetJson(root);
root.prettyPrintTo(*response);
char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("attachment; filename=\"%s-backup.json\""), (char *) getSetting("hostname").c_str());
response->addHeader("Content-Disposition", buffer);
response->setLength();
request->send(response);
}


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

@ -171,44 +171,29 @@ bool _wifiClean(unsigned char num) {
// Inject hardcoded networks
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
@ -345,11 +330,6 @@ String getNetwork() {
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() {
return jw.connected();
}


+ 28
- 4
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);
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"];
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["uptime"] = getUptime();
root["rssi"] = WiFi.RSSI();
root["distance"] = wifiDistance(WiFi.RSSI());
#if NTP_SUPPORT
if (ntpSynced()) root["now"] = now();
#endif
@ -255,10 +270,14 @@ void _wsOnStart(JsonObject& root) {
root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).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["humCorrection"] = getSetting("humCorrection", SENSOR_HUMIDITY_CORRECTION).toFloat();
root["wsAuth"] = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1;
}
}
@ -376,7 +395,12 @@ void wsSend_P(uint32_t client_id, PGM_P payload) {
void wsConfigure() {
#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
}


+ 37
- 2
code/extra_scripts.py View File

@ -1,12 +1,15 @@
#!/usr/bin/env python
from subprocess import call
import os
import time
Import("env")
# ------------------------------------------------------------------------------
# Utils
# ------------------------------------------------------------------------------
class Color:
class Color(object):
BLACK = '\x1b[1;30m'
RED = '\x1b[1;31m'
GREEN = '\x1b[1;32m'
@ -31,16 +34,48 @@ def clr(color, text):
# 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):
time.sleep(1)
time.sleep(2)
size = target[0].get_size()
print clr(Color.LIGHT_BLUE, "Binary size: %s bytes" % size)
#if size > 512000:
# print clr(Color.LIGHT_RED, "File too large for OTA!")
# 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
# ------------------------------------------------------------------------------
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)

+ 28
- 24
code/gulpfile.js View File

@ -36,22 +36,17 @@ const inline = require('gulp-inline');
const inlineImages = require('gulp-css-base64');
const favicon = require('gulp-base64-favicon');
const htmllint = require('gulp-htmllint');
const gutil = require('gulp-util');
const log = require('fancy-log');
const csslint = require('gulp-csslint');
const dataFolder = 'espurna/data/';
const staticFolder = 'espurna/static/';
String.prototype.replaceAll = function(search, replacement) {
var target = this;
return target.split(search).join(replacement);
};
var toHeader = function(filename) {
var source = dataFolder + filename;
var destination = staticFolder + filename + '.h';
var safename = filename.replaceAll('.', '_');
var safename = filename.split('.').join('_');
var wstream = fs.createWriteStream(destination);
wstream.on('error', function (err) {
@ -64,9 +59,11 @@ var toHeader = function(filename) {
wstream.write('const uint8_t ' + safename + '[] PROGMEM = {');
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));
if (i<data.length-1) {
if (i < (data.length - 1)) {
wstream.write(',');
}
}
@ -76,10 +73,17 @@ var toHeader = function(filename) {
};
function htmllintReporter(filepath, issues) {
var htmllintReporter = function(filepath, issues) {
if (issues.length > 0) {
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;
}
@ -91,9 +95,9 @@ gulp.task('build_certs', 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() {
@ -101,29 +105,29 @@ gulp.task('buildfs_embeded', ['buildfs_inline'], function() {
});
gulp.task('buildfs_inline', function() {
return gulp.src('html/*.html')
.pipe(htmllint({
return gulp.src('html/*.html').
pipe(htmllint({
'failOnError': true,
'rules': {
'id-class-style': false,
'label-req-for': false,
}
}, htmllintReporter))
.pipe(favicon())
.pipe(inline({
}, htmllintReporter)).
pipe(favicon()).
pipe(inline({
base: 'html/',
js: [uglify],
css: [cleancss, inlineImages],
disabledTypes: ['svg', 'img']
}))
.pipe(htmlmin({
})).
pipe(htmlmin({
collapseWhitespace: true,
removeComments: true,
minifyCSS: 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 {
font-size: 100%;
padding: .5em .5em;
white-space: normal;
}
.pure-g {
@ -55,6 +56,10 @@ h2 {
margin: -10px 0 10px 0;
}
.hint a {
color:inherit;
}
legend.module,
.module {
display: none;
@ -143,7 +148,8 @@ div.state {
.button-rfb-forget,
.button-del-network,
.button-del-schedule,
.button-upgrade {
.button-upgrade,
.button-settings-factory {
background: rgb(192, 0, 0); /* redish */
}
@ -154,6 +160,7 @@ div.state {
.button-rfb-learn,
.button-upgrade-browse,
.button-ha-add,
.button-ha-config,
.button-settings-backup,
.button-settings-restore,
.button-apikey {
@ -272,6 +279,7 @@ span.slider {
display: none;
}
#haConfig,
#scanResult {
color: #888;
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>
</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>
</li>
@ -118,6 +118,10 @@
<a href="#" class="pure-menu-link" data="panel-domoticz">DOMOTICZ</a>
</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">
<a href="#" class="pure-menu-link" data="panel-idb">INFLUXDB</a>
</li>
@ -127,7 +131,7 @@
</li>
<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 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-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-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-4"></div>
<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>
@ -286,10 +290,11 @@
<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">
<option value="1">WiFi status</option>
<option value="8">Relay status</option>
<option value="0">MQTT managed</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="7">Always OFF</option>
</select>
@ -297,11 +302,12 @@
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
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 "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.
</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>
<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>
</div>
</div>
@ -375,11 +365,11 @@
<fieldset>
<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-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">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 class="pure-g">
@ -469,6 +459,11 @@
</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">
<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>
@ -521,7 +516,8 @@
<div class="pure-g">
<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-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 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 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-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>
<input name="upgrade" type="file" tabindex="16" />
</div>
@ -569,7 +567,7 @@
<div class="pure-g">
<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>
<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
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 />
Subscribtions will still be done to single topics.
Subscriptions will still be done to single topics.
</div>
</div>
@ -800,6 +798,61 @@
</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="header">
@ -930,6 +983,22 @@
</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">
<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">
@ -971,7 +1040,7 @@
<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" />
<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 class="pure-g module module-hlw">
@ -1004,11 +1073,12 @@
<div class="panel" id="panel-rfb">
<div class="header">
<h1>RFBRIDGE</h1>
<h1>RADIO FREQUENCY</h1>
<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.
<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>


+ 2
- 2
code/memanalyzer.py View File

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


+ 4
- 8
code/ota.py View File

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


+ 3
- 4
code/package.json View File

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

+ 116
- 30
code/platformio.ini View File

@ -16,9 +16,9 @@ lib_deps =
Brzo I2C
https://bitbucket.org/xoseperez/debounceevent.git#2.0.1
Embedis
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/plerup/espsoftwareserial#7077979
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/hlw8012.git#1.1.0
https://github.com/markszabo/IRremoteESP8266#v2.2.0
@ -29,22 +29,34 @@ lib_deps =
https://github.com/xoseperez/NtpClient.git#b35e249
OneWire
PMS Library
PZEM004T
PubSubClient
https://github.com/xoseperez/RemoteSwitch-arduino-library.git
rc-switch
https://github.com/xoseperez/Time
lib_ignore =
extra_scripts = extra_scripts.py
# ------------------------------------------------------------------------------
[env:espurna-core]
[env:espurna-core-1MB]
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} -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
extra_scripts = ${common.extra_scripts}
@ -904,30 +916,6 @@ upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
[env:magichome-led-controller-23]
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} -DMAGICHOME_LED_CONTROLLER_23
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
[env:magichome-led-controller-23-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} -DMAGICHOME_LED_CONTROLLER_23
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
[env:huacanxing-h801]
platform = ${common.platform}
framework = arduino
@ -1375,6 +1363,31 @@ upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
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]
platform = ${common.platform}
framework = arduino
@ -1411,6 +1424,79 @@ upload_speed = 460800
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
[env:gizwits-witty-cloud]
platform = ${common.platform}
framework = arduino
board = esp12e
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags} -DGIZWITS_WITTY_CLOUD
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
[env:gizwits-witty-cloud-ota]
platform = ${common.platform}
framework = arduino
board = esp12e
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags} -DGIZWITS_WITTY_CLOUD
upload_speed = 115200
upload_port = "${env.ESPURNA_IP}"
upload_flags = --auth=${env.ESPURNA_AUTH} --port 8266
extra_scripts = ${common.extra_scripts}
[env:euromate-wifi-stecker-shuko]
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} -DEUROMATE_WIFI_STECKER_SCHUKO
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
[env:euromate-wifi-stecker-shuko-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} -DEUROMATE_WIFI_STECKER_SCHUKO
upload_speed = 115200
upload_port = "${env.ESPURNA_IP}"
upload_flags = --auth=${env.ESPURNA_AUTH} --port 8266
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
# ------------------------------------------------------------------------------


+ 10
- 0
pre-commit View File

@ -52,6 +52,14 @@ travis = "[![travis](https://travis-ci.org/{USER}/{REPO}.svg?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()
with open(README, "w") as fh:
for line in lines:
@ -61,6 +69,8 @@ with open(README, "w") as fh:
fh.write(version)
elif "![branch]" in line:
fh.write(branch)
elif "![codacy]" in line:
fh.write(codacy)
else:
fh.write(line)


Loading…
Cancel
Save