Browse Source

Merge pull request #5 from xoseperez/dev

sync to upstream
ota
Colin Shorts 6 years ago
committed by GitHub
parent
commit
e7117b9e5f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 20238 additions and 18270 deletions
  1. +2
    -2
      .github/stale.yml
  2. +39
    -0
      CHANGELOG.md
  3. +29
    -14
      README.md
  4. +7
    -2
      code/build.sh
  5. +1
    -1
      code/eagle.flash.1m0m1s.ld
  6. +3
    -3
      code/eagle.flash.1m0m2s.ld
  7. +20
    -0
      code/eagle.flash.2m1m4s.ld
  8. +1
    -1
      code/eagle.flash.4m1m4s.ld
  9. +1
    -1
      code/eagle.flash.4m3m4e.ld
  10. +1
    -1
      code/eagle.flash.512k0m1s.ld
  11. +73
    -32
      code/espurna/alexa.ino
  12. +2
    -1
      code/espurna/config/all.h
  13. +10
    -0
      code/espurna/config/arduino.h
  14. +139
    -3
      code/espurna/config/defaults.h
  15. +0
    -51
      code/espurna/config/dependencies.h
  16. +80
    -13
      code/espurna/config/general.h
  17. +230
    -11
      code/espurna/config/hardware.h
  18. +6
    -0
      code/espurna/config/progmem.h
  19. +3
    -4
      code/espurna/config/prototypes.h
  20. +39
    -20
      code/espurna/config/sensors.h
  21. +8
    -0
      code/espurna/config/types.h
  22. +1
    -1
      code/espurna/config/version.h
  23. +73
    -0
      code/espurna/config/webui.h
  24. BIN
      code/espurna/data/index.all.html.gz
  25. BIN
      code/espurna/data/index.light.html.gz
  26. BIN
      code/espurna/data/index.rfbridge.html.gz
  27. BIN
      code/espurna/data/index.rfm69.html.gz
  28. BIN
      code/espurna/data/index.sensor.html.gz
  29. BIN
      code/espurna/data/index.small.html.gz
  30. +6
    -1
      code/espurna/domoticz.ino
  31. +4
    -0
      code/espurna/eeprom.ino
  32. +156
    -0
      code/espurna/encoder.ino
  33. +16
    -2
      code/espurna/espurna.ino
  34. +40
    -0
      code/espurna/filters/LastFilter.h
  35. +22
    -10
      code/espurna/homeassistant.ino
  36. +10
    -5
      code/espurna/influxdb.ino
  37. +341
    -35
      code/espurna/ir.ino
  38. +2
    -2
      code/espurna/led.ino
  39. +90
    -77
      code/espurna/light.ino
  40. +102
    -0
      code/espurna/migrate.ino
  41. +14
    -6
      code/espurna/mqtt.ino
  42. +2
    -2
      code/espurna/nofuss.ino
  43. +2
    -2
      code/espurna/ntp.ino
  44. +4
    -7
      code/espurna/ota.ino
  45. +19
    -17
      code/espurna/pwm.c
  46. +4
    -3
      code/espurna/relay.ino
  47. +2
    -2
      code/espurna/rfm69.ino
  48. +2
    -2
      code/espurna/scheduler.ino
  49. +131
    -36
      code/espurna/sensor.ino
  50. +2
    -2
      code/espurna/sensors/CSE7766Sensor.h
  51. +10
    -2
      code/espurna/sensors/DHTSensor.h
  52. +15
    -0
      code/espurna/sensors/ECH1560Sensor.h
  53. +5
    -0
      code/espurna/sensors/EmonSensor.h
  54. +4
    -2
      code/espurna/sensors/HLW8012Sensor.h
  55. +3
    -0
      code/espurna/sensors/PMSX003Sensor.h
  56. +12
    -1
      code/espurna/sensors/PZEM004TSensor.h
  57. +173
    -0
      code/espurna/sensors/SDS011Sensor.h
  58. +0
    -2
      code/espurna/sensors/SI7021Sensor.h
  59. +21
    -6
      code/espurna/sensors/V9261FSensor.h
  60. +1
    -5
      code/espurna/settings.ino
  61. +3105
    -3046
      code/espurna/static/index.all.html.gz.h
  62. +2966
    -2962
      code/espurna/static/index.light.html.gz.h
  63. +2562
    -2558
      code/espurna/static/index.rfbridge.html.gz.h
  64. +4035
    -4031
      code/espurna/static/index.rfm69.html.gz.h
  65. +2616
    -2601
      code/espurna/static/index.sensor.html.gz.h
  66. +2521
    -2517
      code/espurna/static/index.small.html.gz.h
  67. +41
    -3
      code/espurna/telnet.ino
  68. +2
    -2
      code/espurna/thinkspeak.ino
  69. +71
    -45
      code/espurna/utils.ino
  70. +2
    -2
      code/espurna/web.ino
  71. +16
    -5
      code/espurna/wifi.ino
  72. +2
    -17
      code/espurna/ws.ino
  73. +0
    -4
      code/extra_scripts.py
  74. +3
    -3
      code/gulpfile.js
  75. +60
    -53
      code/html/custom.js
  76. +38
    -22
      code/html/index.html
  77. +8
    -2
      code/ota.py
  78. +206
    -4
      code/platformio.ini
  79. BIN
      images/devices/ag-l4.jpg
  80. BIN
      images/devices/allterco-shelly1.jpg
  81. BIN
      images/devices/blitzwolf-bw-shp2.jpg
  82. BIN
      images/devices/homecube-16a.jpg
  83. BIN
      images/devices/itead-s26.jpg
  84. BIN
      images/devices/lohas-9w.jpg
  85. +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
markComment: >
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.
# Comment to post when removing the stale label.
@ -32,7 +32,7 @@ markComment: >
# Comment to post when closing a stale Issue or Pull Request.
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
limitPerRun: 30


+ 39
- 0
CHANGELOG.md View File

