Browse Source

Merge branch 'dev' into v2

v2
Xose Pérez 6 years ago
parent
commit
d834f49302
58 changed files with 19340 additions and 18064 deletions
  1. +2
    -2
      .github/stale.yml
  2. +11
    -7
      README.md
  3. +2
    -2
      code/espurna/alexa.ino
  4. +1
    -1
      code/espurna/button.ino
  5. +1
    -1
      code/espurna/config/all.h
  6. +6
    -0
      code/espurna/config/arduino.h
  7. +138
    -7
      code/espurna/config/defaults.h
  8. +3
    -0
      code/espurna/config/device.h
  9. +85
    -16
      code/espurna/config/general.h
  10. +6
    -0
      code/espurna/config/progmem.h
  11. +3
    -3
      code/espurna/config/prototypes.h
  12. +29
    -18
      code/espurna/config/sensors.h
  13. +8
    -0
      code/espurna/config/types.h
  14. BIN
      code/espurna/data/index.all.html.gz
  15. BIN
      code/espurna/data/index.light.html.gz
  16. BIN
      code/espurna/data/index.rfbridge.html.gz
  17. BIN
      code/espurna/data/index.rfm69.html.gz
  18. BIN
      code/espurna/data/index.sensor.html.gz
  19. BIN
      code/espurna/data/index.small.html.gz
  20. +60
    -0
      code/espurna/device.ino
  21. +1
    -1
      code/espurna/domoticz.ino
  22. +9
    -0
      code/espurna/eeprom.ino
  23. +156
    -0
      code/espurna/encoder.ino
  24. +19
    -1
      code/espurna/espurna.ino
  25. +19
    -6
      code/espurna/homeassistant.ino
  26. +7
    -5
      code/espurna/influxdb.ino
  27. +345
    -41
      code/espurna/ir.ino
  28. +3
    -5
      code/espurna/led.ino
  29. +90
    -77
      code/espurna/light.ino
  30. +15
    -5
      code/espurna/mqtt.ino
  31. +5
    -4
      code/espurna/nofuss.ino
  32. +4
    -3
      code/espurna/ntp.ino
  33. +3
    -6
      code/espurna/ota.ino
  34. +6
    -4
      code/espurna/relay.ino
  35. +4
    -1
      code/espurna/rfm69.ino
  36. +3
    -3
      code/espurna/scheduler.ino
  37. +36
    -10
      code/espurna/sensor.ino
  38. +4
    -0
      code/espurna/sensors/PMSX003Sensor.h
  39. +173
    -0
      code/espurna/sensors/SDS011Sensor.h
  40. +0
    -2
      code/espurna/sensors/SI7021Sensor.h
  41. +17
    -6
      code/espurna/settings.ino
  42. +3010
    -3002
      code/espurna/static/index.all.html.gz.h
  43. +2966
    -2963
      code/espurna/static/index.light.html.gz.h
  44. +2562
    -2559
      code/espurna/static/index.rfbridge.html.gz.h
  45. +4035
    -4032
      code/espurna/static/index.rfm69.html.gz.h
  46. +2616
    -2608
      code/espurna/static/index.sensor.html.gz.h
  47. +2521
    -2517
      code/espurna/static/index.small.html.gz.h
  48. +41
    -3
      code/espurna/telnet.ino
  49. +1
    -1
      code/espurna/thinkspeak.ino
  50. +109
    -43
      code/espurna/utils.ino
  51. +7
    -2
      code/espurna/wifi.ino
  52. +4
    -16
      code/espurna/ws.ino
  53. +55
    -53
      code/html/custom.js
  54. +21
    -17
      code/html/index.html
  55. +32
    -1
      code/memanalyzer.py
  56. +85
    -7
      code/platformio.ini
  57. BIN
      images/devices/itead-s26.jpg
  58. +1
    -3
      pre-commit

+ 2
- 2
.github/stale.yml View File

@ -23,7 +23,7 @@ staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable # Comment to post when marking as stale. Set to `false` to disable
markComment: > markComment: >
This issue has been automatically marked as stale because it has not had This issue has been automatically marked as stale because it has not had
recent activity. It will be closed in 30 days if no further activity occurs.
recent activity. It will be closed in 7 days if no further activity occurs.
Thank you for your contributions. Thank you for your contributions.
# Comment to post when removing the stale label. # Comment to post when removing the stale label.
@ -32,7 +32,7 @@ markComment: >
# Comment to post when closing a stale Issue or Pull Request. # Comment to post when closing a stale Issue or Pull Request.
closeComment: > closeComment: >
This issue will be auto-closed because there hasn't been any activity for a few months. Feel free to open a new one if you still experience this problem.
This issue will be auto-closed because there hasn't been any activity for two months. Feel free to open a new one if you still experience this problem.
# Limit the number of actions per hour, from 1-30. Default is 30 # Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30 limitPerRun: 30


+ 11
- 7
README.md View File

@ -4,9 +4,9 @@ ESPurna ("spark" in Catalan) is a custom firmware for ESP8285/ESP8266 based smar
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries. It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
[![version](https://img.shields.io/badge/version-2.0.0RC1-brightgreen.svg)](CHANGELOG.md) [![version](https://img.shields.io/badge/version-2.0.0RC1-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-softcfg-orange.svg)](https://github.com/xoseperez/espurna/tree/softcfg/)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=softcfg)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/softcfg.svg)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![branch](https://img.shields.io/badge/branch-v2-orange.svg)](https://github.com/xoseperez/espurna/tree/v2/)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=v2)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/v2.svg)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE) [![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE)
<br /> <br />
[![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=xose%2eperez%40gmail%2ecom&lc=US&no_note=0&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHostedGuest) [![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=xose%2eperez%40gmail%2ecom&lc=US&no_note=0&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHostedGuest)
@ -17,12 +17,14 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
## Contributors ## Contributors
**Without your help this project would not be possible**. I (@xoseperez) simply can't spend all the time I wish on ESPurna but luckly I recieve a lot of contributions, bug fixes, enhancement suggestions,... from people all around the world. I would like to thank you each and every one of you. The [contributors](https://github.com/xoseperez/espurna/graphs/contributors) page shows the ones that have done a PR in the past, but I also get contributions in the issues, by email or via the [gitter ESPurna channel](https://gitter.im/tinkerman-cat/espurna), those I also want to thank.
**Without your help this project would not be possible**. I (@xoseperez) simply can't spend all the time I wish on ESPurna but luckly I recieve a lot of contributions, bug fixes, enhancement suggestions,... from people all around the world. I would like to thank each and every one of you. The [contributors](https://github.com/xoseperez/espurna/graphs/contributors) page shows the ones that have done a PR in the past, but I also get contributions in the issues, by email or via the [gitter ESPurna channel](https://gitter.im/tinkerman-cat/espurna), those I also want to thank.
**Thank you all very much**. **Thank you all very much**.
## Notice ## Notice
> Ladies and gentlemen in the embedded world, use [PlatformIO](https://platformio.org/). If I could offer you only one tip for the future, [PlatformIO](https://platformio.org/) would be it.
> Please use the [gitter ESPurna channel](https://gitter.im/tinkerman-cat/espurna) for support and questions, you have better chances to get fast answers from me or other ESPurna users. Open an issue here only if you feel there is a bug or you want to request an enhancement. Thank you. > Please use the [gitter ESPurna channel](https://gitter.im/tinkerman-cat/espurna) for support and questions, you have better chances to get fast answers from me or other ESPurna users. Open an issue here only if you feel there is a bug or you want to request an enhancement. Thank you.
## Features ## Features
@ -222,21 +224,23 @@ Here is the list of supported hardware. For more information please refer to the
|![Itead Sonoff 4CH Pro](images/devices/itead-sonoff-4ch-pro.jpg)||| |![Itead Sonoff 4CH Pro](images/devices/itead-sonoff-4ch-pro.jpg)|||
|**Itead Sonoff 4CH Pro**||| |**Itead Sonoff 4CH Pro**|||
|![Itead Sonoff S31](images/devices/itead-sonoff-s31.jpg)|![BlitzWolf BW-SPP2](images/devices/blitzwolf-bw-shp2.jpg)|![Power meters based on V9261F](images/devices/generic-v9261f.jpg)| |![Itead Sonoff S31](images/devices/itead-sonoff-s31.jpg)|![BlitzWolf BW-SPP2](images/devices/blitzwolf-bw-shp2.jpg)|![Power meters based on V9261F](images/devices/generic-v9261f.jpg)|
|**Itead Sonoff S31**|**Blitzwolf BW-SHP2<br />(also by HomeCube, Coosa, Goosund)**|**Power meters based on V9261F**|
|**Itead Sonoff S31**|**Blitzwolf BW-SHP2<br />(also by Coosa, Goosund, HomeCube, Teckin)**|**Power meters based on V9261F**|
|![Itead Sonoff POW](images/devices/itead-sonoff-pow.jpg)|![Itead Sonoff POW](images/devices/itead-sonoff-pow-r2.jpg)|![Vanzavanzu Smart WiFi Plug Mini](images/devices/vanzavanzu-smart-wifi-plug-mini.jpg)| |![Itead Sonoff POW](images/devices/itead-sonoff-pow.jpg)|![Itead Sonoff POW](images/devices/itead-sonoff-pow-r2.jpg)|![Vanzavanzu Smart WiFi Plug Mini](images/devices/vanzavanzu-smart-wifi-plug-mini.jpg)|
|**Itead Sonoff POW**|**Itead Sonoff POW R2**|**Vanzavanzu Smart WiFi Plug Mini**| |**Itead Sonoff POW**|**Itead Sonoff POW R2**|**Vanzavanzu Smart WiFi Plug Mini**|
|![Itead Sonoff Basic](images/devices/itead-sonoff-basic.jpg)|![Itead Sonoff Dual/Dual R2](images/devices/itead-sonoff-dual.jpg)|![Itead Sonoff TH10/TH16](images/devices/itead-sonoff-th.jpg)| |![Itead Sonoff Basic](images/devices/itead-sonoff-basic.jpg)|![Itead Sonoff Dual/Dual R2](images/devices/itead-sonoff-dual.jpg)|![Itead Sonoff TH10/TH16](images/devices/itead-sonoff-th.jpg)|
|**Itead Sonoff Basic**|**Itead Sonoff Dual/Dual R2**|**Itead Sonoff TH10/TH16**| |**Itead Sonoff Basic**|**Itead Sonoff Dual/Dual R2**|**Itead Sonoff TH10/TH16**|
|![Electrodragon WiFi IOT](images/devices/electrodragon-wifi-iot.jpg)|![OpenEnergyMonitor WiFi MQTT Relay / Thermostat](images/devices/openenergymonitor-mqtt-relay.jpg)|| |![Electrodragon WiFi IOT](images/devices/electrodragon-wifi-iot.jpg)|![OpenEnergyMonitor WiFi MQTT Relay / Thermostat](images/devices/openenergymonitor-mqtt-relay.jpg)||
|**Electrodragon WiFi IOT**|**OpenEnergyMonitor WiFi MQTT Relay / Thermostat**|| |**Electrodragon WiFi IOT**|**OpenEnergyMonitor WiFi MQTT Relay / Thermostat**||
|![Itead S20](images/devices/itead-s20.jpg)|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg)|![Neo Coolcam NAS WR01W](images/devices/neo-coolcam-wifi.jpg)|
|**Itead S20**|**WorkChoice EcoPlug**|**Neo Coolcam NAS WR01W**|
|![Itead S20](images/devices/itead-s20.jpg)|![Itead S20](images/devices/itead-s26.jpg)|![Neo Coolcam NAS WR01W](images/devices/neo-coolcam-wifi.jpg)|
|**Itead S20**|**Itead S26**|**Neo Coolcam NAS WR01W**|
|![Schuko Wifi Plug](images/devices/schuko-wifi-plug.jpg)|![KMC 70011](images/devices/kmc-70011.jpg)|![Xenon SM-PW702U](images/devices/xenon-sm-pw702u.jpg)| |![Schuko Wifi Plug](images/devices/schuko-wifi-plug.jpg)|![KMC 70011](images/devices/kmc-70011.jpg)|![Xenon SM-PW702U](images/devices/xenon-sm-pw702u.jpg)|
|**Schuko Wifi Plug**|**KMC 70011**|**Xenon SM-PW702U**| |**Schuko Wifi Plug**|**KMC 70011**|**Xenon SM-PW702U**|
|![Maxcio W-US002S](images/devices/maxcio-w-us002s.jpg)|![HEYGO HY02](images/devices/heygo-hy02.jpg)|![YiDian XS-SSA05](images/devices/yidian-xs-ssa05.jpg)| |![Maxcio W-US002S](images/devices/maxcio-w-us002s.jpg)|![HEYGO HY02](images/devices/heygo-hy02.jpg)|![YiDian XS-SSA05](images/devices/yidian-xs-ssa05.jpg)|
|**Maxcio W-US002S**|**HEYGO HY02**|**YiDian XS-SSA05**| |**Maxcio W-US002S**|**HEYGO HY02**|**YiDian XS-SSA05**|
|![WiOn 50055](images/devices/wion-50055.jpg)|![LINGAN SWA1](images/devices/lingan-swa1.jpg)|![HomeCube 16A](images/devices/homecube-16a.jpg)| |![WiOn 50055](images/devices/wion-50055.jpg)|![LINGAN SWA1](images/devices/lingan-swa1.jpg)|![HomeCube 16A](images/devices/homecube-16a.jpg)|
|**WiOn 50055**|**LINGAN SWA1**|**HomeCube 16A**| |**WiOn 50055**|**LINGAN SWA1**|**HomeCube 16A**|
|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg)|||
|**WorkChoice EcoPlug**|||
|![Tonbux PowerStrip02](images/devices/tonbux-powerstrip02.jpg)|![ForNorm Power Strip](images/devices/fornorm-power-strip.jpg)|![Zhilde ZLD-EU55-W](images/devices/zhilde-zld-eu55-w.jpg)| |![Tonbux PowerStrip02](images/devices/tonbux-powerstrip02.jpg)|![ForNorm Power Strip](images/devices/fornorm-power-strip.jpg)|![Zhilde ZLD-EU55-W](images/devices/zhilde-zld-eu55-w.jpg)|
|**Tonbux PowerStrip02**|**Fornorm Power Strip**|**Zhilde ZLD-EU55-W**| |**Tonbux PowerStrip02**|**Fornorm Power Strip**|**Zhilde ZLD-EU55-W**|
|![Itead Sonoff Touch](images/devices/itead-sonoff-touch.jpg)|![Itead Sonoff T1](images/devices/itead-sonoff-t1.jpg)|![YJZK switch](images/devices/yjzk-2gang-switch.jpg)| |![Itead Sonoff Touch](images/devices/itead-sonoff-touch.jpg)|![Itead Sonoff T1](images/devices/itead-sonoff-t1.jpg)|![YJZK switch](images/devices/yjzk-2gang-switch.jpg)|


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

@ -100,7 +100,6 @@ void alexaSetup() {
// Websockets // Websockets
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_alexaWebSocketOnSend); wsOnSendRegister(_alexaWebSocketOnSend);
wsOnAfterParseRegister(_alexaConfigure);
#endif #endif
// Register wifi callback // Register wifi callback
@ -113,8 +112,9 @@ void alexaSetup() {
// Settings // Settings
settingsRegisterKeyCheck(_alexaKeyCheck); settingsRegisterKeyCheck(_alexaKeyCheck);
// Register loop
// Register main callbacks
espurnaRegisterLoop(alexaLoop); espurnaRegisterLoop(alexaLoop);
espurnaRegisterReload(_alexaConfigure);
} }


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

@ -255,12 +255,12 @@ void buttonSetup() {
// Websocket Callbacks // Websocket Callbacks
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_buttonWebSocketOnSend); wsOnSendRegister(_buttonWebSocketOnSend);
wsOnAfterParseRegister(_buttonConfigure);
#endif #endif
settingsRegisterKeyCheck(_buttonKeyCheck); settingsRegisterKeyCheck(_buttonKeyCheck);
// Register loop // Register loop
espurnaRegisterReload(_buttonConfigure);
espurnaRegisterLoop(_buttonLoop); espurnaRegisterLoop(_buttonLoop);
} }


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

@ -30,11 +30,11 @@
#include "defaults.h" #include "defaults.h"
#include "general.h" #include "general.h"
#include "dependencies.h" #include "dependencies.h"
#include "debug.h"
#include "prototypes.h" #include "prototypes.h"
#include "sensors.h" #include "sensors.h"
#include "webui.h" #include "webui.h"
#include "progmem.h" #include "progmem.h"
#include "debug.h"
#ifdef USE_CORE_VERSION_H #ifdef USE_CORE_VERSION_H
#include "core_version.h" #include "core_version.h"


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

@ -115,6 +115,9 @@
//#define LOHAS_9W //#define LOHAS_9W
//#define YJZK_SWITCH_1CH //#define YJZK_SWITCH_1CH
//#define YJZK_SWITCH_3CH //#define YJZK_SWITCH_3CH
//#define XIAOMI_SMART_DESK_LAMP
//#define ALLTERCO_SHELLY2
//#define PHYX_ESP12_RGB
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Features (values below are non-default values) // Features (values below are non-default values)
@ -129,6 +132,7 @@
//#define DEBUG_UDP_SUPPORT 1 //#define DEBUG_UDP_SUPPORT 1
//#define DEBUG_WEB_SUPPORT 0 //#define DEBUG_WEB_SUPPORT 0
//#define DOMOTICZ_SUPPORT 0 //#define DOMOTICZ_SUPPORT 0
//#define ENCODER_SUPPORT 1
//#define HOMEASSISTANT_SUPPORT 0 //#define HOMEASSISTANT_SUPPORT 0
//#define I2C_SUPPORT 1 //#define I2C_SUPPORT 1
//#define INFLUXDB_SUPPORT 1 //#define INFLUXDB_SUPPORT 1
@ -177,6 +181,8 @@
//#define NTC_SUPPORT 1 //#define NTC_SUPPORT 1
//#define PMSX003_SUPPORT 1 //#define PMSX003_SUPPORT 1
//#define PZEM004T_SUPPORT 1 //#define PZEM004T_SUPPORT 1
//#define SDS011_SUPPORT 1
//#define SENSEAIR_SUPPORT 1
//#define SHT3X_I2C_SUPPORT 1 //#define SHT3X_I2C_SUPPORT 1
//#define SI7021_SUPPORT 1 //#define SI7021_SUPPORT 1
//#define SONAR_SUPPORT 1 //#define SONAR_SUPPORT 1


+ 138
- 7
code/espurna/config/defaults.h View File

@ -208,6 +208,138 @@
#define BUTTON8_RELAY 0 #define BUTTON8_RELAY 0
#endif #endif
// -----------------------------------------------------------------------------
// Encoders
// -----------------------------------------------------------------------------
#ifndef ENCODER1_PIN1
#define ENCODER1_PIN1 GPIO_NONE
#endif
#ifndef ENCODER2_PIN1
#define ENCODER2_PIN1 GPIO_NONE
#endif
#ifndef ENCODER3_PIN1
#define ENCODER3_PIN1 GPIO_NONE
#endif
#ifndef ENCODER4_PIN1
#define ENCODER4_PIN1 GPIO_NONE
#endif
#ifndef ENCODER5_PIN1
#define ENCODER5_PIN1 GPIO_NONE
#endif
#ifndef ENCODER1_PIN2
#define ENCODER1_PIN2 GPIO_NONE
#endif
#ifndef ENCODER2_PIN2
#define ENCODER2_PIN2 GPIO_NONE
#endif
#ifndef ENCODER3_PIN2
#define ENCODER3_PIN2 GPIO_NONE
#endif
#ifndef ENCODER4_PIN2
#define ENCODER4_PIN2 GPIO_NONE
#endif
#ifndef ENCODER5_PIN2
#define ENCODER5_PIN2 GPIO_NONE
#endif
#ifndef ENCODER1_BUTTON_PIN
#define ENCODER1_BUTTON_PIN GPIO_NONE
#endif
#ifndef ENCODER2_BUTTON_PIN
#define ENCODER2_BUTTON_PIN GPIO_NONE
#endif
#ifndef ENCODER3_BUTTON_PIN
#define ENCODER3_BUTTON_PIN GPIO_NONE
#endif
#ifndef ENCODER4_BUTTON_PIN
#define ENCODER4_BUTTON_PIN GPIO_NONE
#endif
#ifndef ENCODER5_BUTTON_PIN
#define ENCODER5_BUTTON_PIN GPIO_NONE
#endif
#ifndef ENCODER1_BUTTON_LOGIC
#define ENCODER1_BUTTON_LOGIC HIGH
#endif
#ifndef ENCODER2_BUTTON_LOGIC
#define ENCODER2_BUTTON_LOGIC HIGH
#endif
#ifndef ENCODER3_BUTTON_LOGIC
#define ENCODER3_BUTTON_LOGIC HIGH
#endif
#ifndef ENCODER4_BUTTON_LOGIC
#define ENCODER4_BUTTON_LOGIC HIGH
#endif
#ifndef ENCODER5_BUTTON_LOGIC
#define ENCODER5_BUTTON_LOGIC HIGH
#endif
#ifndef ENCODER1_BUTTON_MODE
#define ENCODER1_BUTTON_MODE INPUT_PULLUP
#endif
#ifndef ENCODER2_BUTTON_MODE
#define ENCODER2_BUTTON_MODE INPUT_PULLUP
#endif
#ifndef ENCODER3_BUTTON_MODE
#define ENCODER3_BUTTON_MODE INPUT_PULLUP
#endif
#ifndef ENCODER4_BUTTON_MODE
#define ENCODER4_BUTTON_MODE INPUT_PULLUP
#endif
#ifndef ENCODER5_BUTTON_MODE
#define ENCODER5_BUTTON_MODE INPUT_PULLUP
#endif
#ifndef ENCODER1_MODE
#define ENCODER1_MODE 1
#endif
#ifndef ENCODER2_MODE
#define ENCODER2_MODE 1
#endif
#ifndef ENCODER3_MODE
#define ENCODER3_MODE 1
#endif
#ifndef ENCODER4_MODE
#define ENCODER4_MODE 1
#endif
#ifndef ENCODER5_MODE
#define ENCODER5_MODE 1
#endif
#ifndef ENCODER1_CHANNEL1
#define ENCODER1_CHANNEL1 0
#endif
#ifndef ENCODER2_CHANNEL1
#define ENCODER2_CHANNEL1 0
#endif
#ifndef ENCODER3_CHANNEL1
#define ENCODER3_CHANNEL1 0
#endif
#ifndef ENCODER4_CHANNEL1
#define ENCODER4_CHANNEL1 0
#endif
#ifndef ENCODER5_CHANNEL1
#define ENCODER5_CHANNEL1 0
#endif
#ifndef ENCODER1_CHANNEL2
#define ENCODER1_CHANNEL2 1
#endif
#ifndef ENCODER2_CHANNEL2
#define ENCODER2_CHANNEL2 1
#endif
#ifndef ENCODER3_CHANNEL2
#define ENCODER3_CHANNEL2 1
#endif
#ifndef ENCODER4_CHANNEL2
#define ENCODER4_CHANNEL2 1
#endif
#ifndef ENCODER5_CHANNEL2
#define ENCODER5_CHANNEL2 1
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Relays // Relays
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -420,9 +552,13 @@
// General // General
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Default hostname will be ESPURNA-XXXXXX, where XXXXXX is last 3 octets of chipID
// Device name (DNS, SoftAP SSID, ALEXA etc.)
// If empty, default will be ESPURNA-XXXXXX, where XXXXXX is last 3 octets of chipID
// When set, must be 1..31 characters. See:
// https://github.com/xoseperez/espurna/issues/921
// https://github.com/xoseperez/espurna/issues/1151
#ifndef HOSTNAME #ifndef HOSTNAME
#define HOSTNAME ""
#define HOSTNAME ""
#endif #endif
// Relay providers // Relay providers
@ -434,8 +570,3 @@
#ifndef LIGHT_PROVIDER #ifndef LIGHT_PROVIDER
#define LIGHT_PROVIDER LIGHT_PROVIDER_NONE #define LIGHT_PROVIDER LIGHT_PROVIDER_NONE
#endif #endif
// App revision, populated by the build script
#ifndef APP_REVISION
#define APP_REVISION ""
#endif

+ 3
- 0
code/espurna/config/device.h View File

@ -100,6 +100,9 @@ enum devices {
DEVICE_LOHAS_9W, DEVICE_LOHAS_9W,
DEVICE_YJZK_SWITCH_1CH, DEVICE_YJZK_SWITCH_1CH,
DEVICE_YJZK_SWITCH_3CH, DEVICE_YJZK_SWITCH_3CH,
DEVICE_XIAOMI_SMART_DESK_LAMP,
DEVICE_ALLTERCO_SHELLY2,
DEVICE_PHYX_ESP12_RGB,
DEVICE_LAST DEVICE_LAST


+ 85
- 16
code/espurna/config/general.h View File

@ -7,8 +7,13 @@
// GENERAL // GENERAL
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
#define DEVICE_NAME MANUFACTURER "_" DEVICE // Concatenate both to get a unique device name
// When defined, ADMIN_PASS must be 8..63 printable ASCII characters. See:
// https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access#Target_users_(authentication_key_distribution)
// https://github.com/xoseperez/espurna/issues/1151
#ifndef ADMIN_PASS #ifndef ADMIN_PASS
#define ADMIN_PASS "fibonacci" // Default password (WEB, OTA, WIFI)
#define ADMIN_PASS "fibonacci" // Default password (WEB, OTA, WIFI SoftAP)
#endif #endif
#ifndef USE_PASSWORD #ifndef USE_PASSWORD
@ -93,6 +98,10 @@
#define TELNET_STA 0 // By default, disallow connections via STA interface #define TELNET_STA 0 // By default, disallow connections via STA interface
#endif #endif
#ifndef TELNET_PASSWORD
#define TELNET_PASSWORD 1 // Request password to start telnet session by default
#endif
#define TELNET_PORT 23 // Port to listen to telnet clients #define TELNET_PORT 23 // Port to listen to telnet clients
#define TELNET_MAX_CLIENTS 1 // Max number of concurrent telnet clients #define TELNET_MAX_CLIENTS 1 // Max number of concurrent telnet clients
@ -207,6 +216,14 @@
#define BUTTON_LNGLNGCLICK_DELAY 10000 // Time in ms holding the button down to get a long-long click #define BUTTON_LNGLNGCLICK_DELAY 10000 // Time in ms holding the button down to get a long-long click
#endif #endif
//------------------------------------------------------------------------------
// ENCODER
//------------------------------------------------------------------------------
#ifndef ENCODER_SUPPORT
#define ENCODER_SUPPORT 0
#endif
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// LED // LED
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -287,6 +304,9 @@
#define WIFI_AP_CAPTIVE 1 // Captive portal enabled when in AP mode #define WIFI_AP_CAPTIVE 1 // Captive portal enabled when in AP mode
#endif #endif
#ifndef WIFI_FALLBACK_APMODE
#define WIFI_FALLBACK_APMODE 1 // Fallback to AP mode if no STA connection
#endif
#ifndef WIFI_SLEEP_MODE #ifndef WIFI_SLEEP_MODE
#define WIFI_SLEEP_MODE WIFI_NONE_SLEEP // WIFI_NONE_SLEEP, WIFI_LIGHT_SLEEP or WIFI_MODEM_SLEEP #define WIFI_SLEEP_MODE WIFI_NONE_SLEEP // WIFI_NONE_SLEEP, WIFI_LIGHT_SLEEP or WIFI_MODEM_SLEEP
@ -705,7 +725,8 @@
#define MQTT_TOPIC_BOARD "board" #define MQTT_TOPIC_BOARD "board"
#define MQTT_TOPIC_PULSE "pulse" #define MQTT_TOPIC_PULSE "pulse"
#define MQTT_TOPIC_SPEED "speed" #define MQTT_TOPIC_SPEED "speed"
#define MQTT_TOPIC_IR "ir"
#define MQTT_TOPIC_IRIN "irin"
#define MQTT_TOPIC_IROUT "irout"
// Light module // Light module
#define MQTT_TOPIC_CHANNEL "channel" #define MQTT_TOPIC_CHANNEL "channel"
@ -889,6 +910,14 @@
#define HOMEASSISTANT_PAYLOAD_OFF "0" // Payload for OFF and unavailable messages #define HOMEASSISTANT_PAYLOAD_OFF "0" // Payload for OFF and unavailable messages
#endif #endif
#ifndef HOMEASSISTANT_PAYLOAD_AVAILABLE
#define HOMEASSISTANT_PAYLOAD_AVAILABLE "1" // Payload for available messages
#endif
#ifndef HOMEASSISTANT_PAYLOAD_NOT_AVAILABLE
#define HOMEASSISTANT_PAYLOAD_NOT_AVAILABLE "0" // Payload for available messages
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// INFLUXDB // INFLUXDB
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -1046,33 +1075,51 @@
#ifndef RF_RAW_SUPPORT #ifndef RF_RAW_SUPPORT
#define RF_RAW_SUPPORT 0 // RF raw codes require a specific firmware for the EFM8BB1 #define RF_RAW_SUPPORT 0 // RF raw codes require a specific firmware for the EFM8BB1
// https://github.com/rhx/RF-Bridge-EFM8BB1
// https://github.com/rhx/RF-Bridge-EFM8BB1
#endif #endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// IR
// IR Bridge
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#ifndef IR_SUPPORT #ifndef IR_SUPPORT
#define IR_SUPPORT 0 // Do not build with IR support by default (10.25Kb) #define IR_SUPPORT 0 // Do not build with IR support by default (10.25Kb)
#endif #endif
#ifndef IR_RECEIVER_PIN
#define IR_RECEIVER_PIN 4 // IR LED
//#define IR_RX_PIN 5 // GPIO the receiver is connected to
//#define IR_TX_PIN 4 // GPIO the transmitter is connected to
#ifndef IR_USE_RAW
#define IR_USE_RAW 0 // Use raw codes
#endif #endif
// 24 Buttons Set of the IR Remote
#ifndef IR_BUTTON_SET
#define IR_BUTTON_SET 1 // IR button set to use (see below)
#ifndef IR_BUFFER_SIZE
#define IR_BUFFER_SIZE 1024
#endif
#ifndef IR_TIMEOUT
#define IR_TIMEOUT 15U
#endif
#ifndef IR_REPEAT
#define IR_REPEAT 1
#endif
#ifndef IR_DELAY
#define IR_DELAY 100
#endif #endif
#ifndef IR_DEBOUNCE #ifndef IR_DEBOUNCE
#define IR_DEBOUNCE 500 // IR debounce time in milliseconds #define IR_DEBOUNCE 500 // IR debounce time in milliseconds
#endif #endif
//Remote Buttons SET 1 (for the original Remote shipped with the controller)
#if IR_SUPPORT
#ifndef IR_BUTTON_SET
#define IR_BUTTON_SET 0 // IR button set to use (see below)
#endif
// -----------------------------------------------------------------------------
// Remote Buttons SET 1 (for the original Remote shipped with the controller)
#if IR_BUTTON_SET == 1 #if IR_BUTTON_SET == 1
/* /*
@ -1218,7 +1265,29 @@
//{ 0xE0E08877, IR_BUTTON_MODE_TOGGLE, 9 } //Extra Button //{ 0xE0E08877, IR_BUTTON_MODE_TOGGLE, 9 } //Extra Button
}; };
#endif #endif
#endif // IR_SUPPORT
//Remote Buttons SET 4
#if IR_BUTTON_SET == 4
/*
+------+------+------+
| OFF | SRC | MUTE |
+------+------+------+
...
+------+------+------+
*/
#define IR_BUTTON_COUNT 1
const unsigned long IR_BUTTON[IR_BUTTON_COUNT][3] PROGMEM = {
{ 0xFFB24D, IR_BUTTON_MODE_TOGGLE, 0 } // Toggle Relay #0
};
#endif
#ifndef IR_BUTTON_COUNT
#define IR_BUTTON_COUNT 0
#endif
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Custom RF module // Custom RF module
@ -1257,11 +1326,11 @@
#endif #endif
#ifndef RFM69_NODE_ID #ifndef RFM69_NODE_ID
#define RFM69_NODE_ID 2
#define RFM69_NODE_ID 1
#endif #endif
#ifndef RFM69_GATEWAY_ID #ifndef RFM69_GATEWAY_ID
#define RFM69_GATEWAY_ID 2
#define RFM69_GATEWAY_ID 1
#endif #endif
#ifndef RFM69_NETWORK_ID #ifndef RFM69_NETWORK_ID
@ -1269,7 +1338,7 @@
#endif #endif
#ifndef RFM69_PROMISCUOUS #ifndef RFM69_PROMISCUOUS
#define RFM69_PROMISCUOUS 1
#define RFM69_PROMISCUOUS 0
#endif #endif
#ifndef RFM69_PROMISCUOUS_SENDS #ifndef RFM69_PROMISCUOUS_SENDS


+ 6
- 0
code/espurna/config/progmem.h View File

@ -55,6 +55,9 @@ PROGMEM const char espurna_modules[] =
#if DOMOTICZ_SUPPORT #if DOMOTICZ_SUPPORT
"DOMOTICZ " "DOMOTICZ "
#endif #endif
#if ENCODER_SUPPORT
"ENCODER "
#endif
#if HOMEASSISTANT_SUPPORT #if HOMEASSISTANT_SUPPORT
"HOMEASSISTANT " "HOMEASSISTANT "
#endif #endif
@ -193,6 +196,9 @@ PROGMEM const char espurna_sensors[] =
#if PZEM004T_SUPPORT #if PZEM004T_SUPPORT
"PZEM004T " "PZEM004T "
#endif #endif
#if SDS011_SUPPORT
"SDS011 "
#endif
#if SENSEAIR_SUPPORT #if SENSEAIR_SUPPORT
"SENSEAIR " "SENSEAIR "
#endif #endif


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

@ -31,6 +31,9 @@ extern "C" {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void debugSend(const char * format, ...); void debugSend(const char * format, ...);
void debugSend_P(PGM_P format, ...); void debugSend_P(PGM_P format, ...);
extern "C" {
void custom_crash_callback(struct rst_info*, uint32_t, uint32_t);
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Domoticz // Domoticz
@ -154,12 +157,9 @@ void webRequestRegister(web_request_callback_f callback);
void wsSend(ws_on_send_callback_f sender); void wsSend(ws_on_send_callback_f sender);
typedef std::function<void(uint32_t, const char *, JsonObject&)> ws_on_action_callback_f; typedef std::function<void(uint32_t, const char *, JsonObject&)> ws_on_action_callback_f;
void wsOnActionRegister(ws_on_action_callback_f callback); void wsOnActionRegister(ws_on_action_callback_f callback);
typedef std::function<void(void)> ws_on_after_parse_callback_f;
void wsOnAfterParseRegister(ws_on_after_parse_callback_f callback);
#else #else
#define ws_on_send_callback_f void * #define ws_on_send_callback_f void *
#define ws_on_action_callback_f void * #define ws_on_action_callback_f void *
#define ws_on_after_parse_callback_f void *
#define ws_on_receive_callback_f void * #define ws_on_receive_callback_f void *
#endif #endif


+ 29
- 18
code/espurna/config/sensors.h View File

@ -5,13 +5,13 @@
#define SENSOR_DEBUG 0 // Debug sensors #define SENSOR_DEBUG 0 // Debug sensors
#define SENSOR_READ_INTERVAL 6 // Read data from sensors every 6 seconds #define SENSOR_READ_INTERVAL 6 // Read data from sensors every 6 seconds
#define SENSOR_READ_MIN_INTERVAL 6 // Minimum read interval
#define SENSOR_READ_MIN_INTERVAL 1 // Minimum read interval
#define SENSOR_READ_MAX_INTERVAL 3600 // Maximum 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_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_EVERY 10 // Report every this many readings
#define SENSOR_REPORT_MIN_EVERY 1 // Minimum every value #define SENSOR_REPORT_MIN_EVERY 1 // Minimum every value
#define SENSOR_REPORT_MAX_EVERY 12 // Maximum
#define SENSOR_REPORT_MAX_EVERY 60 // Maximum
#define SENSOR_USE_INDEX 0 // Use the index in topic (i.e. temperature/0) #define SENSOR_USE_INDEX 0 // Use the index in topic (i.e. temperature/0)
// even if just one sensor (0 for backwards compatibility) // even if just one sensor (0 for backwards compatibility)
@ -36,6 +36,10 @@
#define HUMIDITY_MIN_CHANGE 0 // Minimum humidity change to report #define HUMIDITY_MIN_CHANGE 0 // Minimum humidity change to report
#endif #endif
#ifndef ENERGY_MAX_CHANGE
#define ENERGY_MAX_CHANGE 0 // Maximum energy change to report (if >0 it will allways report when delta(E) is greater than this)
#endif
#ifndef SENSOR_SAVE_EVERY #ifndef SENSOR_SAVE_EVERY
#define SENSOR_SAVE_EVERY 0 // Save accumulating values to EEPROM (atm only energy) #define SENSOR_SAVE_EVERY 0 // Save accumulating values to EEPROM (atm only energy)
// A 0 means do not save and it's the default value // A 0 means do not save and it's the default value
@ -467,6 +471,23 @@
#define NTC_BETA 3977 // Beta coeficient #define NTC_BETA 3977 // Beta coeficient
#endif #endif
//------------------------------------------------------------------------------
// SDS011 particulates sensor
// Enable support by passing SDS011_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef SDS011_SUPPORT
#define SDS011_SUPPORT 0
#endif
#ifndef SDS011_RX_PIN
#define SDS011_RX_PIN 14
#endif
#ifndef SDS011_TX_PIN
#define SDS011_TX_PIN 12
#endif
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// SenseAir CO2 sensor // SenseAir CO2 sensor
// Enable support by passing SENSEAIR_SUPPORT=1 build flag // Enable support by passing SENSEAIR_SUPPORT=1 build flag
@ -659,6 +680,7 @@
HLW8012_SUPPORT || \ HLW8012_SUPPORT || \
MHZ19_SUPPORT || \ MHZ19_SUPPORT || \
NTC_SUPPORT || \ NTC_SUPPORT || \
SDS011_SUPPORT || \
SENSEAIR_SUPPORT || \ SENSEAIR_SUPPORT || \
PMSX003_SUPPORT || \ PMSX003_SUPPORT || \
PZEM004T_SUPPORT || \ PZEM004T_SUPPORT || \
@ -708,12 +730,6 @@
#if SENSOR_SUPPORT #if SENSOR_SUPPORT
#if SENSOR_DEBUG
#include "../config/debug.h"
#endif
#include "../sensors/BaseSensor.h"
#if AM2320_SUPPORT #if AM2320_SUPPORT
#include "../sensors/AM2320Sensor.h" #include "../sensors/AM2320Sensor.h"
#endif #endif
@ -731,12 +747,10 @@
#endif #endif
#if CSE7766_SUPPORT #if CSE7766_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/CSE7766Sensor.h" #include "../sensors/CSE7766Sensor.h"
#endif #endif
#if DALLAS_SUPPORT #if DALLAS_SUPPORT
#include <OneWire.h>
#include "../sensors/DallasSensor.h" #include "../sensors/DallasSensor.h"
#endif #endif
@ -769,7 +783,7 @@
#endif #endif
#if GEIGER_SUPPORT #if GEIGER_SUPPORT
#include "../sensors/GeigerSensor.h" // The main file for geiger counting module
#include "../sensors/GeigerSensor.h"
#endif #endif
#if GUVAS12SD_SUPPORT #if GUVAS12SD_SUPPORT
@ -777,32 +791,30 @@
#endif #endif
#if HLW8012_SUPPORT #if HLW8012_SUPPORT
#include <HLW8012.h>
#include "../sensors/HLW8012Sensor.h" #include "../sensors/HLW8012Sensor.h"
#endif #endif
#if MHZ19_SUPPORT #if MHZ19_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/MHZ19Sensor.h" #include "../sensors/MHZ19Sensor.h"
#endif #endif
#if NTC_SUPPORT #if NTC_SUPPORT
#include "../sensors/AnalogSensor.h"
#include "../sensors/NTCSensor.h" #include "../sensors/NTCSensor.h"
#endif #endif
#if SDS011_SUPPORT
#include "../sensors/SDS011Sensor.h"
#endif
#if SENSEAIR_SUPPORT #if SENSEAIR_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/SenseAirSensor.h" #include "../sensors/SenseAirSensor.h"
#endif #endif
#if PMSX003_SUPPORT #if PMSX003_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/PMSX003Sensor.h" #include "../sensors/PMSX003Sensor.h"
#endif #endif
#if PZEM004T_SUPPORT #if PZEM004T_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/PZEM004TSensor.h" #include "../sensors/PZEM004TSensor.h"
#endif #endif
@ -823,7 +835,6 @@
#endif #endif
#if V9261F_SUPPORT #if V9261F_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/V9261FSensor.h" #include "../sensors/V9261FSensor.h"
#endif #endif


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

@ -77,6 +77,13 @@
#define BUTTON_SET_PULLUP 4 #define BUTTON_SET_PULLUP 4
#endif #endif
//------------------------------------------------------------------------------
// ENCODER
//------------------------------------------------------------------------------
#define ENCODER_MODE_CHANNEL 0
#define ENCODER_MODE_RATIO 1
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// RELAY // RELAY
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -300,6 +307,7 @@
#define SENSOR_SENSEAIR_ID 0x24 #define SENSOR_SENSEAIR_ID 0x24
#define SENSOR_GEIGER_ID 0x25 #define SENSOR_GEIGER_ID 0x25
#define SENSOR_NTC_ID 0x26 #define SENSOR_NTC_ID 0x26
#define SENSOR_SDS011_ID 0x27
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Magnitudes // Magnitudes


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


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


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


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


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


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


+ 60
- 0
code/espurna/device.ino View File

@ -2046,6 +2046,66 @@ void _deviceLoad() {
setSetting("rlyGPIO", 0, 4); setSetting("rlyGPIO", 0, 4);
setSetting("rlyType", 0, RELAY_TYPE_NORMAL); setSetting("rlyType", 0, RELAY_TYPE_NORMAL);
#elif defined(XIAOMI_SMART_DESK_LAMP)
setSetting("board", DEVICE_XIAOMI_SMART_DESK_LAMP);
setSetting("device", "XIAOMI_SMART_DESK_LAMP");
setSetting("fw", ESPURNA_DIMMER);
setSetting("btnGPIO", 0, 2);
setSetting("btnGPIO", 1, 14);
setSetting("btnRelay", 0, 0);
setSetting("btnLngDelay", 500);
setSetting("btnDblClick", 0, BUTTON_MODE_NONE);
setSetting("btnLngClick", 0, BUTTON_MODE_NONE);
setSetting("btnLngLngClick", 0, BUTTON_MODE_NONE);
setSetting("btnDblClick", 1, BUTTON_MODE_AP);
setSetting("btnLngLngClick", 1, BUTTON_MODE_RESET);
setSetting("rlyProvider", RELAY_PROVIDER_LIGHT);
setSetting("litChGPIO", 0, 5);
setSetting("litChGPIO", 1, 4);
setSetting("litChLogic", 0, 0);
setSetting("litChLogic", 1, 0);
setSetting("enc1stGPIO", 0, 12);
setSetting("enc2ndGPIO", 0, 13);
setSetting("encBtnGPIO", 0, 2);
setSetting("encMode", ENCODER_MODE_RATIO);
#elif defined(ALLTERCO_SHELLY2)
setSetting("board", DEVICE_ALLTERCO_SHELLY2);
setSetting("device", "ALLTERCO_SHELLY2");
setSetting("fw", ESPURNA_BASIC);
setSetting("btnGPIO", 0, 12);
setSetting("btnGPIO", 1, 14);
setSetting("btnMode", 0, BUTTON_SWITCH);
setSetting("btnMode", 1, BUTTON_SWITCH);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("rlyGPIO", 0, 4);
setSetting("rlyGPIO", 1, 5);
setSetting("rlyType", 0, RELAY_TYPE_NORMAL);
setSetting("rlyType", 1, RELAY_TYPE_NORMAL);
#elif defined(PHYX_ESP12_RGB)
setSetting("board", DEVICE_PHYX_ESP12_RGB);
setSetting("device", "PHYX_ESP12_RGB");
setSetting("fw", ESPURNA_DIMMER);
setSetting("rlyProvider", RELAY_PROVIDER_LIGHT);
setSetting("litChGPIO", 0, 4);
setSetting("litChGPIO", 1, 14);
setSetting("litChGPIO", 2, 12);
setSetting("litChLogic", 0, 0);
setSetting("litChLogic", 1, 0);
setSetting("litChLogic", 3, 0);
#endif #endif
} }


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

@ -162,10 +162,10 @@ void domoticzSetup() {
_domoticzConfigure(); _domoticzConfigure();
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_domoticzWebSocketOnSend); wsOnSendRegister(_domoticzWebSocketOnSend);
wsOnAfterParseRegister(_domoticzConfigure);
#endif #endif
settingsRegisterKeyCheck(_domoticzKeyCheck); settingsRegisterKeyCheck(_domoticzKeyCheck);
mqttRegister(_domoticzMqtt); mqttRegister(_domoticzMqtt);
espurnaRegisterReload(_domoticzConfigure);
} }
bool domoticzEnabled() { bool domoticzEnabled() {


+ 9
- 0
code/espurna/eeprom.ino View File

@ -25,6 +25,10 @@ void eepromRotate(bool value) {
} }
} }
uint32_t eepromCurrent() {
return EEPROMr.current();
}
String eepromSectors() { String eepromSectors() {
String response; String response;
for (uint32_t i = 0; i < EEPROMr.size(); i++) { for (uint32_t i = 0; i < EEPROMr.size(); i++) {
@ -38,6 +42,11 @@ String eepromSectors() {
void _eepromInitCommands() { void _eepromInitCommands() {
settingsRegisterCommand(F("EEPROM"), [](Embedis* e) {
infoMemory("EEPROM", SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE - settingsSize());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) { settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) {
EEPROMr.dump(settingsSerial()); EEPROMr.dump(settingsSerial());
DEBUG_MSG_P(PSTR("\n+OK\n")); DEBUG_MSG_P(PSTR("\n+OK\n"));


+ 156
- 0
code/espurna/encoder.ino View File

@ -0,0 +1,156 @@
/*
ENCODER MODULE
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if ENCODER_SUPPORT && (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE)
#include <Encoder.h>
#include <vector>
typedef struct {
Encoder * encoder;
unsigned char button_pin;
unsigned char button_logic;
unsigned char button_mode;
unsigned char mode;
unsigned char channel1; // default
unsigned char channel2; // only if button defined and pressed
} encoder_t;
std::vector<encoder_t> _encoders;
void _encoderConfigure() {
// Clean previous encoders
for (unsigned char i=0; i<_encoders.size(); i++) {
free(_encoders[i].encoder);
}
_encoders.clear();
// Load encoders
#if (ENCODER1_PIN1 != GPIO_NONE) && (ENCODER1_PIN2 != GPIO_NONE)
{
_encoders.push_back({
new Encoder(ENCODER1_PIN1, ENCODER1_PIN2),
ENCODER1_BUTTON_PIN, ENCODER1_BUTTON_LOGIC, ENCODER1_BUTTON_MODE, ENCODER1_MODE,
ENCODER1_CHANNEL1, ENCODER1_CHANNEL2
});
}
#endif
#if (ENCODER2_PIN1 != GPIO_NONE) && (ENCODER2_PIN2 != GPIO_NONE)
{
_encoders.push_back({
new Encoder(ENCODER2_PIN1, ENCODER2_PIN2),
ENCODER2_BUTTON_PIN, ENCODER2_BUTTON_LOGIC, ENCODER2_BUTTON_MODE, ENCODER2_MODE,
ENCODER2_CHANNEL1, ENCODER2_CHANNEL2
});
}
#endif
#if (ENCODER3_PIN1 != GPIO_NONE) && (ENCODER3_PIN2 != GPIO_NONE)
{
_encoders.push_back({
new Encoder(ENCODER3_PIN1, ENCODER3_PIN2),
ENCODER3_BUTTON_PIN, ENCODER3_BUTTON_LOGIC, ENCODER3_BUTTON_MODE, ENCODER3_MODE,
ENCODER3_CHANNEL1, ENCODER3_CHANNEL2
});
}
#endif
#if (ENCODER4_PIN1 != GPIO_NONE) && (ENCODER4_PIN2 != GPIO_NONE)
{
_encoders.push_back({
new Encoder(ENCODER4_PIN1, ENCODER4_PIN2),
ENCODER4_BUTTON_PIN, ENCODER4_BUTTON_LOGIC, ENCODER4_BUTTON_MODE, ENCODER4_MODE,
ENCODER4_CHANNEL1, ENCODER4_CHANNEL2
});
}
#endif
#if (ENCODER5_PIN1 != GPIO_NONE) && (ENCODER5_PIN2 != GPIO_NONE)
{
_encoders.push_back({
new Encoder(ENCODER5_PIN1, ENCODER5_PIN2),
ENCODER5_BUTTON_PIN, ENCODER5_BUTTON_LOGIC, ENCODER5_BUTTON_MODE, ENCODER5_MODE,
ENCODER5_CHANNEL1, ENCODER5_CHANNEL2
});
}
#endif
// Setup encoders
for (unsigned char i=0; i<_encoders.size(); i++) {
if (GPIO_NONE != _encoders[i].button_pin) {
pinMode(_encoders[i].button_pin, _encoders[i].button_mode);
}
}
}
void _encoderLoop() {
// for each encoder
for (unsigned char i=0; i<_encoders.size(); i++) {
// get encoder
encoder_t encoder = _encoders[i];
// read encoder
long delta = encoder.encoder->read();
encoder.encoder->write(0);
if (0 == delta) continue;
DEBUG_MSG_P(PSTR("[ENCODER] Delta: %d\n"), delta);
// action
if (encoder.button_pin == GPIO_NONE) {
// if there is no button, the encoder driver the CHANNEL1
lightChannelStep(encoder.channel1, delta);
} else {
// check if button is pressed
bool pressed = (digitalRead(encoder.button_pin) != encoder.button_logic);
if (ENCODER_MODE_CHANNEL == encoder.mode) {
// the button controls what channel we are changing
lightChannelStep(pressed ? encoder.channel2 : encoder.channel1, delta);
} if (ENCODER_MODE_RATIO == encoder.mode) {
// the button controls if we are changing the channel ratio or the overall brightness
if (pressed) {
lightChannelStep(encoder.channel1, delta);
lightChannelStep(encoder.channel2, -delta);
} else {
lightBrightnessStep(delta);
}
}
}
lightUpdate(true, true);
}
}
// -----------------------------------------------------------------------------
void encoderSetup() {
// Configure encoders
_encoderConfigure();
// Main callbacks
espurnaRegisterLoop(_encoderLoop);
espurnaRegisterReload(_encoderConfigure);
DEBUG_MSG_P(PSTR("[ENCODER] Number of encoders: %u\n"), _encoders.size());
}
#endif // ENCODER_SUPPORT && (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE)

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

@ -25,15 +25,26 @@ Module key prefix: esp (shared with others)
#include <vector> #include <vector>
std::vector<void (*)()> _loop_callbacks; std::vector<void (*)()> _loop_callbacks;
std::vector<void (*)()> _reload_callbacks;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// REGISTER
// GENERAL CALLBACKS
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void espurnaRegisterLoop(void (*callback)()) { void espurnaRegisterLoop(void (*callback)()) {
_loop_callbacks.push_back(callback); _loop_callbacks.push_back(callback);
} }
void espurnaRegisterReload(void (*callback)()) {
_reload_callbacks.push_back(callback);
}
void espurnaReload() {
for (unsigned char i = 0; i < _reload_callbacks.size(); i++) {
(_reload_callbacks[i])();
}
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// BOOTING // BOOTING
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -50,6 +61,9 @@ void setup() {
// Init persistance and terminal features // Init persistance and terminal features
settingsSetup(); settingsSetup();
// Cache initial free heap value
getInitialFreeHeap();
// Serial debug // Serial debug
// Requires SETTINGS // Requires SETTINGS
#if DEBUG_SUPPORT #if DEBUG_SUPPORT
@ -106,9 +120,13 @@ void setup() {
#if BUTTON_SUPPORT #if BUTTON_SUPPORT
buttonSetup(); buttonSetup();
#endif #endif
#if ENCODER_SUPPORT && (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE)
encoderSetup();
#endif
#if LED_SUPPORT #if LED_SUPPORT
ledSetup(); ledSetup();
#endif #endif
#if MQTT_SUPPORT #if MQTT_SUPPORT
mqttSetup(); mqttSetup();
#endif #endif


+ 19
- 6
code/espurna/homeassistant.ino View File

@ -15,6 +15,17 @@ Module key prefix: ha
bool _haEnabled = false; bool _haEnabled = false;
bool _haSendFlag = false; bool _haSendFlag = false;
// -----------------------------------------------------------------------------
// UTILS
// -----------------------------------------------------------------------------
String _haFixName(String name) {
for (unsigned char i=0; i<name.length(); i++) {
if (!isalnum(name.charAt(i))) name.setCharAt(i, '_');
}
return name;
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// SENSORS // SENSORS
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -24,7 +35,7 @@ bool _haSendFlag = false;
void _haSendMagnitude(unsigned char i, JsonObject& config) { void _haSendMagnitude(unsigned char i, JsonObject& config) {
unsigned char type = magnitudeType(i); unsigned char type = magnitudeType(i);
config["name"] = getHostname() + String(" ") + magnitudeTopic(type);
config["name"] =_haFixName(getHostname() + String(" ") + magnitudeTopic(type));
config.set("platform", "mqtt"); config.set("platform", "mqtt");
config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false); config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
config["unit_of_measurement"] = magnitudeUnits(type); config["unit_of_measurement"] = magnitudeUnits(type);
@ -66,10 +77,10 @@ void _haSendSwitch(unsigned char i, JsonObject& config) {
String name = getHostname(); String name = getHostname();
if (relayCount() > 1) { if (relayCount() > 1) {
name += String(" #") + String(i);
name += String("_") + String(i);
} }
config.set("name", name);
config.set("name", _haFixName(name));
config.set("platform", "mqtt"); config.set("platform", "mqtt");
if (relayCount()) { if (relayCount()) {
@ -78,8 +89,8 @@ void _haSendSwitch(unsigned char i, JsonObject& config) {
config["payload_on"] = String(HOMEASSISTANT_PAYLOAD_ON); config["payload_on"] = String(HOMEASSISTANT_PAYLOAD_ON);
config["payload_off"] = String(HOMEASSISTANT_PAYLOAD_OFF); config["payload_off"] = String(HOMEASSISTANT_PAYLOAD_OFF);
config["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false); config["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false);
config["payload_available"] = String(HOMEASSISTANT_PAYLOAD_ON);
config["payload_not_available"] = String(HOMEASSISTANT_PAYLOAD_OFF);
config["payload_available"] = String(HOMEASSISTANT_PAYLOAD_AVAILABLE);
config["payload_not_available"] = String(HOMEASSISTANT_PAYLOAD_NOT_AVAILABLE);
} }
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
@ -288,7 +299,6 @@ void haSetup() {
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_haWebSocketOnSend); wsOnSendRegister(_haWebSocketOnSend);
wsOnAfterParseRegister(_haConfigure);
wsOnActionRegister(_haWebSocketOnAction); wsOnActionRegister(_haWebSocketOnAction);
#endif #endif
@ -303,6 +313,9 @@ void haSetup() {
settingsRegisterKeyCheck(_haKeyCheck); settingsRegisterKeyCheck(_haKeyCheck);
// Main callbacks
espurnaRegisterReload(_haConfigure);
} }
#endif // HOMEASSISTANT_SUPPORT #endif // HOMEASSISTANT_SUPPORT

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

@ -63,7 +63,7 @@ bool idbSend(const char * topic, const char * payload) {
#endif #endif
char * host = strdup(h.c_str()); char * host = strdup(h.c_str());
unsigned int port = getSetting("idbPort", INFLUXDB_PORT).toInt(); unsigned int port = getSetting("idbPort", INFLUXDB_PORT).toInt();
DEBUG_MSG("[INFLUXDB] Sending to %s:%u\n", host, port);
DEBUG_MSG_P(PSTR("[INFLUXDB] Sending to %s:%u\n"), host, port);
bool success = false; bool success = false;
@ -72,7 +72,7 @@ bool idbSend(const char * topic, const char * payload) {
char data[128]; char data[128];
snprintf(data, sizeof(data), "%s,device=%s value=%s", topic, getHostname().c_str(), String(payload).c_str()); snprintf(data, sizeof(data), "%s,device=%s value=%s", topic, getHostname().c_str(), String(payload).c_str());
DEBUG_MSG("[INFLUXDB] Data: %s\n", data);
DEBUG_MSG_P(PSTR("[INFLUXDB] Data: %s\n"), data);
char request[256]; char request[256];
snprintf(request, sizeof(request), "POST /write?db=%s&u=%s&p=%s HTTP/1.1\r\nHost: %s:%u\r\nContent-Length: %d\r\n\r\n%s", snprintf(request, sizeof(request), "POST /write?db=%s&u=%s&p=%s HTTP/1.1\r\nHost: %s:%u\r\nContent-Length: %d\r\n\r\n%s",
@ -86,14 +86,14 @@ bool idbSend(const char * topic, const char * payload) {
if (_idb_client.connected()) _idb_client.stop(); if (_idb_client.connected()) _idb_client.stop();
success = true; success = true;
} else { } else {
DEBUG_MSG("[INFLUXDB] Sent failed\n");
DEBUG_MSG_P(PSTR("[INFLUXDB] Sent failed\n"));
} }
_idb_client.stop(); _idb_client.stop();
while (_idb_client.connected()) yield(); while (_idb_client.connected()) yield();
} else { } else {
DEBUG_MSG("[INFLUXDB] Connection failed\n");
DEBUG_MSG_P(PSTR("[INFLUXDB] Connection failed\n"));
} }
free(host); free(host);
@ -118,11 +118,13 @@ void idbSetup() {
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_idbWebSocketOnSend); wsOnSendRegister(_idbWebSocketOnSend);
wsOnAfterParseRegister(_idbConfigure);
#endif #endif
settingsRegisterKeyCheck(_idbKeyCheck); settingsRegisterKeyCheck(_idbKeyCheck);
// Main callbacks
espurnaRegisterReload(_idbConfigure);
} }
#endif #endif

+ 345
- 41
code/espurna/ir.ino View File

@ -2,38 +2,296 @@
IR MODULE IR MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2018 by Alexander Kolesnikov (raw and MQTT implementation)
Copyright (C) 2017-2018 by François Déchery Copyright (C) 2017-2018 by François Déchery
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Module key prefix: ir Module key prefix: ir
-----------------------------------------------------------------------------
Configuration
-----------------------------------------------------------------------------
To enable transmit functions define IR_TX_PIN
To enable receiver functions define IR_RX_PIN
MQTT input topic: {root}/irin
MQTT output topic: {root}/irout/set
--------------------------------------------------------------------------------
MQTT messages
--------------------------------------------------------------------------------
Decoded messages:
Transmitting:
Payload: 2:121944:32:1 (<type>:<code>:<bits>[:<repeat>])
The repeat value is optional and defaults to 1
Receiving:
Payload: 2:121944:32 (<type>:<code>:<bits>)
Raw messages:
Transmitting:
Payload: 1000,1000,1000,1000,1000,DELAY,COUNT,FREQ:500,500,500,500,500
| IR codes | | IR repeat codes |
codes - time in microseconds when IR LED On/Off. First value - ON, second - Off ...
DELAY - delay in milliseconds between sending repeats
COUNT - how many repeats send. Max 120.
FREQ - modulation frequency. Usually 38kHz. You may set 38, it means 38kHz or set 38000, it meant same.
Repeat codes is optional. You may omit ":" and codes. In this case if repeat count > 0 we repeat main code.
Receiving:
Payload: 1000,1000,1000,1000,1000
| IR codes |
* To support long codes (Air Conditioneer) increase MQTT packet size -DMQTT_MAX_PACKET_SIZE=1200
--------------------------------------------------------------------------------
*/ */
#if IR_SUPPORT #if IR_SUPPORT
#include <IRremoteESP8266.h> #include <IRremoteESP8266.h>
#include <IRrecv.h>
bool _ir_enabled = true; bool _ir_enabled = true;
IRrecv * _ir_recv;
decode_results _ir_results;
unsigned long _ir_last_toggle = 0;
// -----------------------------------------------------------------------------
// PRIVATE
// -----------------------------------------------------------------------------
#if defined(IR_RX_PIN)
#include <IRrecv.h>
IRrecv _ir_receiver(IR_RX_PIN, IR_BUFFER_SIZE, IR_TIMEOUT, true);
decode_results _ir_results;
#endif // defined(IR_RX_PIN)
#if defined(IR_TX_PIN)
#include <IRsend.h>
IRsend _ir_sender(IR_TX_PIN);
#if IR_USE_RAW
uint16_t _ir_freq = 38; // IR modulation freq. for sending codes and repeat codes
uint8_t _ir_repeat_size = 0; // size of repeat array
uint16_t * _ir_raw; // array for sending codes and repeat codes
#else
uint8_t _ir_type = 0; // Type of encoding
uint64_t _ir_code = 0; // Code to transmit
uint16_t _ir_bits = 0; // Code bits
#endif
uint8_t _ir_repeat = 0; // count of times repeating of repeat_code
uint32_t _ir_delay = IR_DELAY; // delay between repeat codes
#endif // defined(IR_TX_PIN)
// MQTT to IR
#if MQTT_SUPPORT && defined(IR_TX_PIN)
void _irMqttCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribe(MQTT_TOPIC_IROUT);
}
if (type == MQTT_MESSAGE_EVENT) {
String t = mqttMagnitude((char *) topic);
// Match topic
if (t.equals(MQTT_TOPIC_IROUT)) {
String data = String(payload);
unsigned int len = data.length();
int col = data.indexOf(":"); // position of ":" which means repeat_code
#if IR_USE_RAW
unsigned char count = 1; // count of code values for allocating array
if (col > 2) { // count & validating repeat code
_ir_repeat_size = 1;
// count & validate repeat-string
for(int i = col+1; i < len; i++) {
if (i < len-1) {
if ( payload[i] == ',' && isDigit(payload[i+1]) && i>0 ) { //validate string
_ir_repeat_size++;
} else if (!isDigit(payload[i])) {
// Error in repeat_code. Use comma separated unsigned integer values.
// Last three is repeat delay, repeat count(<120) and frequency.
// After all you may write ':' and specify repeat code followed by comma.
DEBUG_MSG_P(PSTR("[IR] Error in repeat code.\n"));
return;
}
}
}
len = col; //cut repeat code from main code processing
} // end of counting & validating repeat code
// count & validate main code string
for(int i = 0; i < len; i++) {
if (i<len-1) {
if ( payload[i] == ',' && isDigit(payload[i+1]) && i>0 ) { //validate string
count++;
} else if (!isDigit(payload[i])) {
// Error in main code. Use comma separated unsigned integer values.
// Last three is repeat delay, repeat count(<120) and frequency.
// After all you may write ':' and specify repeat code followed by comma.
DEBUG_MSG_P(PSTR("[IR] Error in main code.\n"));
return;
}
}
}
_ir_raw = (uint16_t*)calloc(count, sizeof(uint16_t)); // allocating array for main codes
String value = ""; // for populating values of array from comma separated string
int j = 0; // for populating values of array from comma separated string
// populating main code array from part of MQTT string
for (int i = 0; i < len; i++) {
if (payload[i] != ',') {
value = value + data[i];
}
if ((payload[i] == ',') || (i == len - 1)) {
_ir_raw[j]= value.toInt();
value = "";
j++;
}
}
// if count>3 then we have values, repeat delay, count and modulation frequency
_ir_repeat=0;
if (count>3) {
if (_ir_raw[count-2] <= 120) { // if repeat count > 120 it's to long and ussualy unusual. maybe we get raw code without this parameters and just use defaults for freq.
_ir_freq = _ir_raw[count-1];
_ir_repeat = _ir_raw[count-2];
_ir_delay = _ir_raw[count-3];
count = count - 3;
}
}
DEBUG_MSG_P(PSTR("[IR] Raw IR output %d codes, repeat %d times on %d(k)Hz freq.\n"), count, _ir_repeat, _ir_freq);
/*
DEBUG_MSG_P(PSTR("[IR] main codes: "));
for(int i = 0; i < count; i++) {
DEBUG_MSG_P(PSTR("%d,"),_ir_raw[i]);
}
DEBUG_MSG_P(PSTR("\n"));
*/
#if defined(IR_RX_PIN)
_ir_receiver.disableIRIn();
#endif
_ir_sender.sendRaw(_ir_raw, count, _ir_freq);
if (_ir_repeat==0) { // no repeat, cleaning array, enabling receiver
free(_ir_raw);
#if defined(IR_RX_PIN)
_ir_receiver.enableIRIn();
#endif
} else if (col>2) { // repeat with repeat_code
DEBUG_MSG_P(PSTR("[IR] Repeat codes count: %d\n"), _ir_repeat_size);
free(_ir_raw);
_ir_raw = (uint16_t*)calloc(_ir_repeat_size, sizeof(uint16_t));
String value = ""; // for populating values of array from comma separated string
int j = 0; // for populating values of array from comma separated string
len = data.length(); //redifining length to full lenght
void _irProcessCode(unsigned long code, unsigned char type) {
// populating repeat code array from part of MQTT string
for (int i = col+1; i < len; i++) {
value = value + data[i];
if ((payload[i] == ',') || (i == len - 1)) {
_ir_raw[j]= value.toInt();
value = "";
j++;
}
}
} else { // if repeat code not specified (col<=2) repeat with current main code
_ir_repeat_size = count;
}
#else
_ir_repeat = 0;
if (col > 0) {
_ir_type = data.toInt();
_ir_code = data.substring(col+1).toInt();
col = data.indexOf(":", col+1);
if (col > 0) {
_ir_bits = data.substring(col+1).toInt();
col = data.indexOf(":", col+1);
if (col > 2) {
_ir_repeat = data.substring(col+1).toInt();
} else {
_ir_repeat = IR_REPEAT;
}
}
}
if (_ir_repeat > 0) {
DEBUG_MSG_P(PSTR("[IR] IROUT: %d:%lu:%d:%d\n"), _ir_type, (unsigned long) _ir_code, _ir_bits, _ir_repeat);
} else {
DEBUG_MSG_P(PSTR("[IR] Wrong MQTT payload format (%s)\n"), payload);
}
#endif // IR_USE_RAW
} // end of match topic
} // end of MQTT message
} //end of function
void _irTXLoop() {
static uint32_t last = 0;
if ((_ir_repeat > 0) && (millis() - last > _ir_delay)) {
last = millis();
// Send message
#if IR_USE_RAW
_ir_sender.sendRaw(_ir_raw, _ir_repeat_size, _ir_freq);
#else
_ir_sender.send(_ir_type, _ir_code, _ir_bits);
#endif
// Update repeat count
--_ir_repeat;
if (0 == _ir_repeat) {
#if IR_USE_RAW
free(_ir_raw);
#endif
#if defined(IR_RX_PIN)
_ir_receiver.enableIRIn();
#endif
}
}
}
#endif // MQTT_SUPPORT && defined(IR_TX_PIN)
// Receiving
#if defined(IR_RX_PIN)
// Check valid code
static unsigned long last_code = 0;
static unsigned long last_time = 0;
if (code == 0xFFFFFFFF) return;
if (type == 0xFF) return;
if ((last_code == code) && (millis() - last_time < IR_DEBOUNCE)) return;
last_code = code;
last_time = millis();
DEBUG_MSG_P(PSTR("[IR] Received 0x%08X (%d)\n"), code, type);
void _irProcess(unsigned char type, unsigned long code) {
#if IR_BUTTON_SET > 0 #if IR_BUTTON_SET > 0
@ -92,51 +350,97 @@ void _irProcessCode(unsigned long code, unsigned char type) {
} }
if (!found) { if (!found) {
DEBUG_MSG_P(PSTR("[IR] Ignoring code\n"));
DEBUG_MSG_P(PSTR("[IR] Code does not match any action\n"));
} }
#endif #endif
#if MQTT_SUPPORT
char buffer[16];
snprintf_P(buffer, sizeof(buffer), "0x%08X", code);
mqttSend(MQTT_TOPIC_IR, buffer);
#endif
} }
bool _irKeyCheck(const char * key) {
return (strncmp(key, "ir", 2) == 0);
void _irRXLoop() {
if (_ir_receiver.decode(&_ir_results)) {
_ir_receiver.resume(); // Receive the next value
// Debounce
static unsigned long last_time = 0;
if (millis() - last_time < IR_DEBOUNCE) return;
last_time = millis();
// Check code
if (_ir_results.value < 1) return;
if (_ir_results.decode_type < 1) return;
if (_ir_results.bits < 1) return;
#if IR_USE_RAW
char * payload;
String value = "";
for (int i = 1; i < _ir_results.rawlen; i++) {
if (i>1) value = value + ",";
value = value + String(_ir_results.rawbuf[i] * RAWTICK);
}
payload = const_cast<char*>(value.c_str());
#else
char payload[32];
snprintf_P(payload, sizeof(payload), PSTR("%u:%lu:%u"), _ir_results.decode_type, (unsigned long) _ir_results.value, _ir_results.bits);
#endif
DEBUG_MSG_P(PSTR("[IR] IRIN: %s\n"), payload);
#if not IR_USE_RAW
_irProcess(_ir_results.decode_type, (unsigned long) _ir_results.value);
#endif
#if MQTT_SUPPORT
if (strlen(payload)>0) {
mqttSend(MQTT_TOPIC_IRIN, (const char *) payload);
}
#endif
}
} }
#endif // defined(IR_RX_PIN)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// PUBLIC API // PUBLIC API
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void irSetup() {
bool _irKeyCheck(const char * key) {
return (strncmp(key, "ir", 2) == 0);
}
_ir_enabled = (getSetting("irEnabled", 1).toInt() == 1);
void _irLoop() {
#if defined(IR_RX_PIN)
_irRXLoop();
#endif
#if MQTT_SUPPORT && defined(IR_TX_PIN)
_irTXLoop();
#endif
}
if (_ir_enabled) {
void irSetup() {
_ir_recv = new IRrecv(getSetting("irGPIO", IR_RECEIVER_PIN).toInt());
_ir_recv->enableIRIn();
_ir_enabled = (getSetting("irEnabled", 1).toInt() == 1);
// Register loop
espurnaRegisterLoop(irLoop);
#if defined(IR_RX_PIN)
_ir_receiver.enableIRIn();
DEBUG_MSG_P(PSTR("[IR] Receiver initialized \n"));
#endif
}
#if MQTT_SUPPORT && defined(IR_TX_PIN)
_ir_sender.begin();
mqttRegister(_irMqttCallback);
DEBUG_MSG_P(PSTR("[IR] Transmitter initialized \n"));
#endif
// Key Check // Key Check
settingsRegisterKeyCheck(_irKeyCheck); settingsRegisterKeyCheck(_irKeyCheck);
}
espurnaRegisterLoop(_irLoop);
void irLoop() {
if (_ir_recv->decode(&_ir_results)) {
_irProcessCode(_ir_results.value, _ir_results.decode_type);
_ir_recv->resume(); // Receive the next value
}
} }
#endif // IR_SUPPORT #endif // IR_SUPPORT

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

@ -289,18 +289,16 @@ void ledSetup() {
_ledConfigure(); _ledConfigure();
// Callbacks
#if MQTT_SUPPORT #if MQTT_SUPPORT
mqttRegister(_ledMQTTCallback); mqttRegister(_ledMQTTCallback);
#endif #endif
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_ledWebSocketOnSend); wsOnSendRegister(_ledWebSocketOnSend);
wsOnAfterParseRegister(_ledConfigure);
#endif #endif
// Registers
espurnaRegisterLoop(_ledLoop);
settingsRegisterKeyCheck(_ledKeyCheck); settingsRegisterKeyCheck(_ledKeyCheck);
espurnaRegisterReload(_ledConfigure);
espurnaRegisterLoop(_ledLoop);
} }


+ 90
- 77
code/espurna/light.ino View File

@ -148,13 +148,9 @@ void _generateBrightness() {
} else { } else {
// Don't apply brightness, it is already in the target:
// Apply brightness equally to all channels
for (unsigned char i=0; i < _light_channel.size(); i++) { for (unsigned char i=0; i < _light_channel.size(); i++) {
if (_light_has_color & (i<3)) {
_light_channel[i].value = _light_channel[i].inputValue * brightness;
} else {
_light_channel[i].value = _light_channel[i].inputValue;
}
_light_channel[i].value = _light_channel[i].inputValue * brightness;
} }
} }
@ -572,6 +568,7 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl
} }
void lightMQTT() { void lightMQTT() {
char buffer[20]; char buffer[20];
if (_light_has_color) { if (_light_has_color) {
@ -587,13 +584,10 @@ void lightMQTT() {
_toHSV(buffer, sizeof(buffer)); _toHSV(buffer, sizeof(buffer));
mqttSend(MQTT_TOPIC_COLOR_HSV, buffer); mqttSend(MQTT_TOPIC_COLOR_HSV, buffer);
// Brightness
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _light_brightness);
mqttSend(MQTT_TOPIC_BRIGHTNESS, buffer);
// Mireds // Mireds
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _light_mireds); snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _light_mireds);
mqttSend(MQTT_TOPIC_MIRED, buffer); mqttSend(MQTT_TOPIC_MIRED, buffer);
} }
// Channels // Channels
@ -602,6 +596,10 @@ void lightMQTT() {
mqttSend(MQTT_TOPIC_CHANNEL, i, buffer); mqttSend(MQTT_TOPIC_CHANNEL, i, buffer);
} }
// Brightness
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _light_brightness);
mqttSend(MQTT_TOPIC_BRIGHTNESS, buffer);
} }
void lightMQTTGroup() { void lightMQTTGroup() {
@ -738,12 +736,16 @@ unsigned int lightChannel(unsigned char id) {
return 0; return 0;
} }
void lightChannel(unsigned char id, unsigned int value) {
void lightChannel(unsigned char id, int value) {
if (id <= _light_channel.size()) { if (id <= _light_channel.size()) {
_light_channel[id].inputValue = constrain(value, 0, LIGHT_MAX_VALUE); _light_channel[id].inputValue = constrain(value, 0, LIGHT_MAX_VALUE);
} }
} }
void lightChannelStep(unsigned char id, int steps) {
lightChannel(id, lightChannel(id) + steps * LIGHT_STEP);
}
unsigned int lightBrightness() { unsigned int lightBrightness() {
return _light_brightness; return _light_brightness;
} }
@ -780,7 +782,6 @@ void _lightWebSocketOnSend(JsonObject& root) {
} }
if (useRGB) { if (useRGB) {
root["rgb"] = lightColor(true); root["rgb"] = lightColor(true);
root["brightness"] = lightBrightness();
} else { } else {
root["hsv"] = lightColor(false); root["hsv"] = lightColor(false);
} }
@ -789,9 +790,11 @@ void _lightWebSocketOnSend(JsonObject& root) {
for (unsigned char id=0; id < _light_channel.size(); id++) { for (unsigned char id=0; id < _light_channel.size(); id++) {
channels.add(lightChannel(id)); channels.add(lightChannel(id));
} }
root["brightness"] = lightBrightness();
} }
void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (_light_has_color) { if (_light_has_color) {
if (strcmp(action, "color") == 0) { if (strcmp(action, "color") == 0) {
if (data.containsKey("rgb")) { if (data.containsKey("rgb")) {
@ -802,10 +805,6 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
lightColor(data["hsv"], false); lightColor(data["hsv"], false);
lightUpdate(true, true); lightUpdate(true, true);
} }
if (data.containsKey("brightness")) {
lightBrightness(data["brightness"]);
lightUpdate(true, true);
}
} }
if (_light_use_cct) { if (_light_use_cct) {
if (strcmp(action, "mireds") == 0) { if (strcmp(action, "mireds") == 0) {
@ -821,6 +820,14 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
lightUpdate(true, true); lightUpdate(true, true);
} }
} }
if (strcmp(action, "brightness") == 0) {
if (data.containsKey("value")) {
lightBrightness(data["value"]);
lightUpdate(true, true);
}
}
} }
#endif #endif
@ -828,35 +835,70 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
#if API_SUPPORT #if API_SUPPORT
void _lightAPISetup() { void _lightAPISetup() {
// API entry points (protected with apikey)
if (_light_has_color) {
apiRegister(MQTT_TOPIC_COLOR_RGB,
[](char * buffer, size_t len) {
if (getSetting("litCSS", LIGHT_USE_CSS).toInt() == 1) {
_toRGB(buffer, len);
} else {
_toLong(buffer, len);
if (_light_has_color) {
apiRegister(MQTT_TOPIC_COLOR_RGB,
[](char * buffer, size_t len) {
if (getSetting("litCSS", LIGHT_USE_CSS).toInt() == 1) {
_toRGB(buffer, len);
} else {
_toLong(buffer, len);
}
},
[](const char * payload) {
lightColor(payload, true);
lightUpdate(true, true);
} }
},
[](const char * payload) {
lightColor(payload, true);
lightUpdate(true, true);
}
);
);
apiRegister(MQTT_TOPIC_COLOR_HSV,
[](char * buffer, size_t len) {
_toHSV(buffer, len);
},
[](const char * payload) {
lightColor(payload, false);
lightUpdate(true, true);
}
);
apiRegister(MQTT_TOPIC_COLOR_HSV,
[](char * buffer, size_t len) {
_toHSV(buffer, len);
},
[](const char * payload) {
lightColor(payload, false);
lightUpdate(true, true);
}
);
apiRegister(MQTT_TOPIC_KELVIN,
[](char * buffer, size_t len) {},
[](const char * payload) {
_fromKelvin(atol(payload));
lightUpdate(true, true);
}
);
apiRegister(MQTT_TOPIC_MIRED,
[](char * buffer, size_t len) {},
[](const char * payload) {
_fromMireds(atol(payload));
lightUpdate(true, true);
}
);
}
for (unsigned int id=0; id<_light_channel.size(); id++) {
char key[15];
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_CHANNEL, id);
apiRegister(key,
[id](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), lightChannel(id));
},
[id](const char * payload) {
lightChannel(id, atoi(payload));
lightUpdate(true, true);
}
);
}
apiRegister(MQTT_TOPIC_BRIGHTNESS, apiRegister(MQTT_TOPIC_BRIGHTNESS,
[](char * buffer, size_t len) { [](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), _light_brightness);
snprintf_P(buffer, len, PSTR("%d"), _light_brightness);
}, },
[](const char * payload) { [](const char * payload) {
lightBrightness(atoi(payload)); lightBrightness(atoi(payload));
@ -864,37 +906,6 @@ void _lightAPISetup() {
} }
); );
apiRegister(MQTT_TOPIC_KELVIN,
[](char * buffer, size_t len) {},
[](const char * payload) {
_fromKelvin(atol(payload));
lightUpdate(true, true);
}
);
apiRegister(MQTT_TOPIC_MIRED,
[](char * buffer, size_t len) {},
[](const char * payload) {
_fromMireds(atol(payload));
lightUpdate(true, true);
}
);
}
for (unsigned int id=0; id<_light_channel.size(); id++) {
char key[15];
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_CHANNEL, id);
apiRegister(key,
[id](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), lightChannel(id));
},
[id](const char * payload) {
lightChannel(id, atoi(payload));
lightUpdate(true, true);
}
);
}
} }
#endif // API_SUPPORT #endif // API_SUPPORT
@ -1102,12 +1113,6 @@ void lightSetup() {
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_lightWebSocketOnSend); wsOnSendRegister(_lightWebSocketOnSend);
wsOnActionRegister(_lightWebSocketOnAction); wsOnActionRegister(_lightWebSocketOnAction);
wsOnAfterParseRegister([]() {
#if LIGHT_SAVE_ENABLED == 0
lightSave();
#endif
_lightConfigure();
});
#endif #endif
#if API_SUPPORT #if API_SUPPORT
@ -1124,6 +1129,14 @@ void lightSetup() {
settingsRegisterKeyCheck(_lightKeyCheck); settingsRegisterKeyCheck(_lightKeyCheck);
// Main callbacks
espurnaRegisterReload([]() {
#if LIGHT_SAVE_ENABLED == 0
lightSave();
#endif
_lightConfigure();
});
} }
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE #endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE

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