@ -3,6 +3,45 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.13.2] 2018-08-27
### Fixed
- Fix relay overflow window length
- Fix TravisCI release condition (thanks to @mcspr, [#1042](https://github.com/xoseperez/espurna/issues/1042))
- Fix Sonoff RFBridge build in Arduino IDE ([#1043](https://github.com/xoseperez/espurna/issues/1043))
- Using corrent path separator in gulpfile.js (thanks to @InduPrakash, [#1045](https://github.com/xoseperez/espurna/issues/1045))
- Fix KMC70011 LED logic (thanks to @zerog2k, [#1056](https://github.com/xoseperez/espurna/issues/1056))
- Fix Luani HVIO to use 1MB flash size and toggle switch (thanks to @BauerPh, [#1065](https://github.com/xoseperez/espurna/issues/1065) and [#1068](https://github.com/xoseperez/espurna/issues/1068))
- Fix switches in Microsoft Edge (thanks to @Valcob, [#1066](https://github.com/xoseperez/espurna/issues/1066))
- Fix build.sh error handling (thanks to @mcspr, [#1075](https://github.com/xoseperez/espurna/issues/1075))
- Correctly init Serial on RELAY_PROVIDER_STM ([#1130](https://github.com/xoseperez/espurna/issues/1130))
- Disconnect before running WPS and SmartConfig discovery ([#1146](https://github.com/xoseperez/espurna/issues/1146))
- Fix sort fields in OTA manager
### Added
- Support for YJZK 1Ch and 3CH switches (thanks to @CollinShorts and @q32103940, [#1047](https://github.com/xoseperez/espurna/issues/1047))
- Support for AG-L4 color desk lamp (thanks to @zerog2k, [#1050](https://github.com/xoseperez/espurna/issues/1050))
- Option to cofigure ON/OFF payload at build time ([#1085](https://github.com/xoseperez/espurna/issues/1085))
- Option to change default payload for HA ([#1085](https://github.com/xoseperez/espurna/issues/1085))
- Support for Allterco Shelly1 (thanks to @abmantis, [#1128](https://github.com/xoseperez/espurna/issues/1128))
- Support for HomeCube 16A (thanks to @hyteoo, [#1106](https://github.com/xoseperez/espurna/issues/1106))
- Support for multiple sonar sensors (thanks to @ruimarinho, [#1116](https://github.com/xoseperez/espurna/issues/1116))
- Support for hardware serial on PMSX003 device (thanks to @ruimarinho, [#1122](https://github.com/xoseperez/espurna/issues/1122))
- Support for Lohas 9W bulbs (thanks to @steveway, [#1135](https://github.com/xoseperez/espurna/issues/1135))
- Show literal for webUI image in info ([#1142](https://github.com/xoseperez/espurna/issues/1142))
- Add RFBRIDGE code to full webUI image ([#1157](https://github.com/xoseperez/espurna/issues/1157))
- Handle events in EventSensor
- Option to remove API_SUPPORT at build time
- Option to save total energy in EEPROM after X reports, disabled by default
- Support for DHT12 sensor (thanks to Altan Altay)
- Support for 2MB flash boards
### Changed
- Update PlatformIO support to 3.6.X branch
- Explicitly disable ATC on RFM69 gateway ([#938](https://github.com/xoseperez/espurna/issues/938))
- Reduce memory footprint of API calls ([#1133](https://github.com/xoseperez/espurna/issues/1133))
- Init relay GPIO when in inverse mode to be OFF ([#1078](https://github.com/xoseperez/espurna/issues/1078))
## [1.13.1] 2018-07-10
### Fixed
- Build issues with Arduino IDE ([#975](https://github.com/xoseperez/espurna/issues/975))


+ 29
- 14
README.md View File

@ -3,10 +3,10 @@
ESPurna ("spark" in Catalan) is a custom firmware for ESP8285/ESP8266 based smart switches, lights and sensors.
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
[![version](https://img.shields.io/badge/version-1.13.2b-brightgreen.svg)](CHANGELOG.md)
[![version](https://img.shields.io/badge/version-1.13.3a-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-dev-orange.svg)](https://github.com/xoseperez/espurna/tree/dev/)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=dev)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/dev.svg)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![codacy](https://api.codacy.com/project/badge/Grade/c9496e25cf07434cba786b462cb15f49)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE)
<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)
@ -15,8 +15,16 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
---
## 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 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**.
## 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.
## Features
@ -68,7 +76,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Support for [direct control of the encoder/decoder bypassing the EFM8BB1](https://github.com/xoseperez/espurna/wiki/Hardware-Itead-Sonoff-RF-Bridge---Direct-Hack)
* Support for [different **sensors**](Sensors)
* Environment
* **DHT11 / DHT22 / DHT21 / AM2301 / Itead's SI7021**
* **DHT11 / DHT12 / DHT22 / DHT21 / AM2301 / Itead's SI7021**
* **BMP280** and **BME280** temperature, humidity (BME280) and pressure sensor by Bosch
* **TMP35** and **TMP36** analog temperature sensors
* **NTC** temperature sensors
@ -82,6 +90,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* **BH1750** luminosity sensor
* **GUVAS12SD** UV sensor
* **GEIGER COUNTER** by RH Electronics
* **HC-SR04**, **SRF05**, **SRF06**, **DYP-ME007**, **JSN-SR04T** & **Parallax PING)))™** distance sensors
* Power monitoring
* **HLW8012** using the [HLW8012 Library](https://bitbucket.org/xoseperez/hlw8012) (Sonoff POW)
* **CSE7766** and **CSE7759B** power monitor chips
@ -89,8 +98,8 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Non-invasive **current sensor** using **internal ADC** or **ADC712** or **ADC121** or **ADS1115**
* **V9261F** power monitor chip
* **PZEM0004T** power monitor board
* Raw analog and digital sensors
* Simple pulse counter
* Raw **analog** and **digital** sensors
* Simple **pulse counter** with **event triggering** option
* Support for (almost) any UART based sensor via the **UART-to-MQTT module**
* Support for different units (Fahrenheit or Celsius, Watts or Kilowatts, Joules or kWh)
* Support for LED lights
@ -215,29 +224,33 @@ 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**|||
|![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**|**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**|**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**|**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**|**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**|**HEYGO HY02**|**YiDian XS-SSA05**|
|![WiOn 50055](images/devices/wion-50055.jpg)|![LINGAN SWA1](images/devices/lingan-swa1.jpg)||
|**WiOn 50055**|**LINGAN SWA1**||
|![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**|
|![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**|**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 2-gang switch](images/devices/yjzk-2gang-switch.jpg)|
|**Itead Sonoff Touch**|**Itead Sonoff T1**|**YJZK 2-gang switch**|
|![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**|**Itead Sonoff T1**|**YJZK 1/2/3-gangs switch**|
|![Itead Slampher](images/devices/itead-slampher.jpg)|![Arilux E27](images/devices/arilux-e27.jpg)|![Itead Sonoff B1](images/devices/itead-sonoff-b1.jpg)|
|**Itead Slampher**|**Arilux E27**|**Itead Sonoff B1**|
|![AI-Thinker Wifi Light / Noduino OpenLight](images/devices/aithinker-ai-light.jpg)|![Authometion LYT8266](images/devices/authometion-lyt8266.jpg)||
|**AI-Thinker Wifi Light / Noduino OpenLight**|**Authometion LYT8266**||
|![AI-Thinker Wifi Light / Noduino OpenLight](images/devices/aithinker-ai-light.jpg)|![Authometion LYT8266](images/devices/authometion-lyt8266.jpg)|![AG-L4](images/devices/ag-l4.jpg)|
|**AI-Thinker Wifi Light / Noduino OpenLight**|**Authometion LYT8266**|**AG-L4**|
|![Lohas 9W](images/devices/lohas-9w.jpg)|||
|**Lohas 9W**|||
|![Itead Sonoff LED](images/devices/itead-sonoff-led.jpg)|![Itead BN-SZ01](images/devices/itead-bn-sz01.jpg)|![InterMitTech QuinLED 2.6](images/devices/intermittech-quinled-2.6.jpg)|
|**Itead Sonoff LED**|**Itead BN-SZ01**|**InterMitTech QuinLED 2.6**|
|![Arilux AL-LC01 (RGB)](images/devices/arilux-al-lc01.jpg)|![Arilux AL-LC02 (RGBW)](images/devices/arilux-al-lc02.jpg)|![Arilux AL-LC06 (RGBWWCW)](images/devices/arilux-al-lc06.jpg)|
@ -248,6 +261,8 @@ Here is the list of supported hardware. For more information please refer to the
|**Itead Sonoff SV**|**Itead 1CH Inching**|**Itead Motor Clockwise/Anticlockwise**|
|![Jan Goedeke Wifi Relay (NO/NC)](images/devices/jangoe-wifi-relay.jpg)|![Jorge García Wifi + Relays Board Kit](images/devices/jorgegarcia-wifi-relays.jpg)|![EXS Wifi Relay v3.1](images/devices/exs-wifi-relay-v31.jpg)|
|**Jan Goedeke Wifi Relay (NO/NC)**|**Jorge García Wifi + Relays Board Kit**|**EXS Wifi Relay v3.1**|
|![Allterco Shelly1](images/devices/allterco-shelly1.jpg)|||
|**Alterco Shelly1**|||
|![ManCaveMade ESP-Live](images/devices/mancavemade-esp-live.jpg)|![Wemos D1 Mini Relay Shield](images/devices/wemos-d1-mini-relayshield.jpg)|![Witty Cloud](images/devices/witty-cloud.jpg)|
|**ManCaveMade ESP-Live**|**Wemos D1 Mini Relay Shield**|**Witty Cloud**|
|![IKE ESPike](images/devices/ike-espike.jpg)|![Pilotak ESP DIN](images/devices/pilotak-esp-din.jpg)|![Arniex Swifitch](images/devices/arniex-swifitch.jpg)|


+ 7
- 2
code/build.sh View File

@ -10,6 +10,8 @@ is_git() {
}
# Script settings
destination=../firmware
version=$(grep APP_VERSION espurna/config/version.h | awk '{print $3}' | sed 's/"//g')
if is_git; then
@ -104,13 +106,13 @@ build_environments() {
platformio run --silent --environment $environment || exit 1
stat -c %s .pioenvs/$environment/firmware.bin
[[ "${TRAVIS_BUILD_STAGE_NAME}" = "Test" ]] || \
mv .pioenvs/$environment/firmware.bin ../firmware/espurna-$version/espurna-$version-$environment.bin
mv .pioenvs/$environment/firmware.bin $destination/espurna-$version/espurna-$version-$environment.bin
done
echo "--------------------------------------------------------------"
}
# Parameters
while getopts "lp" opt; do
while getopts "lpd:" opt; do
case $opt in
l)
print_available
@ -119,6 +121,9 @@ while getopts "lp" opt; do
p)
par_build=true
;;
d)
destination=$OPTARG
;;
esac
done


+ 1
- 1
code/eagle.flash.1m0m1s.ld View File

@ -1,4 +1,4 @@
/* Flash Split for 1M chips */
/* Flash Split for 1M chips, no SPIFFS, 1 sector for EEPROM */
/* sketch 999KB */
/* eeprom 4KB */
/* reserved 16KB */


+ 3
- 3
code/eagle.flash.1m0m2s.ld View File

@ -1,4 +1,4 @@
/* Flash Split for 1M chips, no SPIFFS */
/* Flash Split for 1M chips, no SPIFFS, 2 sectors for EEPROM */
/* sketch 995KB */
/* eeprom 8KB */
/* reserved 16KB */
@ -13,7 +13,7 @@ MEMORY
PROVIDE ( _SPIFFS_start = 0x402FA000 );
PROVIDE ( _SPIFFS_end = 0x402FA000 );
PROVIDE ( _SPIFFS_page = 0 );
PROVIDE ( _SPIFFS_block = 0 );
PROVIDE ( _SPIFFS_page = 0x0 );
PROVIDE ( _SPIFFS_block = 0x0 );
INCLUDE "../ld/eagle.app.v6.common.ld"

+ 20
- 0
code/eagle.flash.2m1m4s.ld View File

@ -0,0 +1,20 @@
/* Flash Split for 2M chips, ~1M SPIFFS, 4 sectors for EEPROM */
/* sketch 1019KB */
/* spiffs 992KB */
/* eeprom 16KB */
/* reserved 16KB */
MEMORY
{
dport0_0_seg : org = 0x3FF00000, len = 0x10
dram0_0_seg : org = 0x3FFE8000, len = 0x14000
iram1_0_seg : org = 0x40100000, len = 0x8000
irom0_0_seg : org = 0x40201010, len = 0xfeff0
}
PROVIDE ( _SPIFFS_start = 0x40300000 );
PROVIDE ( _SPIFFS_end = 0x403F8000 );
PROVIDE ( _SPIFFS_page = 0x100 );
PROVIDE ( _SPIFFS_block = 0x2000 );
INCLUDE "../ld/eagle.app.v6.common.ld"

+ 1
- 1
code/eagle.flash.4m1m4s.ld View File

@ -1,4 +1,4 @@
/* Flash Split for 4M chips */
/* Flash Split for 4M chips, ~1M for SPIFFS, 4 sectors for EEPROM */
/* sketch 1019KB */
/* empty/ota? 2048KB */
/* spiffs 992KB */


+ 1
- 1
code/eagle.flash.4m3m4e.ld View File

@ -1,4 +1,4 @@
/* Flash Split for 4M chips */
/* Flash Split for 4M chips, ~3M for SPIFFS, 4 sectors for EEPROM */
/* sketch 1019KB */
/* spiffs 3040KB */
/* eeprom 16KB */


+ 1
- 1
code/eagle.flash.512k0m1s.ld View File

@ -1,4 +1,4 @@
/* Flash Split for 512K chips */
/* Flash Split for 512K chips, no SPIFFS, 1 sector for EEPROM */
/* sketch 487KB */
/* eeprom 4KB */
/* reserved 16KB */


+ 73
- 32
code/espurna/alexa.ino View File

@ -11,13 +11,13 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <fauxmoESP.h>
fauxmoESP alexa;
struct AlexaDevChange {
AlexaDevChange(unsigned char device_id, bool state) : device_id(device_id), state(state) {};
unsigned char device_id = 0;
bool state = false;
};
#include <queue>
static std::queue<AlexaDevChange> _alexa_dev_changes;
typedef struct {
unsigned char device_id;
bool state;
unsigned char value;
} alexa_queue_element_t;
static std::queue<alexa_queue_element_t> _alexa_queue;
// -----------------------------------------------------------------------------
// ALEXA
@ -29,15 +29,19 @@ bool _alexaWebSocketOnReceive(const char * key, JsonVariant& value) {
void _alexaWebSocketOnSend(JsonObject& root) {
root["alexaVisible"] = 1;
root["alexaEnabled"] = getSetting("alexaEnabled", ALEXA_ENABLED).toInt() == 1;
root["alexaEnabled"] = alexaEnabled();
}
void _alexaConfigure() {
alexa.enable(getSetting("alexaEnabled", ALEXA_ENABLED).toInt() == 1);
alexa.enable(wifiConnected() && alexaEnabled());
}
// -----------------------------------------------------------------------------
bool alexaEnabled() {
return (getSetting("alexaEnabled", ALEXA_ENABLED).toInt() == 1);
}
void alexaSetup() {
// Backwards compatibility
@ -46,36 +50,60 @@ void alexaSetup() {
// Load & cache settings
_alexaConfigure();
#if WEB_SUPPORT
// Uses hostname as base name for all devices
// TODO: use custom switch name when available
String hostname = getSetting("hostname");
// Lights
#if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
// Global switch
alexa.addDevice(hostname.c_str());
// For each channel
for (unsigned char i = 1; i <= lightChannels(); i++) {
alexa.addDevice((hostname + " " + i).c_str());
}
// Relays
#else
unsigned int relays = relayCount();
if (relays == 1) {
alexa.addDevice(hostname.c_str());
} else {
for (unsigned int i=1; i<=relays; i++) {
alexa.addDevice((hostname + " " + i).c_str());
}
}
// Websockets
#endif
// Websockets
#if WEB_SUPPORT
wsOnSendRegister(_alexaWebSocketOnSend);
wsOnAfterParseRegister(_alexaConfigure);
wsOnReceiveRegister(_alexaWebSocketOnReceive);
#endif
unsigned int relays = relayCount();
String hostname = getSetting("hostname");
if (relays == 1) {
alexa.addDevice(hostname.c_str());
} else {
for (unsigned int i=0; i<relays; i++) {
alexa.addDevice((hostname + "_" + i).c_str());
// Register wifi callback
wifiRegister([](justwifi_messages_t code, char * parameter) {
if ((MESSAGE_CONNECTED == code) || (MESSAGE_DISCONNECTED == code)) {
_alexaConfigure();
}
}
alexa.onSetState([&](unsigned char device_id, const char * name, bool state) {
AlexaDevChange change(device_id, state);
_alexa_dev_changes.push(change);
});
alexa.onGetState([](unsigned char device_id, const char * name) {
return relayStatus(device_id);
// Callback
alexa.onSetState([&](unsigned char device_id, const char * name, bool state, unsigned char value) {
alexa_queue_element_t element;
element.device_id = device_id;
element.state = state;
element.value = value;
_alexa_queue.push(element);
});
// Register loop
// Register main callbacks
espurnaRegisterLoop(alexaLoop);
espurnaRegisterReload(_alexaConfigure);
}
@ -83,11 +111,24 @@ void alexaLoop() {
alexa.handle();
while (!_alexa_dev_changes.empty()) {
AlexaDevChange& change = _alexa_dev_changes.front();
DEBUG_MSG_P(PSTR("[ALEXA] Device #%u state: %s\n"), change.device_id, change.state ? "ON" : "OFF");
relayStatus(change.device_id, change.state);
_alexa_dev_changes.pop();
while (!_alexa_queue.empty()) {
alexa_queue_element_t element = _alexa_queue.front();
DEBUG_MSG_P(PSTR("[ALEXA] Device #%u state: %s value: %d\n"), element.device_id, element.state ? "ON" : "OFF", element.value);
#if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
if (0 == element.device_id) {
relayStatus(0, element.state);
} else {
lightState(element.device_id - 1, element.state);
lightChannel(element.device_id - 1, element.value);
lightUpdate(true, true);
}
#else
relayStatus(element.device_id, element.state);
#endif
_alexa_queue.pop();
}
}


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

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


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

@ -91,6 +91,13 @@
//#define BH_ONOFRE
//#define ITEAD_SONOFF_IFAN02
//#define GENERIC_AG_L4
//#define ALLTERCO_SHELLY1
//#define LOHAS_9W
//#define YJZK_SWITCH_1CH
//#define YJZK_SWITCH_3CH
//#define XIAOMI_SMART_DESK_LAMP
//#define ALLTERCO_SHELLY2
//#define PHYX_ESP12_RGB
//--------------------------------------------------------------------------------
// Features (values below are non-default values)
@ -105,6 +112,7 @@
//#define DEBUG_UDP_SUPPORT 1
//#define DEBUG_WEB_SUPPORT 0
//#define DOMOTICZ_SUPPORT 0
//#define ENCODER_SUPPORT 1
//#define HOMEASSISTANT_SUPPORT 0
//#define I2C_SUPPORT 1
//#define INFLUXDB_SUPPORT 1
@ -153,6 +161,8 @@
//#define NTC_SUPPORT 1
//#define PMSX003_SUPPORT 1
//#define PZEM004T_SUPPORT 1
//#define SDS011_SUPPORT 1
//#define SENSEAIR_SUPPORT 1
//#define SHT3X_I2C_SUPPORT 1
//#define SI7021_SUPPORT 1
//#define SONAR_SUPPORT 1


+ 139
- 3
code/espurna/config/defaults.h View File

@ -208,6 +208,138 @@
#define BUTTON8_RELAY 0
#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
// -----------------------------------------------------------------------------
@ -424,9 +556,13 @@
// 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
#define HOSTNAME ""
#define HOSTNAME ""
#endif
// Relay providers
@ -441,5 +577,5 @@
// App revision, populated by the build script
#ifndef APP_REVISION
#define APP_REVISION ""
#define APP_REVISION NULL
#endif

+ 0
- 51
code/espurna/config/dependencies.h View File

@ -53,54 +53,3 @@
#undef NTP_SUPPORT
#define NTP_SUPPORT 1 // Scheduler needs NTP
#endif
// -----------------------------------------------------------------------------
// WEB UI IMAGE
// -----------------------------------------------------------------------------
#define WEBUI_IMAGE_SMALL 0
#define WEBUI_IMAGE_LIGHT 1
#define WEBUI_IMAGE_SENSOR 2
#define WEBUI_IMAGE_RFBRIDGE 4
#define WEBUI_IMAGE_RFM69 8
#define WEBUI_IMAGE_FULL 15
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
#ifdef WEBUI_IMAGE
#undef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_FULL
#else
#define WEBUI_IMAGE WEBUI_IMAGE_LIGHT
#endif
#endif
#if SENSOR_SUPPORT == 1
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_SENSOR
#else
#undef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_FULL
#endif
#endif
#if defined(ITEAD_SONOFF_RFBRIDGE)
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_RFBRIDGE
#else
#undef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_FULL
#endif
#endif
#if RFM69_SUPPORT == 1
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_RFM69
#else
#undef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_FULL
#endif
#endif
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_SMALL
#endif

+ 80
- 13
code/espurna/config/general.h View File

@ -9,8 +9,11 @@
#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
#define ADMIN_PASS "fibonacci" // Default password (WEB, OTA, WIFI)
#define ADMIN_PASS "fibonacci" // Default password (WEB, OTA, WIFI SoftAP)
#endif
#ifndef USE_PASSWORD
@ -107,6 +110,10 @@
#define TELNET_STA 0 // By default, disallow connections via STA interface
#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_MAX_CLIENTS 1 // Max number of concurrent telnet clients
@ -221,6 +228,14 @@
#define BUTTON_LNGLNGCLICK_DELAY 10000 // Time in ms holding the button down to get a long-long click
#endif
//------------------------------------------------------------------------------
// ENCODER
//------------------------------------------------------------------------------
#ifndef ENCODER_SUPPORT
#define ENCODER_SUPPORT 0
#endif
//------------------------------------------------------------------------------
// LED
//------------------------------------------------------------------------------
@ -301,6 +316,9 @@
#define WIFI_AP_CAPTIVE 1 // Captive portal enabled when in AP mode
#endif
#ifndef WIFI_FALLBACK_APMODE
#define WIFI_FALLBACK_APMODE 1 // Fallback to AP mode if no STA connection
#endif
#ifndef WIFI_SLEEP_MODE
#define WIFI_SLEEP_MODE WIFI_NONE_SLEEP // WIFI_NONE_SLEEP, WIFI_LIGHT_SLEEP or WIFI_MODEM_SLEEP
@ -719,7 +737,8 @@
#define MQTT_TOPIC_BOARD "board"
#define MQTT_TOPIC_PULSE "pulse"
#define MQTT_TOPIC_SPEED "speed"
#define MQTT_TOPIC_IR "ir"
#define MQTT_TOPIC_IRIN "irin"
#define MQTT_TOPIC_IROUT "irout"
// Light module
#define MQTT_TOPIC_CHANNEL "channel"
@ -900,6 +919,14 @@
#define HOMEASSISTANT_PAYLOAD_OFF "0" // Payload for OFF and unavailable messages
#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
// -----------------------------------------------------------------------------
@ -1057,33 +1084,51 @@
#ifndef RF_RAW_SUPPORT
#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
// -----------------------------------------------------------------------------
// IR
// IR Bridge
// -----------------------------------------------------------------------------
#ifndef IR_SUPPORT
#define IR_SUPPORT 0 // Do not build with IR support by default (10.25Kb)
#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
// 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
#ifndef IR_DEBOUNCE
#define IR_DEBOUNCE 500 // IR debounce time in milliseconds
#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
/*
@ -1229,7 +1274,29 @@
//{ 0xE0E08877, IR_BUTTON_MODE_TOGGLE, 9 } //Extra Button
};
#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


+ 230
- 11
code/espurna/config/hardware.h View File

@ -70,13 +70,13 @@
// Buttons
#define BUTTON1_PIN 0
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
#define BUTTON1_RELAY 1
// Relays
// Hidden button will enter AP mode if dblclick and reset the device when long-long-clicked
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
// Light
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
@ -721,7 +721,7 @@
// the port and remove UART noise on serial line
#if not RFB_DIRECT
#define SERIAL_BAUDRATE 19200
//#define DEBUG_SERIAL_SUPPORT 0
#define DEBUG_SERIAL_SUPPORT 0
#endif
#elif defined(ITEAD_SONOFF_B1)
@ -934,6 +934,32 @@
// YJZK
// -----------------------------------------------------------------------------
#elif defined(YJZK_SWITCH_1CH)
// Info
#define MANUFACTURER "YJZK"
#define DEVICE "SWITCH_1CH"
// Buttons
#define BUTTON1_PIN 0
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_PRESS BUTTON_MODE_TOGGLE
#define BUTTON1_CLICK BUTTON_MODE_NONE
#define BUTTON1_DBLCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGLNGCLICK BUTTON_MODE_RESET
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 13
#define LED1_PIN_INVERSE 0
#elif defined(YJZK_SWITCH_2CH)
// Info
@ -945,7 +971,18 @@
#define BUTTON2_PIN 9
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_PRESS BUTTON_MODE_TOGGLE
#define BUTTON1_CLICK BUTTON_MODE_NONE
#define BUTTON1_DBLCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGLNGCLICK BUTTON_MODE_RESET
#define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON2_PRESS BUTTON_MODE_TOGGLE
#define BUTTON2_CLICK BUTTON_MODE_NONE
#define BUTTON2_DBLCLICK BUTTON_MODE_NONE
#define BUTTON2_LNGCLICK BUTTON_MODE_NONE
#define BUTTON2_LNGLNGCLICK BUTTON_MODE_RESET
#define BUTTON1_RELAY 1
#define BUTTON2_RELAY 2
@ -961,6 +998,57 @@
#define LED1_PIN 13
#define LED1_PIN_INVERSE 0
// YJZK 3CH switch
// Also Lixin Touch Wifi 3M
#elif defined(YJZK_SWITCH_3CH)
// Info
#define MANUFACTURER "YJZK"
#define DEVICE "SWITCH_3CH"
// Buttons
#define BUTTON1_PIN 0
#define BUTTON2_PIN 9
#define BUTTON3_PIN 10
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_PRESS BUTTON_MODE_TOGGLE
#define BUTTON1_CLICK BUTTON_MODE_NONE
#define BUTTON1_DBLCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGLNGCLICK BUTTON_MODE_RESET
#define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON2_PRESS BUTTON_MODE_TOGGLE
#define BUTTON2_CLICK BUTTON_MODE_NONE
#define BUTTON2_DBLCLICK BUTTON_MODE_NONE
#define BUTTON2_LNGCLICK BUTTON_MODE_NONE
#define BUTTON2_LNGLNGCLICK BUTTON_MODE_RESET
#define BUTTON3_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON3_PRESS BUTTON_MODE_TOGGLE
#define BUTTON3_CLICK BUTTON_MODE_NONE
#define BUTTON3_DBLCLICK BUTTON_MODE_NONE
#define BUTTON3_LNGCLICK BUTTON_MODE_NONE
#define BUTTON3_LNGLNGCLICK BUTTON_MODE_RESET
#define BUTTON1_RELAY 1
#define BUTTON2_RELAY 2
#define BUTTON3_RELAY 3
// Relays
#define RELAY1_PIN 12
#define RELAY2_PIN 5
#define RELAY3_PIN 4
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#define RELAY2_TYPE RELAY_TYPE_NORMAL
#define RELAY3_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 13
#define LED1_PIN_INVERSE 0
// -----------------------------------------------------------------------------
// Electrodragon boards
@ -1068,7 +1156,7 @@
// IR
#define IR_SUPPORT 1
#define IR_RECEIVER_PIN 4
#define IR_RX_PIN 4
#define IR_BUTTON_SET 1
#elif defined(MAGICHOME_LED_CONTROLLER_20)
@ -1097,7 +1185,7 @@
// IR
#define IR_SUPPORT 1
#define IR_RECEIVER_PIN 4
#define IR_RX_PIN 4
#define IR_BUTTON_SET 1
// -----------------------------------------------------------------------------
@ -2441,7 +2529,7 @@
// Red
#define LED3_PIN 0
#define LED3_PIN_INVERSE 0
#define LED2_MODE LED_MODE_OFF
#define LED3_MODE LED_MODE_OFF
// HJL01 / BL0937
#ifndef HLW8012_SUPPORT
@ -2457,7 +2545,6 @@
#define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// VANZAVANZU Smart Outlet Socket (based on BL0937 or HJL-01)
// https://www.amazon.com/Smart-Plug-Wifi-Mini-VANZAVANZU/dp/B078PHD6S5
@ -2503,6 +2590,8 @@
#define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
#elif defined(GENERIC_AG_L4)
// Info
@ -2547,6 +2636,136 @@
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
// -----------------------------------------------------------------------------
#elif defined(ALLTERCO_SHELLY1)
// Info
#define MANUFACTURER "ALLTERCO"
#define DEVICE "SHELLY1"
// Buttons
#define BUTTON1_PIN 5
#define BUTTON1_MODE BUTTON_SWITCH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 4
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#elif defined(ALLTERCO_SHELLY2)
// Info
#define MANUFACTURER "ALLTERCO"
#define DEVICE "SHELLY2"
// Buttons
#define BUTTON1_PIN 12
#define BUTTON2_PIN 14
#define BUTTON1_MODE BUTTON_SWITCH
#define BUTTON2_MODE BUTTON_SWITCH
#define BUTTON1_RELAY 1
#define BUTTON2_RELAY 2
// Relays
#define RELAY1_PIN 4
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#define RELAY2_PIN 5
#define RELAY2_TYPE RELAY_TYPE_NORMAL
// -----------------------------------------------------------------------------
#elif defined(LOHAS_9W)
// Info
#define MANUFACTURER "LOHAS"
#define DEVICE "E27_9W"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX
#define DUMMY_RELAY_COUNT 1
// Light
#define LIGHT_CHANNELS 5
#define MY92XX_MODEL MY92XX_MODEL_MY9231
#define MY92XX_CHIPS 2
#define MY92XX_DI_PIN 13
#define MY92XX_DCKI_PIN 15
#define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT
#define MY92XX_MAPPING 0, 1, 2, 3, 4
#define LIGHT_WHITE_FACTOR (0.1) // White LEDs are way more bright in the B1
// -----------------------------------------------------------------------------
#elif defined(XIAOMI_SMART_DESK_LAMP)
// Info
#define MANUFACTURER "XIAOMI"
#define DEVICE "SMART_DESK_LAMP"
// Buttons
#define BUTTON1_PIN 2
#define BUTTON2_PIN 14
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH | BUTTON_SET_PULLUP
#define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH | BUTTON_SET_PULLUP
// This button doubles as switch here and as encoder mode switch below
// Clicking it (for less than 500ms) will turn the light on and off
// Double and Long clicks will not work as these are used to modify the encoder action
#define BUTTON1_RELAY 1
#define BUTTON_LNGCLICK_DELAY 500
#define BUTTON1_DBLCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGLNGCLICK BUTTON_MODE_NONE
// Hidden button will enter AP mode if dblclick and reset the device when long-long-clicked
#define BUTTON2_DBLCLICK BUTTON_MODE_AP
#define BUTTON2_LNGLNGCLICK BUTTON_MODE_RESET
// Light
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
#define LIGHT_STEP 8
#define LIGHT_CHANNELS 2
#define LIGHT_CH1_PIN 5 // warm white
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_PIN 4 // cold white
#define LIGHT_CH2_INVERSE 0
// Encoder
// If mode is ENCODER_MODE_RATIO, the value ratio between both channels is changed
// when the button is not pressed, and the overall brightness when pressed
// If mode is ENCODER_MODE_CHANNEL, the first channel value is changed
// when the button is not pressed, and the second channel when pressed
// If no ENCODERX_BUTTON_PIN defined it will only change the value of the first defined channel
#define ENCODER_SUPPORT 1
#define ENCODER1_PIN1 12
#define ENCODER1_PIN2 13
#define ENCODER1_BUTTON_PIN 2 // active low by default, with software pullup
#define ENCODER1_CHANNEL1 0 // please note this value is 0-based (LIGHT_CH1 above)
#define ENCODER1_CHANNEL2 1 // please note this value is 0-based (LIGHT_CH2 above)
#define ENCODER1_MODE ENCODER_MODE_RATIO
#elif defined(PHYX_ESP12_RGB)
// Info
#define MANUFACTURER "PHYX"
#define DEVICE "ESP12_RGB"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
// Light
#define LIGHT_CHANNELS 3
#define LIGHT_CH1_PIN 4 // RED
#define LIGHT_CH2_PIN 14 // GREEN
#define LIGHT_CH3_PIN 12 // BLUE
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
// -----------------------------------------------------------------------------
// TEST boards (do not use!!)
// -----------------------------------------------------------------------------
@ -2590,7 +2809,7 @@
#define SHT3X_I2C_SUPPORT 1
#define SI7021_SUPPORT 1
#define PMSX003_SUPPORT 1
#define SENSEAIR_SUPPORT 1
#define SENSEAIR_SUPPORT1
// A bit of lights - pin 5
@ -2649,7 +2868,7 @@
// IR - pin 4
#define IR_SUPPORT 1
#define IR_RECEIVER_PIN 4
#define IR_RX_PIN 4
#define IR_BUTTON_SET 1
// A bit of DHT - pin 5
@ -2693,7 +2912,7 @@
#define NOFUSS_SUPPORT 1
#define UART_MQTT_SUPPORT 1
#define INFLUXDB_SUPPORT 1
#define IR_SUPPORT 1
#define IR_SUPPORT 1
#elif defined(TRAVIS03)


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

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


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

@ -31,6 +31,9 @@ extern "C" {
// -----------------------------------------------------------------------------
void debugSend(const char * format, ...);
void debugSend_P(PGM_P format, ...);
extern "C" {
void custom_crash_callback(struct rst_info*, uint32_t, uint32_t);
}
// -----------------------------------------------------------------------------
// Domoticz
@ -156,15 +159,11 @@ void webRequestRegister(web_request_callback_f callback);
typedef std::function<void(uint32_t, const char *, JsonObject&)> ws_on_action_callback_f;
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);
typedef std::function<bool(const char *, JsonVariant&)> ws_on_receive_callback_f;
void wsOnReceiveRegister(ws_on_receive_callback_f callback);
#else
#define ws_on_send_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 *
#endif


+ 39
- 20
code/espurna/config/sensors.h View File

@ -5,13 +5,13 @@
#define SENSOR_DEBUG 0 // Debug sensors
#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_INIT_INTERVAL 10000 // Try to re-init non-ready sensors every 10s
#define SENSOR_REPORT_EVERY 10 // Report every this many readings
#define SENSOR_REPORT_MIN_EVERY 1 // Minimum every value
#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)
// even if just one sensor (0 for backwards compatibility)
@ -36,6 +36,18 @@
#define HUMIDITY_MIN_CHANGE 0 // Minimum humidity change to report
#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
#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 number different from 0 means it should store the value in EEPROM
// after these many reports
// Warning: this might wear out flash fast!
#endif
#define SENSOR_PUBLISH_ADDRESSES 0 // Publish sensor addresses
#define SENSOR_ADDRESS_TOPIC "address" // Topic to publish sensor addresses
@ -295,7 +307,7 @@
#endif
#ifndef EVENTS_INTERRUPT_MODE
#define EVENTS_INTERRUPT_MODE RISING // RISING, FALLING, BOTH
#define EVENTS_INTERRUPT_MODE RISING // RISING, FALLING, CHANGE
#endif
#define EVENTS_DEBOUNCE 50 // Do not register events within less than 50 millis
@ -318,7 +330,7 @@
#endif
#ifndef GEIGER_INTERRUPT_MODE
#define GEIGER_INTERRUPT_MODE RISING // RISING, FALLING, BOTH
#define GEIGER_INTERRUPT_MODE RISING // RISING, FALLING, CHANGE
#endif
#define GEIGER_DEBOUNCE 25 // Do not register events within less than 25 millis.
@ -455,6 +467,23 @@
#define NTC_BETA 3977 // Beta coeficient
#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
// Enable support by passing SENSEAIR_SUPPORT=1 build flag
@ -646,6 +675,7 @@
HLW8012_SUPPORT || \
MHZ19_SUPPORT || \
NTC_SUPPORT || \
SDS011_SUPPORT || \
SENSEAIR_SUPPORT || \
PMSX003_SUPPORT || \
PZEM004T_SUPPORT || \
@ -695,12 +725,6 @@
#if SENSOR_SUPPORT
#if SENSOR_DEBUG
#include "../config/debug.h"
#endif
#include "../sensors/BaseSensor.h"
#if AM2320_SUPPORT
#include "../sensors/AM2320Sensor.h"
#endif
@ -718,12 +742,10 @@
#endif
#if CSE7766_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/CSE7766Sensor.h"
#endif
#if DALLAS_SUPPORT
#include <OneWire.h>
#include "../sensors/DallasSensor.h"
#endif
@ -756,7 +778,7 @@
#endif
#if GEIGER_SUPPORT
#include "../sensors/GeigerSensor.h" // The main file for geiger counting module
#include "../sensors/GeigerSensor.h"
#endif
#if GUVAS12SD_SUPPORT
@ -764,32 +786,30 @@
#endif
#if HLW8012_SUPPORT
#include <HLW8012.h>
#include "../sensors/HLW8012Sensor.h"
#endif
#if MHZ19_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/MHZ19Sensor.h"
#endif
#if NTC_SUPPORT
#include "../sensors/AnalogSensor.h"
#include "../sensors/NTCSensor.h"
#endif
#if SDS011_SUPPORT
#include "../sensors/SDS011Sensor.h"
#endif
#if SENSEAIR_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/SenseAirSensor.h"
#endif
#if PMSX003_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/PMSX003Sensor.h"
#endif
#if PZEM004T_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/PZEM004TSensor.h"
#endif
@ -810,7 +830,6 @@
#endif
#if V9261F_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/V9261FSensor.h"
#endif


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

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


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

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

+ 73
- 0
code/espurna/config/webui.h View File

@ -0,0 +1,73 @@
// -----------------------------------------------------------------------------
// WEB UI IMAGE
// -----------------------------------------------------------------------------
#define WEBUI_IMAGE_SMALL 0
#define WEBUI_IMAGE_LIGHT 1
#define WEBUI_IMAGE_SENSOR 2
#define WEBUI_IMAGE_RFBRIDGE 4
#define WEBUI_IMAGE_RFM69 8
#define WEBUI_IMAGE_FULL 15
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
#ifdef WEBUI_IMAGE
#undef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_FULL
#else
#define WEBUI_IMAGE WEBUI_IMAGE_LIGHT
#endif
#endif
#if SENSOR_SUPPORT == 1
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_SENSOR
#else
#undef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_FULL
#endif
#endif
#if defined(ITEAD_SONOFF_RFBRIDGE)
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_RFBRIDGE
#else
#undef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_FULL
#endif
#endif
#if RFM69_SUPPORT == 1
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_RFM69
#else
#undef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_FULL
#endif
#endif
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_SMALL
#endif
#include <pgmspace.h>
PROGMEM const char espurna_webui[] =
#if WEBUI_IMAGE == WEBUI_IMAGE_SMALL
"SMALL"
#endif
#if WEBUI_IMAGE == WEBUI_IMAGE_LIGHT
"LIGHT"
#endif
#if WEBUI_IMAGE == WEBUI_IMAGE_SENSOR
"SENSOR"
#endif
#if WEBUI_IMAGE == WEBUI_IMAGE_RFBRIDGE
"RFBRIDGE"
#endif
#if WEBUI_IMAGE == WEBUI_IMAGE_RFM69
"RFM69"
#endif
#if WEBUI_IMAGE == WEBUI_IMAGE_FULL
"FULL"
#endif
"";

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


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

@ -157,13 +157,18 @@ unsigned int domoticzIdx(unsigned char relayID) {
}
void domoticzSetup() {
_domoticzConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_domoticzWebSocketOnSend);
wsOnAfterParseRegister(_domoticzConfigure);
wsOnReceiveRegister(_domoticzWebSocketOnReceive);
#endif
// Callbacks
mqttRegister(_domoticzMqtt);
espurnaRegisterReload(_domoticzConfigure);
}
bool domoticzEnabled() {


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

@ -21,6 +21,10 @@ void eepromRotate(bool value) {
}
}
uint32_t eepromCurrent() {
return EEPROMr.current();
}
String eepromSectors() {
String response;
for (uint32_t i = 0; i < EEPROMr.size(); i++) {


+ 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)

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

@ -23,15 +23,26 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <vector>
std::vector<void (*)()> _loop_callbacks;
std::vector<void (*)()> _reload_callbacks;
// -----------------------------------------------------------------------------
// REGISTER
// GENERAL CALLBACKS
// -----------------------------------------------------------------------------
void espurnaRegisterLoop(void (*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
// -----------------------------------------------------------------------------
@ -99,14 +110,17 @@ void setup() {
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
lightSetup();
#endif
relaySetup();
#if BUTTON_SUPPORT
buttonSetup();
#endif
#if ENCODER_SUPPORT && (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE)
encoderSetup();
#endif
#if LED_SUPPORT
ledSetup();
#endif
#if MQTT_SUPPORT
mqttSetup();
#endif


+ 40
- 0
code/espurna/filters/LastFilter.h View File

@ -0,0 +1,40 @@
// -----------------------------------------------------------------------------
// Last Filter
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
#pragma once
#include "BaseFilter.h"
class LastFilter : public BaseFilter {
public:
void add(double value) {
_value = value;
}
unsigned char count() {
return 1;
}
void reset() {
_value = 0;
}
double result() {
return _value;
}
void resize(unsigned char size) {}
protected:
double _value = 0;
};
#endif // SENSOR_SUPPORT

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

@ -13,6 +13,17 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
bool _haEnabled = 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
// -----------------------------------------------------------------------------
@ -22,7 +33,7 @@ bool _haSendFlag = false;
void _haSendMagnitude(unsigned char i, JsonObject& config) {
unsigned char type = magnitudeType(i);
config["name"] = getSetting("hostname") + String(" ") + magnitudeTopic(type);
config["name"] = _haFixName(getSetting("hostname") + String(" ") + magnitudeTopic(type));
config.set("platform", "mqtt");
config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
config["unit_of_measurement"] = magnitudeUnits(type);
@ -64,10 +75,10 @@ void _haSendSwitch(unsigned char i, JsonObject& config) {
String name = getSetting("hostname");
if (relayCount() > 1) {
name += String(" #") + String(i);
name += String("_") + String(i);
}
config.set("name", name);
config.set("name", _haFixName(name));
config.set("platform", "mqtt");
if (relayCount()) {
@ -76,8 +87,8 @@ void _haSendSwitch(unsigned char i, JsonObject& config) {
config["payload_on"] = String(HOMEASSISTANT_PAYLOAD_ON);
config["payload_off"] = String(HOMEASSISTANT_PAYLOAD_OFF);
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
@ -286,20 +297,21 @@ void haSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_haWebSocketOnSend);
wsOnAfterParseRegister(_haConfigure);
wsOnActionRegister(_haWebSocketOnAction);
wsOnReceiveRegister(_haWebSocketOnReceive);
#endif
#if TERMINAL_SUPPORT
_haInitCommands();
#endif
// On MQTT connect check if we have something to send
mqttRegister([](unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) _haSend();
});
#if TERMINAL_SUPPORT
_haInitCommands();
#endif
// Main callbacks
espurnaRegisterReload(_haConfigure);
}


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

@ -51,7 +51,7 @@ bool idbSend(const char * topic, const char * payload) {
#endif
char * host = strdup(h.c_str());
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;
@ -60,7 +60,7 @@ bool idbSend(const char * topic, const char * payload) {
char data[128];
snprintf(data, sizeof(data), "%s,device=%s value=%s", topic, getSetting("hostname").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];
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",
@ -74,14 +74,14 @@ bool idbSend(const char * topic, const char * payload) {
if (_idb_client.connected()) _idb_client.stop();
success = true;
} else {
DEBUG_MSG("[INFLUXDB] Sent failed\n");
DEBUG_MSG_P(PSTR("[INFLUXDB] Sent failed\n"));
}
_idb_client.stop();
while (_idb_client.connected()) yield();
} else {
DEBUG_MSG("[INFLUXDB] Connection failed\n");
DEBUG_MSG_P(PSTR("[INFLUXDB] Connection failed\n"));
}
free(host);
@ -100,12 +100,17 @@ bool idbEnabled() {
}
void idbSetup() {
_idbConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_idbWebSocketOnSend);
wsOnAfterParseRegister(_idbConfigure);
wsOnReceiveRegister(_idbWebSocketOnReceive);
#endif
// Main callbacks
espurnaRegisterReload(_idbConfigure);
}
#endif

+ 341
- 35
code/espurna/ir.ino View File

@ -2,34 +2,292 @@
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) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
-----------------------------------------------------------------------------
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
#include <IRremoteESP8266.h>
#include <IRrecv.h>
IRrecv * _ir_recv;
decode_results _ir_results;
unsigned long _ir_last_toggle = 0;
#if defined(IR_RX_PIN)
// -----------------------------------------------------------------------------
// PRIVATE
// -----------------------------------------------------------------------------
#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
// 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
void _irProcessCode(unsigned long code, unsigned char type) {
// 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
}
// Check valid code
unsigned long last_code = 0;
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;
DEBUG_MSG_P(PSTR("[IR] Received 0x%08X (%d)\n"), code, type);
}
}
#endif // MQTT_SUPPORT && defined(IR_TX_PIN)
// Receiving
#if defined(IR_RX_PIN)
void _irProcess(unsigned char type, unsigned long code) {
#if IR_BUTTON_SET > 0
@ -88,38 +346,86 @@ void _irProcessCode(unsigned long code, unsigned char type) {
}
if (!found) {
DEBUG_MSG_P(PSTR("[IR] Ignoring code\n"));
DEBUG_MSG_P(PSTR("[IR] Code does not match any action\n"));
}
#endif
#if MQTT_SUPPORT
char buffer[16];
snprintf_P(buffer, sizeof(buffer), "0x%08X", code);
mqttSend(MQTT_TOPIC_IR, buffer);
#endif
}
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
}
}
// -----------------------------------------------------------------------------
// PUBLIC API
#endif // defined(IR_RX_PIN)
// -----------------------------------------------------------------------------
void _irLoop() {
#if defined(IR_RX_PIN)
_irRXLoop();
#endif
#if MQTT_SUPPORT && defined(IR_TX_PIN)
_irTXLoop();
#endif
}
void irSetup() {
_ir_recv = new IRrecv(IR_RECEIVER_PIN);
_ir_recv->enableIRIn();
#if defined(IR_RX_PIN)
_ir_receiver.enableIRIn();
DEBUG_MSG_P(PSTR("[IR] Receiver initialized \n"));
#endif
// Register loop
espurnaRegisterLoop(irLoop);
#if MQTT_SUPPORT && defined(IR_TX_PIN)
_ir_sender.begin();
mqttRegister(_irMqttCallback);
DEBUG_MSG_P(PSTR("[IR] Transmitter initialized \n"));
#endif
}
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

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

@ -170,14 +170,14 @@ void ledSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_ledWebSocketOnSend);
wsOnAfterParseRegister(_ledConfigure);
wsOnReceiveRegister(_ledWebSocketOnReceive);
#endif
DEBUG_MSG_P(PSTR("[LED] Number of leds: %d\n"), _leds.size());
// Register loop
// Main callbacks
espurnaRegisterLoop(ledLoop);
espurnaRegisterReload(_ledConfigure);
}


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

@ -145,13 +145,9 @@ void _generateBrightness() {
} 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++) {
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;
}
}
@ -569,6 +565,7 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl
}
void lightMQTT() {
char buffer[20];
if (_light_has_color) {
@ -584,13 +581,10 @@ void lightMQTT() {
_toHSV(buffer, sizeof(buffer));
mqttSend(MQTT_TOPIC_COLOR_HSV, buffer);
// Brightness
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _light_brightness);
mqttSend(MQTT_TOPIC_BRIGHTNESS, buffer);
// Mireds
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _light_mireds);
mqttSend(MQTT_TOPIC_MIRED, buffer);
}
// Channels
@ -599,6 +593,10 @@ void lightMQTT() {
mqttSend(MQTT_TOPIC_CHANNEL, i, buffer);
}
// Brightness
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _light_brightness);
mqttSend(MQTT_TOPIC_BRIGHTNESS, buffer);
}
void lightMQTTGroup() {
@ -735,12 +733,16 @@ unsigned int lightChannel(unsigned char id) {
return 0;
}
void lightChannel(unsigned char id, unsigned int value) {
void lightChannel(unsigned char id, int value) {
if (id <= _light_channel.size()) {
_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() {
return _light_brightness;
}
@ -783,7 +785,6 @@ void _lightWebSocketOnSend(JsonObject& root) {
}
if (useRGB) {
root["rgb"] = lightColor(true);
root["brightness"] = lightBrightness();
} else {
root["hsv"] = lightColor(false);
}
@ -792,9 +793,11 @@ void _lightWebSocketOnSend(JsonObject& root) {
for (unsigned char id=0; id < _light_channel.size(); id++) {
channels.add(lightChannel(id));
}
root["brightness"] = lightBrightness();
}
void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (_light_has_color) {
if (strcmp(action, "color") == 0) {
if (data.containsKey("rgb")) {
@ -805,10 +808,6 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
lightColor(data["hsv"], false);
lightUpdate(true, true);
}
if (data.containsKey("brightness")) {
lightBrightness(data["brightness"]);
lightUpdate(true, true);
}
}
if (_light_use_cct) {
if (strcmp(action, "mireds") == 0) {
@ -824,6 +823,14 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
lightUpdate(true, true);
}
}
if (strcmp(action, "brightness") == 0) {
if (data.containsKey("value")) {
lightBrightness(data["value"]);
lightUpdate(true, true);
}
}
}
#endif
@ -831,35 +838,70 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
#if API_SUPPORT
void _lightAPISetup() {
// API entry points (protected with apikey)
if (_light_has_color) {
apiRegister(MQTT_TOPIC_COLOR_RGB,
[](char * buffer, size_t len) {
if (getSetting("useCSS", 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("useCSS", 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,
[](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), _light_brightness);
snprintf_P(buffer, len, PSTR("%d"), _light_brightness);
},
[](const char * payload) {
lightBrightness(atoi(payload));
@ -867,37 +909,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
@ -1076,12 +1087,6 @@ void lightSetup() {
wsOnSendRegister(_lightWebSocketOnSend);
wsOnActionRegister(_lightWebSocketOnAction);
wsOnReceiveRegister(_lightWebSocketOnReceive);
wsOnAfterParseRegister([]() {
#if LIGHT_SAVE_ENABLED == 0
lightSave();
#endif
_lightConfigure();
});
#endif
#if API_SUPPORT
@ -1096,6 +1101,14 @@ void lightSetup() {
_lightInitCommands();
#endif
// Main callbacks
espurnaRegisterReload([]() {
#if LIGHT_SAVE_ENABLED == 0
lightSave();
#endif
_lightConfigure();
});
}
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE

+ 102
- 0
code/espurna/migrate.ino View File

@ -1087,6 +1087,108 @@ void migrate() {
setSetting("chLogic", 2, 0);
setSetting("relays", 1);
#elif defined(ALLTERCO_SHELLY1)
setSetting("board", 83);
setSetting("btnGPIO", 0, 5);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(LOHAS_9W)
setSetting("board", 84);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_MY92XX);
setSetting("myModel", MY92XX_MODEL_MY9231);
setSetting("myChips", 2);
setSetting("myDIGPIO", 13);
setSetting("myDCKIGPIO", 15);
setSetting("relays", 1);
#elif defined(YJZK_SWITCH_1CH)
setSetting("board", 85);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 0);
setSetting("ledWifi", 0);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(YJZK_SWITCH_3CH)
setSetting("board", 86);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 0);
setSetting("ledWifi", 0);
setSetting("btnGPIO", 0, 0);
setSetting("btnGPIO", 1, 9);
setSetting("btnGPIO", 2, 10);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("btnRelay", 2, 2);
setSetting("relayGPIO", 0, 12);
setSetting("relayGPIO", 1, 5);
setSetting("relayGPIO", 2, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
#elif defined(XIAOMI_SMART_DESK_LAMP)
setSetting("board", 87);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("relays", 1);
setSetting("chGPIO", 0, 5);
setSetting("chGPIO", 1, 4);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
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("enc1stGPIO", 0, 12);
setSetting("enc2ndGPIO", 0, 13);
setSetting("encBtnGPIO", 0, 2);
setSetting("encMode", ENCODER_MODE_RATIO);
#elif defined(ALLTERCO_SHELLY2)
setSetting("board", 88);
setSetting("btnGPIO", 0, 12);
setSetting("btnGPIO", 1, 14);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("relayGPIO", 0, 4);
setSetting("relayGPIO", 1, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(XIAOMI_SMART_DESK_LAMP)
setSetting("board", 89);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("relays", 1);
setSetting("chGPIO", 0, 4);
setSetting("chGPIO", 1, 14);
setSetting("chGPIO", 2, 12);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 3, 0);
#else
// Allow users to define new settings without migration config


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

@ -574,7 +574,16 @@ unsigned char _mqttBuildTree(JsonObject& root, char parent) {
JsonObject& elements = root.createNestedObject(element.topic);
unsigned char num = _mqttBuildTree(elements, i);
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);
}
}
}
}
@ -703,7 +712,6 @@ void mqttUnsubscribe(const char * topic) {
void mqttEnabled(bool status) {
_mqtt_enabled = status;
setSetting("mqttEnabled", status ? 1 : 0);
}
bool mqttEnabled() {
@ -736,7 +744,7 @@ void mqttSetBroker(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() {
@ -751,7 +759,7 @@ void mqttReset() {
void mqttSetup() {
_mqttBackwards();
DEBUG_MSG_P(PSTR("[MQTT] Async %s, SSL %s, Autoconnect %s\n"),
MQTT_USE_ASYNC ? "ENABLED" : "DISABLED",
ASYNC_TCP_SSL_ENABLED ? "ENABLED" : "DISABLED",
@ -809,7 +817,6 @@ void mqttSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_mqttWebSocketOnSend);
wsOnAfterParseRegister(_mqttConfigure);
wsOnReceiveRegister(_mqttWebSocketOnReceive);
#endif
@ -817,8 +824,9 @@ void mqttSetup() {
_mqttInitCommands();
#endif
// Register loop
// Main callbacks
espurnaRegisterLoop(mqttLoop);
espurnaRegisterReload(_mqttConfigure);
}


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

@ -154,7 +154,6 @@ void nofussSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_nofussWebSocketOnSend);
wsOnAfterParseRegister(_nofussConfigure);
wsOnReceiveRegister(_nofussWebSocketOnReceive);
#endif
@ -162,8 +161,9 @@ void nofussSetup() {
_nofussInitCommands();
#endif
// Register loop
// Main callbacks
espurnaRegisterLoop(nofussLoop);
espurnaRegisterReload(_nofussConfigure);
}


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

@ -179,11 +179,11 @@ void ntpSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_ntpWebSocketOnSend);
wsOnReceiveRegister(_ntpWebSocketOnReceive);
wsOnAfterParseRegister([]() { _ntp_configure = true; });
#endif
// Register loop
// Main callbacks
espurnaRegisterLoop(_ntpLoop);
espurnaRegisterReload([]() { _ntp_configure = true; });
}


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

@ -16,7 +16,7 @@ void _otaConfigure() {
ArduinoOTA.setPort(OTA_PORT);
ArduinoOTA.setHostname(getSetting("hostname").c_str());
#if USE_PASSWORD
ArduinoOTA.setPassword(getSetting("adminPass", ADMIN_PASS).c_str());
ArduinoOTA.setPassword(getAdminPass().c_str());
#endif
}
@ -203,16 +203,13 @@ void otaSetup() {
_otaConfigure();
#if WEB_SUPPORT
wsOnAfterParseRegister(_otaConfigure);
#endif
#if TERMINAL_SUPPORT
_otaInitCommands();
#endif
// Register loop
// Main callbacks
espurnaRegisterLoop(_otaLoop);
espurnaRegisterReload(_otaConfigure);
// -------------------------------------------------------------------------
@ -238,7 +235,7 @@ void otaSetup() {
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;
unsigned int _prog = (progress / (total / 100));


+ 19
- 17
code/espurna/pwm.c View File

@ -19,27 +19,29 @@
/* Set the following three defines to your needs */
#ifndef SDK_PWM_PERIOD_COMPAT_MODE
#define SDK_PWM_PERIOD_COMPAT_MODE 0
#define SDK_PWM_PERIOD_COMPAT_MODE 0
#endif
#ifndef PWM_MAX_CHANNELS
#define PWM_MAX_CHANNELS 8
#define PWM_MAX_CHANNELS 8
#endif
#define PWM_DEBUG 0
#define PWM_USE_NMI 1
#define PWM_DEBUG 0
#define PWM_USE_NMI 1
/* no user servicable parts beyond this point */
#define PWM_MAX_TICKS 0x7fffff
#define PWM_MAX_TICKS 0x7fffff
#if SDK_PWM_PERIOD_COMPAT_MODE
#define PWM_PERIOD_TO_TICKS(x) (x * 0.2)
#define PWM_DUTY_TO_TICKS(x) (x * 5)
#define PWM_MAX_DUTY (PWM_MAX_TICKS * 0.2)
#define PWM_MAX_PERIOD (PWM_MAX_TICKS * 5)
#define PWM_PERIOD_TO_TICKS(x) (x * 0.2)
#define PWM_DUTY_TO_TICKS(x) (x * 5)
#define PWM_MAX_DUTY (PWM_MAX_TICKS * 0.2)
#define PWM_MAX_PERIOD (PWM_MAX_TICKS * 5)
#else
#define PWM_PERIOD_TO_TICKS(x) (x)
#define PWM_DUTY_TO_TICKS(x) (x)
#define PWM_MAX_DUTY PWM_MAX_TICKS
#define PWM_MAX_PERIOD PWM_MAX_TICKS
#define PWM_PERIOD_TO_TICKS(x) (x)
#define PWM_DUTY_TO_TICKS(x) (x)
#define PWM_MAX_DUTY PWM_MAX_TICKS
#define PWM_MAX_PERIOD PWM_MAX_TICKS
#endif
#include <c_types.h>
@ -48,8 +50,8 @@
#include "libs/pwm.h"
// from SDK hw_timer.c
#define TIMER1_DIVIDE_BY_16 0x0004
#define TIMER1_ENABLE_TIMER 0x0080
#define TIMER1_DIVIDE_BY_16 0x0004
#define TIMER1_ENABLE_TIMER 0x0080
struct pwm_phase {
uint32_t ticks; ///< delay until next phase, in 200ns units
@ -400,7 +402,7 @@ pwm_start(void)
void ICACHE_FLASH_ATTR
pwm_set_duty(uint32_t duty, uint8_t channel)
{
if (channel > PWM_MAX_CHANNELS)
if (channel >= PWM_MAX_CHANNELS)
return;
if (duty > PWM_MAX_DUTY)
@ -412,7 +414,7 @@ pwm_set_duty(uint32_t duty, uint8_t channel)
uint32_t ICACHE_FLASH_ATTR
pwm_get_duty(uint8_t channel)
{
if (channel > PWM_MAX_CHANNELS)
if (channel >= PWM_MAX_CHANNELS)
return 0;
return pwm_duty[channel];
}


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

@ -618,7 +618,6 @@ void _relayWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
void relaySetupWS() {
wsOnSendRegister(_relayWebSocketOnStart);
wsOnActionRegister(_relayWebSocketOnAction);
wsOnAfterParseRegister(_relayConfigure);
wsOnReceiveRegister(_relayWebSocketOnReceive);
}
@ -1004,8 +1003,6 @@ void relaySetup() {
_relayBoot();
_relayLoop();
espurnaRegisterLoop(_relayLoop);
#if WEB_SUPPORT
relaySetupWS();
#endif
@ -1019,6 +1016,10 @@ void relaySetup() {
_relayInitCommands();
#endif
// Main callbacks
espurnaRegisterLoop(_relayLoop);
espurnaRegisterReload(_relayConfigure);
DEBUG_MSG_P(PSTR("[RELAY] Number of relays: %d\n"), _relays.size());
}

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

@ -268,12 +268,12 @@ void rfm69Setup() {
#if WEB_SUPPORT
wsOnSendRegister(_rfm69WebSocketOnSend);
wsOnReceiveRegister(_rfm69WebSocketOnReceive);
wsOnAfterParseRegister(_rfm69Configure);
wsOnActionRegister(_rfm69WebSocketOnAction);
#endif
// Register loop
// Main callbacks
espurnaRegisterLoop(_rfm69Loop);
espurnaRegisterReload(_rfm69Configure);
}


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

@ -216,11 +216,11 @@ void schSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_schWebSocketOnSend);
wsOnReceiveRegister(_schWebSocketOnReceive);
wsOnAfterParseRegister(_schConfigure);
#endif
// Register loop
// Main callbacks
espurnaRegisterLoop(_schLoop);
espurnaRegisterReload(_schConfigure);
}


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

@ -9,6 +9,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#if SENSOR_SUPPORT
#include <vector>
#include "filters/LastFilter.h"
#include "filters/MaxFilter.h"
#include "filters/MedianFilter.h"
#include "filters/MovingAverageFilter.h"
@ -21,9 +22,9 @@ typedef struct {
unsigned char type; // Type of measurement
unsigned char global; // Global index in its type
double current; // Current (last) value, unfiltered
double filtered; // Filtered (averaged) value
double reported; // Last reported value
double min_change; // Minimum value change to report
double max_change; // Maximum value change to report
} sensor_magnitude_t;
std::vector<BaseSensor *> _sensors;
@ -34,6 +35,7 @@ unsigned char _counts[MAGNITUDE_MAX];
bool _sensor_realtime = API_REAL_TIME_VALUES;
unsigned long _sensor_read_interval = 1000 * SENSOR_READ_INTERVAL;
unsigned char _sensor_report_every = SENSOR_REPORT_EVERY;
unsigned char _sensor_save_every = SENSOR_SAVE_EVERY;
unsigned char _sensor_power_units = SENSOR_POWER_UNITS;
unsigned char _sensor_energy_units = SENSOR_ENERGY_UNITS;
unsigned char _sensor_temperature_units = SENSOR_TEMPERATURE_UNITS;
@ -100,7 +102,7 @@ bool _sensorWebSocketOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, "sns", 3) == 0) return true;
if (strncmp(key, "tmp", 3) == 0) return true;
if (strncmp(key, "hum", 3) == 0) return true;
if (strncmp(key, "energy", 6) == 0) return true;
if (strncmp(key, "ene", 3) == 0) return true;
return false;
}
@ -127,8 +129,8 @@ void _sensorWebSocketSendData(JsonObject& root) {
element["error"] = magnitude.sensor->error();
if (magnitude.type == MAGNITUDE_ENERGY) {
if (_sensor_energy_reset_ts.length() == 0) _sensorReset();
element["description"] = magnitude.sensor->slot(magnitude.local) + _sensor_energy_reset_ts;
if (_sensor_energy_reset_ts.length() == 0) _sensorResetTS();
element["description"] = magnitude.sensor->slot(magnitude.local) + String(" (since ") + _sensor_energy_reset_ts + String(")");
} else {
element["description"] = magnitude.sensor->slot(magnitude.local);
}
@ -193,15 +195,16 @@ void _sensorWebSocketStart(JsonObject& root) {
}
if (_magnitudes.size() > 0) {
root["sensorsVisible"] = 1;
root["snsVisible"] = 1;
//root["apiRealTime"] = _sensor_realtime;
root["pwrUnits"] = _sensor_power_units;
root["energyUnits"] = _sensor_energy_units;
root["eneUnits"] = _sensor_energy_units;
root["tmpUnits"] = _sensor_temperature_units;
root["tmpCorrection"] = _sensor_temperature_correction;
root["humCorrection"] = _sensor_humidity_correction;
root["snsRead"] = _sensor_read_interval / 1000;
root["snsReport"] = _sensor_report_every;
root["snsSave"] = _sensor_save_every;
}
/*
@ -239,7 +242,7 @@ void _sensorAPISetup() {
apiRegister(topic.c_str(), [magnitude_id](char * buffer, size_t len) {
sensor_magnitude_t magnitude = _magnitudes[magnitude_id];
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);
});
@ -293,11 +296,18 @@ void _sensorPost() {
}
}
void _sensorReset() {
void _sensorResetTS() {
#if NTP_SUPPORT
if (ntpSynced()) {
_sensor_energy_reset_ts = String(" (since ") + ntpDateTime() + String(")");
if (_sensor_energy_reset_ts.length() == 0) {
_sensor_energy_reset_ts = ntpDateTime(now() - millis() / 1000);
} else {
_sensor_energy_reset_ts = ntpDateTime(now());
}
} else {
_sensor_energy_reset_ts = String();
}
setSetting("snsResetTS", _sensor_energy_reset_ts);
#endif
}
@ -320,13 +330,13 @@ void _sensorLoad() {
*/
#if AM2320_SUPPORT
{
AM2320Sensor * sensor = new AM2320Sensor();
sensor->setAddress(AM2320_ADDRESS);
_sensors.push_back(sensor);
}
#endif
#if AM2320_SUPPORT
{
AM2320Sensor * sensor = new AM2320Sensor();
sensor->setAddress(AM2320_ADDRESS);
_sensors.push_back(sensor);
}
#endif
#if ANALOG_SUPPORT
{
@ -512,6 +522,15 @@ void _sensorLoad() {
}
#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
{
NTCSensor * sensor = new NTCSensor();
@ -613,6 +632,7 @@ void _sensorCallback(unsigned char i, unsigned char type, double value) {
void _sensorInit() {
_sensors_ready = true;
_sensor_save_every = getSetting("snsSave", 0).toInt();
for (unsigned char i=0; i<_sensors.size(); i++) {
@ -639,17 +659,30 @@ void _sensorInit() {
new_magnitude.type = type;
new_magnitude.global = _counts[type];
new_magnitude.current = 0;
new_magnitude.filtered = 0;
new_magnitude.reported = 0;
new_magnitude.min_change = 0;
if (type == MAGNITUDE_DIGITAL) {
new_magnitude.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) {
new_magnitude.filter = new LastFilter();
} else if (MAGNITUDE_DIGITAL == type) {
new_magnitude.filter = new MaxFilter();
} else if (type == MAGNITUDE_COUNT || type == MAGNITUDE_GEIGER_CPM|| type == MAGNITUDE_GEIGER_SIEVERT) { // For geiger counting moving average filter is the most appropriate if needed at all.
} else if (MAGNITUDE_COUNT == type || MAGNITUDE_GEIGER_CPM == type || MAGNITUDE_GEIGER_SIEVERT == type) { // For geiger counting moving average filter is the most appropriate if needed at all.
new_magnitude.filter = new MovingAverageFilter();
} else {
new_magnitude.filter = new MedianFilter();
}
new_magnitude.filter->resize(_sensor_report_every);
_magnitudes.push_back(new_magnitude);
DEBUG_MSG_P(PSTR("[SENSOR] -> %s:%d\n"), magnitudeTopic(type).c_str(), _counts[type]);
@ -671,6 +704,8 @@ void _sensorInit() {
EmonAnalogSensor * sensor = (EmonAnalogSensor *) _sensors[i];
sensor->setCurrentRatio(0, getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat());
sensor->setVoltage(getSetting("pwrVoltage", EMON_MAINS_VOLTAGE).toInt());
double value = (_sensor_save_every > 0) ? getSetting("eneTotal", 0).toInt() : 0;
if (value > 0) sensor->resetEnergy(0, value);
}
#endif // EMON_ANALOG_SUPPORT
@ -692,6 +727,9 @@ void _sensorInit() {
value = getSetting("pwrRatioP", HLW8012_POWER_RATIO).toFloat();
if (value > 0) sensor->setPowerRatio(value);
value = (_sensor_save_every > 0) ? getSetting("eneTotal", 0).toInt() : 0;
if (value > 0) sensor->resetEnergy(value);
}
#endif // HLW8012_SUPPORT
@ -713,6 +751,9 @@ void _sensorInit() {
value = getSetting("pwrRatioP", 0).toFloat();
if (value > 0) sensor->setPowerRatio(value);
value = (_sensor_save_every > 0) ? getSetting("eneTotal", 0).toInt() : 0;
if (value > 0) sensor->resetEnergy(value);
}
#endif // CSE7766_SUPPORT
@ -726,12 +767,14 @@ void _sensorConfigure() {
// General sensor settings
_sensor_read_interval = 1000 * constrain(getSetting("snsRead", SENSOR_READ_INTERVAL).toInt(), SENSOR_READ_MIN_INTERVAL, SENSOR_READ_MAX_INTERVAL);
_sensor_report_every = constrain(getSetting("snsReport", SENSOR_REPORT_EVERY).toInt(), SENSOR_REPORT_MIN_EVERY, SENSOR_REPORT_MAX_EVERY);
_sensor_save_every = getSetting("snsSave", SENSOR_SAVE_EVERY).toInt();
_sensor_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
_sensor_power_units = getSetting("pwrUnits", SENSOR_POWER_UNITS).toInt();
_sensor_energy_units = getSetting("energyUnits", SENSOR_ENERGY_UNITS).toInt();
_sensor_energy_units = getSetting("eneUnits", SENSOR_ENERGY_UNITS).toInt();
_sensor_temperature_units = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt();
_sensor_temperature_correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat();
_sensor_humidity_correction = getSetting("humCorrection", SENSOR_HUMIDITY_CORRECTION).toFloat();
_sensor_energy_reset_ts = getSetting("snsResetTS", "");
// Specific sensor settings
for (unsigned char i=0; i<_sensors.size(); i++) {
@ -755,7 +798,8 @@ void _sensorConfigure() {
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
delSetting("eneTotal");
_sensorResetTS();
}
sensor->setVoltage(getSetting("pwrVoltage", EMON_MAINS_VOLTAGE).toInt());
@ -769,7 +813,8 @@ void _sensorConfigure() {
EmonADC121Sensor * sensor = (EmonADC121Sensor *) _sensors[i];
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
delSetting("eneTotal");
_sensorResetTS();
}
}
#endif
@ -779,7 +824,8 @@ void _sensorConfigure() {
EmonADS1X15Sensor * sensor = (EmonADS1X15Sensor *) _sensors[i];
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
delSetting("eneTotal");
_sensorResetTS();
}
}
#endif
@ -809,7 +855,8 @@ void _sensorConfigure() {
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
delSetting("eneTotal");
_sensorResetTS();
}
if (getSetting("pwrResetCalibration", 0).toInt() == 1) {
@ -847,7 +894,8 @@ void _sensorConfigure() {
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
delSetting("eneTotal");
_sensorResetTS();
}
if (getSetting("pwrResetCalibration", 0).toInt() == 1) {
@ -868,6 +916,11 @@ void _sensorConfigure() {
_magnitudes[i].filter->resize(_sensor_report_every);
}
// General processing
if (0 == _sensor_save_every) {
delSetting("eneTotal");
}
// Save settings
delSetting("pwrExpectedP");
delSetting("pwrExpectedC");
@ -1024,6 +1077,7 @@ void sensorSetup() {
// Backwards compatibility
moveSetting("powerUnits", "pwrUnits");
moveSetting("energyUnits", "eneUnits");
// Load sensors
_sensorLoad();
@ -1037,7 +1091,6 @@ void sensorSetup() {
wsOnSendRegister(_sensorWebSocketStart);
wsOnReceiveRegister(_sensorWebSocketOnReceive);
wsOnSendRegister(_sensorWebSocketSendData);
wsOnAfterParseRegister(_sensorConfigure);
#endif
// API
@ -1050,8 +1103,9 @@ void sensorSetup() {
_sensorInitCommands();
#endif
// Register loop
// Main callbacks
espurnaRegisterLoop(sensorLoop);
espurnaRegisterReload(_sensorConfigure);
}
@ -1074,6 +1128,7 @@ void sensorLoop() {
// Check if we should read new data
static unsigned long last_update = 0;
static unsigned long report_count = 0;
static unsigned long save_count = 0;
if (millis() - last_update > _sensor_read_interval) {
last_update = millis();
@ -1097,6 +1152,10 @@ void sensorLoop() {
if (magnitude.sensor->status()) {
// -------------------------------------------------------------
// Instant value
// -------------------------------------------------------------
current = magnitude.sensor->value(magnitude.local);
// Completely remove spurious values if relay is OFF
@ -1113,17 +1172,26 @@ void sensorLoop() {
}
#endif
// -------------------------------------------------------------
// Processing (filters)
// -------------------------------------------------------------
magnitude.filter->add(current);
// Special case
if (magnitude.type == MAGNITUDE_COUNT) {
// Special case for MovingAvergaeFilter
if (MAGNITUDE_COUNT == magnitude.type ||
MAGNITUDE_GEIGER_CPM ==magnitude. type ||
MAGNITUDE_GEIGER_SIEVERT == magnitude.type) {
current = magnitude.filter->result();
}
current = _magnitudeProcess(magnitude.type, current);
_magnitudes[i].current = current;
// -------------------------------------------------------------
// Debug
// -------------------------------------------------------------
#if SENSOR_DEBUG
{
char buffer[64];
@ -1137,23 +1205,50 @@ void sensorLoop() {
}
#endif // SENSOR_DEBUG
// Time to report (we do it every _sensor_report_every readings)
if (report_count == 0) {
// -------------------------------------------------------------
// Report
// (we do it every _sensor_report_every readings)
// -------------------------------------------------------------
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();
magnitude.filter->reset();
filtered = _magnitudeProcess(magnitude.type, filtered);
_magnitudes[i].filtered = filtered;
magnitude.filter->reset();
// Check if there is a minimum change threshold to report
if (fabs(filtered - magnitude.reported) >= magnitude.min_change) {
_magnitudes[i].reported = filtered;
_sensorReport(i, filtered);
} // if (fabs(filtered - magnitude.reported) >= magnitude.min_change)
// -------------------------------------------------------------
// Saving to EEPROM
// (we do it every _sensor_save_every readings)
// -------------------------------------------------------------
if (_sensor_save_every > 0) {
save_count = (save_count + 1) % _sensor_save_every;
if (0 == save_count) {
if (MAGNITUDE_ENERGY == magnitude.type) {
setSetting("eneTotal", current);
saveSettings();
}
} // if (0 == save_count)
} // if (_sensor_save_every > 0)
} // if (report_count == 0)
} // if (magnitude.sensor->status())
} // for (unsigned char i=0; i<_magnitudes.size(); i++)


+ 2
- 2
code/espurna/sensors/CSE7766Sensor.h View File

@ -102,8 +102,8 @@ class CSE7766Sensor : public BaseSensor {
_ratioC = _ratioV = _ratioP = 1.0;
}
void resetEnergy() {
_energy = 0;
void resetEnergy(double value = 0) {
_energy = value;
}
// ---------------------------------------------------------------------


+ 10
- 2
code/espurna/sensors/DHTSensor.h View File

@ -15,6 +15,7 @@
#define DHT_MIN_INTERVAL 2000
#define DHT_CHIP_DHT11 11
#define DHT_CHIP_DHT12 12
#define DHT_CHIP_DHT22 22
#define DHT_CHIP_DHT21 21
#define DHT_CHIP_AM2301 21
@ -145,7 +146,7 @@ class DHTSensor : public BaseSensor {
pinMode(_gpio, OUTPUT);
noInterrupts();
digitalWrite(_gpio, LOW);
if (_type == DHT_CHIP_DHT11) {
if ((_type == DHT_CHIP_DHT11) || (_type == DHT_CHIP_DHT12)) {
nice_delay(20);
} else {
delayMicroseconds(500);
@ -201,6 +202,9 @@ class DHTSensor : public BaseSensor {
// Get humidity from Data[0] and Data[1]
if (_type == DHT_CHIP_DHT11) {
_humidity = dhtData[0];
} else if (_type == DHT_CHIP_DHT12) {
_humidity = dhtData[0];
_humidity += dhtData[1] * 0.1;
} else {
_humidity = dhtData[0] * 256 + dhtData[1];
_humidity /= 10;
@ -209,6 +213,10 @@ class DHTSensor : public BaseSensor {
// Get temp from Data[2] and Data[3]
if (_type == DHT_CHIP_DHT11) {
_temperature = dhtData[2];
} else if (_type == DHT_CHIP_DHT12) {
_temperature = (dhtData[2] & 0x7F);
_temperature += dhtData[3] * 0.1;
if (dhtData[2] & 0x80) _temperature *= -1;
} else {
_temperature = (dhtData[2] & 0x7F) * 256 + dhtData[3];
_temperature /= 10;
@ -238,7 +246,7 @@ class DHTSensor : public BaseSensor {
unsigned char _errors = 0;
double _temperature = 0;
unsigned int _humidity = 0;
double _humidity = 0;
};


+ 15
- 0
code/espurna/sensors/ECH1560Sensor.h View File

@ -59,6 +59,12 @@ class ECH1560Sensor : public BaseSensor {
return _inverted;
}
// ---------------------------------------------------------------------
void resetEnergy(double value = 0) {
_energy = value;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
@ -106,6 +112,7 @@ class ECH1560Sensor : public BaseSensor {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_APPARENT;
if (index == 3) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
@ -114,6 +121,7 @@ class ECH1560Sensor : public BaseSensor {
if (index == 0) return _current;
if (index == 1) return _voltage;
if (index == 2) return _apparent;
if (index == 3) return _energy;
return 0;
}
@ -260,6 +268,12 @@ class ECH1560Sensor : public BaseSensor {
_apparent = ( (float) byte1 * 255 + (float) byte2 + (float) byte3 / 255.0) / 2;
_current = _apparent / _voltage;
static unsigned long last = 0;
if (last > 0) {
_energy += (_apparent * (millis() - last) / 1000);
}
last = millis();
_dosync = false;
}
@ -287,6 +301,7 @@ class ECH1560Sensor : public BaseSensor {
double _apparent = 0;
double _voltage = 0;
double _current = 0;
double _energy = 0;
unsigned char _data[24];


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

@ -55,6 +55,11 @@ class EmonSensor : public I2CSensor {
}
}
void resetEnergy(unsigned char channel, double value = 0) {
if (channel >= _channels) return;
_energy[channel] = value;
}
// ---------------------------------------------------------------------
void setVoltage(double voltage) {


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

@ -48,7 +48,8 @@ class HLW8012Sensor : public BaseSensor {
_hlw8012->resetMultipliers();
}
void resetEnergy() {
void resetEnergy(double value = 0) {
_energy_offset = value;
_hlw8012->resetEnergy();
}
@ -200,7 +201,7 @@ class HLW8012Sensor : public BaseSensor {
if (index == 3) return _hlw8012->getReactivePower();
if (index == 4) return _hlw8012->getApparentPower();
if (index == 5) return 100 * _hlw8012->getPowerFactor();
if (index == 6) return _hlw8012->getEnergy();
if (index == 6) return (_energy_offset + _hlw8012->getEnergy());
return 0;
}
@ -261,6 +262,7 @@ class HLW8012Sensor : public BaseSensor {
unsigned char _cf = GPIO_NONE;
unsigned char _cf1 = GPIO_NONE;
bool _sel_current = true;
double _energy_offset = 0;
HLW8012 * _hlw8012 = NULL;


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

@ -295,6 +295,9 @@ class PMSX003Sensor : public BaseSensor, PMSX003 {
}
} else {
readCycle = -1;
if (_readCount == 1) {
wakeUp();
}
}
#endif


+ 12
- 1
code/espurna/sensors/PZEM004TSensor.h View File

@ -59,6 +59,16 @@ class PZEM004TSensor : public BaseSensor {
return _pin_tx;
}
// ---------------------------------------------------------------------
void resetEnergy(double value = 0) {
if (_ready) {
_energy_offset = value - (_pzem->energy(_ip) * 3600);
} else {
_energy_offset = value;
}
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
@ -117,7 +127,7 @@ class PZEM004TSensor : public BaseSensor {
if (index == 0) response = _pzem->current(_ip);
if (index == 1) response = _pzem->voltage(_ip);
if (index == 2) response = _pzem->power(_ip);
if (index == 3) response = _pzem->energy(_ip) * 3600;
if (index == 3) response = _energy_offset + (_pzem->energy(_ip) * 3600);
if (response < 0) response = 0;
return response;
}
@ -133,6 +143,7 @@ class PZEM004TSensor : public BaseSensor {
IPAddress _ip;
HardwareSerial * _serial = NULL;
PZEM004T * _pzem = NULL;
double _energy_offset = 0;
};


+ 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
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"


+ 21
- 6
code/espurna/sensors/V9261FSensor.h View File

@ -56,6 +56,12 @@ class V9261FSensor : public BaseSensor {
return _inverted;
}
// ---------------------------------------------------------------------
void resetEnergy(double value = 0) {
_energy = value;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
@ -106,6 +112,7 @@ class V9261FSensor : public BaseSensor {
if (index == 3) return MAGNITUDE_POWER_REACTIVE;
if (index == 4) return MAGNITUDE_POWER_APPARENT;
if (index == 5) return MAGNITUDE_POWER_FACTOR;
if (index == 6) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
@ -117,6 +124,7 @@ class V9261FSensor : public BaseSensor {
if (index == 3) return _reactive;
if (index == 4) return _apparent;
if (index == 5) return _apparent > 0 ? 100 * _active / _apparent : 100;
if (index == 6) return _energy;
return 0;
}
@ -130,6 +138,7 @@ class V9261FSensor : public BaseSensor {
static unsigned char state = 0;
static unsigned long last = 0;
static unsigned long ts = 0;
static bool found = false;
static unsigned char index = 0;
@ -138,10 +147,10 @@ class V9261FSensor : public BaseSensor {
while (_serial->available()) {
_serial->flush();
found = true;
last = millis();
ts = millis();
}
if (found && (millis() - last > V9261F_SYNC_INTERVAL)) {
if (found && (millis() - ts > V9261F_SYNC_INTERVAL)) {
_serial->flush();
index = 0;
state = 1;
@ -164,7 +173,7 @@ class V9261FSensor : public BaseSensor {
_data[index] = _serial->read();
if (index++ >= 19) {
_serial->flush();
last = millis();
ts = millis();
state = 3;
}
}
@ -208,9 +217,14 @@ class V9261FSensor : public BaseSensor {
_apparent = fs_sqrt(_reactive * _reactive + _active * _active);
if (last > 0) {
_energy += (_active * (millis() - last) / 1000);
}
last = millis();
}
last = millis();
ts = millis();
index = 0;
state = 4;
@ -218,10 +232,10 @@ class V9261FSensor : public BaseSensor {
while (_serial->available()) {
_serial->flush();
last = millis();
ts = millis();
}
if (millis() - last > V9261F_SYNC_INTERVAL) {
if (millis() - ts > V9261F_SYNC_INTERVAL) {
state = 1;
}
@ -249,6 +263,7 @@ class V9261FSensor : public BaseSensor {
double _voltage = 0;
double _current = 0;
double _apparent = 0;
double _energy = 0;
double _ratioP = V9261F_POWER_FACTOR;
double _ratioC = V9261F_CURRENT_FACTOR;


+ 1
- 5
code/espurna/settings.ino View File

@ -242,10 +242,6 @@ void _settingsInitCommands() {
settingsRegisterCommand(F("INFO"), [](Embedis* e) {
info();
wifiDebug();
//StreamString s;
//WiFi.printDiag(s);
//DEBUG_MSG(s.c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
@ -276,7 +272,7 @@ void _settingsInitCommands() {
#if WEB_SUPPORT
settingsRegisterCommand(F("RELOAD"), [](Embedis* e) {
wsReload();
espurnaReload();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif


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


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


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


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


+ 2616
- 2601
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

@ -15,6 +15,9 @@ Parts of the code have been borrowed from Thomas Sarlandie's NetServer
AsyncServer * _telnetServer;
AsyncClient * _telnetClients[TELNET_MAX_CLIENTS];
bool _telnetFirst = true;
#if TELNET_PASSWORD
bool _authenticated[TELNET_MAX_CLIENTS];
#endif
// -----------------------------------------------------------------------------
// Private methods
@ -35,8 +38,8 @@ void _telnetWebSocketOnSend(JsonObject& root) {
void _telnetDisconnect(unsigned char clientId) {
_telnetClients[clientId]->free();
_telnetClients[clientId] = NULL;
delete _telnetClients[clientId];
_telnetClients[clientId] = NULL;
wifiReconnectCheck();
DEBUG_MSG_P(PSTR("[TELNET] Client #%d disconnected\n"), clientId);
}
@ -51,11 +54,24 @@ bool _telnetWrite(unsigned char clientId, void *data, size_t len) {
unsigned char _telnetWrite(void *data, size_t len) {
unsigned char count = 0;
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;
}
bool _telnetWrite(unsigned char clientId, const char * message) {
_telnetWrite(clientId, (void *) message, strlen(message));
}
void _telnetData(unsigned char clientId, void *data, size_t len) {
// Skip first message since it's always garbage
@ -80,7 +96,22 @@ void _telnetData(unsigned char clientId, void *data, size_t len) {
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);
}
@ -109,6 +140,7 @@ void _telnetNewClient(AsyncClient *client) {
}
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
if (!_telnetClients[i] || !_telnetClients[i]->connected()) {
_telnetClients[i] = client;
@ -143,8 +175,14 @@ void _telnetNewClient(AsyncClient *client) {
debugClearCrashInfo();
#endif
#if TELNET_PASSWORD
_authenticated[i] = false;
_telnetWrite(i, "Password: ");
#endif
_telnetFirst = true;
wifiReconnectCheck();
return;
}


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

@ -259,7 +259,6 @@ void tspkSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_tspkWebSocketOnSend);
wsOnAfterParseRegister(_tspkConfigure);
wsOnReceiveRegister(_tspkWebSocketOnReceive);
#endif
@ -268,8 +267,9 @@ void tspkSetup() {
THINGSPEAK_USE_SSL ? "ENABLED" : "DISABLED"
);
// Register loop
// Main callbacks
espurnaRegisterLoop(tspkLoop);
espurnaRegisterReload(_tspkConfigure);
}


+ 71
- 45
code/espurna/utils.ino View File

@ -33,6 +33,10 @@ String getBoardName() {
return getSetting("boardName", DEVICE_NAME);
}
String getAdminPass() {
return getSetting("adminPass", ADMIN_PASS);
}
String getCoreVersion() {
String version = ESP.getCoreVersion();
#ifdef ARDUINO_ESP8266_RELEASE
@ -52,10 +56,6 @@ String getCoreRevision() {
#endif
}
unsigned long maxSketchSpace() {
return (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
}
// WTF
// Calling ESP.getFreeHeap() is making the system crash on a specific
// AiLight bulb, but anywhere else...
@ -74,6 +74,10 @@ String getEspurnaSensors() {
}
#endif
String getEspurnaWebUI() {
return FPSTR(espurna_webui);
}
String buildTime() {
const char time_now[] = __TIME__; // hh:mm:ss
@ -249,7 +253,7 @@ void _info_print_memory_layout_line(const char * name, unsigned long bytes, bool
if (reset) index = 0;
if (0 == bytes) return;
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;
}
@ -259,29 +263,34 @@ void _info_print_memory_layout_line(const char * name, unsigned long bytes) {
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 APP_REVISION == NULL
DEBUG_MSG_P(PSTR("[MAIN] " APP_NAME " " APP_VERSION "\n"));
#else
DEBUG_MSG_P(PSTR("[MAIN] " APP_NAME " " APP_VERSION " (" APP_REVISION ")\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"));
// -------------------------------------------------------------------------
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"));
// -------------------------------------------------------------------------
_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("Reserved", 1 * SPI_FLASH_SEC_SIZE, true);
@ -292,61 +301,77 @@ void info() {
_info_print_memory_layout_line("Reserved", 4 * SPI_FLASH_SEC_SIZE);
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
FSInfo fs_info;
bool fs = SPIFFS.info(fs_info);
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 {
DEBUG_MSG_P(PSTR("[INIT] No SPIFFS partition\n"));
DEBUG_MSG_P(PSTR("[MAIN] No SPIFFS partition\n"));
}
DEBUG_MSG_P(PSTR("\n"));
#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] WEBUI IMAGE CODE: %u\n"), WEBUI_IMAGE);
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("[MAIN] Boot version: %d\n"), ESP.getBootVersion());
DEBUG_MSG_P(PSTR("[MAIN] Boot mode: %d\n"), ESP.getBootMode());
unsigned char reason = resetReason();
if (reason > 0) {
char buffer[32];
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 {
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("[INIT] Settings size: %u bytes\n"), settingsSize());
DEBUG_MSG_P(PSTR("[INIT] Free heap: %u bytes\n"), getFreeHeap());
// -------------------------------------------------------------------------
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] WebUI image: %s\n"), getEspurnaWebUI().c_str());
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[MAIN] Firmware MD5: %s\n"), (char *) ESP.getSketchMD5().c_str());
DEBUG_MSG_P(PSTR("[MAIN] Settings size: %u bytes (%d%%)\n"), settingsSize(), 100 * settingsSize() / SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("[MAIN] Free heap: %u bytes\n"), getFreeHeap());
#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
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 (!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
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("\n\n---8<-------\n\n"));
}
@ -447,6 +472,7 @@ int __get_adc_mode() {
bool isNumber(const char * s) {
unsigned char len = strlen(s);
if (0 == len) return false;
bool decimal = false;
for (unsigned char i=0; i<len; i++) {
if (s[i] == '-') {


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

@ -347,7 +347,7 @@ void _onRequest(AsyncWebServerRequest *request){
bool webAuthenticate(AsyncWebServerRequest *request) {
#if USE_PASSWORD
String password = getSetting("adminPass", ADMIN_PASS);
String password = getAdminPass();
char httpPassword[password.length() + 1];
password.toCharArray(httpPassword, password.length() + 1);
return request->authenticate(WEB_USERNAME, httpPassword);
@ -422,7 +422,7 @@ void webSetup() {
#else
_server->begin();
#endif
DEBUG_MSG_P(PSTR("[WEBSERVER] Webserver running on port %u\n"), port);
}


+ 16
- 5
code/espurna/wifi.ino View File

@ -34,13 +34,13 @@ void _wifiConfigure() {
jw.setHostname(getSetting("hostname").c_str());
#if USE_PASSWORD
jw.setSoftAP(getSetting("hostname").c_str(), getSetting("adminPass", ADMIN_PASS).c_str());
jw.setSoftAP(getSetting("hostname").c_str(), getAdminPass().c_str());
#else
jw.setSoftAP(getSetting("hostname").c_str());
#endif
jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
wifiReconnectCheck();
jw.enableAPFallback(true);
jw.enableAPFallback(WIFI_FALLBACK_APMODE);
jw.cleanNetworks();
_wifi_ap_mode = getSetting("apmode", WIFI_AP_FALLBACK).toInt();
@ -226,6 +226,7 @@ void _wifiCallback(justwifi_messages_t code, char * parameter) {
if (MESSAGE_WPS_ERROR == code || MESSAGE_SMARTCONFIG_ERROR == code) {
_wifi_wps_running = false;
_wifi_smartconfig_running = false;
jw.enableAP(true);
}
if (MESSAGE_WPS_SUCCESS == code || MESSAGE_SMARTCONFIG_SUCCESS == code) {
@ -249,6 +250,7 @@ void _wifiCallback(justwifi_messages_t code, char * parameter) {
_wifi_wps_running = false;
_wifi_smartconfig_running = false;
jw.enableAP(true);
}
@ -383,6 +385,11 @@ void _wifiDebugCallback(justwifi_messages_t code, char * parameter) {
void _wifiInitCommands() {
settingsRegisterCommand(F("WIFI"), [](Embedis* e) {
wifiDebug();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("WIFI.RESET"), [](Embedis* e) {
_wifiConfigure();
wifiDisconnect();
@ -487,7 +494,7 @@ void wifiDebug(WiFiMode_t modes) {
if (((modes & WIFI_AP) > 0) && ((WiFi.getMode() & WIFI_AP) > 0)) {
DEBUG_MSG_P(PSTR("[WIFI] -------------------------------------- MODE AP\n"));
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), getSetting("hostname").c_str());
DEBUG_MSG_P(PSTR("[WIFI] PASS %s\n"), getSetting("adminPass", ADMIN_PASS).c_str());
DEBUG_MSG_P(PSTR("[WIFI] PASS %s\n"), getAdminPass().c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.softAPIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.softAPmacAddress().c_str());
footer = true;
@ -550,12 +557,16 @@ void wifiStartAP() {
#if defined(JUSTWIFI_ENABLE_WPS)
void wifiStartWPS() {
jw.enableAP(false);
jw.disconnect();
jw.startWPS();
}
#endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
void wifiStartSmartConfig() {
jw.enableAP(false);
jw.disconnect();
jw.startSmartConfig();
}
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
@ -607,7 +618,6 @@ void wifiSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_wifiWebSocketOnSend);
wsOnReceiveRegister(_wifiWebSocketOnReceive);
wsOnAfterParseRegister(_wifiConfigure);
wsOnActionRegister(_wifiWebSocketOnAction);
#endif
@ -615,8 +625,9 @@ void wifiSetup() {
_wifiInitCommands();
#endif
// Register loop
// Main callbacks
espurnaRegisterLoop(wifiLoop);
espurnaRegisterReload(_wifiConfigure);
}


+ 2
- 17
code/espurna/ws.ino View File

@ -20,7 +20,6 @@ Ticker _web_defer;
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_after_parse_callback_f> _ws_on_after_parse_callbacks;
std::vector<ws_on_receive_callback_f> _ws_on_receive_callbacks;
// -----------------------------------------------------------------------------
@ -256,7 +255,7 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
if (save) {
// Callbacks
wsReload();
espurnaReload();
// This should got to callback as well
// but first change management has to be in place
@ -303,8 +302,7 @@ bool _wsOnReceive(const char * key, JsonVariant& value) {
void _wsOnStart(JsonObject& root) {
#if USE_PASSWORD && WEB_FORCE_PASS_CHANGE
String adminPass = getSetting("adminPass", ADMIN_PASS);
bool changePassword = adminPass.equals(ADMIN_PASS);
bool changePassword = getAdminPass().equals(ADMIN_PASS);
#else
bool changePassword = false;
#endif
@ -429,10 +427,6 @@ void wsOnActionRegister(ws_on_action_callback_f 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) {
if (_ws.count() > 0) {
DynamicJsonBuffer jsonBuffer;
@ -479,15 +473,6 @@ void wsSend_P(uint32_t client_id, PGM_P payload) {
_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() {
_ws.onEvent(_wsEvent);


+ 0
- 4
code/extra_scripts.py View File

@ -4,11 +4,7 @@ from __future__ import print_function
import os
import sys
from subprocess import call
import click
from platformio import util
import distutils.spawn
Import("env", "projenv")


+ 3
- 3
code/gulpfile.js View File

@ -72,8 +72,8 @@ var toHeader = function(name, debug) {
output += '#define ' + safename + '_len ' + source.contents.length + '\n';
output += 'const uint8_t ' + safename + '[] PROGMEM = {';
for (var i=0; i<source.contents.length; i++) {
if (i > 0) output += ',';
if (0 === (i % 20)) output += '\n';
if (i > 0) { output += ','; }
if (0 === (i % 20)) { output += '\n'; }
output += '0x' + ('00' + source.contents[i].toString(16)).slice(-2);
}
output += '\n};';
@ -115,7 +115,7 @@ var buildWebUI = function(module) {
if ('all' === module) {
modules['light'] = true;
modules['sensor'] = true;
modules['rfbridge'] = false; // we will never be adding this except when building RFBRIDGE
modules['rfbridge'] = true;
modules['rfm69'] = false; // we will never be adding this except when building RFM69GW
} else if ('small' !== module) {
modules[module] = true;


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

@ -150,13 +150,17 @@ function validateForm(form) {
// http://www.the-art-of-web.com/javascript/validate-password/
// 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
var adminPass1 = $("input[name='adminPass']", form).first().val();
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;
}
@ -173,13 +177,13 @@ function validateForm(form) {
// No other symbols, punctuation characters, or blank spaces are permitted.
// 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 hasChanged = hostname.attr("hasChanged") || 0;
if (0 === hasChanged) {
var hasChanged = ("true" === hostname.attr("hasChanged"));
if (!hasChanged) {
return true;
}
@ -296,10 +300,18 @@ function sendConfig(data) {
websock.send(JSON.stringify({config: data}));
}
function resetOriginals() {
function setOriginalsFromValues(force) {
var force = (true === force);
$("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;
}
@ -895,31 +907,29 @@ function initMagnitudes(data) {
<!-- removeIf(!light)-->
function initColorRGB() {
function initColor(rgb) {
// check if already initialized
var done = $("#colors > div").length;
if (done > 0) { return; }
// add template
var template = $("#colorRGBTemplate").children();
var template = $("#colorTemplate").children();
var line = $(template).clone();
line.appendTo("#colors");
// init color wheel
$("input[name='color']").wheelColorPicker({
sliders: "wrgbp"
sliders: (rgb ? "wrgbp" : "whsvp")
}).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) {
// check if already initialized
@ -992,7 +980,7 @@ function initChannels(num) {
sendAction("channel", {id: id, value: value});
};
// add templates
// add channel templates
var i = 0;
var template = $("#channelTemplate").children();
for (i=0; i<max; i++) {
@ -1007,11 +995,25 @@ function initChannels(num) {
}
// Init channel dropdowns
for (i=0; i<num; i++) {
$("select.islight").append(
$("<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)-->
@ -1180,13 +1182,13 @@ function processData(data) {
<!-- removeIf(!light)-->
if ("rgb" === key) {
initColorRGB();
initColor(true);
$("input[name='color']").wheelColorPicker("setValue", value, true);
return;
}
if ("hsv" === key) {
initColorHSV();
initColor(false);
// wheelColorPicker expects HSV to be between 0 and 1 all of them
var chunks = value.split(",");
var obj = {};
@ -1412,6 +1414,11 @@ function processData(data) {
if ("ntpStatus" === key) {
value = value ? "SYNC'D" : "NOT SYNC'D";
}
if ("app_revision" === key) {
if (0 === value) {
value = "";
}
}
if ("uptime" === key) {
ago = 0;
var uptime = parseInt(value, 10);
@ -1464,7 +1471,7 @@ function processData(data) {
generateAPIKey();
}
resetOriginals();
setOriginalsFromValues();
}
@ -1478,27 +1485,27 @@ function hasChanged() {
newValue = $(this).val();
originalValue = $(this).attr("original");
}
var hasChanged = $(this).attr("hasChanged") || 0;
var hasChanged = ("true" === $(this).attr("hasChanged"));
var action = $(this).attr("action");
if (typeof originalValue === "undefined") { return; }
if ("none" === action) { return; }
if (newValue !== originalValue) {
if (0 === hasChanged) {
if (!hasChanged) {
++numChanged;
if ("reconnect" === action) { ++numReconnect; }
if ("reboot" === action) { ++numReboot; }
if ("reload" === action) { ++numReload; }
$(this).attr("hasChanged", 1);
$(this).attr("hasChanged", true);
}
} else {
if (1 === hasChanged) {
if (hasChanged) {
--numChanged;
if ("reconnect" === action) { --numReconnect; }
if ("reboot" === action) { --numReboot; }
if ("reload" === action) { --numReload; }
$(this).attr("hasChanged", 0);
$(this).attr("hasChanged", false);
}
}


+ 38
- 22
code/html/index.html View File

@ -43,11 +43,11 @@
<div class="pure-g">
<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-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 />
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 class="pure-g">
@ -137,8 +137,8 @@
</li>
<!-- removeIf(!sensor) -->
<li class="pure-menu-item module module-sensors">
<a href="#" class="pure-menu-link" data="panel-sensors">SENSORS</a>
<li class="pure-menu-item module module-sns">
<a href="#" class="pure-menu-link" data="panel-sns">SENSORS</a>
</li>
<!-- endRemoveIf(!sensor) -->
@ -173,6 +173,7 @@
<div class="footer">
&copy; 2016-2018<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="https://github.com/xoseperez/espurna" target="_blank">ESPurna @ GitHub</a><br/>
GPLv3 license<br/>
@ -323,7 +324,7 @@
<div class="pure-g">
<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-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
@ -522,16 +523,16 @@
<div class="pure-g">
<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-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 />
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 class="pure-g">
<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 class="pure-g">
@ -1141,7 +1142,7 @@
</div>
<!-- removeIf(!sensor) -->
<div class="panel" id="panel-sensors">
<div class="panel" id="panel-sns">
<div class="header">
<h1>SENSOR CONFIGURATION</h1>
@ -1159,23 +1160,30 @@
<div class="pure-g">
<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">
<option value="1">1 second</option>
<option value="6">6 seconds</option>
<option value="10">10 seconds</option>
<option value="15">15 seconds</option>
<option value="30">30 seconds</option>
<option value="60">1 minute</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>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
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 class="pure-g">
<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-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
@ -1183,6 +1191,18 @@
</div>
</div>
<div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-lg-1-4">Save every</label>
<div class="pure-u-1 pure-u-lg-1-4"><input name="snsSave" class="pure-u-1" type="number" min="0" step="1" max="200" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
Save aggregated data to EEPROM after these many reports. At the moment this only applies to total energy readings.
Please mind: saving data to EEPROM too often will wear out the flash memory quickly.
Set it to 0 to disable this feature (default value).
</div>
</div>
<div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-lg-1-4">Power units</label>
<select name="pwrUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
@ -1191,9 +1211,9 @@
</select>
</div>
<div class="pure-g module module-hlw module-cse module-emon module-pzem">
<div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-lg-1-4">Energy units</label>
<select name="energyUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
<select name="eneUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
<option value="0">Joules (J)</option>
<option value="1">Kilowatts·hour (kWh)</option>
</select>
@ -1348,7 +1368,7 @@
<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="ip" 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-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>
<input class="pure-u-1 pure-u-lg-3-4 more" name="gw" type="text" action="reconnect" value="" maxlength="15" tabindex="0" autocomplete="false" />
@ -1518,12 +1538,15 @@
<!-- endRemoveIf(!sensor) -->
<!-- removeIf(!light) -->
<div id="colorRGBTemplate" class="template">
<div id="colorTemplate" 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="brightnessTemplate" class="template">
<div class="pure-g">
<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">
@ -1531,13 +1554,6 @@
</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 class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Channel #</label>


+ 8
- 2
code/ota.py View File

@ -39,6 +39,8 @@ def on_service_state_change(zeroconf, service_type, name, state_change):
Callback that adds discovered devices to "devices" list
"""
global discover_last
if state_change is ServiceStateChange.Added:
discover_last = time.time()
info = zeroconf.get_service_info(service_type, name)
@ -60,6 +62,11 @@ def on_service_state_change(zeroconf, service_type, name, state_change):
for key, item in info.properties.items():
device[key.decode('UTF-8')] = item.decode('UTF-8');
# rename fields (needed for sorting by name)
device['app'] = device['app_name']
device['device'] = device['target_board']
device['version'] = device['app_version']
devices.append(device)
@ -141,7 +148,6 @@ def get_board_by_mac(mac):
"""
Returns the required data to flash a given board
"""
hostname = hostname.lower()
for device in devices:
if device.get('mac', '').lower() == mac:
board = {}
@ -209,7 +215,7 @@ def input_board():
# Choose board size of none before
if board.get('size', 0) == 0:
try:
board['size'] = int(input("Board memory size (1 for 1M, 4 for 4M): "))
board['size'] = int(input("Board memory size (1 for 1M, 2 for 2M, 4 for 4M): "))
except ValueError:
print("Wrong memory size")
return None


+ 206
- 4
code/platformio.ini View File

@ -1,5 +1,5 @@
[platformio]
env_default = wemos-d1mini-relayshield
env_default = nodemcu-lolin
src_dir = espurna
data_dir = espurna/data
@ -41,6 +41,7 @@ debug_flags = -DDEBUG_ESP_CORE -DDEBUG_ESP_SSL -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP
build_flags = -g -w -DMQTT_MAX_PACKET_SIZE=400 -DNO_GLOBAL_EEPROM ${sysenv.ESPURNA_FLAGS} -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH
build_flags_512k = ${common.build_flags} -Wl,-Teagle.flash.512k0m1s.ld
build_flags_1m0m = ${common.build_flags} -Wl,-Teagle.flash.1m0m1s.ld
build_flags_2m1m = ${common.build_flags} -Wl,-Teagle.flash.2m1m4s.ld
build_flags_4m1m = ${common.build_flags} -Wl,-Teagle.flash.4m1m4s.ld
build_flags_4m3m = ${common.build_flags} -Wl,-Teagle.flash.4m3m4s.ld
@ -55,6 +56,7 @@ upload_flags = --auth="${sysenv.ESPURNA_AUTH}"
# ------------------------------------------------------------------------------
framework = arduino
board_1m = esp01_1m
board_2m = esp_wroom_02
board_4m = esp12e
flash_mode = dout
monitor_speed = 115200
@ -70,20 +72,21 @@ lib_deps =
ArduinoJson
https://github.com/marvinroger/async-mqtt-client#v0.8.1
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
Embedis
Encoder
https://github.com/plerup/espsoftwareserial#3.4.1
https://github.com/me-no-dev/ESPAsyncTCP#55cd520
https://github.com/me-no-dev/ESPAsyncWebServer#05306e4
https://bitbucket.org/xoseperez/fauxmoesp.git#2.4.2
https://bitbucket.org/xoseperez/fauxmoesp.git#3.0.1
https://github.com/xoseperez/hlw8012.git#1.1.0
https://github.com/markszabo/IRremoteESP8266#v2.2.0
https://github.com/xoseperez/justwifi.git#2.0.1
https://github.com/madpilot/mDNSResolver#4cfcda1
https://github.com/xoseperez/my92xx#3.0.1
https://bitbucket.org/xoseperez/nofuss.git#0.2.5
https://github.com/xoseperez/NtpClient.git#0016a59
https://github.com/xoseperez/NtpClient.git#0942ebc
OneWire
PZEM004T
PubSubClient
@ -108,6 +111,17 @@ build_flags = ${common.build_flags_1m0m} -DESPURNA_CORE
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:espurna-core-2MB]
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} -DESPURNA_CORE
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:espurna-core-4MB]
platform = ${common.platform}
framework = ${common.framework}
@ -136,6 +150,19 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:esp8266-2m-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} -D${sysenv.ESPURNA_BOARD}
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:esp8266-4m-ota]
platform = ${common.platform}
framework = ${common.framework}
@ -1699,6 +1726,31 @@ upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:yjzk-switch-1ch]
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} -DYJZK_SWITCH_1CH
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:yjzk-switch-1ch-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} -DYJZK_SWITCH_1CH
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:yjzk-switch-2ch]
platform = ${common.platform}
framework = ${common.framework}
@ -1724,6 +1776,31 @@ upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:yjzk-switch-3ch]
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} -DYJZK_SWITCH_3CH
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:yjzk-switch-3ch-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} -DYJZK_SWITCH_3CH
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:generic-8ch]
platform = ${common.platform}
framework = ${common.framework}
@ -2446,3 +2523,128 @@ upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:lohas-e27-9w]
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} -DLOHAS_9W
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:lohas-e27-9w-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} -DLOHAS_9W
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:allterco-shelly1]
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_SHELLY1
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:allterco-shelly1-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_SHELLY1
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: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/ag-l4.jpg View File

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

BIN
images/devices/allterco-shelly1.jpg View File

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

BIN
images/devices/blitzwolf-bw-shp2.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 15 KiB Width: 400  |  Height: 400  |  Size: 28 KiB

BIN
images/devices/homecube-16a.jpg View File

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

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

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

BIN
images/devices/lohas-9w.jpg View File

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

+ 1
- 3
pre-commit View File

@ -84,9 +84,7 @@ TEMPLATES = {
"(https://travis-ci.org/{USER}/{REPO})\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)]" \
"(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"


Loading…
Cancel
Save