@ -585,7 +585,16 @@ unsigned char _mqttBuildTree(JsonObject& root, char parent) {
JsonObject& elements = root.createNestedObject(element.topic); JsonObject& elements = root.createNestedObject(element.topic);
unsigned char num = _mqttBuildTree(elements, i); unsigned char num = _mqttBuildTree(elements, i);
if (0 == num) { if (0 == num) {
root.set(element.topic, element.message);
if (isNumber(element.message)) {
double value = atof(element.message);
if (value == int(value)) {
root.set(element.topic, int(value));
} else {
root.set(element.topic, value);
}
} else {
root.set(element.topic, element.message);
}
} }
} }
} }
@ -714,7 +723,6 @@ void mqttUnsubscribe(const char * topic) {
void mqttEnabled(bool status) { void mqttEnabled(bool status) {
_mqtt_enabled = status; _mqtt_enabled = status;
setSetting("mqttEnabled", status ? 1 : 0);
} }
bool mqttEnabled() { bool mqttEnabled() {
@ -747,7 +755,7 @@ void mqttSetBroker(IPAddress ip, unsigned int port) {
} }
void mqttSetBrokerIfNone(IPAddress ip, unsigned int port) { void mqttSetBrokerIfNone(IPAddress ip, unsigned int port) {
if (!hasSetting("mqttServer")) mqttSetBroker(ip, port);
if (getSetting("mqttServer", MQTT_SERVER).length() == 0) mqttSetBroker(ip, port);
} }
void mqttReset() { void mqttReset() {
@ -821,7 +829,6 @@ void mqttSetup() {
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_mqttWebSocketOnSend); wsOnSendRegister(_mqttWebSocketOnSend);
wsOnAfterParseRegister(_mqttConfigure);
#endif #endif
#if TERMINAL_SUPPORT #if TERMINAL_SUPPORT
@ -829,9 +836,12 @@ void mqttSetup() {
#endif #endif
// Register // Register
espurnaRegisterLoop(mqttLoop);
settingsRegisterKeyCheck(_mqttKeyCheck); settingsRegisterKeyCheck(_mqttKeyCheck);
// Main callbacks
espurnaRegisterLoop(mqttLoop);
espurnaRegisterReload(_mqttConfigure);
} }
void mqttLoop() { void mqttLoop() {


+ 5
- 4
code/espurna/nofuss.ino View File

@ -69,7 +69,7 @@ void _nofussConfigure() {
} }
bool _nofussKeyCheck(const char * key) { bool _nofussKeyCheck(const char * key) {
return (strncmp(key, "nof", 6) == 0);
return (strncmp(key, "nof", 3) == 0);
} }
void _nofussBackwards() { void _nofussBackwards() {
@ -163,17 +163,18 @@ void nofussSetup() {
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_nofussWebSocketOnSend); wsOnSendRegister(_nofussWebSocketOnSend);
wsOnAfterParseRegister(_nofussConfigure);
#endif #endif
#if TERMINAL_SUPPORT #if TERMINAL_SUPPORT
_nofussInitCommands(); _nofussInitCommands();
#endif #endif
// Register
espurnaRegisterLoop(nofussLoop);
settingsRegisterKeyCheck(_nofussKeyCheck); settingsRegisterKeyCheck(_nofussKeyCheck);
// Main callbacks
espurnaRegisterLoop(nofussLoop);
espurnaRegisterReload(_nofussConfigure);
} }
void nofussLoop() { void nofussLoop() {


+ 4
- 3
code/espurna/ntp.ino View File

@ -186,13 +186,14 @@ void ntpSetup() {
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_ntpWebSocketOnSend); wsOnSendRegister(_ntpWebSocketOnSend);
wsOnAfterParseRegister([]() { _ntp_configure = true; });
#endif #endif
// Register
espurnaRegisterLoop(_ntpLoop);
settingsRegisterKeyCheck(_ntpKeyCheck); settingsRegisterKeyCheck(_ntpKeyCheck);
// Main callbacks
espurnaRegisterLoop(_ntpLoop);
espurnaRegisterReload([]() { _ntp_configure = true; });
} }
#endif // NTP_SUPPORT #endif // NTP_SUPPORT

+ 3
- 6
code/espurna/ota.ino View File

@ -208,10 +208,6 @@ void otaSetup() {
_otaBackwards(); _otaBackwards();
_otaConfigure(); _otaConfigure();
#if WEB_SUPPORT
wsOnAfterParseRegister(_otaConfigure);
#endif
#if TERMINAL_SUPPORT #if TERMINAL_SUPPORT
_otaInitCommands(); _otaInitCommands();
#endif #endif
@ -219,8 +215,9 @@ void otaSetup() {
// Register settings key check // Register settings key check
settingsRegisterKeyCheck(_otaKeyCheck); settingsRegisterKeyCheck(_otaKeyCheck);
// Register loop
// Main callbacks
espurnaRegisterLoop(_otaLoop); espurnaRegisterLoop(_otaLoop);
espurnaRegisterReload(_otaConfigure);
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -246,7 +243,7 @@ void otaSetup() {
deferredReset(100, CUSTOM_RESET_OTA); deferredReset(100, CUSTOM_RESET_OTA);
}); });
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
static unsigned int _progOld; static unsigned int _progOld;
unsigned int _prog = (progress / (total / 100)); unsigned int _prog = (progress / (total / 100));


+ 6
- 4
code/espurna/relay.ino View File

@ -667,7 +667,6 @@ void _relayWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
void relaySetupWS() { void relaySetupWS() {
wsOnSendRegister(_relayWebSocketOnStart); wsOnSendRegister(_relayWebSocketOnStart);
wsOnActionRegister(_relayWebSocketOnAction); wsOnActionRegister(_relayWebSocketOnAction);
wsOnAfterParseRegister(_relayConfigure);
} }
#endif // WEB_SUPPORT #endif // WEB_SUPPORT
@ -1013,9 +1012,6 @@ void relaySetup() {
_relayBoot(); _relayBoot();
_relayLoop(); _relayLoop();
settingsRegisterKeyCheck(_relayKeyCheck);
espurnaRegisterLoop(_relayLoop);
#if WEB_SUPPORT #if WEB_SUPPORT
relaySetupWS(); relaySetupWS();
#endif #endif
@ -1029,4 +1025,10 @@ void relaySetup() {
_relayInitCommands(); _relayInitCommands();
#endif #endif
settingsRegisterKeyCheck(_relayKeyCheck);
// Main callbacks
espurnaRegisterLoop(_relayLoop);
espurnaRegisterReload(_relayConfigure);
} }

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

@ -269,12 +269,15 @@ void rfm69Setup() {
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_rfm69WebSocketOnSend); wsOnSendRegister(_rfm69WebSocketOnSend);
wsOnAfterParseRegister(_rfm69Configure);
wsOnActionRegister(_rfm69WebSocketOnAction); wsOnActionRegister(_rfm69WebSocketOnAction);
#endif #endif
settingsRegisterKeyCheck(_rfm69KeyCheck); settingsRegisterKeyCheck(_rfm69KeyCheck);
// Main callbacks
espurnaRegisterLoop(_rfm69Loop); espurnaRegisterLoop(_rfm69Loop);
espurnaRegisterReload(_rfm69Configure);
} }


+ 3
- 3
code/espurna/scheduler.ino View File

@ -217,13 +217,13 @@ void schSetup() {
// Update websocket clients // Update websocket clients
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_schWebSocketOnSend); wsOnSendRegister(_schWebSocketOnSend);
wsOnAfterParseRegister(_schConfigure);
#endif #endif
settingsRegisterKeyCheck(_schKeyCheck); settingsRegisterKeyCheck(_schKeyCheck);
// Register loop
// Main callbacks
espurnaRegisterLoop(_schLoop); espurnaRegisterLoop(_schLoop);
espurnaRegisterReload(_schConfigure);
} }


+ 36
- 10
code/espurna/sensor.ino View File

@ -25,10 +25,10 @@ typedef struct {
unsigned char local; // Local index in its provider unsigned char local; // Local index in its provider
unsigned char type; // Type of measurement unsigned char type; // Type of measurement
unsigned char global; // Global index in its type unsigned char global; // Global index in its type
double current; // Lat raw value (unfiltered)
double filtered; // Last filtered value (averaged)
double reported; // Last reported value (averaged)
double current; // Current (last) value, unfiltered
double reported; // Last reported value
double min_change; // Minimum value change to report double min_change; // Minimum value change to report
double max_change; // Maximum value change to report
} sensor_magnitude_t; } sensor_magnitude_t;
std::vector<BaseSensor *> _sensors; std::vector<BaseSensor *> _sensors;
@ -236,7 +236,7 @@ void _sensorAPISetup() {
apiRegister(topic.c_str(), [magnitude_id](char * buffer, size_t len) { apiRegister(topic.c_str(), [magnitude_id](char * buffer, size_t len) {
sensor_magnitude_t magnitude = _magnitudes[magnitude_id]; sensor_magnitude_t magnitude = _magnitudes[magnitude_id];
unsigned char decimals = _magnitudeDecimals(magnitude.type); unsigned char decimals = _magnitudeDecimals(magnitude.type);
double value = _sensor_realtime ? magnitude.current : magnitude.filtered;
double value = _sensor_realtime ? magnitude.current : magnitude.reported;
dtostrf(value, 1-len, decimals, buffer); dtostrf(value, 1-len, decimals, buffer);
}); });
@ -571,6 +571,15 @@ void _sensorLoad() {
} }
#endif #endif
#if SDS011_SUPPORT
{
SDS011Sensor * sensor = new SDS011Sensor();
sensor->setRX(SDS011_RX_PIN);
sensor->setTX(SDS011_TX_PIN);
_sensors.push_back(sensor);
}
#endif
#if NTC_SUPPORT #if NTC_SUPPORT
if (getSetting("ntcEnabled", 0).toInt() == 1) { if (getSetting("ntcEnabled", 0).toInt() == 1) {
NTCSensor * sensor = new NTCSensor(); NTCSensor * sensor = new NTCSensor();
@ -704,9 +713,19 @@ void _sensorInit() {
new_magnitude.type = type; new_magnitude.type = type;
new_magnitude.global = _counts[type]; new_magnitude.global = _counts[type];
new_magnitude.current = 0; new_magnitude.current = 0;
new_magnitude.filtered = 0;
new_magnitude.reported = 0; new_magnitude.reported = 0;
new_magnitude.min_change = 0; new_magnitude.min_change = 0;
new_magnitude.max_change = 0;
// TODO: find a proper way to extend this to min/max of any magnitude
if (MAGNITUDE_ENERGY == type) {
new_magnitude.max_change = getSetting("eneMaxDelta", ENERGY_MAX_CHANGE).toFloat();
} else if (MAGNITUDE_TEMPERATURE == type) {
new_magnitude.min_change = getSetting("tmpMinDelta", TEMPERATURE_MIN_CHANGE).toFloat();
} else if (MAGNITUDE_HUMIDITY == type) {
new_magnitude.min_change = getSetting("humMinDelta", HUMIDITY_MIN_CHANGE).toFloat();
}
if (MAGNITUDE_ENERGY == type) { if (MAGNITUDE_ENERGY == type) {
new_magnitude.filter = new LastFilter(); new_magnitude.filter = new LastFilter();
} else if (MAGNITUDE_DIGITAL == type) { } else if (MAGNITUDE_DIGITAL == type) {
@ -717,6 +736,7 @@ void _sensorInit() {
new_magnitude.filter = new MedianFilter(); new_magnitude.filter = new MedianFilter();
} }
new_magnitude.filter->resize(_sensor_report_every); new_magnitude.filter->resize(_sensor_report_every);
_magnitudes.push_back(new_magnitude); _magnitudes.push_back(new_magnitude);
DEBUG_MSG_P(PSTR("[SENSOR] -> %s:%d\n"), magnitudeTopic(type).c_str(), _counts[type]); DEBUG_MSG_P(PSTR("[SENSOR] -> %s:%d\n"), magnitudeTopic(type).c_str(), _counts[type]);
@ -1119,7 +1139,6 @@ void sensorSetup() {
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_sensorWebSocketStart); wsOnSendRegister(_sensorWebSocketStart);
wsOnSendRegister(_sensorWebSocketSendData); wsOnSendRegister(_sensorWebSocketSendData);
wsOnAfterParseRegister(_sensorConfigure);
#endif #endif
// API // API
@ -1134,8 +1153,9 @@ void sensorSetup() {
settingsRegisterKeyCheck(_sensorKeyCheck); settingsRegisterKeyCheck(_sensorKeyCheck);
// Register loop
// Main callbacks
espurnaRegisterLoop(sensorLoop); espurnaRegisterLoop(sensorLoop);
espurnaRegisterReload(_sensorConfigure);
} }
@ -1240,12 +1260,18 @@ void sensorLoop() {
// (we do it every _sensor_report_every readings) // (we do it every _sensor_report_every readings)
// ------------------------------------------------------------- // -------------------------------------------------------------
if (0 == report_count) {
bool report = (0 == report_count);
if ((MAGNITUDE_ENERGY == magnitude.type) && (magnitude.max_change > 0)) {
// for MAGNITUDE_ENERGY, filtered value is last value
double value = _magnitudeProcess(magnitude.type, current);
report = (fabs(value - magnitude.reported) >= magnitude.max_change);
} // if ((MAGNITUDE_ENERGY == magnitude.type) && (magnitude.max_change > 0))
if (report) {
filtered = magnitude.filter->result(); filtered = magnitude.filter->result();
magnitude.filter->reset();
filtered = _magnitudeProcess(magnitude.type, filtered); filtered = _magnitudeProcess(magnitude.type, filtered);
_magnitudes[i].filtered = filtered;
magnitude.filter->reset();
// Check if there is a minimum change threshold to report // Check if there is a minimum change threshold to report
if (fabs(filtered - magnitude.reported) >= magnitude.min_change) { if (fabs(filtered - magnitude.reported) >= magnitude.min_change) {


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

@ -11,6 +11,7 @@
#include "Arduino.h" #include "Arduino.h"
#include "BaseSensor.h" #include "BaseSensor.h"
#include <SoftwareSerial.h> #include <SoftwareSerial.h>
// Generic data // Generic data
@ -292,6 +293,9 @@ class PMSX003Sensor : public BaseSensor, PMSX003 {
} }
} else { } else {
readCycle = -1; readCycle = -1;
if (_readCount == 1) {
wakeUp();
}
} }
#endif #endif


+ 173
- 0
code/espurna/sensors/SDS011Sensor.h View File

@ -0,0 +1,173 @@
// -----------------------------------------------------------------------------
// SDS011 particulates sensor
// Based on: https://github.com/ricki-z/SDS011
//
// Uses SoftwareSerial library
// Copyright (C) 2018 by Lucas Pleß <hello at lucas-pless dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && SDS011_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <SoftwareSerial.h>
class SDS011Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
SDS011Sensor(): BaseSensor() {
_count = 2;
_sensor_id = SENSOR_SDS011_ID;
}
~SDS011Sensor() {
if (_serial) delete _serial;
}
// ---------------------------------------------------------------------
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;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
unsigned char getTX() {
return _pin_tx;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_serial) delete _serial;
_serial = new SoftwareSerial(_pin_rx, _pin_tx);
_serial->begin(9600);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "SDS011 @ 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) {
char buffer[6];
snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_PM2dot5;
if (index == 1) return MAGNITUDE_PM10;
return MAGNITUDE_NONE;
}
void pre() {
_read();
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _p2dot5;
if (index == 1) return _p10;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _read() {
byte buffer;
int value;
int len = 0;
int pm10_serial = 0;
int pm25_serial = 0;
int checksum_is;
int checksum_ok = 0;
while ((_serial->available() > 0) && (_serial->available() >= (10-len))) {
buffer = _serial->read();
value = int(buffer);
switch (len) {
case (0): if (value != 170) { len = -1; }; break;
case (1): if (value != 192) { len = -1; }; break;
case (2): pm25_serial = value; checksum_is = value; break;
case (3): pm25_serial += (value << 8); checksum_is += value; break;
case (4): pm10_serial = value; checksum_is += value; break;
case (5): pm10_serial += (value << 8); checksum_is += value; break;
case (6): checksum_is += value; break;
case (7): checksum_is += value; break;
case (8): if (value == (checksum_is % 256)) { checksum_ok = 1; } else { len = -1; }; break;
case (9): if (value != 171) { len = -1; }; break;
}
len++;
if (len == 10) {
if(checksum_ok == 1) {
_p10 = (float)pm10_serial/10.0;
_p2dot5 = (float)pm25_serial/10.0;
len = 0; checksum_ok = 0; pm10_serial = 0.0; pm25_serial = 0.0; checksum_is = 0;
_error = SENSOR_ERROR_OK;
} else {
_error = SENSOR_ERROR_CRC;
}
}
yield();
}
}
double _p2dot5 = 0;
double _p10 = 0;
unsigned int _pin_rx;
unsigned int _pin_tx;
SoftwareSerial * _serial = NULL;
};
#endif // SENSOR_SUPPORT && SDS011_SUPPORT

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

@ -10,8 +10,6 @@
#undef I2C_SUPPORT #undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support. #define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h" #include "Arduino.h"
#include "I2CSensor.h" #include "I2CSensor.h"


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

@ -278,7 +278,12 @@ void _settingsInitCommands() {
}); });
settingsRegisterCommand(F("HEAP"), [](Embedis* e) { settingsRegisterCommand(F("HEAP"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Free HEAP: %d bytes\n"), getFreeHeap());
infoMemory("Heap", getInitialFreeHeap(), getFreeHeap());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("STACK"), [](Embedis* e) {
infoMemory("Stack", 4096, getFreeStack());
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
@ -289,10 +294,6 @@ void _settingsInitCommands() {
settingsRegisterCommand(F("INFO"), [](Embedis* e) { settingsRegisterCommand(F("INFO"), [](Embedis* e) {
info(); info();
wifiDebug();
//StreamString s;
//WiFi.printDiag(s);
//DEBUG_MSG(s.c_str());
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
@ -347,7 +348,7 @@ void _settingsInitCommands() {
#if WEB_SUPPORT #if WEB_SUPPORT
settingsRegisterCommand(F("RELOAD"), [](Embedis* e) { settingsRegisterCommand(F("RELOAD"), [](Embedis* e) {
wsReload();
espurnaReload();
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
#endif #endif
@ -368,6 +369,16 @@ void _settingsInitCommands() {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
settingsRegisterCommand(F("CONFIG"), [](Embedis* e) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
settingsGetJson(root);
String output;
root.printTo(output);
DEBUG_MSG(output.c_str());
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


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


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


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


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


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


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


+ 41
- 3
code/espurna/telnet.ino View File

@ -17,6 +17,9 @@ Module key prefix: tel
AsyncServer * _telnetServer; AsyncServer * _telnetServer;
AsyncClient * _telnetClients[TELNET_MAX_CLIENTS]; AsyncClient * _telnetClients[TELNET_MAX_CLIENTS];
bool _telnetFirst = true; bool _telnetFirst = true;
#if TELNET_PASSWORD
bool _authenticated[TELNET_MAX_CLIENTS];
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Private methods // Private methods
@ -33,8 +36,8 @@ void _telnetWebSocketOnSend(JsonObject& root) {
void _telnetDisconnect(unsigned char clientId) { void _telnetDisconnect(unsigned char clientId) {
_telnetClients[clientId]->free(); _telnetClients[clientId]->free();
_telnetClients[clientId] = NULL;
delete _telnetClients[clientId]; delete _telnetClients[clientId];
_telnetClients[clientId] = NULL;
wifiReconnectCheck(); wifiReconnectCheck();
DEBUG_MSG_P(PSTR("[TELNET] Client #%d disconnected\n"), clientId); DEBUG_MSG_P(PSTR("[TELNET] Client #%d disconnected\n"), clientId);
} }
@ -49,11 +52,24 @@ bool _telnetWrite(unsigned char clientId, void *data, size_t len) {
unsigned char _telnetWrite(void *data, size_t len) { unsigned char _telnetWrite(void *data, size_t len) {
unsigned char count = 0; unsigned char count = 0;
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) { for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
if (_telnetWrite(i, data, len)) ++count;
#if TELNET_PASSWORD
// Do not send broadcast messages to unauthenticated clients
if (_authenticated[i]) {
if (_telnetWrite(i, data, len)) ++count;
}
#else
if (_telnetWrite(i, data, len)) ++count;
#endif
} }
return count; return count;
} }
bool _telnetWrite(unsigned char clientId, const char * message) {
return _telnetWrite(clientId, (void *) message, strlen(message));
}
void _telnetData(unsigned char clientId, void *data, size_t len) { void _telnetData(unsigned char clientId, void *data, size_t len) {
// Skip first message since it's always garbage // Skip first message since it's always garbage
@ -78,7 +94,22 @@ void _telnetData(unsigned char clientId, void *data, size_t len) {
return; return;
} }
// Inject into Embedis stream
// Password
#if TELNET_PASSWORD
if (!_authenticated[clientId]) {
String password = getAdminPass();
if (strncmp(p, password.c_str(), password.length()) == 0) {
DEBUG_MSG_P(PSTR("[TELNET] Client #%d authenticated\n"), clientId);
_telnetWrite(clientId, "Welcome!\n");
_authenticated[clientId] = true;
} else {
_telnetWrite(clientId, "Password: ");
}
return;
}
#endif // TELNET_PASSWORD
// Inject command
settingsInject(data, len); settingsInject(data, len);
} }
@ -107,6 +138,7 @@ void _telnetNewClient(AsyncClient *client) {
} }
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) { for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
if (!_telnetClients[i] || !_telnetClients[i]->connected()) { if (!_telnetClients[i] || !_telnetClients[i]->connected()) {
_telnetClients[i] = client; _telnetClients[i] = client;
@ -141,8 +173,14 @@ void _telnetNewClient(AsyncClient *client) {
debugClearCrashInfo(); debugClearCrashInfo();
#endif #endif
#if TELNET_PASSWORD
_authenticated[i] = false;
_telnetWrite(i, "Password: ");
#endif
_telnetFirst = true; _telnetFirst = true;
wifiReconnectCheck(); wifiReconnectCheck();
return; return;
} }


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

@ -261,7 +261,6 @@ void tspkSetup() {
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_tspkWebSocketOnSend); wsOnSendRegister(_tspkWebSocketOnSend);
wsOnAfterParseRegister(_tspkConfigure);
#endif #endif
DEBUG_MSG_P(PSTR("[THINGSPEAK] Async %s, SSL %s\n"), DEBUG_MSG_P(PSTR("[THINGSPEAK] Async %s, SSL %s\n"),
@ -273,6 +272,7 @@ void tspkSetup() {
// Register loop // Register loop
espurnaRegisterLoop(tspkLoop); espurnaRegisterLoop(tspkLoop);
espurnaRegisterReload(_tspkConfigure);
} }


+ 109
- 43
code/espurna/utils.ino View File

@ -8,6 +8,11 @@ Module key prefix: esp (shared with others)
*/ */
extern "C" {
#include <cont.h>
extern cont_t g_cont;
}
#include <Ticker.h> #include <Ticker.h>
Ticker _defer_reset; Ticker _defer_reset;
@ -52,6 +57,10 @@ String getDevice() {
return getSetting("device", "GENERIC_CUSTOM"); return getSetting("device", "GENERIC_CUSTOM");
} }
String getAdminPass() {
return getSetting("adminPass", ADMIN_PASS);
}
String getCoreVersion() { String getCoreVersion() {
String version = ESP.getCoreVersion(); String version = ESP.getCoreVersion();
#ifdef ARDUINO_ESP8266_RELEASE #ifdef ARDUINO_ESP8266_RELEASE
@ -79,6 +88,22 @@ unsigned int getFreeHeap() {
return ESP.getFreeHeap(); return ESP.getFreeHeap();
} }
unsigned int getInitialFreeHeap() {
static unsigned int _heap = 0;
if (0 == _heap) {
_heap = getFreeHeap();
}
return _heap;
}
unsigned int getUsedHeap() {
return getInitialFreeHeap() - getFreeHeap();
}
unsigned int getFreeStack() {
return cont_get_free_stack(&g_cont);
}
String getEspurnaModules() { String getEspurnaModules() {
return FPSTR(espurna_modules); return FPSTR(espurna_modules);
} }
@ -155,7 +180,7 @@ void heartbeat() {
if (serial) { if (serial) {
DEBUG_MSG_P(PSTR("[MAIN] Uptime: %lu seconds\n"), uptime_seconds); DEBUG_MSG_P(PSTR("[MAIN] Uptime: %lu seconds\n"), uptime_seconds);
DEBUG_MSG_P(PSTR("[MAIN] Free heap: %lu bytes\n"), free_heap);
infoMemory("Heap", getInitialFreeHeap(), getFreeHeap());
#if ADC_MODE_VALUE == ADC_VCC #if ADC_MODE_VALUE == ADC_VCC
DEBUG_MSG_P(PSTR("[MAIN] Power: %lu mV\n"), ESP.getVcc()); DEBUG_MSG_P(PSTR("[MAIN] Power: %lu mV\n"), ESP.getVcc());
#endif #endif
@ -268,7 +293,7 @@ void _info_print_memory_layout_line(const char * name, unsigned long bytes, bool
if (reset) index = 0; if (reset) index = 0;
if (0 == bytes) return; if (0 == bytes) return;
unsigned int _sectors = info_bytes2sectors(bytes); unsigned int _sectors = info_bytes2sectors(bytes);
DEBUG_MSG_P(PSTR("[INIT] %-20s: %8lu bytes / %4d sectors (%4d to %4d)\n"), name, bytes, _sectors, index, index + _sectors - 1);
DEBUG_MSG_P(PSTR("[MAIN] %-20s: %8lu bytes / %4d sectors (%4d to %4d)\n"), name, bytes, _sectors, index, index + _sectors - 1);
index += _sectors; index += _sectors;
} }
@ -276,31 +301,50 @@ void _info_print_memory_layout_line(const char * name, unsigned long bytes) {
_info_print_memory_layout_line(name, bytes, false); _info_print_memory_layout_line(name, bytes, false);
} }
void infoMemory(const char * name, unsigned int total_memory, unsigned int free_memory) {
DEBUG_MSG_P(
PSTR("[MAIN] %-6s: %5u bytes total - %5u bytes used (%2u%%) - %5u bytes free (%2u%%)\n"),
name,
total_memory,
total_memory - free_memory,
100 * (total_memory - free_memory) / total_memory,
free_memory,
100 * free_memory / total_memory
);
}
void info() { void info() {
DEBUG_MSG_P(PSTR("\n\n"));
if (strlen(APP_REVISION) > 0) {
DEBUG_MSG_P(PSTR("[INIT] %s %s (%s)\n"), (char *) APP_NAME, (char *) APP_VERSION, (char *) APP_REVISION);
} else {
DEBUG_MSG_P(PSTR("[INIT] %s %s\n"), (char *) APP_NAME, (char *) APP_VERSION);
}
DEBUG_MSG_P(PSTR("[INIT] %s\n"), (char *) APP_AUTHOR);
DEBUG_MSG_P(PSTR("[INIT] %s\n\n"), (char *) APP_WEBSITE);
DEBUG_MSG_P(PSTR("[INIT] CPU chip ID: 0x%06X\n"), ESP.getChipId());
DEBUG_MSG_P(PSTR("[INIT] CPU frequency: %u MHz\n"), ESP.getCpuFreqMHz());
DEBUG_MSG_P(PSTR("[INIT] SDK version: %s\n"), ESP.getSdkVersion());
DEBUG_MSG_P(PSTR("[INIT] Core version: %s\n"), getCoreVersion().c_str());
DEBUG_MSG_P(PSTR("[INIT] Core revision: %s\n"), getCoreRevision().c_str());
DEBUG_MSG_P(PSTR("\n\n---8<-------\n\n"));
// -------------------------------------------------------------------------
#if defined(APP_REVISION)
DEBUG_MSG_P(PSTR("[MAIN] " APP_NAME " " APP_VERSION " (" APP_REVISION ")\n"));
#else
DEBUG_MSG_P(PSTR("[MAIN] " APP_NAME " " APP_VERSION "\n"));
#endif
DEBUG_MSG_P(PSTR("[MAIN] " APP_AUTHOR "\n"));
DEBUG_MSG_P(PSTR("[MAIN] " APP_WEBSITE "\n\n"));
DEBUG_MSG_P(PSTR("[MAIN] CPU chip ID: 0x%06X\n"), ESP.getChipId());
DEBUG_MSG_P(PSTR("[MAIN] CPU frequency: %u MHz\n"), ESP.getCpuFreqMHz());
DEBUG_MSG_P(PSTR("[MAIN] SDK version: %s\n"), ESP.getSdkVersion());
DEBUG_MSG_P(PSTR("[MAIN] Core version: %s\n"), getCoreVersion().c_str());
DEBUG_MSG_P(PSTR("[MAIN] Core revision: %s\n"), getCoreRevision().c_str());
DEBUG_MSG_P(PSTR("\n")); DEBUG_MSG_P(PSTR("\n"));
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
FlashMode_t mode = ESP.getFlashChipMode(); FlashMode_t mode = ESP.getFlashChipMode();
DEBUG_MSG_P(PSTR("[INIT] Flash chip ID: 0x%06X\n"), ESP.getFlashChipId());
DEBUG_MSG_P(PSTR("[INIT] Flash speed: %u Hz\n"), ESP.getFlashChipSpeed());
DEBUG_MSG_P(PSTR("[INIT] Flash mode: %s\n"), mode == FM_QIO ? "QIO" : mode == FM_QOUT ? "QOUT" : mode == FM_DIO ? "DIO" : mode == FM_DOUT ? "DOUT" : "UNKNOWN");
DEBUG_MSG_P(PSTR("[MAIN] Flash chip ID: 0x%06X\n"), ESP.getFlashChipId());
DEBUG_MSG_P(PSTR("[MAIN] Flash speed: %u Hz\n"), ESP.getFlashChipSpeed());
DEBUG_MSG_P(PSTR("[MAIN] Flash mode: %s\n"), mode == FM_QIO ? "QIO" : mode == FM_QOUT ? "QOUT" : mode == FM_DIO ? "DIO" : mode == FM_DOUT ? "DOUT" : "UNKNOWN");
DEBUG_MSG_P(PSTR("\n")); DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
_info_print_memory_layout_line("Flash size (CHIP)", ESP.getFlashChipRealSize(), true); _info_print_memory_layout_line("Flash size (CHIP)", ESP.getFlashChipRealSize(), true);
_info_print_memory_layout_line("Flash size (SDK)", ESP.getFlashChipSize(), true); _info_print_memory_layout_line("Flash size (SDK)", ESP.getFlashChipSize(), true);
_info_print_memory_layout_line("Reserved", 1 * SPI_FLASH_SEC_SIZE, true); _info_print_memory_layout_line("Reserved", 1 * SPI_FLASH_SEC_SIZE, true);
@ -311,62 +355,83 @@ void info() {
_info_print_memory_layout_line("Reserved", 4 * SPI_FLASH_SEC_SIZE); _info_print_memory_layout_line("Reserved", 4 * SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("\n")); DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[INIT] EEPROM sectors: %s\n"), (char *) eepromSectors().c_str());
DEBUG_MSG_P(PSTR("\n"));
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
#if SPIFFS_SUPPORT #if SPIFFS_SUPPORT
FSInfo fs_info; FSInfo fs_info;
bool fs = SPIFFS.info(fs_info); bool fs = SPIFFS.info(fs_info);
if (fs) { if (fs) {
DEBUG_MSG_P(PSTR("[INIT] SPIFFS total size: %8u bytes / %4d sectors\n"), fs_info.totalBytes, sectors(fs_info.totalBytes));
DEBUG_MSG_P(PSTR("[INIT] used size: %8u bytes\n"), fs_info.usedBytes);
DEBUG_MSG_P(PSTR("[INIT] block size: %8u bytes\n"), fs_info.blockSize);
DEBUG_MSG_P(PSTR("[INIT] page size: %8u bytes\n"), fs_info.pageSize);
DEBUG_MSG_P(PSTR("[INIT] max files: %8u\n"), fs_info.maxOpenFiles);
DEBUG_MSG_P(PSTR("[INIT] max length: %8u\n"), fs_info.maxPathLength);
DEBUG_MSG_P(PSTR("[MAIN] SPIFFS total size: %8u bytes / %4d sectors\n"), fs_info.totalBytes, sectors(fs_info.totalBytes));
DEBUG_MSG_P(PSTR("[MAIN] used size: %8u bytes\n"), fs_info.usedBytes);
DEBUG_MSG_P(PSTR("[MAIN] block size: %8u bytes\n"), fs_info.blockSize);
DEBUG_MSG_P(PSTR("[MAIN] page size: %8u bytes\n"), fs_info.pageSize);
DEBUG_MSG_P(PSTR("[MAIN] max files: %8u\n"), fs_info.maxOpenFiles);
DEBUG_MSG_P(PSTR("[MAIN] max length: %8u\n"), fs_info.maxPathLength);
} else { } else {
DEBUG_MSG_P(PSTR("[INIT] No SPIFFS partition\n"));
DEBUG_MSG_P(PSTR("[MAIN] No SPIFFS partition\n"));
} }
DEBUG_MSG_P(PSTR("\n")); DEBUG_MSG_P(PSTR("\n"));
#endif #endif
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[INIT] BOARD: %s\n"), getBoardName().c_str());
DEBUG_MSG_P(PSTR("[INIT] SUPPORT: %s\n"), getEspurnaModules().c_str());
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("[INIT] SENSORS: %s\n"), getEspurnaSensors().c_str());
#endif // SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("[INIT] FIRMWARE IMAGE: %s\n"), ESPURNA_IMAGE_NAME);
DEBUG_MSG_P(PSTR("[INIT] WEBUI IMAGE: %s\n"), getEspurnaWebUI().c_str());
DEBUG_MSG_P(PSTR("[MAIN] EEPROM sectors: %s\n"), (char *) eepromSectors().c_str());
DEBUG_MSG_P(PSTR("[MAIN] EEPROM current: %lu\n"), eepromCurrent());
DEBUG_MSG_P(PSTR("\n")); DEBUG_MSG_P(PSTR("\n"));
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
infoMemory("EEPROM", SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE - settingsSize());
infoMemory("Heap", getInitialFreeHeap(), getFreeHeap());
infoMemory("Stack", 4096, getFreeStack());
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[MAIN] Boot version: %d\n"), ESP.getBootVersion());
DEBUG_MSG_P(PSTR("[MAIN] Boot mode: %d\n"), ESP.getBootMode());
unsigned char reason = resetReason(); unsigned char reason = resetReason();
if (reason > 0) { if (reason > 0) {
char buffer[32]; char buffer[32];
strcpy_P(buffer, custom_reset_string[reason-1]); strcpy_P(buffer, custom_reset_string[reason-1]);
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), buffer);
DEBUG_MSG_P(PSTR("[MAIN] Last reset reason: %s\n"), buffer);
} else { } else {
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), (char *) ESP.getResetReason().c_str());
DEBUG_MSG_P(PSTR("[MAIN] Last reset reason: %s\n"), (char *) ESP.getResetReason().c_str());
DEBUG_MSG_P(PSTR("[MAIN] Last reset info: %s\n"), (char *) ESP.getResetInfo().c_str());
} }
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[MAIN] Board: %s\n"), getBoardName().c_str());
DEBUG_MSG_P(PSTR("[MAIN] Support: %s\n"), getEspurnaModules().c_str());
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("[MAIN] Sensors: %s\n"), getEspurnaSensors().c_str());
#endif // SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("[MAIN] Firmware image: %s\n"), ESPURNA_IMAGE_NAME);
DEBUG_MSG_P(PSTR("[MAIN] WebUI image: %s\n"), getEspurnaWebUI().c_str());
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[INIT] Settings size: %u bytes\n"), settingsSize());
DEBUG_MSG_P(PSTR("[INIT] Free heap: %u bytes\n"), getFreeHeap());
DEBUG_MSG_P(PSTR("[MAIN] Firmware MD5: %s\n"), (char *) ESP.getSketchMD5().c_str());
#if ADC_MODE_VALUE == ADC_VCC #if ADC_MODE_VALUE == ADC_VCC
DEBUG_MSG_P(PSTR("[INIT] Power: %u mV\n"), ESP.getVcc());
DEBUG_MSG_P(PSTR("[MAIN] Power: %u mV\n"), ESP.getVcc());
#endif #endif
DEBUG_MSG_P(PSTR("[MAIN] Power saving delay value: %lu ms\n"), systemLoopDelay());
DEBUG_MSG_P(PSTR("[INIT] Power saving delay value: %lu ms\n"), systemLoopDelay());
// -------------------------------------------------------------------------
#if SYSTEM_CHECK_ENABLED #if SYSTEM_CHECK_ENABLED
if (!systemCheck()) DEBUG_MSG_P(PSTR("\n[INIT] Device is in SAFE MODE\n"));
if (!systemCheck()) {
DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[MAIN] Device is in SAFE MODE\n"));
}
#endif #endif
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("\n\n---8<-------\n\n"));
} }
@ -467,6 +532,7 @@ int __get_adc_mode() {
bool isNumber(const char * s) { bool isNumber(const char * s) {
unsigned char len = strlen(s); unsigned char len = strlen(s);
if (0 == len) return false;
bool decimal = false; bool decimal = false;
for (unsigned char i=0; i<len; i++) { for (unsigned char i=0; i<len; i++) {
if (s[i] == '-') { if (s[i] == '-') {


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

@ -42,7 +42,7 @@ void _wifiConfigure() {
#endif #endif
jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT); jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
wifiReconnectCheck(); wifiReconnectCheck();
jw.enableAPFallback(true);
jw.enableAPFallback(WIFI_FALLBACK_APMODE);
jw.cleanNetworks(); jw.cleanNetworks();
_wifi_ap_mode = getSetting("wifiMode", WIFI_AP_FALLBACK).toInt(); _wifi_ap_mode = getSetting("wifiMode", WIFI_AP_FALLBACK).toInt();
@ -387,6 +387,11 @@ void _wifiDebugCallback(justwifi_messages_t code, char * parameter) {
void _wifiInitCommands() { void _wifiInitCommands() {
settingsRegisterCommand(F("WIFI"), [](Embedis* e) {
wifiDebug();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("WIFI.RESET"), [](Embedis* e) { settingsRegisterCommand(F("WIFI.RESET"), [](Embedis* e) {
_wifiConfigure(); _wifiConfigure();
wifiDisconnect(); wifiDisconnect();
@ -621,7 +626,6 @@ void wifiSetup() {
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_wifiWebSocketOnSend); wsOnSendRegister(_wifiWebSocketOnSend);
wsOnAfterParseRegister(_wifiConfigure);
wsOnActionRegister(_wifiWebSocketOnAction); wsOnActionRegister(_wifiWebSocketOnAction);
#endif #endif
@ -633,6 +637,7 @@ void wifiSetup() {
// Register loop // Register loop
espurnaRegisterLoop(wifiLoop); espurnaRegisterLoop(wifiLoop);
espurnaRegisterReload(_wifiConfigure);
} }


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

@ -22,7 +22,6 @@ Ticker _web_defer;
std::vector<ws_on_send_callback_f> _ws_on_send_callbacks; std::vector<ws_on_send_callback_f> _ws_on_send_callbacks;
std::vector<ws_on_action_callback_f> _ws_on_action_callbacks; std::vector<ws_on_action_callback_f> _ws_on_action_callbacks;
std::vector<ws_on_after_parse_callback_f> _ws_on_after_parse_callbacks;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Private methods // Private methods
@ -250,7 +249,7 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
if (save) { if (save) {
// Callbacks // Callbacks
wsReload();
espurnaReload();
// This should got to callback as well // This should got to callback as well
// but first change management has to be in place // but first change management has to be in place
@ -310,7 +309,9 @@ void _wsOnStart(JsonObject& root) {
root["app_name"] = APP_NAME; root["app_name"] = APP_NAME;
root["app_version"] = APP_VERSION; root["app_version"] = APP_VERSION;
root["app_build"] = buildTime(); root["app_build"] = buildTime();
root["app_revision"] = APP_REVISION;
#if defined(APP_REVISION)
root["app_revision"] = APP_REVISION;
#endif
root["manufacturer"] = getManufacturer(); root["manufacturer"] = getManufacturer();
root["chipid"] = String(chipid); root["chipid"] = String(chipid);
root["mac"] = WiFi.macAddress(); root["mac"] = WiFi.macAddress();
@ -405,10 +406,6 @@ void wsOnActionRegister(ws_on_action_callback_f callback) {
_ws_on_action_callbacks.push_back(callback); _ws_on_action_callbacks.push_back(callback);
} }
void wsOnAfterParseRegister(ws_on_after_parse_callback_f callback) {
_ws_on_after_parse_callbacks.push_back(callback);
}
void wsSend(ws_on_send_callback_f callback) { void wsSend(ws_on_send_callback_f callback) {
if (_ws.count() > 0) { if (_ws.count() > 0) {
DynamicJsonBuffer jsonBuffer; DynamicJsonBuffer jsonBuffer;
@ -455,15 +452,6 @@ void wsSend_P(uint32_t client_id, PGM_P payload) {
_ws.text(client_id, buffer); _ws.text(client_id, buffer);
} }
// This method being public makes
// _ws_on_after_parse_callbacks strange here,
// it should belong somewhere else.
void wsReload() {
for (unsigned char i = 0; i < _ws_on_after_parse_callbacks.size(); i++) {
(_ws_on_after_parse_callbacks[i])();
}
}
void wsSetup() { void wsSetup() {
_ws.onEvent(_wsEvent); _ws.onEvent(_wsEvent);


+ 55
- 53
code/html/custom.js View File

@ -150,13 +150,17 @@ function validateForm(form) {
// http://www.the-art-of-web.com/javascript/validate-password/ // http://www.the-art-of-web.com/javascript/validate-password/
// at least one lowercase and one uppercase letter or number // at least one lowercase and one uppercase letter or number
// at least five characters (letters, numbers or special characters)
var re_password = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{5,}$/;
// at least eight characters (letters, numbers or special characters)
// MUST be 8..63 printable ASCII characters. See:
// https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access#Target_users_(authentication_key_distribution)
// https://github.com/xoseperez/espurna/issues/1151
var re_password = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{8,63}$/;
// password // password
var adminPass1 = $("input[name='adminPass']", form).first().val(); var adminPass1 = $("input[name='adminPass']", form).first().val();
if (adminPass1.length > 0 && !re_password.test(adminPass1)) { if (adminPass1.length > 0 && !re_password.test(adminPass1)) {
alert("The password you have entered is not valid, it must have at least 5 characters, 1 lowercase and 1 uppercase or number!");
alert("The password you have entered is not valid, it must be 8..63 characters and have at least 1 lowercase and 1 uppercase / number!");
return false; return false;
} }
@ -173,13 +177,13 @@ function validateForm(form) {
// No other symbols, punctuation characters, or blank spaces are permitted. // No other symbols, punctuation characters, or blank spaces are permitted.
// Negative lookbehind does not work in Javascript // Negative lookbehind does not work in Javascript
// var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{1,32}(?<!-)$');
// var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{1,31}(?<!-)$');
var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{0,31}[A-Za-z0-9]$');
var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{0,30}[A-Za-z0-9]$');
var hostname = $("input[name='hostname']", form); var hostname = $("input[name='hostname']", form);
var hasChanged = hostname.attr("hasChanged") || 0;
if (0 === hasChanged) {
var hasChanged = ("true" === hostname.attr("hasChanged"));
if (!hasChanged) {
return true; return true;
} }
@ -296,10 +300,18 @@ function sendConfig(data) {
websock.send(JSON.stringify({config: data})); websock.send(JSON.stringify({config: data}));
} }
function resetOriginals() {
function setOriginalsFromValues(force) {
var force = (true === force);
$("input,select").each(function() { $("input,select").each(function() {
$(this).attr("original", $(this).val());
var initial = (null === $(this).attr("original"));
if (force || initial) {
$(this).attr("original", $(this).val());
}
}); });
}
function resetOriginals() {
setOriginalsFromValues(true);
numReboot = numReconnect = numReload = 0; numReboot = numReconnect = numReload = 0;
} }
@ -895,31 +907,29 @@ function initMagnitudes(data) {
<!-- removeIf(!light)--> <!-- removeIf(!light)-->
function initColorRGB() {
function initColor(rgb) {
// check if already initialized // check if already initialized
var done = $("#colors > div").length; var done = $("#colors > div").length;
if (done > 0) { return; } if (done > 0) { return; }
// add template // add template
var template = $("#colorRGBTemplate").children();
var template = $("#colorTemplate").children();
var line = $(template).clone(); var line = $(template).clone();
line.appendTo("#colors"); line.appendTo("#colors");
// init color wheel // init color wheel
$("input[name='color']").wheelColorPicker({ $("input[name='color']").wheelColorPicker({
sliders: "wrgbp"
sliders: (rgb ? "wrgbp" : "whsvp")
}).on("sliderup", function() { }).on("sliderup", function() {
var value = $(this).wheelColorPicker("getValue", "css");
sendAction("color", {rgb: value});
});
// init bright slider
$("#brightness").on("change", function() {
var value = $(this).val();
var parent = $(this).parents(".pure-g");
$("span", parent).html(value);
sendAction("color", {brightness: value});
if (rgb) {
var value = $(this).wheelColorPicker("getValue", "css");
sendAction("color", {rgb: value});
} else {
var color = $(this).wheelColorPicker("getColor");
var value = parseInt(color.h * 360, 10) + "," + parseInt(color.s * 100, 10) + "," + parseInt(color.v * 100, 10);
sendAction("color", {hsv: value});
}
}); });
} }
@ -940,28 +950,6 @@ function initCCT() {
}); });
} }
function initColorHSV() {
// check if already initialized
var done = $("#colors > div").length;
if (done > 0) { return; }
// add template
var template = $("#colorHSVTemplate").children();
var line = $(template).clone();
line.appendTo("#colors");
// init color wheel
$("input[name='color']").wheelColorPicker({
sliders: "whsvp"
}).on("sliderup", function() {
var color = $(this).wheelColorPicker("getColor");
var value = parseInt(color.h * 360, 10) + "," + parseInt(color.s * 100, 10) + "," + parseInt(color.v * 100, 10);
sendAction("color", {hsv: value});
});
}
function initChannels(num) { function initChannels(num) {
// check if already initialized // check if already initialized
@ -992,7 +980,7 @@ function initChannels(num) {
sendAction("channel", {id: id, value: value}); sendAction("channel", {id: id, value: value});
}; };
// add templates
// add channel templates
var i = 0; var i = 0;
var template = $("#channelTemplate").children(); var template = $("#channelTemplate").children();
for (i=0; i<max; i++) { for (i=0; i<max; i++) {
@ -1007,11 +995,25 @@ function initChannels(num) {
} }
// Init channel dropdowns
for (i=0; i<num; i++) { for (i=0; i<num; i++) {
$("select.islight").append( $("select.islight").append(
$("<option></option>").attr("value",i).text("Channel #" + i)); $("<option></option>").attr("value",i).text("Channel #" + i));
} }
// add brightness template
var template = $("#brightnessTemplate").children();
var line = $(template).clone();
line.appendTo("#channels");
// init bright slider
$("#brightness").on("change", function() {
var value = $(this).val();
var parent = $(this).parents(".pure-g");
$("span", parent).html(value);
sendAction("brightness", {value: value});
});
} }
<!-- endRemoveIf(!light)--> <!-- endRemoveIf(!light)-->
@ -1180,13 +1182,13 @@ function processData(data) {
<!-- removeIf(!light)--> <!-- removeIf(!light)-->
if ("rgb" === key) { if ("rgb" === key) {
initColorRGB();
initColor(true);
$("input[name='color']").wheelColorPicker("setValue", value, true); $("input[name='color']").wheelColorPicker("setValue", value, true);
return; return;
} }
if ("hsv" === key) { if ("hsv" === key) {
initColorHSV();
initColor(false);
// wheelColorPicker expects HSV to be between 0 and 1 all of them // wheelColorPicker expects HSV to be between 0 and 1 all of them
var chunks = value.split(","); var chunks = value.split(",");
var obj = {}; var obj = {};
@ -1464,7 +1466,7 @@ function processData(data) {
generateAPIKey(); generateAPIKey();
} }
resetOriginals();
setOriginalsFromValues();
} }
@ -1478,27 +1480,27 @@ function hasChanged() {
newValue = $(this).val(); newValue = $(this).val();
originalValue = $(this).attr("original"); originalValue = $(this).attr("original");
} }
var hasChanged = $(this).attr("hasChanged") || 0;
var hasChanged = ("true" === $(this).attr("hasChanged"));
var action = $(this).attr("action"); var action = $(this).attr("action");
if (typeof originalValue === "undefined") { return; } if (typeof originalValue === "undefined") { return; }
if ("none" === action) { return; } if ("none" === action) { return; }
if (newValue !== originalValue) { if (newValue !== originalValue) {
if (0 === hasChanged) {
if (!hasChanged) {
++numChanged; ++numChanged;
if ("reconnect" === action) { ++numReconnect; } if ("reconnect" === action) { ++numReconnect; }
if ("reboot" === action) { ++numReboot; } if ("reboot" === action) { ++numReboot; }
if ("reload" === action) { ++numReload; } if ("reload" === action) { ++numReload; }
$(this).attr("hasChanged", 1);
$(this).attr("hasChanged", true);
} }
} else { } else {
if (1 === hasChanged) {
if (hasChanged) {
--numChanged; --numChanged;
if ("reconnect" === action) { --numReconnect; } if ("reconnect" === action) { --numReconnect; }
if ("reboot" === action) { --numReboot; } if ("reboot" === action) { --numReboot; }
if ("reload" === action) { --numReload; } if ("reload" === action) { --numReload; }
$(this).attr("hasChanged", 0);
$(this).attr("hasChanged", false);
} }
} }


+ 21
- 17
code/html/index.html View File

@ -43,11 +43,11 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Admin password</label> <label class="pure-u-1 pure-u-lg-1-4">Admin password</label>
<input name="adminPass" class="pure-u-1 pure-u-lg-3-4" type="password" tabindex="1" autocomplete="false" />
<input name="adminPass" class="pure-u-1 pure-u-lg-3-4" maxlength="63" type="password" tabindex="1" autocomplete="false" />
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"> <div class="pure-u-1 pure-u-lg-3-4 hint">
The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br /> The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br />
It must have at least <strong>five characters</strong> (numbers and letters and any of these special characters: _,.;:~!?@#$%^&amp;*&lt;&gt;\|(){}[]) and at least <strong>one lowercase</strong> and <strong>one uppercase</strong> or <strong>one number</strong>.</div>
It must be <strong>8..63 characters</strong> (numbers and letters and any of these special characters: _,.;:~!?@#$%^&amp;*&lt;&gt;\|(){}[]) and have at least <strong>one lowercase</strong> and <strong>one uppercase</strong> or <strong>one number</strong>.</div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
@ -173,6 +173,7 @@
<div class="footer"> <div class="footer">
&copy; 2016-2018<br /> &copy; 2016-2018<br />
Xose Pérez<br/> Xose Pérez<br/>
<a href="https://twitter.com/xoseperez" target="_blank">@xoseperez</a><br/>
<a href="http://tinkerman.cat" target="_blank">http://tinkerman.cat</a><br/> <a href="http://tinkerman.cat" target="_blank">http://tinkerman.cat</a><br/>
<a href="https://github.com/xoseperez/espurna" target="_blank">ESPurna @ GitHub</a><br/> <a href="https://github.com/xoseperez/espurna" target="_blank">ESPurna @ GitHub</a><br/>
GPLv3 license<br/> GPLv3 license<br/>
@ -323,7 +324,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Hostname</label> <label class="pure-u-1 pure-u-lg-1-4">Hostname</label>
<input name="hostname" class="pure-u-1 pure-u-lg-1-4" maxlength="32" type="text" action="reboot" tabindex="1" />
<input name="hostname" class="pure-u-1 pure-u-lg-1-4" maxlength="31" type="text" action="reboot" tabindex="1" />
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"> <div class="pure-u-1 pure-u-lg-3-4 hint">
@ -522,16 +523,16 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Admin password</label> <label class="pure-u-1 pure-u-lg-1-4">Admin password</label>
<input name="adminPass" class="pure-u-1 pure-u-lg-3-4" type="password" action="reboot" tabindex="11" autocomplete="false" />
<input name="adminPass" class="pure-u-1 pure-u-lg-3-4" maxlength="63" type="password" action="reboot" tabindex="11" autocomplete="false" />
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"> <div class="pure-u-1 pure-u-lg-3-4 hint">
The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br /> The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br />
It must have at least <strong>five characters</strong> (numbers and letters and any of these special characters: _,.;:~!?@#$%^&amp;*&lt;&gt;\|(){}[]) and at least <strong>one lowercase</strong> and <strong>one uppercase</strong> or <strong>one number</strong>.</div>
It must be <strong>8..63 characters</strong> (numbers and letters and any of these special characters: _,.;:~!?@#$%^&amp;*&lt;&gt;\|(){}[]) and have at least <strong>one lowercase</strong> and <strong>one uppercase</strong> or <strong>one number</strong>.</div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Repeat password</label> <label class="pure-u-1 pure-u-lg-1-4">Repeat password</label>
<input name="adminPass" class="pure-u-1 pure-u-lg-3-4" type="password" action="reboot" tabindex="12" autocomplete="false" />
<input name="adminPass" class="pure-u-1 pure-u-lg-3-4" maxlength="63" type="password" action="reboot" tabindex="12" autocomplete="false" />
</div> </div>
<div class="pure-g"> <div class="pure-g">
@ -1159,23 +1160,30 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Read interval</label> <label class="pure-u-1 pure-u-lg-1-4">Read interval</label>
<select class="pure-u-1 pure-u-lg-1-4" name="snsRead"> <select class="pure-u-1 pure-u-lg-1-4" name="snsRead">
<option value="1">1 second</option>
<option value="6">6 seconds</option> <option value="6">6 seconds</option>
<option value="10">10 seconds</option> <option value="10">10 seconds</option>
<option value="15">15 seconds</option> <option value="15">15 seconds</option>
<option value="30">30 seconds</option> <option value="30">30 seconds</option>
<option value="60">1 minute</option> <option value="60">1 minute</option>
<option value="300">5 minutes</option> <option value="300">5 minutes</option>
<option value="600">10 minutes</option>
<option value="900">15 minutes</option>
<option value="1800">30 minutes</option>
<option value="3600">60 minutes</option>
</select> </select>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"> <div class="pure-u-1 pure-u-lg-3-4 hint">
Select the interval between readings. These will be filtered and averaged for the report. The default and recommended value is 6 seconds.
Select the interval between readings. These will be filtered and averaged for the report.
Please mind some sensors do not have fast refresh intervals. Check the sensor datasheet to know the minimum read interval.
The default and recommended value is 6 seconds.
</div> </div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Report every</label> <label class="pure-u-1 pure-u-lg-1-4">Report every</label>
<div class="pure-u-1 pure-u-lg-1-4"><input name="snsReport" class="pure-u-1" type="number" min="1" step="1" max="12" /></div>
<div class="pure-u-1 pure-u-lg-1-4"><input name="snsReport" class="pure-u-1" type="number" min="1" step="1" max="60" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"> <div class="pure-u-1 pure-u-lg-3-4 hint">
@ -1360,7 +1368,7 @@
<label class="pure-u-1 pure-u-lg-1-4 more">Static IP</label> <label class="pure-u-1 pure-u-lg-1-4 more">Static IP</label>
<input class="pure-u-1 pure-u-lg-3-4 more" name="wifiIP" type="text" action="reconnect" value="" maxlength="15" tabindex="0" autocomplete="false" /> <input class="pure-u-1 pure-u-lg-3-4 more" name="wifiIP" type="text" action="reconnect" value="" maxlength="15" tabindex="0" autocomplete="false" />
<div class="pure-u-0 pure-u-lg-1-4 more"></div> <div class="pure-u-0 pure-u-lg-1-4 more"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint more">Leave empty for DNS negotiation</div>
<div class="pure-u-1 pure-u-lg-3-4 hint more">Leave empty for DHCP negotiation</div>
<label class="pure-u-1 pure-u-lg-1-4 more">Gateway IP</label> <label class="pure-u-1 pure-u-lg-1-4 more">Gateway IP</label>
<input class="pure-u-1 pure-u-lg-3-4 more" name="wifiGW" type="text" action="reconnect" value="" maxlength="15" tabindex="0" autocomplete="false" /> <input class="pure-u-1 pure-u-lg-3-4 more" name="wifiGW" type="text" action="reconnect" value="" maxlength="15" tabindex="0" autocomplete="false" />
@ -1530,12 +1538,15 @@
<!-- endRemoveIf(!sensor) --> <!-- endRemoveIf(!sensor) -->
<!-- removeIf(!light) --> <!-- removeIf(!light) -->
<div id="colorRGBTemplate" class="template">
<div id="colorTemplate" class="template">
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Color</label> <label class="pure-u-1 pure-u-lg-1-4">Color</label>
<input class="pure-u-1 pure-u-lg-1-4" data-wcp-layout="block" name="color" readonly /> <input class="pure-u-1 pure-u-lg-1-4" data-wcp-layout="block" name="color" readonly />
</div> </div>
</div>
<div id="brightnessTemplate" class="template">
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Brightness</label> <label class="pure-u-1 pure-u-lg-1-4">Brightness</label>
<input type="range" min="0" max="255" class="slider pure-u-lg-1-4" id="brightness"> <input type="range" min="0" max="255" class="slider pure-u-lg-1-4" id="brightness">
@ -1543,13 +1554,6 @@
</div> </div>
</div> </div>
<div id="colorHSVTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Color</label>
<input class="pure-u-1 pure-u-lg-1-4" data-wcp-layout="block" name="color" readonly />
</div>
</div>
<div id="channelTemplate" class="template"> <div id="channelTemplate" class="template">
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Channel #</label> <label class="pure-u-1 pure-u-lg-1-4">Channel #</label>


+ 32
- 1
code/memanalyzer.py View File

@ -105,6 +105,10 @@ def run(env_, modules_):
command = "ESPURNA_BOARD=\"WEMOS_D1_MINI_RELAYSHIELD\" ESPURNA_FLAGS=\"%s\" platformio run --silent --environment %s 2>/dev/null" % (flags, env_) command = "ESPURNA_BOARD=\"WEMOS_D1_MINI_RELAYSHIELD\" ESPURNA_FLAGS=\"%s\" platformio run --silent --environment %s 2>/dev/null" % (flags, env_)
subprocess.check_call(command, shell=True) subprocess.check_call(command, shell=True)
def calc_free(module):
free = 80 * 1024 - module['data'] - module['rodata'] - module['bss']
free = free + (16 - free % 16)
module['free'] = free
def modules_get(): def modules_get():
modules_ = SortedDict() modules_ = SortedDict()
@ -173,27 +177,50 @@ try:
else: else:
print("Analyzing %s configuration\n" % ("CORE" if args.core > 0 else "DEFAULT")) print("Analyzing %s configuration\n" % ("CORE" if args.core > 0 else "DEFAULT"))
output_format = "{:<20}|{:<11}|{:<11}|{:<11}|{:<11}|{:<11}|{:<12}"
output_format = "{:<20}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}"
print(output_format.format( print(output_format.format(
"Module", "Module",
"Cache IRAM", "Cache IRAM",
"Init RAM", "Init RAM",
"R.O. RAM", "R.O. RAM",
"Uninit RAM", "Uninit RAM",
"Available RAM",
"Flash ROM", "Flash ROM",
"Binary size" "Binary size"
)) ))
print(output_format.replace("<", ">").format(
"",
".text",
".data",
".rodata",
".bss",
"heap + stack",
".irom0.text",
""
))
print(output_format.format(
"-" * 20,
"-" * 15,
"-" * 15,
"-" * 15,
"-" * 15,
"-" * 15,
"-" * 15,
"-" * 15
))
# Build the core without modules to get base memory usage # Build the core without modules to get base memory usage
run(env, modules) run(env, modules)
base = analyse_memory(".pioenvs/%s/firmware.elf" % env) base = analyse_memory(".pioenvs/%s/firmware.elf" % env)
base['size'] = file_size(".pioenvs/%s/firmware.bin" % env) base['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
calc_free(base)
print(output_format.format( print(output_format.format(
"CORE" if args.core == 1 else "DEFAULT", "CORE" if args.core == 1 else "DEFAULT",
base['text'], base['text'],
base['data'], base['data'],
base['rodata'], base['rodata'],
base['bss'], base['bss'],
base['free'],
base['irom0_text'], base['irom0_text'],
base['size'], base['size'],
)) ))
@ -206,6 +233,7 @@ try:
run(env, modules) run(env, modules)
results[module] = analyse_memory(".pioenvs/%s/firmware.elf" % env) results[module] = analyse_memory(".pioenvs/%s/firmware.elf" % env)
results[module]['size'] = file_size(".pioenvs/%s/firmware.bin" % env) results[module]['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
calc_free(results[module])
modules[module] = 0 modules[module] = 0
print(output_format.format( print(output_format.format(
@ -214,6 +242,7 @@ try:
results[module]['data'] - base['data'], results[module]['data'] - base['data'],
results[module]['rodata'] - base['rodata'], results[module]['rodata'] - base['rodata'],
results[module]['bss'] - base['bss'], results[module]['bss'] - base['bss'],
results[module]['free'] - base['free'],
results[module]['irom0_text'] - base['irom0_text'], results[module]['irom0_text'] - base['irom0_text'],
results[module]['size'] - base['size'], results[module]['size'] - base['size'],
)) ))
@ -226,6 +255,7 @@ try:
run(env, modules) run(env, modules)
total = analyse_memory(".pioenvs/%s/firmware.elf" % env) total = analyse_memory(".pioenvs/%s/firmware.elf" % env)
total['size'] = file_size(".pioenvs/%s/firmware.bin" % env) total['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
calc_free(total)
if len(test_modules) > 1: if len(test_modules) > 1:
print(output_format.format( print(output_format.format(
@ -234,6 +264,7 @@ try:
total['data'] - base['data'], total['data'] - base['data'],
total['rodata'] - base['rodata'], total['rodata'] - base['rodata'],
total['bss'] - base['bss'], total['bss'] - base['bss'],
total['free'] - base['free'],
total['irom0_text'] - base['irom0_text'], total['irom0_text'] - base['irom0_text'],
total['size'] - base['size'], total['size'] - base['size'],
)) ))


+ 85
- 7
code/platformio.ini View File

@ -1,5 +1,5 @@
[platformio] [platformio]
env_default = wemos-d1mini-relayshield
env_default = nodemcu-lolin
src_dir = espurna src_dir = espurna
data_dir = espurna/data data_dir = espurna/data
@ -12,10 +12,12 @@ data_dir = espurna/data
# platformIO 1.5.0 = arduino core 2.3.0 # platformIO 1.5.0 = arduino core 2.3.0
# platformIO 1.6.0 = arduino core 2.4.0 # platformIO 1.6.0 = arduino core 2.4.0
# platformIO 1.7.3 = arduino core 2.4.1 # platformIO 1.7.3 = arduino core 2.4.1
# platformIO 1.8.0 = arduino core 2.4.2
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
platform_150 = espressif8266@1.5.0 platform_150 = espressif8266@1.5.0
platform_160 = espressif8266@1.6.0 platform_160 = espressif8266@1.6.0
platform_173 = espressif8266@1.7.3 platform_173 = espressif8266@1.7.3
platform_180 = espressif8266@1.8.0
platform = ${common.platform_150} platform = ${common.platform_150}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -72,20 +74,21 @@ lib_deps =
ArduinoJson ArduinoJson
https://github.com/marvinroger/async-mqtt-client#v0.8.1 https://github.com/marvinroger/async-mqtt-client#v0.8.1
Brzo I2C Brzo I2C
https://bitbucket.org/xoseperez/debounceevent.git#2.0.1
https://github.com/xoseperez/debounceevent.git#2.0.4
https://github.com/xoseperez/eeprom_rotate#0.9.1 https://github.com/xoseperez/eeprom_rotate#0.9.1
Embedis Embedis
Encoder
https://github.com/plerup/espsoftwareserial#3.4.1 https://github.com/plerup/espsoftwareserial#3.4.1
https://github.com/me-no-dev/ESPAsyncTCP#55cd520 https://github.com/me-no-dev/ESPAsyncTCP#55cd520
https://github.com/me-no-dev/ESPAsyncWebServer#05306e4 https://github.com/me-no-dev/ESPAsyncWebServer#05306e4
https://bitbucket.org/xoseperez/fauxmoesp.git#3.0.0
https://bitbucket.org/xoseperez/fauxmoesp.git#3.0.1
https://github.com/xoseperez/hlw8012.git#1.1.0 https://github.com/xoseperez/hlw8012.git#1.1.0
https://github.com/markszabo/IRremoteESP8266#v2.2.0 https://github.com/markszabo/IRremoteESP8266#v2.2.0
https://github.com/xoseperez/justwifi.git#2.0.1
https://github.com/xoseperez/justwifi.git#2.0.2
https://github.com/madpilot/mDNSResolver#4cfcda1 https://github.com/madpilot/mDNSResolver#4cfcda1
https://github.com/xoseperez/my92xx#3.0.1 https://github.com/xoseperez/my92xx#3.0.1
https://bitbucket.org/xoseperez/nofuss.git#0.2.5 https://bitbucket.org/xoseperez/nofuss.git#0.2.5
https://github.com/xoseperez/NtpClient.git#0016a59
https://github.com/xoseperez/NtpClient.git#0942ebc
OneWire OneWire
PZEM004T PZEM004T
PubSubClient PubSubClient
@ -2541,7 +2544,7 @@ extra_scripts = ${common.extra_scripts}
[env:allterco-shelly1] [env:allterco-shelly1]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
board = ${common.board_1m}
board = ${common.board_2m}
board_build.flash_mode = ${common.flash_mode} board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}
@ -2552,7 +2555,7 @@ extra_scripts = ${common.extra_scripts}
[env:allterco-shelly1-ota] [env:allterco-shelly1-ota]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
board = ${common.board_1m}
board = ${common.board_2m}
board_build.flash_mode = ${common.flash_mode} board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}
@ -2562,3 +2565,78 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:allterco-shelly2]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_2m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_2m1m} -DALLTERCO_SHELLY2
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:allterco-shelly2-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_2m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_2m1m} -DALLTERCO_SHELLY2
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:xiaomi-smart-desk-lamp]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DXIAOMI_SMART_DESK_LAMP
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:xiaomi-smart-desk-lamp-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DXIAOMI_SMART_DESK_LAMP
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:phyx-esp12-rgb]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DPHYX_ESP12_RGB
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:phyx-esp12-rgb-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DPHYX_ESP12_RGB
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}

BIN
images/devices/itead-s26.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 18 KiB

+ 1
- 3
pre-commit View File

@ -84,9 +84,7 @@ TEMPLATES = {
"(https://travis-ci.org/{USER}/{REPO})\n", "(https://travis-ci.org/{USER}/{REPO})\n",
"![version]": "[![version](https://img.shields.io/badge/version-{VERSION}-brightgreen.svg)](CHANGELOG.md)\n", "![version]": "[![version](https://img.shields.io/badge/version-{VERSION}-brightgreen.svg)](CHANGELOG.md)\n",
"![branch]": "[![branch](https://img.shields.io/badge/branch-{BRANCH}-orange.svg)]" \ "![branch]": "[![branch](https://img.shields.io/badge/branch-{BRANCH}-orange.svg)]" \
"(https://github.com/{USER}/{REPO}/tree/{BRANCH}/)\n",
"![codacy]": "[![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/{BRANCH}.svg)]" \
"(https://www.codacy.com/app/{USER}/{REPO}/dashboard)\n"
"(https://github.com/{USER}/{REPO}/tree/{BRANCH}/)\n"
} }
README = "README.md" README = "README.md"


Loading…
Cancel
Save