Browse Source

Merge branch 'dev' into softuart

softuart
Xose Pérez 5 years ago
parent
commit
89d933d246
182 changed files with 18577 additions and 5823 deletions
  1. +41
    -0
      .github/stale.yml
  2. +2
    -0
      .gitignore
  3. +1
    -1
      .travis.yml
  4. +137
    -3
      CHANGELOG.md
  5. +101
    -45
      README.md
  6. +8
    -3
      code/.gitignore
  7. +24
    -2
      code/build.sh
  8. +4
    -4
      code/eagle.flash.1m0m1s.ld
  9. +19
    -0
      code/eagle.flash.1m0m2s.ld
  10. +21
    -0
      code/eagle.flash.4m1m4s.ld
  11. +20
    -0
      code/eagle.flash.4m3m4e.ld
  12. +18
    -0
      code/eagle.flash.512k0m1s.ld
  13. +6
    -0
      code/espurna/alexa.ino
  14. +13
    -3
      code/espurna/api.ino
  15. +37
    -14
      code/espurna/button.ino
  16. +4
    -2
      code/espurna/config/all.h
  17. +36
    -1
      code/espurna/config/arduino.h
  18. +17
    -0
      code/espurna/config/debug.h
  19. +28
    -6
      code/espurna/config/defaults.h
  20. +50
    -0
      code/espurna/config/dependencies.h
  21. +528
    -329
      code/espurna/config/general.h
  22. +1016
    -26
      code/espurna/config/hardware.h
  23. +280
    -0
      code/espurna/config/progmem.h
  24. +26
    -8
      code/espurna/config/prototypes.h
  25. +263
    -181
      code/espurna/config/sensors.h
  26. +302
    -0
      code/espurna/config/types.h
  27. +2
    -1
      code/espurna/config/version.h
  28. BIN
      code/espurna/data/index.html.gz
  29. +99
    -29
      code/espurna/debug.ino
  30. +17
    -0
      code/espurna/domoticz.ino
  31. +86
    -0
      code/espurna/eeprom.ino
  32. +17
    -2
      code/espurna/espurna.ino
  33. +202
    -78
      code/espurna/homeassistant.ino
  34. +31
    -16
      code/espurna/i2c.ino
  35. +7
    -2
      code/espurna/influxdb.ino
  36. +12
    -1
      code/espurna/ir.ino
  37. +47
    -16
      code/espurna/led.ino
  38. +47
    -27
      code/espurna/libs/StreamInjector.h
  39. +1
    -1
      code/espurna/libs/WebSocketIncommingBuffer.h
  40. +636
    -0
      code/espurna/libs/fs_math.c
  41. +116
    -0
      code/espurna/libs/fs_math.h
  42. +0
    -0
      code/espurna/libs/pwm.c
  43. +299
    -279
      code/espurna/light.ino
  44. +1
    -4
      code/espurna/mdns.ino
  45. +306
    -0
      code/espurna/migrate.ino
  46. +128
    -65
      code/espurna/mqtt.ino
  47. +10
    -1
      code/espurna/nofuss.ino
  48. +31
    -4
      code/espurna/ntp.ino
  49. +11
    -1
      code/espurna/ota.ino
  50. +147
    -46
      code/espurna/relay.ino
  51. +144
    -61
      code/espurna/rf.ino
  52. +174
    -64
      code/espurna/rfbridge.ino
  53. +71
    -31
      code/espurna/scheduler.ino
  54. +409
    -80
      code/espurna/sensor.ino
  55. +198
    -0
      code/espurna/sensors/AM2320Sensor.h
  56. +6
    -1
      code/espurna/sensors/AnalogSensor.h
  57. +5
    -1
      code/espurna/sensors/BH1750Sensor.h
  58. +23
    -12
      code/espurna/sensors/BMX280Sensor.h
  59. +8
    -2
      code/espurna/sensors/BaseSensor.h
  60. +378
    -0
      code/espurna/sensors/CSE7766Sensor.h
  61. +3
    -2
      code/espurna/sensors/DHTSensor.h
  62. +2
    -10
      code/espurna/sensors/DallasSensor.h
  63. +1
    -0
      code/espurna/sensors/DigitalSensor.h
  64. +25
    -14
      code/espurna/sensors/ECH1560Sensor.h
  65. +1
    -6
      code/espurna/sensors/EmonADS1X15Sensor.h
  66. +4
    -0
      code/espurna/sensors/EmonAnalogSensor.h
  67. +28
    -7
      code/espurna/sensors/EmonSensor.h
  68. +1
    -0
      code/espurna/sensors/EventSensor.h
  69. +174
    -0
      code/espurna/sensors/GUVAS12SDSensor.h
  70. +298
    -0
      code/espurna/sensors/GeigerSensor.h
  71. +119
    -0
      code/espurna/sensors/HCSR04Sensor.h
  72. +6
    -0
      code/espurna/sensors/HLW8012Sensor.h
  73. +36
    -14
      code/espurna/sensors/I2CSensor.h
  74. +4
    -1
      code/espurna/sensors/MHZ19Sensor.h
  75. +210
    -35
      code/espurna/sensors/PMSX003Sensor.h
  76. +133
    -0
      code/espurna/sensors/PZEM004TSensor.h
  77. +8
    -2
      code/espurna/sensors/SHT3XI2CSensor.h
  78. +23
    -12
      code/espurna/sensors/SI7021Sensor.h
  79. +233
    -0
      code/espurna/sensors/SenseAirSensor.h
  80. +94
    -0
      code/espurna/sensors/TMP3XSensor.h
  81. +7
    -2
      code/espurna/sensors/V9261FSensor.h
  82. +90
    -59
      code/espurna/settings.ino
  83. +3251
    -3191
      code/espurna/static/index.html.gz.h
  84. +71
    -23
      code/espurna/system.ino
  85. +27
    -0
      code/espurna/telnet.ino
  86. +14
    -1
      code/espurna/thinkspeak.ino
  87. +105
    -0
      code/espurna/uartmqtt.ino
  88. +109
    -158
      code/espurna/utils.ino
  89. +34
    -7
      code/espurna/web.ino
  90. +259
    -60
      code/espurna/wifi.ino
  91. +124
    -15
      code/espurna/ws.ino
  92. +22
    -13
      code/extra_scripts.py
  93. +13
    -8
      code/gulpfile.js
  94. +42
    -5
      code/html/custom.css
  95. +226
    -52
      code/html/custom.js
  96. BIN
      code/html/favicon.ico
  97. +320
    -119
      code/html/index.html
  98. BIN
      code/html/vendor/images/border-off.png
  99. BIN
      code/html/vendor/images/border-on.png
  100. BIN
      code/html/vendor/images/handle-center.png

+ 41
- 0
.github/stale.yml View File

@ -0,0 +1,41 @@
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 120
# Number of days of inactivity before a stale Issue or Pull Request is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 30
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- enhancement
- bug
- staged for release
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Label to use when marking as stale
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.
Thank you for your contributions.
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here.
# 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.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
only: issues

+ 2
- 0
.gitignore View File

@ -13,3 +13,5 @@ code/utils
custom.h
.python
.env
.DS_Store
.vscode

+ 1
- 1
.travis.yml View File

@ -9,7 +9,7 @@ install:
- pip install -U platformio
- cd code ; npm install --only=dev ; cd ..
script:
- cd code ; ./build.sh; cd ..
- cd code && ./build.sh && cd ..
before_deploy:
- mv firmware/*/espurna-*.bin firmware/
deploy:


+ 137
- 3
CHANGELOG.md View File

@ -3,6 +3,140 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.12.6] 2018-05-02
### Fixed
- Check NTP_SUPPORT for sensors (thanks to @mcspr)
- Fix AM2302 sensor
- Fix hostname truncated to 20 chars when advertised to DHCP ([#774](https://github.com/xoseperez/espurna/issues/774))
- Decouple Serial object from Terminal, Debug modules ([#787](https://github.com/xoseperez/espurna/issues/787))
- Fix Arilux LC-01 definitions ([#797](https://github.com/xoseperez/espurna/issues/797))
- Do not uppercase hostname in web interface ([#799](https://github.com/xoseperez/espurna/issues/799))
- Ensure scheduler has access to all channels independently of the color mode ([#807](https://github.com/xoseperez/espurna/issues/807))
### Added
- Support for IteadStudio Sonoff S31 ([#497](https://github.com/xoseperez/espurna/issues/497))
- Option to ignore daylight saving in scheduler ([#783](https://github.com/xoseperez/espurna/issues/783))
- Report last energy reset datetime in web interface ([#784](https://github.com/xoseperez/espurna/issues/784))
- Added captive portal in AP mode
- Support for IR toggle mode (thanks to @darshkpatel)
- Support for IteadStudio Sonoff POW R2 (thanks to @ColinShorts)
- Support for Luani HVIO (thanks to @wildwiz)
- Support for Zhilde ZLD-EU55-W power strip (thanks to @wildwiz)
- Support for RFB_DIRECT Sonoff Bridge EFM8BB1 bypass hack (thanks to @wildwiz)
- Support for SenseAir S8 CO2 sensor (thanks to @Yonsm)
- Support for PMS5003T/ST sensors (thanks to @Yonsm)
### Changed
- Updated JustWifi Library
- Some cleanup in the web interface
- Refactored configuration files (thanks to @lobradov, @mcspr)
- Changes pre-commit hook (thanks to @mcspr)
## [1.12.5] 2018-04-08
### Fixed
- Fixed expected power calibration ([#676](https://github.com/xoseperez/espurna/issues/676))
- Do not show empty time strings ([#691](https://github.com/xoseperez/espurna/issues/691), thanks to @PieBru)
- Fix load average calculation when system check is disabled ([#707](https://github.com/xoseperez/espurna/issues/707))
- Fixed unstability issues with NtpClientLib using temporary fork ([#743](https://github.com/xoseperez/espurna/issues/743))
- Fixed typos in homeassistant module (thanks to @Cabalist)
- Fixed default HLW8012 calibration for KMC devices (thanks to @gn0st1c)
- Fix MQTT query request
- Fix scheduler debug message
- Fix NTP offset value
### Added
- Option to change NTP timeout via compile-time setting ([#452](https://github.com/xoseperez/espurna/issues/452))
- Added humidity correction to web UI ([#626](https://github.com/xoseperez/espurna/issues/626), tahnks to @ManuelW77)
- Added support for USA DST calculation ([#664](https://github.com/xoseperez/espurna/issues/664))
- Option to reset energy count ([#671](https://github.com/xoseperez/espurna/issues/671))
- Added Sonoff SV prebuild image ([#698](https://github.com/xoseperez/espurna/issues/698), thanks to @akasma74)
- Check and remove unused config keys ([#730](https://github.com/xoseperez/espurna/issues/730))
- Visual Studio metadata files added to .gitignore ([#731](https://github.com/xoseperez/espurna/issues/731), thanks to @gn0st1c)
- Added default MQTT and SSL settings to web UI ([#732](https://github.com/xoseperez/espurna/issues/732), thanks to @mcspr)
- Added option to the web UI to set the light transition length in milliseconds ([#739](https://github.com/xoseperez/espurna/issues/739))
- Improved testing with Travis (thanks to @lobradov)
- Change dimmers using schedule (thanks to @wysiwyng)
- Debug console in web UI (thanks to @lobradov), including command execution
- Option to reset relays in MQTT disconection (thanks to @a-tom-s)
- Option to disable system check from custom header (thanks to @phuonglm)
- Added "board" topic to the heartbeat messages (thanks to @mcspr)
- Added methods to create hierarchical MQTT JSON responses
- Added RESET.SAFE command to reboot into safe mode
- Added SDK and Core versions to the web UI
- Added revision to web UI (only when built from build.sh)
- Support for OBI Powerplug Adapter ([#622](https://github.com/xoseperez/espurna/issues/622), thanks to @Geitde)
- Support for Tunbox Powerstrip02 (thanks to @gn0st1c)
- Support for Lingan SWA1 (thanks to @gn0st1c)
- Support for Heygo HY02 (thanks to @gn0st1c)
- Support for Maxcio WUS0025 (thanks to @gn0st1c)
- Support for Yidian XSSSA05 SWA1 (thanks to @gn0st1c)
- Support for ArnieX Swifitch (thanks to @LubergAlexander)
- Support for IKE ESPIKE board
- Support for AM2320 sensors via I2C (thanks to @gn0st1c)
- Support for GUVAS12SD sensor (thanks to @gn0st1c)
### Changed
- Removed hostname size limit ([#576](https://github.com/xoseperez/espurna/issues/576), [#659](https://github.com/xoseperez/espurna/issues/659))
- Reworked RGBW implementation (thanks to @Skaronator)
- Several web UI layout changes (thanks to @lobradov & @mcspr)
- Button MQTT messages will not have the retain flag (thanks to @lobradov)
- Remove unnecessary code from boot log (thanks to @gn0st1c)
- Updated logo and favicon, added gitter channel
- Force reporting power values as 0 if relay is off
- Using gulp-crass for CSS minification
- Using WIFI_NONE_SLEEP by default
## [1.12.4] 2018-03-05
### Fixed
- Adding a 1ms delay after UDP send to avoid loosing packets ([#438](https://github.com/xoseperez/espurna/issues/438))
- Fixed void return in BMX280 sensor ([#489](https://github.com/xoseperez/espurna/issues/489))
- Fix MQTT keep alive cannot be more than 255 seconds ([#515](https://github.com/xoseperez/espurna/issues/515))
- Do not show scheduler tab in Web UI if build without scheduler support ([#527](https://github.com/xoseperez/espurna/issues/527))
- Fix inline documentation for Sonoff 4CH Pro button modes ([#551](https://github.com/xoseperez/espurna/issues/551))
- Prevent resending messages from rfin in RF Bridge ([#561](https://github.com/xoseperez/espurna/issues/561))
- Fix AnalogSensor description ([#601](https://github.com/xoseperez/espurna/issues/601))
- Fixed missing setting in HASS WS callback (thanks to @mcspr)
- ECH1560 call sync from tick method
- Fixed several issues reported by codacy
### Added
- UART to MQTT module (thanks to Albert Weterings, [#529](https://github.com/xoseperez/espurna/issues/529))
- Added option to show HASS configuration code in ESPurna web UI ([#616](https://github.com/xoseperez/espurna/issues/616))
- OTA upgrade via terminal (using 'ota' command, with SSL support)
- Added I2C scan and clear commands to terminal (only when I2C enabled)
- Added new relay & wifi led mode ([#604](https://github.com/xoseperez/espurna/issues/604))
- Option to enable/disable web auth from web UI
- Added "Reset to factory settings" in web UI (thanks to Teo Pavel, [#569](https://github.com/xoseperez/espurna/issues/569))
- Added {magnitude} placeholder to MQTT root topic
- Option to report energy in kWh and power in kW ([#523](https://github.com/xoseperez/espurna/issues/523))
- Check upgrade file size and signature in web UI
- Automatically dump info on telnet connection if TERMINAL_SUPPORT is disabled
- Two different ESPURNA_CORE images for 1MB and 4MB boards, freeing GPIOs ([#557](https://github.com/xoseperez/espurna/issues/557))
- Initial support for PZEM004T sensor (still beta)
- Support for STM_RELAY board (thanks to Maciej Czerniak)
- Support for KMC 70011 energy monitor (thanks to Wayne Manion, [#598](https://github.com/xoseperez/espurna/issues/598))
- Support for Wifi Stecker Shuko device (thanks to @Geitde, [#622](https://github.com/xoseperez/espurna/issues/622))
- Support for GizWits Witty Cloud device (thanks to Theonedemon)
### Changed
- BMX280 changes to allow for hot-plug ([#353](https://github.com/xoseperez/espurna/issues/353))
- Increase the initial check interval for NTP ([#452](https://github.com/xoseperez/espurna/issues/452))
- Force turning relays off before turning others on when synced ([#491](https://github.com/xoseperez/espurna/issues/491))
- Publish slampher as light to Home Assistant ([#494](https://github.com/xoseperez/espurna/issues/494))
- Force API to return the target status of the relay ([#548](https://github.com/xoseperez/espurna/issues/548))
- Increasing max number of messages in JSON payload to 20 ([#588](https://github.com/xoseperez/espurna/issues/588))
- Change copy from 'Use colorpicker' to 'Use color'. Better hint. ([#590](https://github.com/xoseperez/espurna/issues/590))
- Completely reworked the RF module to use the same web UI as the RFBridge module to learn new codes ([#594](https://github.com/xoseperez/espurna/issues/594))
- Several spelling and grammar changes by Lee Marlow
- Always enabled telnet access in ESPURNA_CORE image
- Updated ESPSoftwareSerial, ESPAsyncTCP and ESPAsyncWebServer libraries
### Removed
- Remove dependency from gulp-util ([#493](https://github.com/xoseperez/espurna/issues/493))
- Removed specific support for Magic Home LED Controller 2.3 ([#512](https://github.com/xoseperez/espurna/issues/512))
- Disabled floating point support when building against Arduino Core 2.4.0 with PIO
- Removed WiFi distance calculation
## [1.12.3] 2018-01-29
### Fixed
- Fix telnet crash due to local reference ([#487](https://github.com/xoseperez/espurna/issues/487))
@ -42,7 +176,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Fixed pulse and pulse_ms order in relay_t structure ([#424](https://github.com/xoseperez/espurna/issues/424))
- Use same buffer size across all terminal-realted classes/methods. Set to 128 chars ([#477](https://github.com/xoseperez/espurna/issues/477), [#478](https://github.com/xoseperez/espurna/issues/478))
- Fix WiFi scan status in web UI
- Several code quality fixes (thanks to Lazar Obradovic)
- Several code quality fixes (thanks to @lobradov)
- Fixed error message on first command over telnet
### Changed
@ -75,7 +209,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Scheduler (contributed by Stefano Cotterli, thank you!, [#131](https://github.com/xoseperez/espurna/issues/131))
- Added "wifi.scan" command to terminal
- Added ESPurna Switch board support
- Added support for python3 in memanalyzer and ota scripts (thanks to Ryan Jarvis)
- Added support for python3 in memanalyzer and ota scripts (thanks to @Cabalist)
- Added BSSID, RSSI, channels and distance to web UI status tab
- Added mDNS name resolving to MQTT, InfluxDB and NoFUSS modules ([#129](https://github.com/xoseperez/espurna/issues/129), disabled by default)
@ -92,7 +226,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.11.4] 2018-01-09
### Fixed
- Fix bug in RF Bridge when RF code contains the stop byte. Check overflow ([#357](https://github.com/xoseperez/espurna/issues/357))
- Fixed typos in code and wiki (Thanks to Ryan Jarvis)
- Fixed typos in code and wiki (Thanks to @Cabalist)
- Fix bug in magnitude topic and units ([#355](https://github.com/xoseperez/espurna/issues/355))
### Added


+ 101
- 45
README.md View File

@ -1,31 +1,37 @@
# ESPurna Firmware
ESPurna ("spark" in Catalan) is a custom firmware for ESP8266 based smart switches and sensors.
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.12.4a-brightgreen.svg)](CHANGELOG.md)
![branch](https://img.shields.io/badge/branch-softuart-orange.svg)
[![version](https://img.shields.io/badge/version-1.13.0c-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-softuart-orange.svg)](https://github.org/xoseperez/espurna/tree/softuart/)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=softuart)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/softuart.svg)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
<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)
[![gitter](https://img.shields.io/gitter/room/tinkermant-cat/espurna.svg)](https://gitter.im/tinkerman-cat/espurna)
[![twitter](https://img.shields.io/twitter/follow/xoseperez.svg?style=social)](https://twitter.com/intent/follow?screen_name=xoseperez)
---
## Notice
> 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
* *KRACK* vulnerability free (when built against Arduino Core 2.4.0)
* *KRACK* vulnerability free (when built with Arduino Core >= 2.4.0)
* Support for **multiple ESP8266-based boards** ([check list](https://github.com/xoseperez/espurna/wiki/Hardware))
* Power saving options
* Wifi **AP Mode** or **STA mode**
* Up to 5 different networks can be defined
* Supports static IP
* Up to 5 different networks can be defined
* Scans for strongest network if more than one defined (also available in web UI)
* Handles correctly multiple AP with the same SSID
* Defaults to AP mode (also available after double clicking the main button)
* Network visibility
* Supports mDNS (service reporting and metadata) both server mode and client mode (.local name resolution)
* Supports NetBIOS, LLMNR and Netbios (when built against Arduino Core 2.4.0) and SSDP (experimental)
* Supports NetBIOS, LLMNR and Netbios (when built with Arduino Core >= 2.4.0) and SSDP (experimental)
* Switch management
* Support for **push buttons** and **toggle switches**
* Configurable **status on boot** per switch (always ON, always OFF, same as before or toggle)
@ -41,47 +47,55 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Change LED notification mode
* Remote reset the board
* Fully configurable in webUI (broker, user, password, QoS, keep alive time, retain flag, client ID)
* **Scheduler** to automatically turn on, off or toggle any relay at a given time and day
* **Scheduler** to automatically turn on, off or toggle any relay at a given time and day, also change light intensity for dimmers
* **Alexa** integration using the [FauxmoESP Library](https://bitbucket.org/xoseperez/fauxmoesp)
* [**Google Assistant**](http://tinkerman.cat/using-google-assistant-control-your-esp8266-devices/) integration using IFTTT and Webhooks (Google Home, Allo)
* [**Domoticz**](https://domoticz.com/) integration via MQTT
* [**Home Assistant**](https://home-assistant.io/) integration via MQTT
* [**Home Assistant**](https://home-assistant.io/) integration
* Support for switches (on/off)
* Support for lights (color, brightness, on/off state)
* Supports MQTT auto-discover feature (switches, lights and sensors)
* Integration via MQTT Discover or copy-pasting configuration code
* [**InfluxDB**](https://www.influxdata.com/) integration via HTTP API
* [**Thingspeak**](https://thingspeak.com/) integration via HTTP API (HTTPS available for custom builds)
* **Sonoff RF Bridge** support
* Multiple virtual switches (tested with up to 16)
* MQTT-to-RF two-way bridge (no need to learn codes)
* Support for https://github.com/rhx/RF-Bridge-EFM8BB1 custom firmware
* Support for different **sensors**
* Support for [https://github.com/Portisch/RF-Bridge-EFM8BB1](https://github.com/Portisch/RF-Bridge-EFM8BB1) custom firmware
* 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**
* **BMP280** and **BME280** temperature, humidity (BME280) and pressure sensor by Bosch
* **SI7021** temperature and humidity sensor
* **SHT3X** temperature and humidity sensor over I2C (Wemos shield)
* **AM2320** temperature and humidity sensor over I2C
* **Dallas OneWire sensors** like the DS18B20
* **MHZ19** CO2 sensor
* **PMSX003** dust sensor
* **SenseAir S8** CO2 sensor
* **PMSX003/PMS5003T/ST** dust sensors
* **BH1750** luminosity sensor
* **GUVAS12SD** UV sensor
* Power monitoring
* **HLW8012** using the [HLW8012 Library](https://bitbucket.org/xoseperez/hlw8012) (Sonoff POW)
* Non-invasive **current sensor** using **internal ADC** or **ADC121** or **ADS1115**
* **ECH1560** power monitor chip
* **V9261F** power monitor chip
* **PZEM0004T** power monitor board
* Raw analog and digital sensors
* Simple pulse counter
* All temperature sensors support Fahrenheit and Celsius
* Support for different units (Fahrenheit or Celsius, Watts or Kilowatts, Joules or kWh)
* Support for LED lights
* MY92XX-based light bulbs and PWM LED strips (dimmers) up to 5 channels (RGB, cold white and warm white, for instance)
* RGB and HSV color codes supported
* Manage channels individually
* Temperature color supported (in mired and kelvin) via MQTT / REST API
* Temperature color supported (in [mired](https://en.wikipedia.org/wiki/Mired) and [kelvin](https://en.wikipedia.org/wiki/Color_temperature)) via MQTT / REST API
* Flicker-free PWM management
* Soft color transitions
* Color synchronization between light using MQTT
* Option to have separate switches for each channel
* Support for simple 433MHz RF receivers
* Support for UART-to-MQTT bidirectional bridge
* Fast asynchronous **HTTP Server**
* Configurable port
* Basic authentication
@ -108,13 +122,14 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Run special commands
* **Telnet support**
* Enable/disable via the web UI
* Show debug info and allows to run terminal commands
* Shows debug info and allows to run terminal commands
* **NTP** for time synchronization
* Supports worldwide time zones
* Compatible with DST
* Compatible with DST (EU and USA)
* **Unstable system check**
* Detects unstable system (crashes on boot continuously) and defaults to a stable system
* Only WiFi AP, OTA and Telnet available if system is flagged as unstable
* Configurable LED notifications based on WiFi status, relays status or MQTT messages.
* Button interface
* Click to toggle relays
* Double click to enter AP mode (only main button)
@ -125,7 +140,17 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
## Notices
---
> **2018-01-24**: This repository has been migrated from Bitbucket to GitHub. There were a number of reason to migrate the repository to GitHub. I like Bitbucket and I'm still using it for a lot of projects, but ESPurna has grown and its community as well. Some users have complain about Bitbucket not being enough community-focused. This change is mainly aimed to use a platform with greater acceptance on the open-source community and tools better suited to them (to you), like the possibility to contribute to the documentation in an easy way.
> **2018-04-08**<br />
> Please use [gitter](https://gitter.im/tinkerman-cat/espurna) for support and questions, you have better chances to get fast answers by 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.
---
> **2018-03-09**<br />
> Default branch in GitHub is now the development branch "dev".<br>
> The stable branch (the one used to create the [binary releases](https://github.com/xoseperez/espurna/releases)) is "[master](https://github.com/xoseperez/espurna/tree/master)".
---
> **2018-01-24**<br />
> This repository has been migrated from Bitbucket to GitHub. There were a number of reason to migrate the repository to GitHub. I like Bitbucket and I'm still using it for a lot of projects, but ESPurna has grown and its community as well. Some users have complain about Bitbucket not being enough community-focused. This change is mainly aimed to use a platform with greater acceptance on the open-source community and tools better suited to them (to you), like the possibility to contribute to the documentation in an easy way.
>
>What happened with all the info in Bitbucket? Well, most of it has been ported to GitHub, albeit with some quirks:
>
@ -133,24 +158,27 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
>* **Issues** are all on GitHub already **but** all issues and comments show up as reported by me. The original reporter is referenced inside the body of the issue (or comment) with a link to his/her profile at Bitbucket and a link to his/her profile at GitHub if it happens to be the same username. I **suggest all reporters to subscribe to the issues they originally filed** (search for your BitBucket username to list them).
>* **Pull requests** historic has not been migrated. At the moment of the migration all pull-requests have been either merged or declined. Of course, those PR merged are in the code base, but the historic and comments in the PR pages will be lost.
>* **Documentation** it's on it way, first step will be to migrate existing wiki, maybe with a new TOC structure
>* **Watchers**, **Forks**, I'm afraid they are all gone. Visit the new repop home and click on the "Watch" button on the top right. And as you do it click also on the "Star" button too :)
>* **Watchers**, **Forks**, I'm afraid they are all gone. Visit the new repo home and click on the "Watch" button on the top right. And as you do it click also on the "Star" button too :)
>
>I apologize for any inconvenience this migration may have caused. I have decided to do it the hard way.
---
> **2018-01-11**: As of current version (1.12.0) ESPurna is tested using Arduino Core 2.3.0 and it's meant to be built against that version.
> **2018-01-11**<br />
> As of current version (1.12.0) ESPurna is tested using Arduino Core 2.3.0 and it's meant to be built against that version.
---
> **2017-08-26**: since version 1.9.0 the default **MQTT topics for commands have changed**. They all now end with "/set". This means you will have to change your controller software (Node-RED or alike) to send messages to -for instance- "/home/living/light/relay/0/set". The device will publish its state in "/home/living/light/relay/0" like before.
> **2017-08-26**<br />
> Since version 1.9.0 the default **MQTT topics for commands have changed**. They all now end with "/set". This means you will have to change your controller software (Node-RED or alike) to send messages to -for instance- "/home/living/light/relay/0/set". The device will publish its state in "/home/living/light/relay/0" like before.
---
> **2017-07-24**: Default flash layout changed in 1.8.3, as an unpredicted consequence devices will not be able to persist/retrieve configuration if flashed with 1.8.3 via **OTA** from **PlatformIO**. Please check issue [#187](https://github.com/xoseperez/espurna/issues/187).
> **2017-07-24**<br />
> Default flash layout changed in 1.8.3, as an unpredicted consequence devices will not be able to persist/retrieve configuration if flashed with 1.8.3 via **OTA** from **PlatformIO**. Please check issue [#187](https://github.com/xoseperez/espurna/issues/187).
---
## Contribute
There are several ways to contribute to ESpurna development. You can contribute to the repository by doing:
There are several ways to contribute to ESPurna development. You can contribute to the repository by doing:
* Pull requests (fixes, enhancements, new features... are very welcome)
* Documentation (I reckon I'm bad at it)
@ -170,30 +198,58 @@ Here is the list of supported hardware. For more information please refer to the
||||
|---|---|---|
|![Tinkerman Espurna H](images/devices/tinkerman-espurna-h.jpg)|![IteadStudio Sonoff RF Bridge](images/devices/itead-sonoff-rfbridge.jpg)||
|**Tinkerman ESPurna H**|**IteadStudio Sonoff RF Bridge**||
|![IteadStudio Sonoff Basic](images/devices/itead-sonoff-basic.jpg)|![IteadStudio Sonoff RF](images/devices/itead-sonoff-rf.jpg)|![Electrodragon WiFi IOT](images/devices/electrodragon-wifi-iot.jpg)|
|**IteadStudio Sonoff Basic**|**IteadStudio Sonoff RF**|**Electrodragon WiFi IOT**|
|![IteadStudio Sonoff Dual](images/devices/itead-sonoff-dual.jpg)|![IteadStudio Sonoff POW](images/devices/itead-sonoff-pow.jpg)|![IteadStudio Sonoff TH10/TH16](images/devices/itead-sonoff-th.jpg)|
|**IteadStudio Sonoff Dual**|**IteadStudio Sonoff POW**|**IteadStudio Sonoff TH10/TH16**|
|![IteadStudio Sonoff 4CH](images/devices/itead-sonoff-4ch.jpg)|![IteadStudio Sonoff 4CH Pro](images/devices/itead-sonoff-4ch-pro.jpg)|![OpenEnergyMonitor WiFi MQTT Relay / Thermostat](images/devices/openenergymonitor-mqtt-relay.jpg)|
|**IteadStudio Sonoff 4CH**|**IteadStudio Sonoff 4CH Pro**|**OpenEnergyMonitor WiFi MQTT Relay / Thermostat**|
|![IteadStudio S20](images/devices/itead-s20.jpg)|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg)|![Power meters based on V9261F and ECH1560](images/devices/generic-v9261f.jpg)|
|**IteadStudio S20**|**WorkChoice EcoPlug**|**Power meters based on V9261F and ECH1560**|
|![IteadStudio Sonoff Touch](images/devices/itead-sonoff-touch.jpg)|![IteadStudio Sonoff T1](images/devices/itead-sonoff-t1.jpg)||
|**IteadStudio Sonoff Touch**|**IteadStudio Sonoff T1**||
|![IteadStudio Slampher](images/devices/itead-slampher.jpg)|![AI-Thinker Wifi Light / Noduino OpenLight](images/devices/aithinker-ai-light.jpg)|![Itead Sonoff B1](images/devices/itead-sonoff-b1.jpg)|
|**IteadStudio Slampher**|**AI-Thinker Wifi Light / Noduino OpenLight**|**IteadStudio Sonoff B1**|
|![MagicHome LED Controller (1.0 and 2.0)](images/devices/magichome-led-controller.jpg)|![Huacanxing H801](images/devices/huacanxing-h801.jpg)|![Itead BN-SZ01](images/devices/itead-bn-sz01.jpg)|
|**MagicHome LED Controller (1.0 and 2.0)**|**Huacanxing H801**|**Itead BN-SZ01**|
|![IteadStudio Sonoff SV](images/devices/itead-sonoff-sv.jpg)|![IteadStudio 1CH Inching](images/devices/itead-1ch-inching.jpg)|![IteadStudio Motor Clockwise/Anticlockwise](images/devices/itead-motor.jpg)|
|**IteadStudio Sonoff SV**|**IteadStudio 1CH Inching**|**IteadStudio Motor Clockwise/Anticlockwise**|
|![Wemos D1 Mini Relay Shield](images/devices/wemos-d1-mini-relayshield.jpg)|![Jan Goedeke Wifi Relay (NO/NC)](images/devices/jangoe-wifi-relay.png)|![Jorge García Wifi + Relays Board Kit](images/devices/jorgegarcia-wifi-relays.jpg)|
|**Wemos D1 Mini Relay Shield**|**Jan Goedeke Wifi Relay (NO/NC)**|**Jorge García Wifi + Relays Board Kit**|
|![EXS Wifi Relay v3.1](images/devices/exs-wifi-relay-v31.jpg)|||
|**EXS Wifi Relay v3.1**|||
**Other supported boards:** Itead Sonoff LED, Itead Sonoff Dual R2, Huacanxing H802, WiOn 50055, ManCaveMade ESP-Live, InterMitTech QuinLED 2.6, Arilux AL-LC01, Arilux AL-LC02, Arilux AL-LC06, Arilux AL-LC11, Arilux E27 light bulb, Xenon SM-PW702U, Authometion LYT8266, YJZK 2-gang switch, Magic Home LED Controller 2.3.
|![Tinkerman Espurna H](images/devices/tinkerman-espurna-h.jpg)|||
|**Tinkerman ESPurna H**|||
|![Itead Sonoff RF Bridge](images/devices/itead-sonoff-rfbridge.jpg)|||
|**Itead Sonoff RF Bridge**|||
|![Itead Sonoff Basic](images/devices/itead-sonoff-basic.jpg)|![Itead Sonoff RF](images/devices/itead-sonoff-rf.jpg)|![Itead Sonoff Dual/Dual R2](images/devices/itead-sonoff-dual.jpg)|
|**Itead Sonoff Basic**|**Itead Sonoff RF**|**Itead Sonoff Dual/Dual R2**|
|![Itead Sonoff POW](images/devices/itead-sonoff-pow.jpg)|![Itead Sonoff TH10/TH16](images/devices/itead-sonoff-th.jpg)|![Electrodragon WiFi IOT](images/devices/electrodragon-wifi-iot.jpg)|
|**Itead Sonoff POW**|**Itead Sonoff TH10/TH16**|**Electrodragon WiFi IOT**|
|![Itead Sonoff 4CH](images/devices/itead-sonoff-4ch.jpg)|![Itead Sonoff 4CH Pro](images/devices/itead-sonoff-4ch-pro.jpg)|![OpenEnergyMonitor WiFi MQTT Relay / Thermostat](images/devices/openenergymonitor-mqtt-relay.jpg)|
|**Itead Sonoff 4CH**|**Itead Sonoff 4CH Pro**|**OpenEnergyMonitor WiFi MQTT Relay / Thermostat**|
|![Itead S20](images/devices/itead-s20.jpg)|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg)|![Power meters based on V9261F and ECH1560](images/devices/generic-v9261f.jpg)|
|**Itead S20**|**WorkChoice EcoPlug**|**Power meters based on V9261F and ECH1560**|
|![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)|![Tonbux PowerStrip02](images/devices/tonbux-powerstrip02.jpg)|
|**WiOn 50055**|**LINGAN SWA1**|**Tonbux PowerStrip02**
|![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 Slampher](images/devices/itead-slampher.jpg)|||
|**Itead Slampher**|||
|![Itead Sonoff B1](images/devices/itead-sonoff-b1.jpg)|![AI-Thinker Wifi Light / Noduino OpenLight](images/devices/aithinker-ai-light.jpg)|![Authometion LYT8266](images/devices/authometion-lyt8266.jpg)|
|**Itead Sonoff B1**|**AI-Thinker Wifi Light / Noduino OpenLight**|**Authometion LYT8266**|
|![Arilux E27](images/devices/arilux-e27.jpg)|![Itead Sonoff LED](images/devices/itead-sonoff-led.jpg)|![Itead BN-SZ01](images/devices/itead-bn-sz01.jpg)|
|**Arilux E27**|**Itead Sonoff LED**|**Itead BN-SZ01**|
|![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)|
|**Arilux AL-LC01 (RGB)**|**Arilux AL-LC02 (RGBW)**|**Arilux AL-LC06 (RGBWWCW)**|
|![Arilux AL-LC11 (RGBWWW) & RF](images/devices/arilux-al-lc11.jpg)|![MagicHome LED Controller (1.0 and 2.x)](images/devices/magichome-led-controller.jpg)|![Huacanxing H801/802](images/devices/huacanxing-h801.jpg)|
|**Arilux AL-LC11 (RGBWWW) & RF**|**MagicHome LED Controller (1.0/2.x)**|**Huacanxing H801/802**|
|![InterMitTech QuinLED 2.6](images/devices/intermittech-quinled-2.6.jpg)||
|**InterMitTech QuinLED 2.6**||
|![Itead Sonoff SV](images/devices/itead-sonoff-sv.jpg)|![Itead 1CH Inching](images/devices/itead-1ch-inching.jpg)|![Itead Motor Clockwise/Anticlockwise](images/devices/itead-motor.jpg)|
|**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**|
|![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)||![Arniex Swifitch](images/devices/arniex-swifitch.jpg)|
|**IKE ESPike**|**STM_RELAY**|**Arniex Swifitch**|
|![Heltec Touch Relay](images/devices/heltec-touch-relay.jpg)|![Generic Relay v4.0](images/devices/generic-relay-40.jpg)|![Generic RGBLed v1.0](images/devices/generic-rgbled-10.jpg)|
|**Heltec Touch Relay**|**Generic Relay v4.0**|**Generic RGBLed v1.0**|
|![Generic DHT11 v1.0](images/devices/generic-dht11-10.jpg)|![Generic DS18B20 v1.0](images/devices/generic-ds18b20-10.jpg)||
|**Generic DHT11 v1.0**|**Generic DS18B20 v1.0**||
|![Tonbux Mosquito Killer](images/devices/tonbux-mosquito-killer.jpg)|||
|**Tonbux Mosquito Killer**|||
**Other supported boards:**
IteadStudio Sonoff S31, IteadStudio Sonoff POW R2, Zhilde ZLD-EU55-W, Luani HVIO
**Other supported boards (beta):**
KMC 4 Outlet, Gosund WS1, Smart Dual Plug, MakerFocus Intelligent Module LM33 for Lamps
## License


+ 8
- 3
code/.gitignore View File

@ -1,8 +1,13 @@
.clang_complete
core_version.h
custom.h
.DS_Store
.gcc-flags.json
.pioenvs
.piolibdeps
.python-version
.travis.yml
.vscode
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
core_version.h
.pioenvs
.piolibdeps
.vscode/launch.json

+ 24
- 2
code/build.sh View File

@ -5,7 +5,10 @@ echo "--------------------------------------------------------------"
echo "ESPURNA FIRMWARE BUILDER"
# Available environments
available=$(grep env: platformio.ini | grep -v ota | grep -v ssl | sed 's/\[env://' | sed 's/\]/ /' | sort)
travis=$(grep env: platformio.ini | grep travis | sed 's/\[env://' | sed 's/\]/ /' | sort)
available=$(grep env: platformio.ini | grep -v ota | grep -v ssl | grep -v travis | sed 's/\[env://' | sed 's/\]/ /' | sort)
# Parameters
environments=$@
if [ "$environments" == "list" ]; then
echo "--------------------------------------------------------------"
@ -18,7 +21,17 @@ fi
# Environments to build
if [ $# -eq 0 ]; then
environments=$available
# Hook to build travis test envs
if [[ "${TRAVIS_BRANCH}" != "" ]]; then
re='^[0-9]+\.[0-9]+\.[0-9]+$'
if ! [[ ${TRAVIS_BRANCH} =~ $re ]]; then
environments=$travis
fi
fi
fi
# Get current version
@ -34,6 +47,13 @@ if [ ! -e node_modules/gulp/bin/gulp.js ]; then
npm install --only=dev
fi
echo "--------------------------------------------------------------"
echo "Get revision..."
revision=$(git rev-parse HEAD)
revision=${revision:0:7}
cp espurna/config/version.h espurna/config/version.h.original
sed -i -e "s/APP_REVISION \".*\"/APP_REVISION \"$revision\"/g" espurna/config/version.h
# Recreate web interface
echo "--------------------------------------------------------------"
echo "Building web interface..."
@ -45,7 +65,9 @@ echo "Building firmware images..."
mkdir -p ../firmware/espurna-$version
for environment in $environments; do
echo "* espurna-$version-$environment.bin"
platformio run --silent --environment $environment || exit
platformio run --silent --environment $environment || exit 1
mv .pioenvs/$environment/firmware.bin ../firmware/espurna-$version/espurna-$version-$environment.bin
done
echo "--------------------------------------------------------------"
mv espurna/config/version.h.original espurna/config/version.h

code/esp8266.flash.1m0.ld → code/eagle.flash.1m0m1s.ld View File

@ -1,4 +1,4 @@
/* Flash Split for 1M chips, no SPIFFS */
/* Flash Split for 1M chips */
/* sketch 999KB */
/* eeprom 20KB */
@ -12,7 +12,7 @@ MEMORY
PROVIDE ( _SPIFFS_start = 0x402FB000 );
PROVIDE ( _SPIFFS_end = 0x402FB000 );
PROVIDE ( _SPIFFS_page = 0 );
PROVIDE ( _SPIFFS_block = 0 );
PROVIDE ( _SPIFFS_page = 0x0 );
PROVIDE ( _SPIFFS_block = 0x0 );
INCLUDE "esp8266.flash.common.ld"
INCLUDE "../ld/eagle.app.v6.common.ld"

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

@ -0,0 +1,19 @@
/* Flash Split for 1M chips, no SPIFFS */
/* sketch 995KB */
/* eeprom 8KB */
/* 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 = 0xf8ff0
}
PROVIDE ( _SPIFFS_start = 0x402FA000 );
PROVIDE ( _SPIFFS_end = 0x402FA000 );
PROVIDE ( _SPIFFS_page = 0 );
PROVIDE ( _SPIFFS_block = 0 );
INCLUDE "../ld/eagle.app.v6.common.ld"

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

@ -0,0 +1,21 @@
/* Flash Split for 4M chips */
/* sketch 1019KB */
/* empty/ota? 2048KB */
/* 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 = 0x40500000 );
PROVIDE ( _SPIFFS_end = 0x405F8000 );
PROVIDE ( _SPIFFS_page = 0x100 );
PROVIDE ( _SPIFFS_block = 0x2000 );
INCLUDE "../ld/eagle.app.v6.common.ld"

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

@ -0,0 +1,20 @@
/* Flash Split for 4M chips */
/* sketch 1019KB */
/* spiffs 3040KB */
/* 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 = 0x405F8000 );
PROVIDE ( _SPIFFS_page = 0x100 );
PROVIDE ( _SPIFFS_block = 0x2000 );
INCLUDE "../ld/eagle.app.v6.common.ld"

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

@ -0,0 +1,18 @@
/* Flash Split for 512K chips */
/* sketch 487KB */
/* eeprom 20KB */
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 = 0x79ff0
}
PROVIDE ( _SPIFFS_start = 0x4027B000 );
PROVIDE ( _SPIFFS_end = 0x4027B000 );
PROVIDE ( _SPIFFS_page = 0x0 );
PROVIDE ( _SPIFFS_block = 0x0 );
INCLUDE "../ld/eagle.app.v6.common.ld"

+ 6
- 0
code/espurna/alexa.ino View File

@ -22,6 +22,11 @@ static std::queue<AlexaDevChange> _alexa_dev_changes;
// -----------------------------------------------------------------------------
// ALEXA
// -----------------------------------------------------------------------------
bool _alexaWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "alexa", 5) == 0);
}
void _alexaWebSocketOnSend(JsonObject& root) {
root["alexaVisible"] = 1;
root["alexaEnabled"] = getSetting("alexaEnabled", ALEXA_ENABLED).toInt() == 1;
@ -46,6 +51,7 @@ void alexaSetup() {
// Websockets
wsOnSendRegister(_alexaWebSocketOnSend);
wsOnAfterParseRegister(_alexaConfigure);
wsOnReceiveRegister(_alexaWebSocketOnReceive);
#endif


+ 13
- 3
code/espurna/api.ino View File

@ -22,6 +22,10 @@ std::vector<web_api_t> _apis;
// -----------------------------------------------------------------------------
bool _apiWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "api", 3) == 0);
}
void _apiWebSocketOnSend(JsonObject& root) {
root["apiEnabled"] = getSetting("apiEnabled", API_ENABLED).toInt() == 1;
root["apiKey"] = getSetting("apiKey");
@ -84,11 +88,11 @@ ArRequestHandlerFunction _bindAPI(unsigned int apiID) {
}
// Get response from callback
char value[API_BUFFER_SIZE];
char value[API_BUFFER_SIZE] = {0};
(api.getFn)(value, API_BUFFER_SIZE);
// The response will be a 404 NOT FOUND if the resource is not available
if (!value) {
if (0 == value[0]) {
DEBUG_MSG_P(PSTR("[API] Sending 404 response\n"));
request->send(404);
return;
@ -98,7 +102,11 @@ ArRequestHandlerFunction _bindAPI(unsigned int apiID) {
// Format response according to the Accept header
if (_asJson(request)) {
char buffer[64];
snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": %s }"), api.key, value);
if (isNumber(value)) {
snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": %s }"), api.key, value);
} else {
snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": \"%s\" }"), api.key, value);
}
request->send(200, "application/json", buffer);
} else {
request->send(200, "text/plain", value);
@ -126,6 +134,7 @@ void _onAPIs(AsyncWebServerRequest *request) {
root[_apis[i].key] = String(buffer);
}
root.printTo(output);
jsonBuffer.clear();
request->send(200, "application/json", output);
} else {
@ -187,6 +196,7 @@ void apiSetup() {
webServer()->on("/apis", HTTP_GET, _onAPIs);
webServer()->on("/rpc", HTTP_GET, _onRPC);
wsOnSendRegister(_apiWebSocketOnSend);
wsOnReceiveRegister(_apiWebSocketOnReceive);
}
#endif // WEB_SUPPORT

+ 37
- 14
code/espurna/button.ino View File

@ -27,7 +27,15 @@ void buttonMQTT(unsigned char id, uint8_t event) {
if (id >= _buttons.size()) return;
char payload[2];
itoa(event, payload, 10);
mqttSend(MQTT_TOPIC_BUTTON, id, payload);
mqttSend(MQTT_TOPIC_BUTTON, id, payload, false, false); // 1st bool = force, 2nd = retain
}
#endif
#if WEB_SUPPORT
bool _buttonWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "btn", 3) == 0);
}
#endif
@ -52,16 +60,18 @@ unsigned char buttonAction(unsigned char id, unsigned char event) {
if (event == BUTTON_EVENT_DBLCLICK) return (actions >> 8) & 0x0F;
if (event == BUTTON_EVENT_LNGCLICK) return (actions >> 12) & 0x0F;
if (event == BUTTON_EVENT_LNGLNGCLICK) return (actions >> 16) & 0x0F;
if (event == BUTTON_EVENT_TRIPLECLICK) return (actions >> 20) & 0x0F;
return BUTTON_MODE_NONE;
}
unsigned long buttonStore(unsigned long pressed, unsigned long click, unsigned long dblclick, unsigned long lngclick, unsigned long lnglngclick) {
unsigned long buttonStore(unsigned long pressed, unsigned long click, unsigned long dblclick, unsigned long lngclick, unsigned long lnglngclick, unsigned long tripleclick) {
unsigned int value;
value = pressed;
value += click << 4;
value += dblclick << 8;
value += lngclick << 12;
value += lnglngclick << 16;
value += tripleclick << 20;
return value;
}
@ -69,13 +79,15 @@ uint8_t mapEvent(uint8_t event, uint8_t count, uint16_t length) {
if (event == EVENT_PRESSED) return BUTTON_EVENT_PRESSED;
if (event == EVENT_CHANGED) return BUTTON_EVENT_CLICK;
if (event == EVENT_RELEASED) {
if (count == 1) {
if (1 == count) {
if (length > BUTTON_LNGLNGCLICK_DELAY) return BUTTON_EVENT_LNGLNGCLICK;
if (length > BUTTON_LNGCLICK_DELAY) return BUTTON_EVENT_LNGCLICK;
return BUTTON_EVENT_CLICK;
}
if (count == 2) return BUTTON_EVENT_DBLCLICK;
if (2 == count) return BUTTON_EVENT_DBLCLICK;
if (3 == count) return BUTTON_EVENT_TRIPLECLICK;
}
return BUTTON_EVENT_NONE;
}
void buttonEvent(unsigned int id, unsigned char event) {
@ -104,7 +116,13 @@ void buttonEvent(unsigned int id, unsigned char event) {
relayStatus(_buttons[id].relayID - 1, false);
}
}
if (action == BUTTON_MODE_AP) createAP();
if (action == BUTTON_MODE_AP) wifiStartAP();
#if defined(JUSTWIFI_ENABLE_WPS)
if (action == BUTTON_MODE_WPS) wifiStartWPS();
#endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
if (action == BUTTON_MODE_SMART_CONFIG) wifiStartSmartConfig();
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
if (action == BUTTON_MODE_RESET) {
deferredReset(100, CUSTOM_RESET_HARDWARE);
}
@ -120,7 +138,7 @@ void buttonSetup() {
#ifdef ITEAD_SONOFF_DUAL
unsigned int actions = buttonStore(BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE);
unsigned int actions = buttonStore(BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE);
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 1});
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 2});
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, BUTTON3_RELAY});
@ -131,49 +149,49 @@ void buttonSetup() {
#if BUTTON1_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON1_PRESS, BUTTON1_CLICK, BUTTON1_DBLCLICK, BUTTON1_LNGCLICK, BUTTON1_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON1_PRESS, BUTTON1_CLICK, BUTTON1_DBLCLICK, BUTTON1_LNGCLICK, BUTTON1_LNGLNGCLICK, BUTTON1_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON1_PIN, BUTTON1_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON1_RELAY});
}
#endif
#if BUTTON2_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON2_PRESS, BUTTON2_CLICK, BUTTON2_DBLCLICK, BUTTON2_LNGCLICK, BUTTON2_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON2_PRESS, BUTTON2_CLICK, BUTTON2_DBLCLICK, BUTTON2_LNGCLICK, BUTTON2_LNGLNGCLICK, BUTTON2_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON2_PIN, BUTTON2_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON2_RELAY});
}
#endif
#if BUTTON3_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON3_PRESS, BUTTON3_CLICK, BUTTON3_DBLCLICK, BUTTON3_LNGCLICK, BUTTON3_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON3_PRESS, BUTTON3_CLICK, BUTTON3_DBLCLICK, BUTTON3_LNGCLICK, BUTTON3_LNGLNGCLICK, BUTTON3_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON3_PIN, BUTTON3_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON3_RELAY});
}
#endif
#if BUTTON4_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON4_PRESS, BUTTON4_CLICK, BUTTON4_DBLCLICK, BUTTON4_LNGCLICK, BUTTON4_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON4_PRESS, BUTTON4_CLICK, BUTTON4_DBLCLICK, BUTTON4_LNGCLICK, BUTTON4_LNGLNGCLICK, BUTTON4_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON4_PIN, BUTTON4_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON4_RELAY});
}
#endif
#if BUTTON5_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON5_PRESS, BUTTON5_CLICK, BUTTON5_DBLCLICK, BUTTON5_LNGCLICK, BUTTON5_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON5_PRESS, BUTTON5_CLICK, BUTTON5_DBLCLICK, BUTTON5_LNGCLICK, BUTTON5_LNGLNGCLICK, BUTTON5_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON5_PIN, BUTTON5_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON5_RELAY});
}
#endif
#if BUTTON6_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON6_PRESS, BUTTON6_CLICK, BUTTON6_DBLCLICK, BUTTON6_LNGCLICK, BUTTON6_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON6_PRESS, BUTTON6_CLICK, BUTTON6_DBLCLICK, BUTTON6_LNGCLICK, BUTTON6_LNGLNGCLICK, BUTTON6_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON6_PIN, BUTTON6_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON6_RELAY});
}
#endif
#if BUTTON7_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON7_PRESS, BUTTON7_CLICK, BUTTON7_DBLCLICK, BUTTON7_LNGCLICK, BUTTON7_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON7_PRESS, BUTTON7_CLICK, BUTTON7_DBLCLICK, BUTTON7_LNGCLICK, BUTTON7_LNGLNGCLICK, BUTTON7_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON7_PIN, BUTTON7_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON7_RELAY});
}
#endif
#if BUTTON8_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON8_PRESS, BUTTON8_CLICK, BUTTON8_DBLCLICK, BUTTON8_LNGCLICK, BUTTON8_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON8_PRESS, BUTTON8_CLICK, BUTTON8_DBLCLICK, BUTTON8_LNGCLICK, BUTTON8_LNGLNGCLICK, BUTTON8_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON8_PIN, BUTTON8_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON8_RELAY});
}
#endif
@ -182,6 +200,11 @@ void buttonSetup() {
DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), _buttons.size());
// Websocket Callbacks
#if WEB_SUPPORT
wsOnReceiveRegister(_buttonWebSocketOnReceive);
#endif
// Register loop
espurnaRegisterLoop(buttonLoop);


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

@ -24,15 +24,17 @@
#endif
#include "version.h"
#include "types.h"
#include "arduino.h"
#include "hardware.h"
#include "defaults.h"
#include "general.h"
#include "prototypes.h"
#include "sensors.h"
#include "dependencies.h"
#include "progmem.h"
#include "debug.h"
#ifdef USE_CORE_VERSION_H
#include "core_version.h"
#endif
#include "build.h"

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

@ -19,6 +19,7 @@
//#define ITEAD_S20
//#define ITEAD_SONOFF_TOUCH
//#define ITEAD_SONOFF_POW
//#define ITEAD_SONOFF_POW_R2
//#define ITEAD_SONOFF_DUAL
//#define ITEAD_SONOFF_DUAL_R2
//#define ITEAD_SONOFF_4CH
@ -32,6 +33,7 @@
//#define ITEAD_SONOFF_T1_1CH
//#define ITEAD_SONOFF_T1_2CH
//#define ITEAD_SONOFF_T1_3CH
//#define ITEAD_SONOFF_S31
//#define YJZK_SWITCH_2CH
//#define ELECTRODRAGON_WIFI_IOT
//#define WORKCHOICE_ECOPLUG
@ -54,20 +56,46 @@
//#define ARILUX_E27
//#define XENON_SM_PW702U
//#define AUTHOMETION_LYT8266
//#define KMC_70011
//#define GENERIC_8CH
//#define ARILUX_AL_LC01
//#define ARILUX_AL_LC11
//#define ARILUX_AL_LC02
//#define WEMOS_D1_TARPUNA_SHIELD
//#define GIZWITS_WITTY_CLOUD
//#define EUROMATE_WIFI_STECKER_SCHUKO
//#define TONBUX_POWERSTRIP02
//#define LINGAN_SWA1
//#define HEYGO_HY02
//#define MAXCIO_WUS002S
//#define YIDIAN_XSSSA05
//#define TONBUX_XSSSA06
//#define GREEN_ESP8266RELAY
//#define IKE_ESPIKE
//#define ARNIEX_SWIFITCH
//#define GENERIC_ESP01S_RELAY_V40
//#define GENERIC_ESP01S_RGBLED_V10
//#define GENERIC_ESP01S_DHT11_V10
//#define GENERIC_ESP01S_DS18B20_V10
//#define HELTEC_TOUCHRELAY
//#define ZHILDE_EU44_W
//#define LUANI_HVIO
//#define ALLNET_4DUINO_IOT_WLAN_RELAIS
//#define TONBUX_MOSQUITO_KILLER
//#define NEO_COOLCAM_POWER_PLUG_WIFI
//#define ESTINK_WIFI_POWER_STRIP
//#define PILOTAK_ESP_DIN_V1
//--------------------------------------------------------------------------------
// Features (values below are non-default values)
//--------------------------------------------------------------------------------
//#define ALEXA_SUPPORT 0
//#define BROKER_SUPPORT 0
//#define DEBUG_SERIAL_SUPPORT 0
//#define DEBUG_TELNET_SUPPORT 0
//#define DEBUG_UDP_SUPPORT 1
//#define DEBUG_WEB_SUPPORT 0
//#define DOMOTICZ_SUPPORT 0
//#define HOMEASSISTANT_SUPPORT 0
//#define I2C_SUPPORT 1
@ -76,7 +104,6 @@
//#define LLMNR_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define MDNS_SERVER_SUPPORT 0
//#define MDNS_CLIENT_SUPPORT 1
//#define BROKER_SUPPORT 0
//#define MQTT_SUPPORT 0
//#define NETBIOS_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define NOFUSS_SUPPORT 1
@ -88,15 +115,18 @@
//#define TELNET_SUPPORT 0
//#define TERMINAL_SUPPORT 0
//#define THINGSPEAK_SUPPORT 0
//#define UART_MQTT_SUPPORT 1
//#define WEB_SUPPORT 0
//--------------------------------------------------------------------------------
// Sensors (values below are non-default values)
//--------------------------------------------------------------------------------
//#define AM2320_SUPPORT 1
//#define ANALOG_SUPPORT 1
//#define BH1750_SUPPORT 1
//#define BMX280_SUPPORT 1
//#define CSE7766_SUPPORT 1
//#define DALLAS_SUPPORT 1
//#define DHT_SUPPORT 1
//#define DIGITAL_SUPPORT 1
@ -105,9 +135,14 @@
//#define EMON_ADS1X15_SUPPORT 1
//#define EMON_ANALOG_SUPPORT 1
//#define EVENTS_SUPPORT 1
//#define GUVAS12SD_SUPPORT 1
//#define HCSR04_SUPPORT 1
//#define HLW8012_SUPPORT 1
//#define MHZ19_SUPPORT 1
//#define PMSX003_SUPPORT 1
//#define PZEM004T_SUPPORT 1
//#define SHT3X_I2C_SUPPORT 1
//#define SI7021_SUPPORT 1
//#define TMP3X_SUPPORT 1
//#define V9261F_SUPPORT 1
//#define GEIGER_SUPPORT 1

+ 17
- 0
code/espurna/config/debug.h View File

@ -0,0 +1,17 @@
#pragma once
// -----------------------------------------------------------------------------
// Debug
// -----------------------------------------------------------------------------
#define DEBUG_SUPPORT DEBUG_SERIAL_SUPPORT || DEBUG_UDP_SUPPORT || DEBUG_TELNET_SUPPORT || DEBUG_WEB_SUPPORT
#if DEBUG_SUPPORT
#define DEBUG_MSG(...) debugSend(__VA_ARGS__)
#define DEBUG_MSG_P(...) debugSend_P(__VA_ARGS__)
#endif
#ifndef DEBUG_MSG
#define DEBUG_MSG(...)
#define DEBUG_MSG_P(...)
#endif

+ 28
- 6
code/espurna/config/defaults.h View File

@ -108,6 +108,31 @@
#define BUTTON8_DBLCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON1_TRIPLECLICK
#define BUTTON1_TRIPLECLICK BUTTON_MODE_SMART_CONFIG
#endif
#ifndef BUTTON2_TRIPLECLICK
#define BUTTON2_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON3_TRIPLECLICK
#define BUTTON3_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON4_TRIPLECLICK
#define BUTTON4_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON5_TRIPLECLICK
#define BUTTON5_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON6_TRIPLECLICK
#define BUTTON6_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON7_TRIPLECLICK
#define BUTTON7_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON8_TRIPLECLICK
#define BUTTON8_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON1_LNGCLICK
#define BUTTON1_LNGCLICK BUTTON_MODE_RESET
#endif
@ -399,12 +424,9 @@
// General
// -----------------------------------------------------------------------------
// Needed for ESP8285 boards under Windows using PlatformIO (?)
#ifndef BUTTON_PUSHBUTTON
#define BUTTON_PUSHBUTTON 0
#define BUTTON_SWITCH 1
#define BUTTON_DEFAULT_HIGH 2
#define BUTTON_SET_PULLUP 4
// Default hostname will be ESPURNA-XXXXXX, where XXXXXX is last 3 octets of chipID
#ifndef HOSTNAME
#define HOSTNAME ""
#endif
// Relay providers


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

@ -0,0 +1,50 @@
#pragma once
//------------------------------------------------------------------------------
// Do not change this file unless you know what you are doing
// Configuration settings are in the general.h file
//------------------------------------------------------------------------------
#if DEBUG_TELNET_SUPPORT
#undef TELNET_SUPPORT
#define TELNET_SUPPORT 1
#endif
#if not WEB_SUPPORT
#undef DEBUG_WEB_SUPPORT
#define DEBUG_WEB_SUPPORT 0
#endif
#if not WEB_SUPPORT
#undef SSDP_SUPPORT
#define SSDP_SUPPORT 0 // SSDP support requires web support
#endif
#if UART_MQTT_SUPPORT
#define MQTT_SUPPORT 1
#undef TERMINAL_SUPPORT
#define TERMINAL_SUPPORT 0
#undef DEBUG_SERIAL_SUPPORT
#define DEBUG_SERIAL_SUPPORT 0
#endif
#if DOMOTICZ_SUPPORT
#undef MQTT_SUPPORT
#define MQTT_SUPPORT 1 // If Domoticz enabled enable MQTT
#endif
#if HOMEASSISTANT_SUPPORT
#undef MQTT_SUPPORT
#define MQTT_SUPPORT 1 // If Home Assistant enabled enable MQTT
#endif
#ifndef ASYNC_TCP_SSL_ENABLED
#if THINGSPEAK_USE_SSL && THINGSPEAK_USE_ASYNC
#undef THINGSPEAK_SUPPORT // Thingspeak in ASYNC mode requires ASYNC_TCP_SSL_ENABLED
#endif
#endif
#if SCHEDULER_SUPPORT
#undef NTP_SUPPORT
#define NTP_SUPPORT 1 // Scheduler needs NTP
#endif

+ 528
- 329
code/espurna/config/general.h
File diff suppressed because it is too large
View File


+ 1016
- 26
code/espurna/config/hardware.h
File diff suppressed because it is too large
View File


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

@ -0,0 +1,280 @@
//--------------------------------------------------------------------------------
// PROGMEM definitions
//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
// Reset reasons
//--------------------------------------------------------------------------------
PROGMEM const char custom_reset_hardware[] = "Hardware button";
PROGMEM const char custom_reset_web[] = "Reboot from web interface";
PROGMEM const char custom_reset_terminal[] = "Reboot from terminal";
PROGMEM const char custom_reset_mqtt[] = "Reboot from MQTT";
PROGMEM const char custom_reset_rpc[] = "Reboot from RPC";
PROGMEM const char custom_reset_ota[] = "Reboot after successful OTA update";
PROGMEM const char custom_reset_http[] = "Reboot from HTTP";
PROGMEM const char custom_reset_nofuss[] = "Reboot after successful NoFUSS update";
PROGMEM const char custom_reset_upgrade[] = "Reboot after successful web update";
PROGMEM const char custom_reset_factory[] = "Factory reset";
PROGMEM const char* const custom_reset_string[] = {
custom_reset_hardware, custom_reset_web, custom_reset_terminal,
custom_reset_mqtt, custom_reset_rpc, custom_reset_ota,
custom_reset_http, custom_reset_nofuss, custom_reset_upgrade,
custom_reset_factory
};
//--------------------------------------------------------------------------------
// Capabilities
//--------------------------------------------------------------------------------
PROGMEM const char espurna_modules[] =
#if ALEXA_SUPPORT
"ALEXA "
#endif
#if BROKER_SUPPORT
"BROKER "
#endif
#if DEBUG_SERIAL_SUPPORT
"DEBUG_SERIAL "
#endif
#if DEBUG_TELNET_SUPPORT
"DEBUG_TELNET "
#endif
#if DEBUG_UDP_SUPPORT
"DEBUG_UDP "
#endif
#if DEBUG_WEB_SUPPORT
"DEBUG_WEB "
#endif
#if DOMOTICZ_SUPPORT
"DOMOTICZ "
#endif
#if HOMEASSISTANT_SUPPORT
"HOMEASSISTANT "
#endif
#if I2C_SUPPORT
"I2C "
#endif
#if INFLUXDB_SUPPORT
"INFLUXDB "
#endif
#if LLMNR_SUPPORT
"LLMNR "
#endif
#if MDNS_SERVER_SUPPORT
"MDNS_SERVER "
#endif
#if MDNS_CLIENT_SUPPORT
"MDNS_CLIENT "
#endif
#if MQTT_SUPPORT
"MQTT "
#endif
#if NETBIOS_SUPPORT
"NETBIOS "
#endif
#if NOFUSS_SUPPORT
"NOFUSS "
#endif
#if NTP_SUPPORT
"NTP "
#endif
#if RF_SUPPORT
"RF "
#endif
#if SCHEDULER_SUPPORT
"SCHEDULER "
#endif
#if SENSOR_SUPPORT
"SENSOR "
#endif
#if SPIFFS_SUPPORT
"SPIFFS "
#endif
#if SSDP_SUPPORT
"SSDP "
#endif
#if TELNET_SUPPORT
"TELNET "
#endif
#if TERMINAL_SUPPORT
"TERMINAL "
#endif
#if THINGSPEAK_SUPPORT
"THINGSPEAK "
#endif
#if UART_MQTT_SUPPORT
"UART_MQTT "
#endif
#if WEB_SUPPORT
"WEB "
#endif
"";
//--------------------------------------------------------------------------------
// Sensors
//--------------------------------------------------------------------------------
#if SENSOR_SUPPORT
PROGMEM const char espurna_sensors[] =
#if AM2320_SUPPORT
"AM2320_I2C "
#endif
#if ANALOG_SUPPORT
"ANALOG "
#endif
#if BH1750_SUPPORT
"BH1750 "
#endif
#if BMX280_SUPPORT
"BMX280 "
#endif
#if CSE7766_SUPPORT
"CSE7766 "
#endif
#if DALLAS_SUPPORT
"DALLAS "
#endif
#if DHT_SUPPORT
"DHTXX "
#endif
#if DIGITAL_SUPPORT
"DIGITAL "
#endif
#if ECH1560_SUPPORT
"ECH1560 "
#endif
#if EMON_ADC121_SUPPORT
"EMON_ADC121 "
#endif
#if EMON_ADS1X15_SUPPORT
"EMON_ADX1X15 "
#endif
#if EMON_ANALOG_SUPPORT
"EMON_ANALOG "
#endif
#if EVENTS_SUPPORT
"EVENTS "
#endif
#if GEIGER_SUPPORT
"GEIGER "
#endif
#if GUVAS12SD_SUPPORT
"GUVAS12SD "
#endif
#if HCSR04_SUPPORT
"HCSR04 "
#endif
#if HLW8012_SUPPORT
"HLW8012 "
#endif
#if MHZ19_SUPPORT
"MHZ19 "
#endif
#if PMSX003_SUPPORT
"PMSX003 "
#endif
#if PZEM004T_SUPPORT
"PZEM004T "
#endif
#if SENSEAIR_SUPPORT
"SENSEAIR "
#endif
#if SHT3X_I2C_SUPPORT
"SHT3X_I2C "
#endif
#if SI7021_SUPPORT
"SI7021 "
#endif
#if TMP3X_SUPPORT
"TMP3X "
#endif
#if V9261F_SUPPORT
"V9261F "
#endif
"";
PROGMEM const unsigned char magnitude_decimals[] = {
0,
1, 0, 2,
3, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 3, 3,
4, 4 // Geiger Counter decimals
};
PROGMEM const char magnitude_unknown_topic[] = "unknown";
PROGMEM const char magnitude_temperature_topic[] = "temperature";
PROGMEM const char magnitude_humidity_topic[] = "humidity";
PROGMEM const char magnitude_pressure_topic[] = "pressure";
PROGMEM const char magnitude_current_topic[] = "current";
PROGMEM const char magnitude_voltage_topic[] = "voltage";
PROGMEM const char magnitude_active_power_topic[] = "power";
PROGMEM const char magnitude_apparent_power_topic[] = "apparent";
PROGMEM const char magnitude_reactive_power_topic[] = "reactive";
PROGMEM const char magnitude_power_factor_topic[] = "factor";
PROGMEM const char magnitude_energy_topic[] = "energy";
PROGMEM const char magnitude_energy_delta_topic[] = "energy_delta";
PROGMEM const char magnitude_analog_topic[] = "analog";
PROGMEM const char magnitude_digital_topic[] = "digital";
PROGMEM const char magnitude_events_topic[] = "events";
PROGMEM const char magnitude_pm1dot0_topic[] = "pm1dot0";
PROGMEM const char magnitude_pm2dot5_topic[] = "pm2dot5";
PROGMEM const char magnitude_pm10_topic[] = "pm10";
PROGMEM const char magnitude_co2_topic[] = "co2";
PROGMEM const char magnitude_lux_topic[] = "lux";
PROGMEM const char magnitude_uv_topic[] = "uv";
PROGMEM const char magnitude_distance_topic[] = "distance";
PROGMEM const char magnitude_hcho_topic[] = "hcho";
PROGMEM const char magnitude_geiger_cpm_topic[] = "ldr_cpm"; // local dose rate [Counts per minute]
PROGMEM const char magnitude_geiger_sv_topic[] = "ldr_uSvh"; // local dose rate [µSievert per hour]
PROGMEM const char* const magnitude_topics[] = {
magnitude_unknown_topic, magnitude_temperature_topic, magnitude_humidity_topic,
magnitude_pressure_topic, magnitude_current_topic, magnitude_voltage_topic,
magnitude_active_power_topic, magnitude_apparent_power_topic, magnitude_reactive_power_topic,
magnitude_power_factor_topic, magnitude_energy_topic, magnitude_energy_delta_topic,
magnitude_analog_topic, magnitude_digital_topic, magnitude_events_topic,
magnitude_pm1dot0_topic, magnitude_pm2dot5_topic, magnitude_pm10_topic,
magnitude_co2_topic, magnitude_lux_topic, magnitude_uv_topic,
magnitude_distance_topic, magnitude_hcho_topic,
magnitude_geiger_cpm_topic, magnitude_geiger_sv_topic // Geiger Counter topics
};
PROGMEM const char magnitude_empty[] = "";
PROGMEM const char magnitude_celsius[] = "°C";
PROGMEM const char magnitude_fahrenheit[] = "°F";
PROGMEM const char magnitude_percentage[] = "%";
PROGMEM const char magnitude_hectopascals[] = "hPa";
PROGMEM const char magnitude_amperes[] = "A";
PROGMEM const char magnitude_volts[] = "V";
PROGMEM const char magnitude_watts[] = "W";
PROGMEM const char magnitude_kw[] = "kW";
PROGMEM const char magnitude_joules[] = "J";
PROGMEM const char magnitude_kwh[] = "kWh";
PROGMEM const char magnitude_ugm3[] = "µg/m³";
PROGMEM const char magnitude_ppm[] = "ppm";
PROGMEM const char magnitude_lux[] = "lux";
PROGMEM const char magnitude_uv[] = "uv";
PROGMEM const char magnitude_distance[] = "m";
PROGMEM const char magnitude_mgm3[] = "mg/m³";
PROGMEM const char magnitude_geiger_cpm[] = "cpm"; // Counts per Minute: Unit of local dose rate (Geiger counting)
PROGMEM const char magnitude_geiger_sv[] = "µSv/h"; // µSievert per hour: 2nd unit of local dose rate (Geiger counting)
PROGMEM const char* const magnitude_units[] = {
magnitude_empty, magnitude_celsius, magnitude_percentage,
magnitude_hectopascals, magnitude_amperes, magnitude_volts,
magnitude_watts, magnitude_watts, magnitude_watts,
magnitude_percentage, magnitude_joules, magnitude_joules,
magnitude_empty, magnitude_empty, magnitude_empty,
magnitude_ugm3, magnitude_ugm3, magnitude_ugm3,
magnitude_ppm, magnitude_lux, magnitude_uv,
magnitude_distance, magnitude_mgm3,
magnitude_geiger_cpm, magnitude_geiger_sv // Geiger counter units
};
#endif

+ 26
- 8
code/espurna/config/prototypes.h View File

@ -7,6 +7,12 @@ extern "C" {
#include "user_interface.h"
}
// -----------------------------------------------------------------------------
// EEPROM_ROTATE
// -----------------------------------------------------------------------------
#include <EEPROM_Rotate.h>
EEPROM_Rotate EEPROMr;
// -----------------------------------------------------------------------------
// WebServer
// -----------------------------------------------------------------------------
@ -33,6 +39,9 @@ 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);
// -----------------------------------------------------------------------------
// WIFI
// -----------------------------------------------------------------------------
@ -60,23 +69,28 @@ template<typename T> bool setSetting(const String& key, T value);
template<typename T> bool setSetting(const String& key, unsigned int index, T value);
template<typename T> String getSetting(const String& key, T defaultValue);
template<typename T> String getSetting(const String& key, unsigned int index, T defaultValue);
bool settingsGetJson(JsonObject& data);
void settingsGetJson(JsonObject& data);
bool settingsRestoreJson(JsonObject& data);
void settingsRegisterCommand(const String& name, void (*call)(Embedis*));
void settingsInject(void *data, size_t len);
Stream & settingsSerial();
// -----------------------------------------------------------------------------
// I2C
// -----------------------------------------------------------------------------
unsigned char i2cFindAndLock(size_t size, unsigned char * addresses);
void i2cScan();
void i2cClearBus();
bool i2cGetLock(unsigned char address);
bool i2cReleaseLock(unsigned char address);
void i2cClearBus();
unsigned char i2cFindAndLock(size_t size, unsigned char * addresses);
void i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len);
void i2c_write_uint8(uint8_t address, uint8_t value);
void i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value);
void i2c_write_uint16(uint8_t address, uint16_t value);
void i2c_write_uint16(uint8_t address, uint8_t reg, uint16_t value);
void i2c_wakeup(uint8_t address);
uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len);
uint8_t i2c_write_uint8(uint8_t address, uint8_t value);
uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value);
uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value1, uint8_t value2);
uint8_t i2c_write_uint16(uint8_t address, uint16_t value);
uint8_t i2c_write_uint16(uint8_t address, uint8_t reg, uint16_t value);
uint8_t i2c_read_uint8(uint8_t address);
uint8_t i2c_read_uint8(uint8_t address, uint8_t reg);
uint16_t i2c_read_uint16(uint8_t address);
@ -109,3 +123,7 @@ template<typename T> void domoticzSend(const char * key, T nvalue, const char *
// Utils
// -----------------------------------------------------------------------------
char * ltrim(char * s);
void nice_delay(unsigned long ms);
#define ARRAYINIT(type, name, ...) \
type name[] = {__VA_ARGS__};

+ 263
- 181
code/espurna/config/sensors.h View File

@ -7,6 +7,7 @@
#define SENSOR_READ_INTERVAL 6 // Read data from sensors every 6 seconds
#define SENSOR_READ_MIN_INTERVAL 6 // Minimum read interval
#define SENSOR_READ_MAX_INTERVAL 3600 // Maximum read interval
#define SENSOR_INIT_INTERVAL 10000 // Try to re-init non-ready sensors every 10s
#define SENSOR_REPORT_EVERY 10 // Report every this many readings
#define SENSOR_REPORT_MIN_EVERY 1 // Minimum every value
@ -15,8 +16,8 @@
#define SENSOR_USE_INDEX 0 // Use the index in topic (i.e. temperature/0)
// even if just one sensor (0 for backwards compatibility)
#ifndef SENSOR_TEMPERATURE_UNITS
#define SENSOR_TEMPERATURE_UNITS TMP_CELSIUS // Temperature units (TMP_CELSIUS | TMP_FAHRENHEIT)
#ifndef SENSOR_POWER_CHECK_STATUS
#define SENSOR_POWER_CHECK_STATUS 1 // If set to 1 the reported power/current/energy will be 0 if the relay[0] is OFF
#endif
#ifndef SENSOR_TEMPERATURE_CORRECTION
@ -27,72 +28,48 @@
#define TEMPERATURE_MIN_CHANGE 0.0 // Minimum temperature change to report
#endif
#ifndef SENSOR_HUMIDITY_CORRECTION
#define SENSOR_HUMIDITY_CORRECTION 0.0 // Offset correction
#endif
#ifndef HUMIDITY_MIN_CHANGE
#define HUMIDITY_MIN_CHANGE 0 // Minimum humidity change to report
#endif
#define HUMIDITY_NORMAL 0
#define HUMIDITY_COMFORTABLE 1
#define HUMIDITY_DRY 2
#define HUMIDITY_WET 3
#define SENSOR_PUBLISH_ADDRESSES 0 // Publish sensor addresses
#define SENSOR_ADDRESS_TOPIC "address" // Topic to publish sensor addresses
//--------------------------------------------------------------------------------
// Sensor ID
// These should remain over time, do not modify them, only add new ones at the end
//--------------------------------------------------------------------------------
#define SENSOR_DHTXX_ID 0x01
#define SENSOR_DALLAS_ID 0x02
#define SENSOR_EMON_ANALOG_ID 0x03
#define SENSOR_EMON_ADC121_ID 0x04
#define SENSOR_EMON_ADS1X15_ID 0x05
#define SENSOR_HLW8012_ID 0x06
#define SENSOR_V9261F_ID 0x07
#define SENSOR_ECH1560_ID 0x08
#define SENSOR_ANALOG_ID 0x09
#define SENSOR_DIGITAL_ID 0x10
#define SENSOR_EVENTS_ID 0x11
#define SENSOR_PMSX003_ID 0x12
#define SENSOR_BMX280_ID 0x13
#define SENSOR_MHZ19_ID 0x14
#define SENSOR_SI7021_ID 0x15
#define SENSOR_SHT3X_I2C_ID 0x16
#define SENSOR_BH1750_ID 0x17
#ifndef SENSOR_TEMPERATURE_UNITS
#define SENSOR_TEMPERATURE_UNITS TMP_CELSIUS // Temperature units (TMP_CELSIUS | TMP_FAHRENHEIT)
#endif
#ifndef SENSOR_ENERGY_UNITS
#define SENSOR_ENERGY_UNITS ENERGY_JOULES // Energy units (ENERGY_JOULES | ENERGY_KWH)
#endif
//--------------------------------------------------------------------------------
// Magnitudes
//--------------------------------------------------------------------------------
#ifndef SENSOR_POWER_UNITS
#define SENSOR_POWER_UNITS POWER_WATTS // Power units (POWER_WATTS | POWER_KILOWATTS)
#endif
#define MAGNITUDE_NONE 0
#define MAGNITUDE_TEMPERATURE 1
#define MAGNITUDE_HUMIDITY 2
#define MAGNITUDE_PRESSURE 3
#define MAGNITUDE_CURRENT 4
#define MAGNITUDE_VOLTAGE 5
#define MAGNITUDE_POWER_ACTIVE 6
#define MAGNITUDE_POWER_APPARENT 7
#define MAGNITUDE_POWER_REACTIVE 8
#define MAGNITUDE_POWER_FACTOR 9
#define MAGNITUDE_ENERGY 10
#define MAGNITUDE_ENERGY_DELTA 11
#define MAGNITUDE_ANALOG 12
#define MAGNITUDE_DIGITAL 13
#define MAGNITUDE_EVENTS 14
#define MAGNITUDE_PM1dot0 15
#define MAGNITUDE_PM2dot5 16
#define MAGNITUDE_PM10 17
#define MAGNITUDE_CO2 18
#define MAGNITUDE_LUX 19
#define MAGNITUDE_MAX 20
// =============================================================================
// Specific data for each sensor
// =============================================================================
//------------------------------------------------------------------------------
// AM2320 Humidity & Temperature sensor over I2C
// Enable support by passing AM2320_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef AM2320_SUPPORT
#define AM2320_SUPPORT 0
#endif
#ifndef AM2320_ADDRESS
#define AM2320_ADDRESS 0x00 // 0x00 means auto
#endif
//------------------------------------------------------------------------------
// Analog sensor
// Enable support by passing ANALOG_SUPPORT=1 build flag
@ -102,11 +79,6 @@
#define ANALOG_SUPPORT 0
#endif
#if ANALOG_SUPPORT
#undef ADC_VCC_ENABLED
#define ADC_VCC_ENABLED 0
#endif
//------------------------------------------------------------------------------
// BH1750
// Enable support by passing BH1750_SUPPORT=1 build flag
@ -123,11 +95,6 @@
#define BH1750_MODE BH1750_CONTINUOUS_HIGH_RES_MODE
#if BH1750_SUPPORT
#undef I2C_SUPPORT
#define I2C_SUPPORT 1
#endif
//------------------------------------------------------------------------------
// BME280/BMP280
// Enable support by passing BMX280_SUPPORT=1 build flag
@ -150,11 +117,6 @@
#define BMX280_HUMIDITY 1 // Oversampling for humidity (set to 0 to disable magnitude, only for BME280)
#define BMX280_PRESSURE 1 // Oversampling for pressure (set to 0 to disable magnitude)
#if BMX280_SUPPORT
#undef I2C_SUPPORT
#define I2C_SUPPORT 1
#endif
//------------------------------------------------------------------------------
// Dallas OneWire temperature sensors
// Enable support by passing DALLAS_SUPPORT=1 build flag
@ -188,6 +150,30 @@
#define DHT_TYPE DHT_CHIP_DHT22
#endif
//------------------------------------------------------------------------------
// CSE7766 based power sensor
// Enable support by passing CSE7766_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef CSE7766_SUPPORT
#define CSE7766_SUPPORT 0
#endif
#ifndef CSE7766_PIN
#define CSE7766_PIN 1 // TX pin from the CSE7766
#endif
#ifndef CSE7766_PIN_INVERSE
#define CSE7766_PIN_INVERSE 0 // Signal is inverted
#endif
#define CSE7766_SYNC_INTERVAL 300 // Safe time between transmissions (ms)
#define CSE7766_BAUDRATE 4800 // UART baudrate
#define CSE7766_V1R 1.0 // 1mR current resistor
#define CSE7766_V2R 1.0 // 1M voltage resistor
//------------------------------------------------------------------------------
// Digital sensor
// Enable support by passing DIGITAL_SUPPORT=1 build flag
@ -255,11 +241,6 @@
#define EMON_ADC121_I2C_ADDRESS 0x00 // 0x00 means auto
#if EMON_ADC121_SUPPORT
#undef I2C_SUPPORT
#define I2C_SUPPORT 1
#endif
//------------------------------------------------------------------------------
// Energy Monitor based on ADS1X15
// Enable support by passing EMON_ADS1X15_SUPPORT=1 build flag
@ -274,11 +255,6 @@
#define EMON_ADS1X15_GAIN ADS1X15_REG_CONFIG_PGA_4_096V
#define EMON_ADS1X15_MASK 0x0F // A0=1 A1=2 A2=4 A3=8
#if EMON_ADS1X15_SUPPORT
#undef I2C_SUPPORT
#define I2C_SUPPORT 1
#endif
//------------------------------------------------------------------------------
// Energy Monitor based on interval analog GPIO
// Enable support by passing EMON_ANALOG_SUPPORT=1 build flag
@ -288,11 +264,6 @@
#define EMON_ANALOG_SUPPORT 0 // Do not build support by default
#endif
#if EMON_ANALOG_SUPPORT
#undef ADC_VCC_ENABLED
#define ADC_VCC_ENABLED 0
#endif
//------------------------------------------------------------------------------
// Counter sensor
// Enable support by passing EVENTS_SUPPORT=1 build flag
@ -314,7 +285,66 @@
#define EVENTS_INTERRUPT_MODE RISING // RISING, FALLING, BOTH
#endif
#define EVENTS_DEBOUNCE 50 // Do not register events within less than 10 millis
#define EVENTS_DEBOUNCE 50 // Do not register events within less than 50 millis
//------------------------------------------------------------------------------
// Geiger sensor
// Enable support by passing GEIGER_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef GEIGER_SUPPORT
#define GEIGER_SUPPORT 0 // Do not build with geiger support by default
#endif
#ifndef GEIGER_PIN
#define GEIGER_PIN D1 // GPIO to monitor "D1" => "GPIO5"
#endif
#ifndef GEIGER_PIN_MODE
#define GEIGER_PIN_MODE INPUT // INPUT, INPUT_PULLUP
#endif
#ifndef GEIGER_INTERRUPT_MODE
#define GEIGER_INTERRUPT_MODE RISING // RISING, FALLING, BOTH
#endif
#define GEIGER_DEBOUNCE 25 // Do not register events within less than 25 millis.
// Value derived here: Debounce time 25ms, because https://github.com/Trickx/espurna/wiki/Geiger-counter
#define GEIGER_CPM2SIEVERT 240 // CPM to µSievert per hour conversion factor
// Typically the literature uses the invers, but I find an integer type more convienient.
#define GEIGER_REPORT_SIEVERTS 1 // Enabler for local dose rate reports in µSv/h
#define GEIGER_REPORT_CPM 1 // Enabler for local dose rate reports in counts per minute
//------------------------------------------------------------------------------
// GUVAS12SD UV Sensor (analog)
// Enable support by passing GUVAS12SD_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef GUVAS12SD_SUPPORT
#define GUVAS12SD_SUPPORT 0
#endif
#ifndef GUVAS12SD_PIN
#define GUVAS12SD_PIN 14
#endif
//------------------------------------------------------------------------------
// HC-SR04
// Enable support by passing HCSR04_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef HCSR04_SUPPORT
#define HCSR04_SUPPORT 0
#endif
#ifndef HCSR04_TRIGGER
#define HCSR04_TRIGGER 12 // GPIO for the trigger pin (output)
#endif
#ifndef HCSR04_ECHO
#define HCSR04_ECHO 14 // GPIO for the echo pin (input)
#endif
//------------------------------------------------------------------------------
// HLW8012 Energy monitor IC
@ -364,11 +394,33 @@
#define MHZ19_SUPPORT 0
#endif
#ifndef MHZ19_RX_PIN
#define MHZ19_RX_PIN 13
#endif
#ifndef MHZ19_TX_PIN
#define MHZ19_TX_PIN 15
#endif
//------------------------------------------------------------------------------
// Particle Monitor based on Plantower PMSX003
// SenseAir CO2 sensor
// Enable support by passing SENSEAIR_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef SENSEAIR_SUPPORT
#define SENSEAIR_SUPPORT 0
#endif
#ifndef SENSEAIR_RX_PIN
#define SENSEAIR_RX_PIN 0
#endif
#ifndef SENSEAIR_TX_PIN
#define SENSEAIR_TX_PIN 2
#endif
//------------------------------------------------------------------------------
// Particle Monitor based on Plantower PMS
// Enable support by passing PMSX003_SUPPORT=1 build flag
//------------------------------------------------------------------------------
@ -376,8 +428,49 @@
#define PMSX003_SUPPORT 0
#endif
#ifndef PMS_TYPE
#define PMS_TYPE PMS_TYPE_X003
#endif
// You can enable smart sleep (read 6-times then sleep on 24-reading-cycles) to extend PMS sensor's life.
// Otherwise the default lifetime of PMS sensor is about 8000-hours/1-years.
// The PMS's fan will stop working on sleeping cycle, and will wake up on reading cycle.
#ifndef PMS_SMART_SLEEP
#define PMS_SMART_SLEEP 0
#endif
#ifndef PMS_RX_PIN
#define PMS_RX_PIN 13
#endif
#ifndef PMS_TX_PIN
#define PMS_TX_PIN 15
#endif
//------------------------------------------------------------------------------
// PZEM004T based power monitor
// Enable support by passing PZEM004T_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef PZEM004T_SUPPORT
#define PZEM004T_SUPPORT 0
#endif
#ifndef PZEM004T_USE_SOFT
#define PZEM004T_USE_SOFT 0 // Software serial is not working atm, use hardware serial
#endif
#ifndef PZEM004T_RX_PIN
#define PZEM004T_RX_PIN 13 // Software serial RX GPIO (if PZEM004T_USE_SOFT == 1)
#endif
#ifndef PZEM004T_TX_PIN
#define PZEM004T_TX_PIN 15 // Software serial TX GPIO (if PZEM004T_USE_SOFT == 1)
#endif
#ifndef PZEM004T_HW_PORT
#define PZEM004T_HW_PORT Serial1 // Hardware serial port (if PZEM004T_USE_SOFT == 0)
#endif
//------------------------------------------------------------------------------
// SHT3X I2C (Wemos) temperature & humidity sensor
@ -392,11 +485,6 @@
#define SHT3X_I2C_ADDRESS 0x00 // 0x00 means auto
#endif
#if SHT3X_I2C_SUPPORT
#undef I2C_SUPPORT
#define I2C_SUPPORT 1
#endif
//------------------------------------------------------------------------------
// SI7021 temperature & humidity sensor
// Enable support by passing SI7021_SUPPORT=1 build flag
@ -410,9 +498,17 @@
#define SI7021_ADDRESS 0x00 // 0x00 means auto
#endif
#if SI7021_SUPPORT
#undef I2C_SUPPORT
#define I2C_SUPPORT 1
//------------------------------------------------------------------------------
// TMP3X analog temperature sensor
// Enable support by passing TMP3X_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef TMP3X_SUPPORT
#define TMP3X_SUPPORT 0
#endif
#ifndef TMP3X_TYPE
#define TMP3X_TYPE TMP3X_TMP35
#endif
//------------------------------------------------------------------------------
@ -442,20 +538,46 @@
#define V9261F_RPOWER_FACTOR V9261F_CURRENT_FACTOR
// =============================================================================
// Sensor helpers configuration
// Sensor helpers configuration - can't move to dependencies.h
// =============================================================================
#ifndef SENSOR_SUPPORT
#if ANALOG_SUPPORT || BH1750_SUPPORT || BMX280_SUPPORT || DALLAS_SUPPORT \
|| DHT_SUPPORT || DIGITAL_SUPPORT || ECH1560_SUPPORT \
|| EMON_ADC121_SUPPORT || EMON_ADS1X15_SUPPORT \
|| EMON_ANALOG_SUPPORT || EVENTS_SUPPORT || HLW8012_SUPPORT \
|| MHZ19_SUPPORT || PMSX003_SUPPORT || SHT3X_I2C_SUPPORT \
|| SI7021_SUPPORT || V9261F_SUPPORT
#define SENSOR_SUPPORT 1
#else
#define SENSOR_SUPPORT 0
#define SENSOR_SUPPORT ( \
AM2320_SUPPORT || \
ANALOG_SUPPORT || \
BH1750_SUPPORT || \
BMX280_SUPPORT || \
CSE7766_SUPPORT || \
DALLAS_SUPPORT || \
DHT_SUPPORT || \
DIGITAL_SUPPORT || \
ECH1560_SUPPORT || \
EMON_ADC121_SUPPORT || \
EMON_ADS1X15_SUPPORT || \
EMON_ANALOG_SUPPORT || \
EVENTS_SUPPORT || \
GEIGER_SUPPORT || \
GUVAS12SD_SUPPORT || \
HCSR04_SUPPORT || \
HLW8012_SUPPORT || \
MHZ19_SUPPORT || \
SENSEAIR_SUPPORT || \
PMSX003_SUPPORT || \
PZEM004T_SUPPORT || \
SHT3X_I2C_SUPPORT || \
SI7021_SUPPORT || \
TMP3X_SUPPORT || \
V9261F_SUPPORT \
)
#endif
// -----------------------------------------------------------------------------
// ADC
// -----------------------------------------------------------------------------
// Default ADC mode is to monitor internal power supply
#ifndef ADC_MODE_VALUE
#define ADC_MODE_VALUE ADC_VCC
#endif
// -----------------------------------------------------------------------------
@ -481,91 +603,22 @@
#define I2C_CLEAR_BUS 0 // Clear I2C bus on boot
#define I2C_PERFORM_SCAN 1 // Perform a bus scan on boot
//--------------------------------------------------------------------------------
// Internal power monitor
// Enable support by passing ADC_VCC_ENABLED=1 build flag
// Do not enable this if using the analog GPIO for any other thing
//--------------------------------------------------------------------------------
#ifndef ADC_VCC_ENABLED
#define ADC_VCC_ENABLED 1
#endif
#if ADC_VCC_ENABLED
ADC_MODE(ADC_VCC);
#endif
//--------------------------------------------------------------------------------
// Class loading
//--------------------------------------------------------------------------------
#if SENSOR_SUPPORT
PROGMEM const unsigned char magnitude_decimals[] = {
0,
1, 0, 2,
3, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0
};
PROGMEM const char magnitude_unknown_topic[] = "unknown";
PROGMEM const char magnitude_temperature_topic[] = "temperature";
PROGMEM const char magnitude_humidity_topic[] = "humidity";
PROGMEM const char magnitude_pressure_topic[] = "pressure";
PROGMEM const char magnitude_current_topic[] = "current";
PROGMEM const char magnitude_voltage_topic[] = "voltage";
PROGMEM const char magnitude_active_power_topic[] = "power";
PROGMEM const char magnitude_apparent_power_topic[] = "apparent";
PROGMEM const char magnitude_reactive_power_topic[] = "reactive";
PROGMEM const char magnitude_power_factor_topic[] = "factor";
PROGMEM const char magnitude_energy_topic[] = "energy";
PROGMEM const char magnitude_energy_delta_topic[] = "energy_delta";
PROGMEM const char magnitude_analog_topic[] = "analog";
PROGMEM const char magnitude_digital_topic[] = "digital";
PROGMEM const char magnitude_events_topic[] = "events";
PROGMEM const char magnitude_pm1dot0_topic[] = "pm1dot0";
PROGMEM const char magnitude_pm2dot5_topic[] = "pm2dot5";
PROGMEM const char magnitude_pm10_topic[] = "pm10";
PROGMEM const char magnitude_co2_topic[] = "co2";
PROGMEM const char magnitude_lux_topic[] = "lux";
PROGMEM const char* const magnitude_topics[] = {
magnitude_unknown_topic, magnitude_temperature_topic, magnitude_humidity_topic,
magnitude_pressure_topic, magnitude_current_topic, magnitude_voltage_topic,
magnitude_active_power_topic, magnitude_apparent_power_topic, magnitude_reactive_power_topic,
magnitude_power_factor_topic, magnitude_energy_topic, magnitude_energy_delta_topic,
magnitude_analog_topic, magnitude_digital_topic, magnitude_events_topic,
magnitude_pm1dot0_topic, magnitude_pm2dot5_topic, magnitude_pm10_topic,
magnitude_co2_topic, magnitude_lux_topic
};
PROGMEM const char magnitude_empty[] = "";
PROGMEM const char magnitude_celsius[] = "C";
PROGMEM const char magnitude_fahrenheit[] = "F";
PROGMEM const char magnitude_percentage[] = "%";
PROGMEM const char magnitude_hectopascals[] = "hPa";
PROGMEM const char magnitude_amperes[] = "A";
PROGMEM const char magnitude_volts[] = "V";
PROGMEM const char magnitude_watts[] = "W";
PROGMEM const char magnitude_joules[] = "J";
PROGMEM const char magnitude_ugm3[] = "µg/m3";
PROGMEM const char magnitude_ppm[] = "ppm";
PROGMEM const char magnitude_lux[] = "lux";
PROGMEM const char* const magnitude_units[] = {
magnitude_empty, magnitude_celsius, magnitude_percentage,
magnitude_hectopascals, magnitude_amperes, magnitude_volts,
magnitude_watts, magnitude_watts, magnitude_watts,
magnitude_percentage, magnitude_joules, magnitude_joules,
magnitude_empty, magnitude_empty, magnitude_empty,
magnitude_ugm3, magnitude_ugm3, magnitude_ugm3,
magnitude_ppm, magnitude_lux
};
#if SENSOR_DEBUG
#include "../config/debug.h"
#endif
#include "../sensors/BaseSensor.h"
#if AM2320_SUPPORT
#include "../sensors/AM2320Sensor.h"
#endif
#if ANALOG_SUPPORT
#include "../sensors/AnalogSensor.h"
#endif
@ -575,10 +628,14 @@ PROGMEM const char* const magnitude_units[] = {
#endif
#if BMX280_SUPPORT
#include <SparkFunBME280.h>
#include "../sensors/BMX280Sensor.h"
#endif
#if CSE7766_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/CSE7766Sensor.h"
#endif
#if DALLAS_SUPPORT
#include <OneWire.h>
#include "../sensors/DallasSensor.h"
@ -612,6 +669,18 @@ PROGMEM const char* const magnitude_units[] = {
#include "../sensors/EventSensor.h"
#endif
#if GEIGER_SUPPORT
#include "../sensors/GeigerSensor.h" // The main file for geiger counting module
#endif
#if GUVAS12SD_SUPPORT
#include "../sensors/GUVAS12SDSensor.h"
#endif
#if HCSR04_SUPPORT
#include "../sensors/HCSR04Sensor.h"
#endif
#if HLW8012_SUPPORT
#include <HLW8012.h>
#include "../sensors/HLW8012Sensor.h"
@ -622,12 +691,21 @@ PROGMEM const char* const magnitude_units[] = {
#include "../sensors/MHZ19Sensor.h"
#endif
#if SENSEAIR_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/SenseAirSensor.h"
#endif
#if PMSX003_SUPPORT
#include <SoftwareSerial.h>
#include <PMS.h>
#include "../sensors/PMSX003Sensor.h"
#endif
#if PZEM004T_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/PZEM004TSensor.h"
#endif
#if SI7021_SUPPORT
#include "../sensors/SI7021Sensor.h"
#endif
@ -636,6 +714,10 @@ PROGMEM const char* const magnitude_units[] = {
#include "../sensors/SHT3XI2CSensor.h"
#endif
#if TMP3X_SUPPORT
#include "../sensors/TMP3XSensor.h"
#endif
#if V9261F_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/V9261FSensor.h"


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

@ -0,0 +1,302 @@
//------------------------------------------------------------------------------
// Type definitions
// Do not touch this definitions
//------------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// WIFI
// -----------------------------------------------------------------------------
#define WIFI_STATE_AP 1
#define WIFI_STATE_STA 2
#define WIFI_STATE_AP_STA 3
#define WIFI_STATE_WPS 4
#define WIFI_STATE_SMARTCONFIG 8
#define WIFI_AP_ALLWAYS 1
#define WIFI_AP_FALLBACK 2
//------------------------------------------------------------------------------
// BUTTONS
//------------------------------------------------------------------------------
#define BUTTON_EVENT_NONE 0
#define BUTTON_EVENT_PRESSED 1
#define BUTTON_EVENT_RELEASED 2
#define BUTTON_EVENT_CLICK 2
#define BUTTON_EVENT_DBLCLICK 3
#define BUTTON_EVENT_LNGCLICK 4
#define BUTTON_EVENT_LNGLNGCLICK 5
#define BUTTON_EVENT_TRIPLECLICK 6
#define BUTTON_MODE_NONE 0
#define BUTTON_MODE_TOGGLE 1
#define BUTTON_MODE_ON 2
#define BUTTON_MODE_OFF 3
#define BUTTON_MODE_AP 4
#define BUTTON_MODE_RESET 5
#define BUTTON_MODE_PULSE 6
#define BUTTON_MODE_FACTORY 7
#define BUTTON_MODE_WPS 8
#define BUTTON_MODE_SMART_CONFIG 9
// Needed for ESP8285 boards under Windows using PlatformIO (?)
#ifndef BUTTON_PUSHBUTTON
#define BUTTON_PUSHBUTTON 0
#define BUTTON_SWITCH 1
#define BUTTON_DEFAULT_HIGH 2
#define BUTTON_SET_PULLUP 4
#endif
//------------------------------------------------------------------------------
// RELAY
//------------------------------------------------------------------------------
#define RELAY_BOOT_OFF 0
#define RELAY_BOOT_ON 1
#define RELAY_BOOT_SAME 2
#define RELAY_BOOT_TOGGLE 3
#define RELAY_TYPE_NORMAL 0
#define RELAY_TYPE_INVERSE 1
#define RELAY_TYPE_LATCHED 2
#define RELAY_TYPE_LATCHED_INVERSE 3
#define RELAY_SYNC_ANY 0
#define RELAY_SYNC_NONE_OR_ONE 1
#define RELAY_SYNC_ONE 2
#define RELAY_SYNC_SAME 3
#define RELAY_PULSE_NONE 0
#define RELAY_PULSE_OFF 1
#define RELAY_PULSE_ON 2
#define RELAY_PROVIDER_RELAY 0
#define RELAY_PROVIDER_DUAL 1
#define RELAY_PROVIDER_LIGHT 2
#define RELAY_PROVIDER_RFBRIDGE 3
#define RELAY_PROVIDER_STM 4
//------------------------------------------------------------------------------
// UDP SYSLOG
//------------------------------------------------------------------------------
// Priority codes:
#define SYSLOG_EMERG 0 /* system is unusable */
#define SYSLOG_ALERT 1 /* action must be taken immediately */
#define SYSLOG_CRIT 2 /* critical conditions */
#define SYSLOG_ERR 3 /* error conditions */
#define SYSLOG_WARNING 4 /* warning conditions */
#define SYSLOG_NOTICE 5 /* normal but significant condition */
#define SYSLOG_INFO 6 /* informational */
#define SYSLOG_DEBUG 7 /* debug-level messages */
// Facility codes:
#define SYSLOG_KERN (0<<3) /* kernel messages */
#define SYSLOG_USER (1<<3) /* random user-level messages */
#define SYSLOG_MAIL (2<<3) /* mail system */
#define SYSLOG_DAEMON (3<<3) /* system daemons */
#define SYSLOG_AUTH (4<<3) /* security/authorization messages */
#define SYSLOG_SYSLOG (5<<3) /* messages generated internally by syslogd */
#define SYSLOG_LPR (6<<3) /* line printer subsystem */
#define SYSLOG_NEWS (7<<3) /* network news subsystem */
#define SYSLOG_UUCP (8<<3) /* UUCP subsystem */
#define SYSLOG_CRON (9<<3) /* clock daemon */
#define SYSLOG_AUTHPRIV (10<<3) /* security/authorization messages (private) */
#define SYSLOG_FTP (11<<3) /* ftp daemon */
#define SYSLOG_LOCAL0 (16<<3) /* reserved for local use */
#define SYSLOG_LOCAL1 (17<<3) /* reserved for local use */
#define SYSLOG_LOCAL2 (18<<3) /* reserved for local use */
#define SYSLOG_LOCAL3 (19<<3) /* reserved for local use */
#define SYSLOG_LOCAL4 (20<<3) /* reserved for local use */
#define SYSLOG_LOCAL5 (21<<3) /* reserved for local use */
#define SYSLOG_LOCAL6 (22<<3) /* reserved for local use */
#define SYSLOG_LOCAL7 (23<<3) /* reserved for local use */
//------------------------------------------------------------------------------
// MQTT
//------------------------------------------------------------------------------
// Internal MQTT events
#define MQTT_CONNECT_EVENT 0
#define MQTT_DISCONNECT_EVENT 1
#define MQTT_MESSAGE_EVENT 2
//------------------------------------------------------------------------------
// LED
//------------------------------------------------------------------------------
#define LED_MODE_MQTT 0 // LED will be managed from MQTT (OFF by default)
#define LED_MODE_WIFI 1 // LED will blink according to the WIFI status
#define LED_MODE_FOLLOW 2 // LED will follow state of linked relay (check RELAY#_LED)
#define LED_MODE_FOLLOW_INVERSE 3 // LED will follow the opposite state of linked relay (check RELAY#_LED)
#define LED_MODE_FINDME 4 // LED will be ON if all relays are OFF
#define LED_MODE_FINDME_WIFI 5 // A mixture between WIFI and FINDME
#define LED_MODE_ON 6 // LED always ON
#define LED_MODE_OFF 7 // LED always OFF
#define LED_MODE_RELAY 8 // If any relay is ON, LED will be ON, otherwise OFF
#define LED_MODE_RELAY_WIFI 9 // A mixture between WIFI and RELAY, the reverse of MIXED
// -----------------------------------------------------------------------------
// UI
// -----------------------------------------------------------------------------
#define UI_TAG_INPUT 0
#define UI_TAG_CHECKBOX 1
#define UI_TAG_SELECT 2
#define WEB_MODE_NORMAL 0
#define WEB_MODE_PASSWORD 1
// -----------------------------------------------------------------------------
// LIGHT
// -----------------------------------------------------------------------------
// Available light providers
#define LIGHT_PROVIDER_NONE 0
#define LIGHT_PROVIDER_MY92XX 1 // works with MY9291 and MY9231
#define LIGHT_PROVIDER_DIMMER 2
// -----------------------------------------------------------------------------
// SCHEDULER
// -----------------------------------------------------------------------------
#define SCHEDULER_TYPE_SWITCH 1
#define SCHEDULER_TYPE_DIM 2
// -----------------------------------------------------------------------------
// IR
// -----------------------------------------------------------------------------
// IR Button modes
#define IR_BUTTON_MODE_NONE 0
#define IR_BUTTON_MODE_RGB 1
#define IR_BUTTON_MODE_HSV 2
#define IR_BUTTON_MODE_BRIGHTER 3
#define IR_BUTTON_MODE_STATE 4
#define IR_BUTTON_MODE_EFFECT 5
#define IR_BUTTON_MODE_TOGGLE 6
#define LIGHT_EFFECT_SOLID 0
#define LIGHT_EFFECT_FLASH 1
#define LIGHT_EFFECT_STROBE 2
#define LIGHT_EFFECT_FADE 3
#define LIGHT_EFFECT_SMOOTH 4
//------------------------------------------------------------------------------
// RESET
//------------------------------------------------------------------------------
#define CUSTOM_RESET_HARDWARE 1 // Reset from hardware button
#define CUSTOM_RESET_WEB 2 // Reset from web interface
#define CUSTOM_RESET_TERMINAL 3 // Reset from terminal
#define CUSTOM_RESET_MQTT 4 // Reset via MQTT
#define CUSTOM_RESET_RPC 5 // Reset via RPC (HTTP)
#define CUSTOM_RESET_OTA 6 // Reset after successful OTA update
#define CUSTOM_RESET_HTTP 7 // Reset via HTTP GET
#define CUSTOM_RESET_NOFUSS 8 // Reset after successful NOFUSS update
#define CUSTOM_RESET_UPGRADE 9 // Reset after update from web interface
#define CUSTOM_RESET_FACTORY 10 // Factory reset from terminal
#define CUSTOM_RESET_MAX 10
//------------------------------------------------------------------------------
// ENVIRONMENTAL
//------------------------------------------------------------------------------
// American Society of Heating, Refrigerating and Air-Conditioning Engineers suggests a range of 45% - 55% humidity to manage health effects and illnesses.
// Comfortable: 30% - 60%
// Recommended: 45% - 55%
// High : 55% - 80%
#define HUMIDITY_NORMAL 0 // > %30
#define HUMIDITY_COMFORTABLE 1 // > %45
#define HUMIDITY_DRY 2 // < %30
#define HUMIDITY_WET 3 // > %70
// United States Environmental Protection Agency - UV Index Scale
// One UV Index unit is equivalent to 25 milliWatts per square meter.
#define UV_INDEX_LOW 0 // 0 to 2 means low danger from the sun's UV rays for the average person.
#define UV_INDEX_MODERATE 1 // 3 to 5 means moderate risk of harm from unprotected sun exposure.
#define UV_INDEX_HIGH 2 // 6 to 7 means high risk of harm from unprotected sun exposure. Protection against skin and eye damage is needed.
#define UV_INDEX_VERY_HIGH 3 // 8 to 10 means very high risk of harm from unprotected sun exposure.
// Take extra precautions because unprotected skin and eyes will be damaged and can burn quickly.
#define UV_INDEX_EXTREME 4 // 11 or more means extreme risk of harm from unprotected sun exposure.
// Take all precautions because unprotected skin and eyes can burn in minutes.
//------------------------------------------------------------------------------
// UNITS
//------------------------------------------------------------------------------
#define POWER_WATTS 0
#define POWER_KILOWATTS 1
#define ENERGY_JOULES 0
#define ENERGY_KWH 1
#define TMP_CELSIUS 0
#define TMP_FAHRENHEIT 1
#define TMP_KELVIN 2
//--------------------------------------------------------------------------------
// Sensor ID
// These should remain over time, do not modify them, only add new ones at the end
//--------------------------------------------------------------------------------
#define SENSOR_DHTXX_ID 0x01
#define SENSOR_DALLAS_ID 0x02
#define SENSOR_EMON_ANALOG_ID 0x03
#define SENSOR_EMON_ADC121_ID 0x04
#define SENSOR_EMON_ADS1X15_ID 0x05
#define SENSOR_HLW8012_ID 0x06
#define SENSOR_V9261F_ID 0x07
#define SENSOR_ECH1560_ID 0x08
#define SENSOR_ANALOG_ID 0x09
#define SENSOR_DIGITAL_ID 0x10
#define SENSOR_EVENTS_ID 0x11
#define SENSOR_PMSX003_ID 0x12
#define SENSOR_BMX280_ID 0x13
#define SENSOR_MHZ19_ID 0x14
#define SENSOR_SI7021_ID 0x15
#define SENSOR_SHT3X_I2C_ID 0x16
#define SENSOR_BH1750_ID 0x17
#define SENSOR_PZEM004T_ID 0x18
#define SENSOR_AM2320_ID 0x19
#define SENSOR_GUVAS12SD_ID 0x20
#define SENSOR_CSE7766_ID 0x21
#define SENSOR_TMP3X_ID 0x22
#define SENSOR_HCSR04_ID 0x23
#define SENSOR_SENSEAIR_ID 0x24
#define SENSOR_GEIGER_ID 0x25
//--------------------------------------------------------------------------------
// Magnitudes
//--------------------------------------------------------------------------------
#define MAGNITUDE_NONE 0
#define MAGNITUDE_TEMPERATURE 1
#define MAGNITUDE_HUMIDITY 2
#define MAGNITUDE_PRESSURE 3
#define MAGNITUDE_CURRENT 4
#define MAGNITUDE_VOLTAGE 5
#define MAGNITUDE_POWER_ACTIVE 6
#define MAGNITUDE_POWER_APPARENT 7
#define MAGNITUDE_POWER_REACTIVE 8
#define MAGNITUDE_POWER_FACTOR 9
#define MAGNITUDE_ENERGY 10
#define MAGNITUDE_ENERGY_DELTA 11
#define MAGNITUDE_ANALOG 12
#define MAGNITUDE_DIGITAL 13
#define MAGNITUDE_EVENTS 14
#define MAGNITUDE_PM1dot0 15
#define MAGNITUDE_PM2dot5 16
#define MAGNITUDE_PM10 17
#define MAGNITUDE_CO2 18
#define MAGNITUDE_LUX 19
#define MAGNITUDE_UV 20
#define MAGNITUDE_DISTANCE 21
#define MAGNITUDE_HCHO 22
#define MAGNITUDE_GEIGER_CPM 23
#define MAGNITUDE_GEIGER_SIEVERT 24
#define MAGNITUDE_MAX 25

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

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

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


+ 99
- 29
code/espurna/debug.ino View File

@ -10,7 +10,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <stdio.h>
#include <stdarg.h>
#include <EEPROM.h>
#include <EEPROM_Rotate.h>
extern "C" {
#include "user_interface.h"
@ -19,10 +19,15 @@ extern "C" {
#if DEBUG_UDP_SUPPORT
#include <WiFiUdp.h>
WiFiUDP _udp_debug;
#if DEBUG_UDP_PORT == 514
char _udp_syslog_header[40] = {0};
#endif
#endif
void _debugSend(char * message) {
bool pause = false;
#if DEBUG_ADD_TIMESTAMP
static bool add_timestamp = true;
char timestamp[10] = {0};
@ -42,11 +47,12 @@ void _debugSend(char * message) {
if (systemCheck()) {
#endif
_udp_debug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
#if DEBUG_ADD_TIMESTAMP
_udp_debug.write(timestamp);
#if DEBUG_UDP_PORT == 514
_udp_debug.write(_udp_syslog_header);
#endif
_udp_debug.write(message);
_udp_debug.endPacket();
pause = true;
#if SYSTEM_CHECK_ENABLED
}
#endif
@ -57,11 +63,35 @@ void _debugSend(char * message) {
_telnetWrite(timestamp, strlen(timestamp));
#endif
_telnetWrite(message, strlen(message));
pause = true;
#endif
#if DEBUG_WEB_SUPPORT
if (wsConnected() && (getFreeHeap() > 10000)) {
DynamicJsonBuffer jsonBuffer(JSON_OBJECT_SIZE(1) + strlen(message) + 17);
JsonObject &root = jsonBuffer.createObject();
#if DEBUG_ADD_TIMESTAMP
char buffer[strlen(timestamp) + strlen(message) + 1];
snprintf_P(buffer, sizeof(buffer), "%s%s", timestamp, message);
root.set("weblog", buffer);
#else
root.set("weblog", message);
#endif
String out;
root.printTo(out);
jsonBuffer.clear();
wsSend(out.c_str());
pause = true;
}
#endif
if (pause) optimistic_yield(100);
}
// -----------------------------------------------------------------------------
void debugSend(const char * format, ...) {
va_list args;
@ -97,6 +127,45 @@ void debugSend_P(PGM_P format_P, ...) {
}
#if DEBUG_WEB_SUPPORT
void debugWebSetup() {
wsOnSendRegister([](JsonObject& root) {
root["dbgVisible"] = 1;
});
wsOnActionRegister([](uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "dbgcmd") == 0) {
const char* command = data.get<const char*>("command");
char buffer[strlen(command) + 2];
snprintf(buffer, sizeof(buffer), "%s\n", command);
settingsInject((void*) buffer, strlen(buffer));
}
});
#if DEBUG_UDP_SUPPORT
#if DEBUG_UDP_PORT == 514
snprintf_P(_udp_syslog_header, sizeof(_udp_syslog_header), PSTR("<%u>%s ESPurna[0]: "), DEBUG_UDP_FAC_PRI, getSetting("hostname").c_str());
#endif
#endif
}
#endif // DEBUG_WEB_SUPPORT
void debugSetup() {
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.begin(SERIAL_BAUDRATE);
#if DEBUG_ESP_WIFI
DEBUG_PORT.setDebugOutput(true);
#endif
#endif
}
// -----------------------------------------------------------------------------
// Save crash info
// Taken from krzychb EspSaveCrash
@ -145,31 +214,31 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
// write crash time to EEPROM
uint32_t crash_time = millis();
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
// write reset info to EEPROM
EEPROM.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON, rst_info->reason);
EEPROM.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE, rst_info->exccause);
EEPROMr.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON, rst_info->reason);
EEPROMr.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE, rst_info->exccause);
// write epc1, epc2, epc3, excvaddr and depc to EEPROM
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, rst_info->epc1);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, rst_info->epc2);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, rst_info->epc3);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, rst_info->excvaddr);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, rst_info->depc);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, rst_info->epc1);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, rst_info->epc2);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, rst_info->epc3);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, rst_info->excvaddr);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, rst_info->depc);
// write stack start and end address to EEPROM
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
// write stack trace to EEPROM
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
for (uint32_t i = stack_start; i < stack_end; i++) {
byte* byteValue = (byte*) i;
EEPROM.write(current_address++, *byteValue);
EEPROMr.write(current_address++, *byteValue);
}
EEPROM.commit();
EEPROMr.commit();
}
@ -178,8 +247,8 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
*/
void debugClearCrashInfo() {
uint32_t crash_time = 0xFFFFFFFF;
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROM.commit();
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROMr.commit();
}
/**
@ -188,28 +257,28 @@ void debugClearCrashInfo() {
void debugDumpCrashInfo() {
uint32_t crash_time;
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
if ((crash_time == 0) || (crash_time == 0xFFFFFFFF)) {
DEBUG_MSG_P(PSTR("[DEBUG] No crash info\n"));
return;
}
DEBUG_MSG_P(PSTR("[DEBUG] Crash at %lu ms\n"), crash_time);
DEBUG_MSG_P(PSTR("[DEBUG] Reason of restart: %u\n"), EEPROM.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON));
DEBUG_MSG_P(PSTR("[DEBUG] Exception cause: %u\n"), EEPROM.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE));
DEBUG_MSG_P(PSTR("[DEBUG] Latest crash was at %lu ms after boot\n"), crash_time);
DEBUG_MSG_P(PSTR("[DEBUG] Reason of restart: %u\n"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON));
DEBUG_MSG_P(PSTR("[DEBUG] Exception cause: %u\n"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE));
uint32_t epc1, epc2, epc3, excvaddr, depc;
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, epc1);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, epc2);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, epc3);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, excvaddr);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, depc);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, epc1);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, epc2);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, epc3);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, excvaddr);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, depc);
DEBUG_MSG_P(PSTR("[DEBUG] epc1=0x%08x epc2=0x%08x epc3=0x%08x\n"), epc1, epc2, epc3);
DEBUG_MSG_P(PSTR("[DEBUG] excvaddr=0x%08x depc=0x%08x\n"), excvaddr, depc);
uint32_t stack_start, stack_end;
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
DEBUG_MSG_P(PSTR("[DEBUG] >>>stack>>>\n[DEBUG] "));
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
int16_t stack_len = stack_end - stack_start;
@ -217,7 +286,7 @@ void debugDumpCrashInfo() {
for (int16_t i = 0; i < stack_len; i += 0x10) {
DEBUG_MSG_P(PSTR("%08x: "), stack_start + i);
for (byte j = 0; j < 4; j++) {
EEPROM.get(current_address, stack_trace);
EEPROMr.get(current_address, stack_trace);
DEBUG_MSG_P(PSTR("%08x "), stack_trace);
current_address += 4;
}
@ -226,4 +295,5 @@ void debugDumpCrashInfo() {
DEBUG_MSG_P(PSTR("<<<stack<<<\n"));
}
#endif // DEBUG_SUPPORT

+ 17
- 0
code/espurna/domoticz.ino View File

@ -43,7 +43,13 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
if (type == MQTT_CONNECT_EVENT) {
// Subscribe to domoticz action topics
mqttSubscribeRaw(dczTopicOut.c_str());
// Send relays state on connection
domoticzSendRelays();
}
if (type == MQTT_MESSAGE_EVENT) {
@ -76,6 +82,10 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
#if WEB_SUPPORT
bool _domoticzWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "dcz", 3) == 0);
}
void _domoticzWebSocketOnSend(JsonObject& root) {
root["dczVisible"] = 1;
@ -134,6 +144,12 @@ void domoticzSendRelay(unsigned char relayID) {
domoticzSend(buffer, relayStatus(relayID) ? "1" : "0");
}
void domoticzSendRelays() {
for (uint8_t relayID=0; relayID < relayCount(); relayID++) {
domoticzSendRelay(relayID);
}
}
unsigned int domoticzIdx(unsigned char relayID) {
char buffer[15];
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%u"), relayID);
@ -145,6 +161,7 @@ void domoticzSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_domoticzWebSocketOnSend);
wsOnAfterParseRegister(_domoticzConfigure);
wsOnReceiveRegister(_domoticzWebSocketOnReceive);
#endif
mqttRegister(_domoticzMqtt);
}


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

@ -0,0 +1,86 @@
/*
EEPROM MODULE
*/
#include <EEPROM_Rotate.h>
// -----------------------------------------------------------------------------
bool eepromRotate(bool value) {
// Enable/disable EEPROM rotation only if we are using more sectors than the
// reserved by the memory layout
if (EEPROMr.size() > EEPROMr.reserved()) {
if (value) {
DEBUG_MSG_P(PSTR("[EEPROM] Reenabling EEPROM rotation\n"));
} else {
DEBUG_MSG_P(PSTR("[EEPROM] Disabling EEPROM rotation\n"));
}
EEPROMr.rotate(value);
}
}
String eepromSectors() {
String response;
for (uint32_t i = 0; i < EEPROMr.size(); i++) {
if (i > 0) response = response + String(", ");
response = response + String(EEPROMr.base() - i);
}
return response;
}
#if TERMINAL_SUPPORT
void _eepromInitCommands() {
settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) {
EEPROMr.dump(settingsSerial());
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
settingsRegisterCommand(F("FLASH.DUMP"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
uint32_t sector = String(e->argv[1]).toInt();
uint32_t max = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE;
if (sector >= max) {
DEBUG_MSG_P(PSTR("-ERROR: Sector out of range\n"));
return;
}
EEPROMr.dump(settingsSerial(), sector);
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
}
#endif
// -----------------------------------------------------------------------------
void eepromSetup() {
#ifdef EEPROM_ROTATE_SECTORS
EEPROMr.size(EEPROM_ROTATE_SECTORS);
#else
// If the memory layout has more than one sector reserved use those,
// otherwise calculate pool size based on memory size.
if (EEPROMr.size() == 1) {
if (EEPROMr.last() > 1000) { // 4Mb boards
EEPROMr.size(4);
} else if (EEPROMr.last() > 250) { // 1Mb boards
EEPROMr.size(2);
}
}
#endif
EEPROMr.offset(EEPROM_ROTATE_DATA);
EEPROMr.begin(EEPROM_SIZE);
#if TERMINAL_SUPPORT
_eepromInitCommands();
#endif
}

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

@ -42,7 +42,15 @@ void setup() {
// Basic modules, will always run
// -------------------------------------------------------------------------
// Init EEPROM, Serial, SPIFFS and system check
// Serial debug
#if DEBUG_SUPPORT
debugSetup();
#endif
// Init EEPROM
eepromSetup();
// Init Serial, SPIFFS and system check
systemSetup();
// Init persistance and terminal features
@ -50,7 +58,7 @@ void setup() {
// Hostname & board name initialization
if (getSetting("hostname").length() == 0) {
setSetting("hostname", getIdentifier());
setDefaultHostname();
}
setBoardName();
@ -80,6 +88,9 @@ void setup() {
webSetup();
wsSetup();
apiSetup();
#if DEBUG_WEB_SUPPORT
debugWebSetup();
#endif
#endif
// lightSetup must be called before relaySetup
@ -147,6 +158,10 @@ void setup() {
#if SCHEDULER_SUPPORT
schSetup();
#endif
#if UART_MQTT_SUPPORT
uartmqttSetup();
#endif
// 3rd party code hook
#if USE_EXTRA


+ 202
- 78
code/espurna/homeassistant.ino View File

@ -14,124 +14,190 @@ bool _haEnabled = false;
bool _haSendFlag = false;
// -----------------------------------------------------------------------------
void _haWebSocketOnSend(JsonObject& root) {
root["haVisible"] = 1;
root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX);
root["haEnabled"] = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
}
// SENSORS
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
void _haSendMagnitude(unsigned char i) {
void _haSendMagnitude(unsigned char i, JsonObject& config) {
String output;
unsigned char type = magnitudeType(i);
config["name"] = getSetting("hostname") + String(" ") + magnitudeTopic(type);
config.set("platform", "mqtt");
config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
config["unit_of_measurement"] = magnitudeUnits(type);
if (_haEnabled) {
}
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
void _haSendMagnitudes() {
unsigned char type = magnitudeType(i);
for (unsigned char i=0; i<magnitudeCount(); i++) {
root["device_class"] = "sensor";
root["name"] = getSetting("hostname") + String(" ") + magnitudeTopic(type);
root["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
root["unit_of_measurement"] = magnitudeUnits(type);
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/sensor/" +
getSetting("hostname") + "_" + String(i) +
"/config";
String output;
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
config.printTo(output);
jsonBuffer.clear();
}
root.printTo(output);
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/sensor/" +
getSetting("hostname") + "_" + String(i) +
"/config";
}
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
#endif // SENSOR_SUPPORT
}
// -----------------------------------------------------------------------------
// SWITCHES & LIGHTS
// -----------------------------------------------------------------------------
void _haSendMagnitudes() {
for (unsigned char i=0; i<magnitudeCount(); i++) {
_haSendMagnitude(i);
void _haSendSwitch(unsigned char i, JsonObject& config) {
String name = getSetting("hostname");
if (relayCount() > 1) {
name += String(" #") + String(i);
}
}
#endif
config.set("name", name);
config.set("platform", "mqtt");
if (relayCount()) {
config["state_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, false);
config["command_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, true);
config["payload_on"] = String("1");
config["payload_off"] = String("0");
config["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false);
config["payload_available"] = String("1");
config["payload_not_available"] = String("0");
}
void _haSendSwitch(unsigned char i) {
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
String output;
if (i == 0) {
if (_haEnabled) {
if (lightHasColor()) {
config["brightness_state_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, false);
config["brightness_command_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, true);
config["rgb_state_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, false);
config["rgb_command_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, true);
config["color_temp_command_topic"] = mqttTopic(MQTT_TOPIC_MIRED, true);
}
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
if (lightChannels() > 3) {
config["white_value_state_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, false);
config["white_value_command_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, true);
}
String name = getSetting("hostname");
if (relayCount() > 1) {
name += String(" switch #") + String(i);
}
root["name"] = name;
root["platform"] = "mqtt";
if (relayCount()) {
root["state_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, false);
root["command_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, true);
root["payload_on"] = String("1");
root["payload_off"] = String("0");
root["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false);
root["payload_available"] = String("1");
root["payload_not_available"] = String("0");
}
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
}
if (i == 0) {
void _haSendSwitches() {
if (lightHasColor()) {
root["brightness_state_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, false);
root["brightness_command_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, true);
root["rgb_state_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, false);
root["rgb_command_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, true);
root["color_temp_command_topic"] = mqttTopic(MQTT_TOPIC_MIRED, true);
}
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light");
#else
String type = String("switch");
#endif
if (lightChannels() > 3) {
root["white_value_state_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, false);
root["white_value_command_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, true);
}
for (unsigned char i=0; i<relayCount(); i++) {
}
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/" + type +
"/" + getSetting("hostname") + "_" + String(i) +
"/config";
String output;
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
config.printTo(output);
jsonBuffer.clear();
}
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
root.printTo(output);
}
#if LIGHT_PROVIDER == LIGHT_PROVIDER_NONE
String component = String("switch");
}
// -----------------------------------------------------------------------------
String _haGetConfig() {
String output;
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light");
#else
String component = String("light");
String type = String("switch");
#endif
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/" + component +
"/" + getSetting("hostname") + "_" + String(i) +
"/config";
for (unsigned char i=0; i<relayCount(); i++) {
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
output += type + ":\n";
bool first = true;
for (auto kv : config) {
if (first) {
output += " - ";
first = false;
} else {
output += " ";
}
output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
}
output += "\n";
}
jsonBuffer.clear();
void _haSendSwitches() {
for (unsigned char i=0; i<relayCount(); i++) {
_haSendSwitch(i);
}
#if SENSOR_SUPPORT
for (unsigned char i=0; i<magnitudeCount(); i++) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
output += "sensor:\n";
bool first = true;
for (auto kv : config) {
if (first) {
output += " - ";
first = false;
} else {
output += " ";
}
output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
}
output += "\n";
jsonBuffer.clear();
}
#endif
return output;
}
void _haSend() {
@ -161,6 +227,57 @@ void _haConfigure() {
_haSend();
}
#if WEB_SUPPORT
bool _haWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "ha", 2) == 0);
}
void _haWebSocketOnSend(JsonObject& root) {
root["haVisible"] = 1;
root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX);
root["haEnabled"] = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
}
void _haWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "haconfig") == 0) {
String output = _haGetConfig();
output.replace(" ", "&nbsp;");
output.replace("\n", "<br />");
output = String("{\"haConfig\": \"") + output + String("\"}");
wsSend(client_id, output.c_str());
}
}
#endif
#if TERMINAL_SUPPORT
void _haInitCommands() {
settingsRegisterCommand(F("HA.CONFIG"), [](Embedis* e) {
DEBUG_MSG(_haGetConfig().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) {
setSetting("haEnabled", "1");
_haConfigure();
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
#endif
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
setSetting("haEnabled", "0");
_haConfigure();
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
#endif
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
#endif
// -----------------------------------------------------------------------------
void haSetup() {
@ -170,6 +287,8 @@ void haSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_haWebSocketOnSend);
wsOnAfterParseRegister(_haConfigure);
wsOnActionRegister(_haWebSocketOnAction);
wsOnReceiveRegister(_haWebSocketOnReceive);
#endif
// On MQTT connect check if we have something to send
@ -177,6 +296,11 @@ void haSetup() {
if (type == MQTT_CONNECT_EVENT) _haSend();
});
#if TERMINAL_SUPPORT
_haInitCommands();
#endif
}
#endif // HOMEASSISTANT_SUPPORT

+ 31
- 16
code/espurna/i2c.ino View File

@ -32,7 +32,7 @@ int _i2cClearbus(int sda, int scl) {
pinMode(sda, INPUT_PULLUP);
pinMode(scl, INPUT_PULLUP);
delay(2500);
nice_delay(2500);
// Wait 2.5 secs. This is strictly only necessary on the first power
// up of the DS3231 module to allow it to initialize properly,
// but is also assists in reliable programming of FioV3 boards as it gives the
@ -68,7 +68,7 @@ int _i2cClearbus(int sda, int scl) {
int counter = 20;
while (scl_low && (counter > 0)) {
counter--;
delay(100);
nice_delay(100);
scl_low = (digitalRead(scl) == LOW);
}
@ -110,17 +110,22 @@ int _i2cClearbus(int sda, int scl) {
#if I2C_USE_BRZO
void i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) {
void i2c_wakeup(uint8_t address) {
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_write_uint8(buffer, len, false);
brzo_i2c_end_transaction();
}
void i2c_write_uint8(uint8_t address, uint8_t value) {
uint8_t i2c_write_uint8(uint8_t address, uint8_t value) {
uint8_t buffer[1] = {value};
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_write_uint8(buffer, 1, false);
brzo_i2c_end_transaction();
return brzo_i2c_end_transaction();
}
uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) {
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_write_uint8(buffer, len, false);
return brzo_i2c_end_transaction();
}
uint8_t i2c_read_uint8(uint8_t address) {
@ -165,16 +170,21 @@ void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len) {
#else // not I2C_USE_BRZO
void i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) {
void i2c_wakeup(uint8_t address) {
Wire.beginTransmission((uint8_t) address);
Wire.write(buffer, len);
Wire.endTransmission();
}
void i2c_write_uint8(uint8_t address, uint8_t value) {
uint8_t i2c_write_uint8(uint8_t address, uint8_t value) {
Wire.beginTransmission((uint8_t) address);
Wire.write((uint8_t) value);
Wire.endTransmission();
return Wire.endTransmission();
}
uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) {
Wire.beginTransmission((uint8_t) address);
Wire.write(buffer, len);
return Wire.endTransmission();
}
uint8_t i2c_read_uint8(uint8_t address) {
@ -226,24 +236,29 @@ void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len) {
#endif // I2C_USE_BRZO
void i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value) {
uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value) {
uint8_t buffer[2] = {reg, value};
i2c_write_buffer(address, buffer, 2);
return i2c_write_buffer(address, buffer, 2);
}
uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value1, uint8_t value2) {
uint8_t buffer[3] = {reg, value1, value2};
return i2c_write_buffer(address, buffer, 3);
}
void i2c_write_uint16(uint8_t address, uint8_t reg, uint16_t value) {
uint8_t i2c_write_uint16(uint8_t address, uint8_t reg, uint16_t value) {
uint8_t buffer[3];
buffer[0] = reg;
buffer[1] = (value >> 8) & 0xFF;
buffer[2] = (value >> 0) & 0xFF;
i2c_write_buffer(address, buffer, 3);
return i2c_write_buffer(address, buffer, 3);
}
void i2c_write_uint16(uint8_t address, uint16_t value) {
uint8_t i2c_write_uint16(uint8_t address, uint16_t value) {
uint8_t buffer[2];
buffer[0] = (value >> 8) & 0xFF;
buffer[1] = (value >> 0) & 0xFF;
i2c_write_buffer(address, buffer, 2);
return i2c_write_buffer(address, buffer, 2);
}
uint16_t i2c_read_uint16_le(uint8_t address, uint8_t reg) {


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

@ -16,6 +16,10 @@ SyncClient _idb_client;
// -----------------------------------------------------------------------------
bool _idbWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "idb", 3) == 0);
}
void _idbWebSocketOnSend(JsonObject& root) {
root["idbVisible"] = 1;
root["idbEnabled"] = getSetting("idbEnabled", INFLUXDB_ENABLED).toInt() == 1;
@ -59,7 +63,7 @@ bool idbSend(const char * topic, const char * payload) {
DEBUG_MSG("[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:%d\r\nContent-Length: %d\r\n\r\n%s",
snprintf(request, sizeof(request), "POST /write?db=%s&u=%s&p=%s HTTP/1.1\r\nHost: %s:%u\r\nContent-Length: %d\r\n\r\n%s",
getSetting("idbDatabase", INFLUXDB_DATABASE).c_str(),
getSetting("idbUsername", INFLUXDB_USERNAME).c_str(), getSetting("idbPassword", INFLUXDB_PASSWORD).c_str(),
host, port, strlen(data), data);
@ -88,7 +92,7 @@ bool idbSend(const char * topic, const char * payload) {
bool idbSend(const char * topic, unsigned char id, const char * payload) {
char measurement[64];
snprintf(measurement, sizeof(measurement), "%s,id=%d", topic, id);
return idbSend(topic, payload);
return idbSend(measurement, payload);
}
bool idbEnabled() {
@ -100,6 +104,7 @@ void idbSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_idbWebSocketOnSend);
wsOnAfterParseRegister(_idbConfigure);
wsOnReceiveRegister(_idbWebSocketOnReceive);
#endif
}


+ 12
- 1
code/espurna/ir.ino View File

@ -14,6 +14,7 @@ Copyright (C) 2017-2018 by François Déchery
IRrecv * _ir_recv;
decode_results _ir_results;
unsigned long _ir_last_toggle = 0;
// -----------------------------------------------------------------------------
// PRIVATE
@ -43,11 +44,21 @@ void _irProcessCode(unsigned long code) {
relayStatus(0, button_value);
}
if (button_mode == IR_BUTTON_MODE_TOGGLE) {
if (millis() - _ir_last_toggle > 250){
relayToggle(button_value);
_ir_last_toggle = millis();
} else {
DEBUG_MSG_P(PSTR("[IR] Ignoring repeated code\n"));
}
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (button_mode == IR_BUTTON_MODE_BRIGHTER) {
lightBrightnessStep(button_value ? 1 : -1);
delay(150); //debounce
nice_delay(150); //debounce
}
if (button_mode == IR_BUTTON_MODE_RGB) {


+ 47
- 16
code/espurna/led.ino View File

@ -58,11 +58,17 @@ void _ledBlink(unsigned char id, unsigned long delayOff, unsigned long delayOn)
}
#if WEB_SUPPORT
bool _ledWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "led", 3) == 0);
}
void _ledWebSocketOnSend(JsonObject& root) {
if (_ledCount() == 0) return;
root["ledVisible"] = 1;
root["ledMode0"] = _ledMode(0);
}
#endif
#if MQTT_SUPPORT
@ -163,6 +169,7 @@ void ledSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_ledWebSocketOnSend);
wsOnAfterParseRegister(_ledConfigure);
wsOnReceiveRegister(_ledWebSocketOnReceive);
#endif
DEBUG_MSG_P(PSTR("[LED] Number of leds: %d\n"), _leds.size());
@ -175,15 +182,39 @@ void ledSetup() {
void ledLoop() {
uint8_t wifi_state = wifiState();
for (unsigned char i=0; i<_leds.size(); i++) {
if (_ledMode(i) == LED_MODE_WIFI) {
if (wifiConnected()) {
if (WiFi.getMode() == WIFI_AP) {
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
} else if (wifi_state & WIFI_STATE_STA) {
_ledBlink(i, 4900, 100);
} else if (wifi_state & WIFI_STATE_AP) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 500, 500);
}
}
if (_ledMode(i) == LED_MODE_FINDME_WIFI) {
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(_leds[i].relay-1)) {
_ledBlink(i, 4900, 100);
} else {
_ledBlink(i, 100, 4900);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(_leds[i].relay-1)) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 4900, 100);
_ledBlink(i, 100, 900);
}
} else {
_ledBlink(i, 500, 500);
@ -191,21 +222,21 @@ void ledLoop() {
}
if (_ledMode(i) == LED_MODE_MIXED) {
if (_ledMode(i) == LED_MODE_RELAY_WIFI) {
if (wifiConnected()) {
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(_leds[i].relay-1)) {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 4900, 100);
}
_ledBlink(i, 100, 4900);
} else {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 100, 900);
} else {
_ledBlink(i, 100, 4900);
}
_ledBlink(i, 4900, 100);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(_leds[i].relay-1)) {
_ledBlink(i, 100, 900);
} else {
_ledBlink(i, 900, 100);
}
} else {
_ledBlink(i, 500, 500);
@ -235,7 +266,7 @@ void ledLoop() {
_ledStatus(i, status);
}
if (_ledMode(i) == LED_MODE_STATUS) {
if (_ledMode(i) == LED_MODE_RELAY) {
bool status = false;
for (unsigned char k=0; k<relayCount(); k++) {
if (relayStatus(k)) {


+ 47
- 27
code/espurna/libs/StreamInjector.h View File

@ -1,6 +1,23 @@
// -----------------------------------------------------------------------------
// Stream Injector
// -----------------------------------------------------------------------------
/*
StreamInjector
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
@ -12,7 +29,7 @@ class StreamInjector : public Stream {
typedef std::function<void(uint8_t ch)> writeCallback;
StreamInjector(Stream& serial, size_t buflen = 128) : _stream(serial), _buffer_size(buflen) {
StreamInjector(size_t buflen = 128) : _buffer_size(buflen) {
_buffer = new char[buflen];
}
@ -20,28 +37,43 @@ class StreamInjector : public Stream {
delete[] _buffer;
}
// ---------------------------------------------------------------------
virtual uint8_t inject(char ch) {
_buffer[_buffer_write] = ch;
_buffer_write = (_buffer_write + 1) % _buffer_size;
return 1;
}
virtual uint8_t inject(char *data, size_t len) {
for (uint8_t i=0; i<len; i++) {
inject(data[i]);
}
return len;
}
virtual void callback(writeCallback c) {
_callback = c;
}
// ---------------------------------------------------------------------
virtual size_t write(uint8_t ch) {
if (_callback) _callback(ch);
return _stream.write(ch);
return 1;
}
virtual int read() {
int ch = _stream.read();
if (ch == -1) {
if (_buffer_read != _buffer_write) {
ch = _buffer[_buffer_read];
_buffer_read = (_buffer_read + 1) % _buffer_size;
}
int ch = -1;
if (_buffer_read != _buffer_write) {
ch = _buffer[_buffer_read];
_buffer_read = (_buffer_read + 1) % _buffer_size;
}
return ch;
}
virtual int available() {
unsigned int bytes = _stream.available();
unsigned int bytes = 0;
if (_buffer_read > _buffer_write) {
bytes += (_buffer_write - _buffer_read + _buffer_size);
} else if (_buffer_read < _buffer_write) {
@ -51,35 +83,23 @@ class StreamInjector : public Stream {
}
virtual int peek() {
int ch = _stream.peek();
if (ch == -1) {
if (_buffer_read != _buffer_write) {
ch = _buffer[_buffer_read];
}
int ch = -1;
if (_buffer_read != _buffer_write) {
ch = _buffer[_buffer_read];
}
return ch;
}
virtual void flush() {
_stream.flush();
_buffer_read = _buffer_write;
}
virtual void inject(char *data, size_t len) {
for (int i=0; i<len; i++) {
_buffer[_buffer_write] = data[i];
_buffer_write = (_buffer_write + 1) % _buffer_size;
}
}
private:
Stream& _stream;
char * _buffer;
unsigned char _buffer_size;
unsigned char _buffer_write = 0;
unsigned char _buffer_read = 0;
writeCallback _callback = NULL;
};

+ 1
- 1
code/espurna/libs/WebSocketIncommingBuffer.h View File

@ -80,8 +80,8 @@ class WebSocketIncommingBuffer {
private:
AwsMessageHandler _cb;
bool _cb_on_fragments;
bool _terminate_string;
bool _cb_on_fragments;
std::vector<uint8_t> *_buffer;
};

+ 636
- 0
code/espurna/libs/fs_math.c View File

@ -0,0 +1,636 @@
/**
* This code is available at
* http://www.mindspring.com/~pfilandr/C/fs_math/
* and it is believed to be public domain.
*/
/* BEGIN fs_math.c */
#include "fs_math.h"
#include <float.h>
/*
** pi == (atan(1.0 / 3) + atan(1.0 / 2)) * 4
*/
static double fs_pi(void);
static long double fs_pil(void);
double fs_sqrt(double x)
{
int n;
double a, b;
if (x > 0 && DBL_MAX >= x) {
for (n = 0; x > 2; x /= 4) {
++n;
}
while (0.5 > x) {
--n;
x *= 4;
}
a = x;
b = (1 + x) / 2;
do {
x = b;
b = (a / x + x) / 2;
} while (x > b);
while (n > 0) {
x *= 2;
--n;
}
while (0 > n) {
x /= 2;
++n;
}
} else {
if (x != 0) {
x = DBL_MAX;
}
}
return x;
}
double fs_log(double x)
{
int n;
double a, b, c, epsilon;
static double A, B, C;
static int initialized;
if (x > 0 && DBL_MAX >= x) {
if (!initialized) {
initialized = 1;
A = fs_sqrt(2);
B = A / 2;
C = fs_log(A);
}
for (n = 0; x > A; x /= 2) {
++n;
}
while (B > x) {
--n;
x *= 2;
}
a = (x - 1) / (x + 1);
x = C * n + a;
c = a * a;
n = 1;
epsilon = DBL_EPSILON * x;
if (0 > a) {
if (epsilon > 0) {
epsilon = -epsilon;
}
do {
n += 2;
a *= c;
b = a / n;
x += b;
} while (epsilon > b);
} else {
if (0 > epsilon) {
epsilon = -epsilon;
}
do {
n += 2;
a *= c;
b = a / n;
x += b;
} while (b > epsilon);
}
x *= 2;
} else {
x = -DBL_MAX;
}
return x;
}
double fs_log10(double x)
{
static double log_10;
static int initialized;
if (!initialized) {
initialized = 1;
log_10 = fs_log(10);
}
return x > 0 && DBL_MAX >= x ? fs_log(x) / log_10 : fs_log(x);
}
double fs_exp(double x)
{
unsigned n, square;
double b, e;
static double x_max, x_min, epsilon;
static int initialized;
if (!initialized) {
initialized = 1;
x_max = fs_log(DBL_MAX);
x_min = fs_log(DBL_MIN);
epsilon = DBL_EPSILON / 4;
}
if (x_max >= x && x >= x_min) {
for (square = 0; x > 1; x /= 2) {
++square;
}
while (-1 > x) {
++square;
x /= 2;
}
e = b = n = 1;
do {
b /= n++;
b *= x;
e += b;
b /= n++;
b *= x;
e += b;
} while (b > epsilon);
while (square-- != 0) {
e *= e;
}
} else {
e = x > 0 ? DBL_MAX : 0;
}
return e;
}
double fs_modf(double value, double *iptr)
{
double a, b;
const double c = value;
if (0 > c) {
value = -value;
}
if (DBL_MAX >= value) {
for (*iptr = 0; value >= 1; value -= b) {
a = value / 2;
b = 1;
while (a >= b) {
b *= 2;
}
*iptr += b;
}
} else {
*iptr = value;
value = 0;
}
if (0 > c) {
*iptr = -*iptr;
value = -value;
}
return value;
}
double fs_fmod(double x, double y)
{
double a, b;
const double c = x;
if (0 > c) {
x = -x;
}
if (0 > y) {
y = -y;
}
if (y != 0 && DBL_MAX >= y && DBL_MAX >= x) {
while (x >= y) {
a = x / 2;
b = y;
while (a >= b) {
b *= 2;
}
x -= b;
}
} else {
x = 0;
}
return 0 > c ? -x : x;
}
double fs_pow(double x, double y)
{
double p = 0;
if (0 > x && fs_fmod(y, 1) == 0) {
if (fs_fmod(y, 2) == 0) {
p = fs_exp(fs_log(-x) * y);
} else {
p = -fs_exp(fs_log(-x) * y);
}
} else {
if (x != 0 || 0 >= y) {
p = fs_exp(fs_log( x) * y);
}
}
return p;
}
static double fs_pi(void)
{
unsigned n;
double a, b, epsilon;
static double p;
static int initialized;
if (!initialized) {
initialized = 1;
epsilon = DBL_EPSILON / 4;
n = 1;
a = 3;
do {
a /= 9;
b = a / n;
n += 2;
a /= 9;
b -= a / n;
n += 2;
p += b;
} while (b > epsilon);
epsilon = DBL_EPSILON / 2;
n = 1;
a = 2;
do {
a /= 4;
b = a / n;
n += 2;
a /= 4;
b -= a / n;
n += 2;
p += b;
} while (b > epsilon);
p *= 4;
}
return p;
}
double fs_cos(double x)
{
unsigned n;
int negative, sine;
double a, b, c;
static double pi, two_pi, half_pi, third_pi, epsilon;
static int initialized;
if (0 > x) {
x = -x;
}
if (DBL_MAX >= x) {
if (!initialized) {
initialized = 1;
pi = fs_pi();
two_pi = 2 * pi;
half_pi = pi / 2;
third_pi = pi / 3;
epsilon = DBL_EPSILON / 2;
}
if (x > two_pi) {
x = fs_fmod(x, two_pi);
}
if (x > pi) {
x = two_pi - x;
}
if (x > half_pi) {
x = pi - x;
negative = 1;
} else {
negative = 0;
}
if (x > third_pi) {
x = half_pi - x;
sine = 1;
} else {
sine = 0;
}
c = x * x;
x = n = 0;
a = 1;
do {
b = a;
a *= c;
a /= ++n;
a /= ++n;
b -= a;
a *= c;
a /= ++n;
a /= ++n;
x += b;
} while (b > epsilon);
if (sine) {
x = fs_sqrt((1 - x) * (1 + x));
}
if (negative) {
x = -x;
}
} else {
x = -DBL_MAX;
}
return x;
}
double fs_log2(double x)
{
static double log_2;
static int initialized;
if (!initialized) {
initialized = 1;
log_2 = fs_log(2);
}
return x > 0 && DBL_MAX >= x ? fs_log(x) / log_2 : fs_log(x);
}
double fs_exp2(double x)
{
static double log_2;
static int initialized;
if (!initialized) {
initialized = 1;
log_2 = fs_log(2);
}
return fs_exp(x * log_2);
}
long double fs_powl(long double x, long double y)
{
long double p;
if (0 > x && fs_fmodl(y, 1) == 0) {
if (fs_fmodl(y, 2) == 0) {
p = fs_expl(fs_logl(-x) * y);
} else {
p = -fs_expl(fs_logl(-x) * y);
}
} else {
if (x != 0 || 0 >= y) {
p = fs_expl(fs_logl( x) * y);
} else {
p = 0;
}
}
return p;
}
long double fs_sqrtl(long double x)
{
long int n;
long double a, b;
if (x > 0 && LDBL_MAX >= x) {
for (n = 0; x > 2; x /= 4) {
++n;
}
while (0.5 > x) {
--n;
x *= 4;
}
a = x;
b = (1 + x) / 2;
do {
x = b;
b = (a / x + x) / 2;
} while (x > b);
while (n > 0) {
x *= 2;
--n;
}
while (0 > n) {
x /= 2;
++n;
}
} else {
if (x != 0) {
x = LDBL_MAX;
}
}
return x;
}
long double fs_logl(long double x)
{
long int n;
long double a, b, c, epsilon;
static long double A, B, C;
static int initialized;
if (x > 0 && LDBL_MAX >= x) {
if (!initialized) {
initialized = 1;
B = 1.5;
do {
A = B;
B = 1 / A + A / 2;
} while (A > B);
B /= 2;
C = fs_logl(A);
}
for (n = 0; x > A; x /= 2) {
++n;
}
while (B > x) {
--n;
x *= 2;
}
a = (x - 1) / (x + 1);
x = C * n + a;
c = a * a;
n = 1;
epsilon = LDBL_EPSILON * x;
if (0 > a) {
if (epsilon > 0) {
epsilon = -epsilon;
}
do {
n += 2;
a *= c;
b = a / n;
x += b;
} while (epsilon > b);
} else {
if (0 > epsilon) {
epsilon = -epsilon;
}
do {
n += 2;
a *= c;
b = a / n;
x += b;
} while (b > epsilon);
}
x *= 2;
} else {
x = -LDBL_MAX;
}
return x;
}
long double fs_expl(long double x)
{
long unsigned n, square;
long double b, e;
static long double x_max, x_min, epsilon;
static int initialized;
if (!initialized) {
initialized = 1;
x_max = fs_logl(LDBL_MAX);
x_min = fs_logl(LDBL_MIN);
epsilon = LDBL_EPSILON / 4;
}
if (x_max >= x && x >= x_min) {
for (square = 0; x > 1; x /= 2) {
++square;
}
while (-1 > x) {
++square;
x /= 2;
}
e = b = n = 1;
do {
b /= n++;
b *= x;
e += b;
b /= n++;
b *= x;
e += b;
} while (b > epsilon);
while (square-- != 0) {
e *= e;
}
} else {
e = x > 0 ? LDBL_MAX : 0;
}
return e;
}
static long double fs_pil(void)
{
long unsigned n;
long double a, b, epsilon;
static long double p;
static int initialized;
if (!initialized) {
initialized = 1;
epsilon = LDBL_EPSILON / 4;
n = 1;
a = 3;
do {
a /= 9;
b = a / n;
n += 2;
a /= 9;
b -= a / n;
n += 2;
p += b;
} while (b > epsilon);
epsilon = LDBL_EPSILON / 2;
n = 1;
a = 2;
do {
a /= 4;
b = a / n;
n += 2;
a /= 4;
b -= a / n;
n += 2;
p += b;
} while (b > epsilon);
p *= 4;
}
return p;
}
long double fs_cosl(long double x)
{
long unsigned n;
int negative, sine;
long double a, b, c;
static long double pi, two_pi, half_pi, third_pi, epsilon;
static int initialized;
if (0 > x) {
x = -x;
}
if (LDBL_MAX >= x) {
if (!initialized) {
initialized = 1;
pi = fs_pil();
two_pi = 2 * pi;
half_pi = pi / 2;
third_pi = pi / 3;
epsilon = LDBL_EPSILON / 2;
}
if (x > two_pi) {
x = fs_fmodl(x, two_pi);
}
if (x > pi) {
x = two_pi - x;
}
if (x > half_pi) {
x = pi - x;
negative = 1;
} else {
negative = 0;
}
if (x > third_pi) {
x = half_pi - x;
sine = 1;
} else {
sine = 0;
}
c = x * x;
x = n = 0;
a = 1;
do {
b = a;
a *= c;
a /= ++n;
a /= ++n;
b -= a;
a *= c;
a /= ++n;
a /= ++n;
x += b;
} while (b > epsilon);
if (sine) {
x = fs_sqrtl((1 - x) * (1 + x));
}
if (negative) {
x = -x;
}
} else {
x = -LDBL_MAX;
}
return x;
}
long double fs_fmodl(long double x, long double y)
{
long double a, b;
const long double c = x;
if (0 > c) {
x = -x;
}
if (0 > y) {
y = -y;
}
if (y != 0 && LDBL_MAX >= y && LDBL_MAX >= x) {
while (x >= y) {
a = x / 2;
b = y;
while (a >= b) {
b *= 2;
}
x -= b;
}
} else {
x = 0;
}
return 0 > c ? -x : x;
}
/* END fs_math.c */

+ 116
- 0
code/espurna/libs/fs_math.h View File

@ -0,0 +1,116 @@
/**
* This code is available at
* http://www.mindspring.com/~pfilandr/C/fs_math/
* and it is believed to be public domain.
*/
/* BEGIN fs_math.h */
/*
** Portable freestanding code.
*/
#ifndef H_FS_MATH_H
#define H_FS_MATH_H
double fs_sqrt(double x);
double fs_log(double x);
double fs_log10(double x);
/*
** exp(x) = 1 + x + x^2/2! + x^3/3! + ...
*/
double fs_exp(double x);
double fs_modf(double value, double *iptr);
double fs_fmod(double x, double y);
double fs_pow(double x, double y);
double fs_cos(double x);
/*
** C99
*/
double fs_log2(double x);
double fs_exp2(double x);
long double fs_powl(long double x, long double y);
long double fs_sqrtl(long double x);
long double fs_logl(long double x);
long double fs_expl(long double x);
long double fs_cosl(long double x);
long double fs_fmodl(long double x, long double y);
#endif
/* END fs_math.h */
#if 0
/*
> > Anybody know where I can get some source code for a
> > reasonably fast double
> > precision square root algorithm in C.
> > I'm looking for one that is not IEEE
> > compliant as I am running on a Z/OS mainframe.
> >
> > I would love to use the standard library but
> > unfortunatly I'm using a
> > stripped down version of C that looses the the runtime library
> > (we have to write our own).
>
> long double Ssqrt(long double x)
> {
> long double a, b;
> size_t c;
size_t is a bug here.
c needs to be a signed type:
long c;
> if (x > 0) {
> c = 0;
> while (x > 4) {
> x /= 4;
> ++c;
> }
> while (1.0 / 4 > x) {
> x *= 4;
> --c;
> }
> a = x;
> b = ((4 > a) + a) / 2;
Not a bug, but should be:
b = (1 + a) / 2;
> do {
> x = b;
> b = (a / x + x) / 2;
> } while (x > b);
> if (c > 0) {
The above line is why c needs to be a signed type,
otherwise the decremented values of c, are greater than zero,
and the function won't work if the initial value of x
is less than 0.25
> while (c--) {
> x *= 2;
> }
> } else {
> while (c++) {
> x /= 2;
> }
> }
> }
> return x;
> }
>
> >
> > That algorithm was actually 4 times slower
> > then the one below, and more
> > code. It was accurate though.
> >
>
> Sorry Pete, I wasn't looking very carefully.
> When I converted your function
> to double precision it's was much quicker, the best I've seen yet.
*/
#endif

code/espurna/pwm.c → code/espurna/libs/pwm.c View File


+ 299
- 279
code/espurna/light.ino View File

@ -12,6 +12,10 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h>
#include <vector>
extern "C" {
#include "libs/fs_math.h"
}
#if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER
#define PWM_CHANNEL_NUM_MAX LIGHT_CHANNELS
extern "C" {
@ -28,7 +32,8 @@ typedef struct {
unsigned char pin;
bool reverse;
bool state;
unsigned char value; // target or nominal value
unsigned char inputValue; // value that has been inputted
unsigned char value; // normalized value including brightness
unsigned char shadow; // represented value
double current; // transition value
} channel_t;
@ -36,11 +41,14 @@ std::vector<channel_t> _light_channel;
bool _light_state = false;
bool _light_use_transitions = false;
unsigned int _light_transition_time = LIGHT_TRANSITION_TIME;
bool _light_has_color = false;
bool _light_use_white = false;
bool _light_use_cct = false;
bool _light_use_gamma = false;
unsigned long _light_steps_left = 1;
unsigned int _light_brightness = LIGHT_MAX_BRIGHTNESS;
unsigned char _light_brightness = LIGHT_MAX_BRIGHTNESS;
unsigned int _light_mireds = round((LIGHT_COLDWHITE_MIRED+LIGHT_WARMWHITE_MIRED)/2);
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX
#include <my92xx.h>
@ -73,99 +81,138 @@ const unsigned char _light_gamma_table[] = {
// UTILS
// -----------------------------------------------------------------------------
void _fromLong(unsigned long value, bool brightness) {
void _setRGBInputValue(unsigned char red, unsigned char green, unsigned char blue) {
_light_channel[0].inputValue = constrain(red, 0, LIGHT_MAX_VALUE);
_light_channel[1].inputValue = constrain(green, 0, LIGHT_MAX_VALUE);;
_light_channel[2].inputValue = constrain(blue, 0, LIGHT_MAX_VALUE);;
}
if (brightness) {
_light_channel[0].value = (value >> 24) & 0xFF;
_light_channel[1].value = (value >> 16) & 0xFF;
_light_channel[2].value = (value >> 8) & 0xFF;
_light_brightness = (value & 0xFF) * LIGHT_MAX_BRIGHTNESS / 255;
} else {
_light_channel[0].value = (value >> 16) & 0xFF;
_light_channel[1].value = (value >> 8) & 0xFF;
_light_channel[2].value = (value) & 0xFF;
}
void _generateBrightness() {
}
double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS;
void _fromRGB(const char * rgb) {
// Convert RGB to RGBW(W)
if (_light_has_color && _light_use_white) {
char * p = (char *) rgb;
if (strlen(p) == 0) return;
// Substract the common part from RGB channels and add it to white channel. So [250,150,50] -> [200,100,0,50]
unsigned char white = std::min(_light_channel[0].inputValue, std::min(_light_channel[1].inputValue, _light_channel[2].inputValue));
for (unsigned int i=0; i < 3; i++) {
_light_channel[i].value = _light_channel[i].inputValue - white;
}
// if color begins with a # then assume HEX RGB
if (p[0] == '#') {
// Split the White Value across 2 White LED Strips.
if (_light_use_cct) {
if (_light_has_color) {
// This change the range from 153-500 to 0-347 so we get a value between 0 and 1 in the end.
double miredFactor = ((double) _light_mireds - (double) LIGHT_COLDWHITE_MIRED)/((double) LIGHT_WARMWHITE_MIRED - (double) LIGHT_COLDWHITE_MIRED);
++p;
unsigned long value = strtoul(p, NULL, 16);
// set cold white
_light_channel[3].inputValue = 0;
_light_channel[3].value = round(((double) 1.0 - miredFactor) * white);
// RGBA values are interpreted like RGB + brightness
_fromLong(value, strlen(p) > 7);
// set warm white
_light_channel[4].inputValue = 0;
_light_channel[4].value = round(miredFactor * white);
} else {
_light_channel[3].inputValue = 0;
_light_channel[3].value = white;
}
// Scale up to equal input values. So [250,150,50] -> [200,100,0,50] -> [250, 125, 0, 63]
unsigned char max_in = std::max(_light_channel[0].inputValue, std::max(_light_channel[1].inputValue, _light_channel[2].inputValue));
unsigned char max_out = std::max(std::max(_light_channel[0].value, _light_channel[1].value), std::max(_light_channel[2].value, _light_channel[3].value));
unsigned char channelSize = _light_use_cct ? 5 : 4;
if (_light_use_cct) {
max_out = std::max(max_out, _light_channel[4].value);
}
// it's a temperature in mireds
} else if (p[0] == 'M') {
double factor = (max_out > 0) ? (double) (max_in / max_out) : 0;
for (unsigned char i=0; i < channelSize; i++) {
_light_channel[i].value = round((double) _light_channel[i].value * factor * brightness);
}
if (_light_has_color) {
unsigned long mireds = atol(p + 1);
_fromMireds(mireds);
// Scale white channel to match brightness
for (unsigned char i=3; i < channelSize; i++) {
_light_channel[i].value = constrain(_light_channel[i].value * LIGHT_WHITE_FACTOR, 0, LIGHT_MAX_BRIGHTNESS);
}
// it's a temperature in kelvin
} else if (p[0] == 'K') {
// For the rest of channels, don't apply brightness, it is already in the inputValue
// i should be 4 when RGBW and 5 when RGBWW
for (unsigned char i=channelSize; i < _light_channel.size(); i++) {
_light_channel[i].value = _light_channel[i].inputValue;
}
if (_light_has_color) {
unsigned long kelvin = atol(p + 1);
_fromKelvin(kelvin);
} else {
// Don't apply brightness, it is already in the target:
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;
}
}
// otherwise assume decimal values separated by commas
}
}
// -----------------------------------------------------------------------------
// Input Values
// -----------------------------------------------------------------------------
void _fromLong(unsigned long value, bool brightness) {
if (brightness) {
_setRGBInputValue((value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF);
_light_brightness = (value & 0xFF) * LIGHT_MAX_BRIGHTNESS / 255;
} else {
_setRGBInputValue((value >> 16) & 0xFF, (value >> 8) & 0xFF, (value) & 0xFF);
}
}
void _fromRGB(const char * rgb) {
char * p = (char *) rgb;
if (strlen(p) == 0) return;
switch (p[0]) {
case '#': // HEX Value
if (_light_has_color) {
++p;
unsigned long value = strtoul(p, NULL, 16);
// RGBA values are interpreted like RGB + brightness
_fromLong(value, strlen(p) > 7);
}
break;
case 'M': // Mired Value
_fromMireds(atol(p + 1));
break;
case 'K': // Kelvin Value
_fromKelvin(atol(p + 1));
break;
default: // assume decimal values separated by commas
char * tok;
unsigned char count = 0;
unsigned char channels = _light_channel.size();
tok = strtok(p, ",");
while (tok != NULL) {
_light_channel[count].value = atoi(tok);
_light_channel[count].inputValue = atoi(tok);
if (++count == channels) break;
tok = strtok(NULL, ",");
}
// RGB but less than 3 values received
// RGB but less than 3 values received, assume it is 0
if (_light_has_color && (count < 3)) {
_light_channel[1].value = _light_channel[0].value;
_light_channel[2].value = _light_channel[0].value;
// check channel 1 and 2:
for (int i = 1; i <= 2; i++) {
if (count < (i+1)) {
_light_channel[i].inputValue = 0;
}
}
}
break;
}
}
void _toRGB(char * rgb, size_t len, bool applyBrightness) {
if (!_light_has_color) return;
float b = applyBrightness ? (float) _light_brightness / LIGHT_MAX_BRIGHTNESS : 1;
unsigned long value = 0;
value += _light_channel[0].value * b;
value <<= 8;
value += _light_channel[1].value * b;
value <<= 8;
value += _light_channel[2].value * b;
snprintf_P(rgb, len, PSTR("#%06X"), value);
}
void _toRGB(char * rgb, size_t len) {
_toRGB(rgb, len, false);
}
// HSV string is expected to be "H,S,V", where:
@ -192,86 +239,116 @@ void _fromHSV(const char * hsv) {
// HSV to RGB transformation -----------------------------------------------
//INPUT: [0,100,57]
//IS: [145,0,0]
//SHOULD: [255,0,0]
double h = (value[0] == 360) ? 0 : (double) value[0] / 60.0;
double f = (h - floor(h));
double s = (double) value[1] / 100.0;
unsigned char v = round((double) value[2] * 255.0 / 100.0);
unsigned char p = round(v * (1.0 - s));
unsigned char q = round(v * (1.0 - s * f));
unsigned char t = round(v * (1.0 - s * (1.0 - f)));
_light_brightness = round((double) value[2] * 2.55); // (255/100)
unsigned char p = round(255 * (1.0 - s));
unsigned char q = round(255 * (1.0 - s * f));
unsigned char t = round(255 * (1.0 - s * (1.0 - f)));
switch (int(h)) {
case 0:
_light_channel[0].value = v;
_light_channel[1].value = t;
_light_channel[2].value = p;
_setRGBInputValue(255, t, p);
break;
case 1:
_light_channel[0].value = q;
_light_channel[1].value = v;
_light_channel[2].value = p;
_setRGBInputValue(q, 255, p);
break;
case 2:
_light_channel[0].value = p;
_light_channel[1].value = v;
_light_channel[2].value = t;
_setRGBInputValue(p, 255, t);
break;
case 3:
_light_channel[0].value = p;
_light_channel[1].value = q;
_light_channel[2].value = v;
_setRGBInputValue(p, q, 255);
break;
case 4:
_light_channel[0].value = t;
_light_channel[1].value = p;
_light_channel[2].value = v;
_setRGBInputValue(t, p, 255);
break;
case 5:
_light_channel[0].value = v;
_light_channel[1].value = p;
_light_channel[2].value = q;
_setRGBInputValue(255, p, q);
break;
default:
_light_channel[0].value = 0;
_light_channel[1].value = 0;
_light_channel[2].value = 0;
_setRGBInputValue(0, 0, 0);
break;
}
}
// Thanks to Sacha Telgenhof for sharing this code in his AiLight library
// https://github.com/stelgenhof/AiLight
void _fromKelvin(unsigned long kelvin) {
if (!_light_has_color) return;
if (_light_use_cct) {
_setRGBInputValue(LIGHT_MAX_VALUE, LIGHT_MAX_VALUE, LIGHT_MAX_VALUE);
return;
}
_light_mireds = constrain(round(1000000UL / kelvin), LIGHT_MIN_MIREDS, LIGHT_MAX_MIREDS);
_light_brightness = LIGHT_MAX_BRIGHTNESS;
// Calculate colors
kelvin /= 100;
unsigned int red = (kelvin <= 66)
? LIGHT_MAX_VALUE
: 329.698727446 * fs_pow((double) (kelvin - 60), -0.1332047592);
unsigned int green = (kelvin <= 66)
? 99.4708025861 * fs_log(kelvin) - 161.1195681661
: 288.1221695283 * fs_pow((double) kelvin, -0.0755148492);
unsigned int blue = (kelvin >= 66)
? LIGHT_MAX_VALUE
: ((kelvin <= 19)
? 0
: 138.5177312231 * fs_log(kelvin - 10) - 305.0447927307);
_setRGBInputValue(red, green, blue);
}
void _toHSV(char * hsv, size_t len) {
// Color temperature is measured in mireds (kelvin = 1e6/mired)
void _fromMireds(unsigned long mireds) {
unsigned long kelvin = constrain(1000000UL / mireds, 1000, 40000);
_fromKelvin(kelvin);
}
if (!_light_has_color) return;
// -----------------------------------------------------------------------------
// Output Values
// -----------------------------------------------------------------------------
void _toRGB(char * rgb, size_t len) {
unsigned long value = 0;
value += _light_channel[0].inputValue;
value <<= 8;
value += _light_channel[1].inputValue;
value <<= 8;
value += _light_channel[2].inputValue;
double min, max;
snprintf_P(rgb, len, PSTR("#%06X"), value);
}
void _toHSV(char * hsv, size_t len) {
double h, s, v;
double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS;
double r = (double) _light_channel[0].value / 255.0;
double g = (double) _light_channel[1].value / 255.0;
double b = (double) _light_channel[2].value / 255.0;
double r = (double) (_light_channel[0].inputValue * brightness) / 255.0;
double g = (double) (_light_channel[1].inputValue * brightness) / 255.0;
double b = (double) (_light_channel[2].inputValue * brightness) / 255.0;
min = (r < g) ? r : g;
min = (min < b) ? min : b;
max = (r > g) ? r : g;
max = (max > b) ? max : b;
double min = std::min(r, std::min(g, b));
double max = std::max(r, std::max(g, b));
v = 100.0 * max;
if (v == 0) {
h = s = 0;
} else {
s = 100.0 * (max - min) / max;
if (s == 0) {
h = 0;
} else {
if (max == r) {
if (g >= b) {
h = 0.0 + 60.0 * (g - b) / (max - min);
@ -284,77 +361,33 @@ void _toHSV(char * hsv, size_t len) {
h = 240.0 + 60.0 * (r - g) / (max - min);
}
}
}
// String
snprintf_P(hsv, len, PSTR("%d,%d,%d"), round(h), round(s), round(v));
}
void _toLong(char * color, size_t len, bool applyBrightness) {
void _toLong(char * color, size_t len) {
if (!_light_has_color) return;
float b = applyBrightness ? (float) _light_brightness / LIGHT_MAX_BRIGHTNESS : 1;
snprintf_P(color, len, PSTR("%d,%d,%d"),
(int) (_light_channel[0].value * b),
(int) (_light_channel[1].value * b),
(int) (_light_channel[2].value * b)
(int) _light_channel[0].inputValue,
(int) _light_channel[1].inputValue,
(int) _light_channel[2].inputValue
);
}
void _toLong(char * color, size_t len) {
_toLong(color, len, false);
}
void _toCSV(char * buffer, size_t len, bool applyBrightness) {
char num[10];
float b = applyBrightness ? (float) _light_brightness / LIGHT_MAX_BRIGHTNESS : 1;
for (unsigned char i=0; i<_light_channel.size(); i++) {
itoa(_light_channel[i].value * b, num, 10);
itoa(_light_channel[i].inputValue * b, num, 10);
if (i>0) strncat(buffer, ",", len--);
strncat(buffer, num, len);
len = len - strlen(num);
}
}
// Thanks to Sacha Telgenhof for sharing this code in his AiLight library
// https://github.com/stelgenhof/AiLight
void _fromKelvin(unsigned long kelvin) {
// Check we have RGB channels
if (!_light_has_color) return;
// Calculate colors
unsigned int red = (kelvin <= 66)
? LIGHT_MAX_VALUE
: 329.698727446 * pow((kelvin - 60), -0.1332047592);
unsigned int green = (kelvin <= 66)
? 99.4708025861 * log(kelvin) - 161.1195681661
: 288.1221695283 * pow(kelvin, -0.0755148492);
unsigned int blue = (kelvin >= 66)
? LIGHT_MAX_VALUE
: ((kelvin <= 19)
? 0
: 138.5177312231 * log(kelvin - 10) - 305.0447927307);
// Save values
_light_channel[0].value = constrain(red, 0, LIGHT_MAX_VALUE);
_light_channel[1].value = constrain(green, 0, LIGHT_MAX_VALUE);
_light_channel[2].value = constrain(blue, 0, LIGHT_MAX_VALUE);
}
// Color temperature is measured in mireds (kelvin = 1e6/mired)
void _fromMireds(unsigned long mireds) {
if (mireds == 0) mireds = 1;
unsigned long kelvin = constrain(1000000UL / mireds, 1000, 40000) / 100;
_fromKelvin(kelvin);
}
// -----------------------------------------------------------------------------
// PROVIDER
// -----------------------------------------------------------------------------
@ -382,31 +415,18 @@ void _shadow() {
// Transitions
unsigned char target;
for (unsigned int i=0; i < _light_channel.size(); i++) {
if (_light_state && _light_channel[i].state) {
target = _light_channel[i].value;
if ((_light_brightness < LIGHT_MAX_BRIGHTNESS) && _light_has_color && (i < 3)) {
target *= ((float) _light_brightness / LIGHT_MAX_BRIGHTNESS);
}
} else {
target = 0;
}
target = _light_state && _light_channel[i].state ? _light_channel[i].value : 0;
if (_light_steps_left == 0) {
_light_channel[i].current = target;
} else {
double difference = (double) (target - _light_channel[i].current) / (_light_steps_left + 1);
_light_channel[i].current = _light_channel[i].current + difference;
}
_light_channel[i].shadow = _light_channel[i].current;
}
// Use white channel for same RGB
if (_light_use_white && _light_has_color) {
if (_light_channel[0].shadow == _light_channel[1].shadow && _light_channel[1].shadow == _light_channel[2].shadow ) {
_light_channel[3].shadow = _light_channel[0].shadow * ((float) _light_brightness / LIGHT_MAX_BRIGHTNESS);
_light_channel[2].shadow = 0;
_light_channel[1].shadow = 0;
_light_channel[0].shadow = 0;
}
}
}
@ -442,17 +462,19 @@ void _lightProviderUpdate() {
void _lightColorSave() {
for (unsigned int i=0; i < _light_channel.size(); i++) {
setSetting("ch", i, _light_channel[i].value);
setSetting("ch", i, _light_channel[i].inputValue);
}
setSetting("brightness", _light_brightness);
setSetting("mireds", _light_mireds);
saveSettings();
}
void _lightColorRestore() {
for (unsigned int i=0; i < _light_channel.size(); i++) {
_light_channel[i].value = getSetting("ch", i, i==0 ? 255 : 0).toInt();
_light_channel[i].inputValue = getSetting("ch", i, i==0 ? 255 : 0).toInt();
}
_light_brightness = getSetting("brightness", LIGHT_MAX_BRIGHTNESS).toInt();
_light_mireds = getSetting("mireds", _light_mireds).toInt();
lightUpdate(false, false);
}
@ -471,7 +493,6 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl
mqttSubscribe(MQTT_TOPIC_BRIGHTNESS);
mqttSubscribe(MQTT_TOPIC_MIRED);
mqttSubscribe(MQTT_TOPIC_KELVIN);
mqttSubscribe(MQTT_TOPIC_COLOR); // DEPRECATE
mqttSubscribe(MQTT_TOPIC_COLOR_RGB);
mqttSubscribe(MQTT_TOPIC_COLOR_HSV);
}
@ -513,7 +534,7 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl
}
// Color
if (t.equals(MQTT_TOPIC_COLOR) || t.equals(MQTT_TOPIC_COLOR_RGB)) { // DEPRECATE MQTT_TOPIC_COLOR
if (t.equals(MQTT_TOPIC_COLOR_RGB)) {
lightColor(payload, true);
lightUpdate(true, mqttForward());
return;
@ -548,19 +569,18 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl
}
void lightMQTT() {
char buffer[20];
if (_light_has_color) {
// Color
if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) {
_toRGB(buffer, sizeof(buffer), false);
_toRGB(buffer, sizeof(buffer));
} else {
_toLong(buffer, sizeof(buffer), false);
_toLong(buffer, sizeof(buffer));
}
mqttSend(MQTT_TOPIC_COLOR, buffer); // DEPRECATE
mqttSend(MQTT_TOPIC_COLOR_RGB, buffer);
_toHSV(buffer, sizeof(buffer));
mqttSend(MQTT_TOPIC_COLOR_HSV, buffer);
@ -568,11 +588,14 @@ void lightMQTT() {
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
for (unsigned int i=0; i < _light_channel.size(); i++) {
itoa(_light_channel[i].value, buffer, 10);
itoa(_light_channel[i].inputValue, buffer, 10);
mqttSend(MQTT_TOPIC_CHANNEL, i, buffer);
}
@ -598,7 +621,7 @@ void lightMQTTGroup() {
void lightBroker() {
char buffer[10];
for (unsigned int i=0; i < _light_channel.size(); i++) {
itoa(_light_channel[i].value, buffer, 10);
itoa(_light_channel[i].inputValue, buffer, 10);
brokerPublish(MQTT_TOPIC_CHANNEL, i, buffer);
}
}
@ -617,14 +640,12 @@ bool lightHasColor() {
return _light_has_color;
}
unsigned char lightWhiteChannels() {
return _light_channel.size() % 3;
}
void lightUpdate(bool save, bool forward, bool group_forward) {
_generateBrightness();
// Configure color transition
_light_steps_left = _light_use_transitions ? LIGHT_TRANSITION_STEPS : 1;
_light_steps_left = _light_use_transitions ? _light_transition_time / LIGHT_TRANSITION_STEP : 1;
_light_transition_ticker.attach_ms(LIGHT_TRANSITION_STEP, _lightProviderUpdate);
// Report channels to local broker
@ -696,7 +717,7 @@ void lightColor(unsigned long color) {
String lightColor(bool rgb) {
char str[12];
if (rgb) {
_toRGB(str, sizeof(str), false);
_toRGB(str, sizeof(str));
} else {
_toHSV(str, sizeof(str));
}
@ -709,14 +730,14 @@ String lightColor() {
unsigned int lightChannel(unsigned char id) {
if (id <= _light_channel.size()) {
return _light_channel[id].value;
return _light_channel[id].inputValue;
}
return 0;
}
void lightChannel(unsigned char id, unsigned int value) {
if (id <= _light_channel.size()) {
_light_channel[id].value = constrain(value, 0, LIGHT_MAX_VALUE);
_light_channel[id].inputValue = constrain(value, 0, LIGHT_MAX_VALUE);
}
}
@ -738,6 +759,12 @@ void lightBrightnessStep(int steps) {
#if WEB_SUPPORT
bool _lightWebSocketOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, "light", 5) == 0) return true;
if (strncmp(key, "use", 3) == 0) return true;
return false;
}
void _lightWebSocketOnSend(JsonObject& root) {
root["colorVisible"] = 1;
root["mqttGroupColor"] = getSetting("mqttGroupColor");
@ -745,10 +772,15 @@ void _lightWebSocketOnSend(JsonObject& root) {
root["useWhite"] = _light_use_white;
root["useGamma"] = _light_use_gamma;
root["useTransitions"] = _light_use_transitions;
root["lightTime"] = _light_transition_time;
root["useCSS"] = getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1;
bool useRGB = getSetting("useRGB", LIGHT_USE_RGB).toInt() == 1;
root["useRGB"] = useRGB;
if (_light_has_color) {
if (_light_use_cct) {
root["useCCT"] = _light_use_cct;
root["mireds"] = _light_mireds;
}
if (useRGB) {
root["rgb"] = lightColor(true);
root["brightness"] = lightBrightness();
@ -757,15 +789,13 @@ void _lightWebSocketOnSend(JsonObject& root) {
}
}
JsonArray& channels = root.createNestedArray("channels");
for (unsigned char id=0; id < lightChannels(); id++) {
for (unsigned char id=0; id < _light_channel.size(); id++) {
channels.add(lightChannel(id));
}
}
void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (_light_has_color) {
if (strcmp(action, "color") == 0) {
if (data.containsKey("rgb")) {
lightColor(data["rgb"], true);
@ -780,7 +810,12 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
lightUpdate(true, true);
}
}
if (_light_use_cct) {
if (strcmp(action, "mireds") == 0) {
_fromMireds(data["mireds"]);
lightUpdate(true, true);
}
}
}
if (strcmp(action, "channel") == 0) {
@ -789,99 +824,76 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
lightUpdate(true, true);
}
}
}
void _lightAPISetup() {
// API entry points (protected with apikey)
if (_light_has_color) {
// DEPRECATE
apiRegister(MQTT_TOPIC_COLOR,
[](char * buffer, size_t len) {
if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) {
_toRGB(buffer, len, false);
} else {
_toLong(buffer, len, false);
}
},
[](const char * payload) {
lightColor(payload, true);
lightUpdate(true, true);
}
);
apiRegister(MQTT_TOPIC_COLOR_RGB,
[](char * buffer, size_t len) {
if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) {
_toRGB(buffer, len, false);
} else {
_toLong(buffer, len, false);
}
},
[](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_BRIGHTNESS,
[](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), _light_brightness);
},
[](const char * payload) {
lightBrightness(atoi(payload));
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);
// 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);
}
);
}
for (unsigned int id=0; id<lightChannels(); id++) {
char key[15];
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_CHANNEL, id);
},
[](const char * payload) {
lightColor(payload, true);
lightUpdate(true, true);
}
);
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_COLOR_HSV,
[](char * buffer, size_t len) {
_toHSV(buffer, len);
},
[](const char * payload) {
lightColor(payload, false);
lightUpdate(true, true);
}
);
}
apiRegister(MQTT_TOPIC_BRIGHTNESS,
[](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), _light_brightness);
},
[](const char * payload) {
lightBrightness(atoi(payload));
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);
}
);
}
}
#endif // WEB_SUPPORT
@ -985,8 +997,15 @@ void _lightConfigure() {
setSetting("useWhite", _light_use_white);
}
_light_use_cct = getSetting("useCCT", LIGHT_USE_CCT).toInt() == 1;
if (_light_use_cct && ((_light_channel.size() < 5) || !_light_use_white)) {
_light_use_cct = false;
setSetting("useCCT", _light_use_cct);
}
_light_use_gamma = getSetting("useGamma", LIGHT_USE_GAMMA).toInt() == 1;
_light_use_transitions = getSetting("useTransitions", LIGHT_USE_TRANSITIONS).toInt() == 1;
_light_transition_time = getSetting("lightTime", LIGHT_TRANSITION_TIME).toInt();
}
@ -1046,13 +1065,14 @@ void lightSetup() {
DEBUG_MSG_P(PSTR("[LIGHT] LIGHT_PROVIDER = %d\n"), LIGHT_PROVIDER);
DEBUG_MSG_P(PSTR("[LIGHT] Number of channels: %d\n"), _light_channel.size());
_lightColorRestore();
_lightConfigure();
_lightColorRestore();
#if WEB_SUPPORT
_lightAPISetup();
wsOnSendRegister(_lightWebSocketOnSend);
wsOnActionRegister(_lightWebSocketOnAction);
wsOnReceiveRegister(_lightWebSocketOnReceive);
wsOnAfterParseRegister([]() {
#if LIGHT_SAVE_ENABLED == 0
lightSave();


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

@ -28,7 +28,7 @@ void _mdnsFindMQTT() {
#endif
void _mdnsServerStart() {
if (MDNS.begin(WiFi.getMode() == WIFI_AP ? APP_NAME : (char *) WiFi.hostname().c_str())) {
if (MDNS.begin((char *) getSetting("hostname").c_str())) {
DEBUG_MSG_P(PSTR("[MDNS] OK\n"));
} else {
DEBUG_MSG_P(PSTR("[MDNS] FAIL\n"));
@ -67,9 +67,6 @@ void mdnsServerSetup() {
itoa(ESP.getFreeSketchSpace(), buffer, 10);
MDNS.addServiceTxt("arduino", "tcp", "free_space", (const char *) buffer);
}
#ifdef APP_BUILD_FLAGS
//MDNS.addServiceTxt("arduino", "tcp", "build_flags", APP_BUILD_FLAGS);
#endif
wifiRegister([](justwifi_messages_t code, char * parameter) {


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

@ -698,6 +698,312 @@ void migrate() {
setSetting("chLogic", 3, 0);
setSetting("relays", 1);
#elif defined(KMC_70011)
setSetting("board", 53);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 14);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 12);
setSetting("cf1GPIO", 5);
setSetting("cfGPIO", 4);
#elif defined(GIZWITS_WITTY_CLOUD)
setSetting("board", 54);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 4);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 15);
setSetting("chGPIO", 1, 12);
setSetting("chGPIO", 2, 13);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("relays", 1);
#elif defined(EUROMATE_WIFI_STECKER_SCHUKO)
setSetting("board", 55);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 0);
setSetting("ledGPIO", 1, 12);
setSetting("ledLogic", 1, 0);
setSetting("btnGPIO", 0, 14);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(TONBUX_POWERSTRIP02)
setSetting("board", 56);
setSetting("relayGPIO", 0, 4);
setSetting("relayGPIO", 1, 13);
setSetting("relayGPIO", 2, 12);
setSetting("relayGPIO", 3, 14);
setSetting("relayGPIO", 4, 16);
setSetting("relayType", 0, RELAY_TYPE_INVERSE);
setSetting("relayType", 1, RELAY_TYPE_INVERSE);
setSetting("relayType", 2, RELAY_TYPE_INVERSE);
setSetting("relayType", 3, RELAY_TYPE_INVERSE);
setSetting("relayType", 4, RELAY_TYPE_NORMAL); // Not a relay. USB ports on/off
setSetting("ledGPIO", 0, 0); // 1 blue led
setSetting("ledLogic", 0, 1);
setSetting("ledGPIO", 1, 3); // 3 red leds
setSetting("ledLogic", 1, 1);
setSetting("btnGPIO", 0, 5);
setSetting("btnRelay", 0, 1);
#elif defined(LINGAN_SWA1)
setSetting("board", 57);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(HEYGO_HY02)
setSetting("board", 58);
setSetting("ledGPIO", 0, 0);
setSetting("ledLogic", 0, 1);
setSetting("ledGPIO", 1, 15);
setSetting("ledLogic", 1, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 3);
setSetting("cf1GPIO", 14);
setSetting("cfGPIO", 5);
#elif defined(MAXCIO_WUS002S)
setSetting("board", 59);
setSetting("ledGPIO", 0, 3);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 2);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 13);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 12);
setSetting("cf1GPIO", 5);
setSetting("cfGPIO", 4);
#elif defined(YIDIAN_XSSSA05)
setSetting("board", 60);
setSetting("ledGPIO", 0, 0);
setSetting("ledLogic", 0, 0);
setSetting("ledGPIO", 1, 5);
setSetting("ledLogic", 1, 0);
setSetting("ledGPIO", 2, 2);
setSetting("ledLogic", 2, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(TONBUX_XSSSA06)
setSetting("board", 61);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(GREEN_ESP8266RELAY)
setSetting("board", 62);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 5);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(IKE_ESPIKE)
setSetting("board", 63);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("btnGPIO", 1, 12);
setSetting("btnRelay", 1, 1);
setSetting("btnGPIO", 2, 13);
setSetting("btnRelay", 2, 2);
setSetting("relayGPIO", 0, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayGPIO", 1, 5);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayGPIO", 2, 16);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
#elif defined(ARNIEX_SWIFITCH)
setSetting("board", 64);
setSetting("ledGPIO", 0, 12);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 4);
setSetting("btnRelay", 0, 1);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_INVERSE);
#elif defined(GENERIC_ESP01S_RELAY_V40)
setSetting("board", 65);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 0);
setSetting("relayGPIO", 0, 0);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(GENERIC_ESP01S_RGBLED_V10)
setSetting("board", 66);
setSetting("ledGPIO", 0, 2);
#elif defined(HELTEC_TOUCHRELAY)
setSetting("board", 67);
setSetting("btnGPIO", 0, 14);
setSetting("btnRelay", 0, 1);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(GENERIC_ESP01S_DHT11_V10)
setSetting("board", 68);
#elif defined(GENERIC_ESP01S_DS18B20_V10)
setSetting("board", 69);
#elif defined(ZHILDE_EU44_W)
setSetting("board", 70);
setSetting("btnGPIO", 0, 3);
setSetting("ledGPIO", 0, 1);
setSetting("ledLogic", 0, 1);
setSetting("relayGPIO", 0, 5);
setSetting("relayGPIO", 1, 4);
setSetting("relayGPIO", 2, 12);
setSetting("relayGPIO", 3, 13);
setSetting("relayGPIO", 4, 14);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
setSetting("relayType", 3, RELAY_TYPE_NORMAL);
setSetting("relayType", 4, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SONOFF_POW_R2)
setSetting("board", 71);
setSetting("ledGPIO", 0, 15);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 5);
setSetting("cf1GPIO", 13);
setSetting("cfGPIO", 14);
#elif defined(LUANI_HVIO)
setSetting("board", 72);
setSetting("ledGPIO", 0, 15);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 12);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayGPIO", 1, 5);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(ALLNET_4DUINO_IOT_WLAN_RELAIS)
setSetting("board", 73);
setSetting("relayGPIO", 0, 14);
setSetting("relayResetGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_LATCHED);
#elif defined(TONBUX_MOSQUITO_KILLER)
setSetting("board", 74);
setSetting("ledGPIO", 0, 15);
setSetting("ledLogic", 0, 1);
setSetting("ledGPIO", 1, 14);
setSetting("ledLogic", 1, 1);
setSetting("ledGPIO", 2, 12);
setSetting("ledLogic", 2, 0);
setSetting("ledGPIO", 3, 16);
setSetting("ledLogic", 3, 0);
setSetting("btnGPIO", 0, 2);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(NEO_COOLCAM_POWER_PLUG_WIFI)
setSetting("board", 75);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(PILOTAK_ESP_DIN_V1)
setSetting("board", 76);
setSetting("ledGPIO", 0, 16);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 4);
setSetting("relayGPIO", 1, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(ESTINK_WIFI_POWER_STRIP)
setSetting("board", 76);
setSetting("btnGPIO", 0, 16);
setSetting("btnRelay", 0, 3);
setSetting("ledGPIO", 0, 0);
setSetting("ledGPIO", 1, 12);
setSetting("ledGPIO", 2, 3);
setSetting("ledGPIO", 3, 5);
setSetting("ledLogic", 0, 1);
setSetting("ledLogic", 1, 1);
setSetting("ledLogic", 2, 1);
setSetting("ledLogic", 3, 1);
setSetting("ledMode", 0, LED_MODE_FINDME);
setSetting("ledMode", 1, LED_MODE_FOLLOW);
setSetting("ledMode", 2, LED_MODE_FOLLOW);
setSetting("ledMode", 3, LED_MODE_FOLLOW);
setSetting("ledRelay", 1, 1);
setSetting("ledRelay", 2, 2);
setSetting("ledRelay", 3, 3);
setSetting("relayGPIO", 0, 14);
setSetting("relayGPIO", 1, 13);
setSetting("relayGPIO", 2, 4);
setSetting("relayGPIO", 3, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
setSetting("relayType", 3, RELAY_TYPE_NORMAL);
#else
// Allow users to define new settings without migration config


+ 128
- 65
code/espurna/mqtt.ino View File

@ -8,7 +8,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#if MQTT_SUPPORT
#include <EEPROM.h>
#include <EEPROM_Rotate.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ArduinoJson.h>
@ -55,8 +55,9 @@ unsigned long _mqtt_connected_at = 0;
std::vector<mqtt_callback_f> _mqtt_callbacks;
typedef struct {
unsigned char parent = 255;
char * topic;
char * message;
char * message = NULL;
} mqtt_message_t;
std::vector<mqtt_message_t> _mqtt_queue;
Ticker _mqtt_flush_ticker;
@ -217,7 +218,6 @@ void _mqttConfigure() {
if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1);
// Placeholders
_mqtt_topic.replace("{identifier}", getSetting("hostname"));
_mqtt_topic.replace("{hostname}", getSetting("hostname"));
_mqtt_topic.replace("{magnitude}", "#");
if (_mqtt_topic.indexOf("#") == -1) _mqtt_topic = _mqtt_topic + "/#";
@ -249,6 +249,14 @@ void _mqttConfigure() {
}
void _mqttBackwards() {
String mqttTopic = getSetting("mqttTopic", MQTT_TOPIC);
if (mqttTopic.indexOf("{identifier}") > 0) {
mqttTopic.replace("{identifier}", "{hostname}");
setSetting("mqttTopic", mqttTopic);
}
}
unsigned long _mqttNextMessageId() {
static unsigned long id = 0;
@ -257,7 +265,7 @@ unsigned long _mqttNextMessageId() {
if (id == 0) {
// read id from EEPROM and shift it
id = EEPROM.read(EEPROM_MESSAGE_ID);
id = EEPROMr.read(EEPROM_MESSAGE_ID);
if (id == 0xFF) {
// There was nothing in EEPROM,
@ -266,9 +274,9 @@ unsigned long _mqttNextMessageId() {
} else {
id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 1);
id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 2);
id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 3);
id = (id << 8) + EEPROMr.read(EEPROM_MESSAGE_ID + 1);
id = (id << 8) + EEPROMr.read(EEPROM_MESSAGE_ID + 2);
id = (id << 8) + EEPROMr.read(EEPROM_MESSAGE_ID + 3);
// Calculate next block and start from there
id = MQTT_MESSAGE_ID_SHIFT * (1 + (id / MQTT_MESSAGE_ID_SHIFT));
@ -279,11 +287,11 @@ unsigned long _mqttNextMessageId() {
// Save to EEPROM every MQTT_MESSAGE_ID_SHIFT
if (id % MQTT_MESSAGE_ID_SHIFT == 0) {
EEPROM.write(EEPROM_MESSAGE_ID + 0, (id >> 24) & 0xFF);
EEPROM.write(EEPROM_MESSAGE_ID + 1, (id >> 16) & 0xFF);
EEPROM.write(EEPROM_MESSAGE_ID + 2, (id >> 8) & 0xFF);
EEPROM.write(EEPROM_MESSAGE_ID + 3, (id >> 0) & 0xFF);
EEPROM.commit();
EEPROMr.write(EEPROM_MESSAGE_ID + 0, (id >> 24) & 0xFF);
EEPROMr.write(EEPROM_MESSAGE_ID + 1, (id >> 16) & 0xFF);
EEPROMr.write(EEPROM_MESSAGE_ID + 2, (id >> 8) & 0xFF);
EEPROMr.write(EEPROM_MESSAGE_ID + 3, (id >> 0) & 0xFF);
EEPROMr.commit();
}
id++;
@ -297,22 +305,26 @@ unsigned long _mqttNextMessageId() {
#if WEB_SUPPORT
bool _mqttWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "mqtt", 3) == 0);
}
void _mqttWebSocketOnSend(JsonObject& root) {
root["mqttVisible"] = 1;
root["mqttStatus"] = mqttConnected();
root["mqttEnabled"] = mqttEnabled();
root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER);
root["mqttPort"] = getSetting("mqttPort", MQTT_PORT);
root["mqttUser"] = getSetting("mqttUser");
root["mqttUser"] = getSetting("mqttUser", MQTT_USER);
root["mqttClientID"] = getSetting("mqttClientID");
root["mqttPassword"] = getSetting("mqttPassword");
root["mqttPassword"] = getSetting("mqttPassword", MQTT_PASS);
root["mqttKeep"] = _mqtt_keepalive;
root["mqttRetain"] = _mqtt_retain;
root["mqttQoS"] = _mqtt_qos;
#if ASYNC_TCP_SSL_ENABLED
root["mqttsslVisible"] = 1;
root["mqttUseSSL"] = getSetting("mqttUseSSL", 0).toInt() == 1;
root["mqttFP"] = getSetting("mqttFP");
root["mqttUseSSL"] = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
root["mqttFP"] = getSetting("mqttFP", MQTT_SSL_FINGERPRINT);
#endif
root["mqttTopic"] = getSetting("mqttTopic", MQTT_TOPIC);
root["mqttUseJson"] = getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1;
@ -484,32 +496,103 @@ String mqttTopic(const char * magnitude, unsigned int index, bool is_set) {
// -----------------------------------------------------------------------------
void mqttSendRaw(const char * topic, const char * message) {
void mqttSendRaw(const char * topic, const char * message, bool retain) {
if (_mqtt.connected()) {
#if MQTT_USE_ASYNC
unsigned int packetId = _mqtt.publish(topic, _mqtt_qos, _mqtt_retain, message);
unsigned int packetId = _mqtt.publish(topic, _mqtt_qos, retain, message);
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s (PID %d)\n"), topic, message, packetId);
#else
_mqtt.publish(topic, message, _mqtt_retain);
_mqtt.publish(topic, message, retain);
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message);
#endif
}
}
void mqttFlush() {
if (!_mqtt.connected()) return;
if (_mqtt_queue.size() == 0) return;
void mqttSendRaw(const char * topic, const char * message) {
mqttSendRaw (topic, message, _mqtt_retain);
}
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
void mqttSend(const char * topic, const char * message, bool force, bool retain) {
bool useJson = force ? false : _mqtt_use_json;
// Equeue message
if (useJson) {
// Set default queue topic
mqttQueueTopic(MQTT_TOPIC_JSON);
// Enqueue new message
mqttEnqueue(topic, message);
// Reset flush timer
_mqtt_flush_ticker.once_ms(MQTT_USE_JSON_DELAY, mqttFlush);
// Send it right away
} else {
mqttSendRaw(mqttTopic(topic, false).c_str(), message, retain);
}
}
void mqttSend(const char * topic, const char * message, bool force) {
mqttSend(topic, message, force, _mqtt_retain);
}
void mqttSend(const char * topic, const char * message) {
mqttSend(topic, message, false);
}
void mqttSend(const char * topic, unsigned int index, const char * message, bool force, bool retain) {
char buffer[strlen(topic)+5];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), topic, index);
mqttSend(buffer, message, force, retain);
}
void mqttSend(const char * topic, unsigned int index, const char * message, bool force) {
mqttSend(topic, index, message, force, _mqtt_retain);
}
void mqttSend(const char * topic, unsigned int index, const char * message) {
mqttSend(topic, index, message, false);
}
// -----------------------------------------------------------------------------
unsigned char _mqttBuildTree(JsonObject& root, char parent) {
unsigned char count = 0;
// Add enqueued messages
for (unsigned char i=0; i<_mqtt_queue.size(); i++) {
mqtt_message_t element = _mqtt_queue[i];
root[element.topic] = element.message;
if (element.parent == parent) {
++count;
JsonObject& elements = root.createNestedObject(element.topic);
unsigned char num = _mqttBuildTree(elements, i);
if (0 == num) {
root.set(element.topic, element.message);
}
}
}
return count;
}
void mqttFlush() {
if (!_mqtt.connected()) return;
if (_mqtt_queue.size() == 0) return;
// Build tree recursively
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
_mqttBuildTree(root, 255);
// Add extra propeties
#if NTP_SUPPORT && MQTT_ENQUEUE_DATETIME
if (ntpSynced()) root[MQTT_TOPIC_TIME] = ntpDateTime();
@ -530,13 +613,17 @@ void mqttFlush() {
// Send
String output;
root.printTo(output);
mqttSendRaw(_mqtt_topic_json.c_str(), output.c_str());
jsonBuffer.clear();
mqttSendRaw(_mqtt_topic_json.c_str(), output.c_str(), false);
// Clear queue
for (unsigned char i = 0; i < _mqtt_queue.size(); i++) {
mqtt_message_t element = _mqtt_queue[i];
free(element.topic);
free(element.message);
if (element.message) {
free(element.message);
}
}
_mqtt_queue.clear();
@ -550,59 +637,32 @@ void mqttQueueTopic(const char * topic) {
}
}
void mqttEnqueue(const char * topic, const char * message) {
int8_t mqttEnqueue(const char * topic, const char * message, unsigned char parent) {
// Queue is not meant to send message "offline"
// We must prevent the queue does not get full while offline
if (!_mqtt.connected()) return;
if (!_mqtt.connected()) return -1;
// Force flusing the queue if the MQTT_QUEUE_MAX_SIZE has been reached
if (_mqtt_queue.size() >= MQTT_QUEUE_MAX_SIZE) mqttFlush();
int8_t index = _mqtt_queue.size();
// Enqueue new message
mqtt_message_t element;
element.parent = parent;
element.topic = strdup(topic);
element.message = strdup(message);
_mqtt_queue.push_back(element);
}
void mqttSend(const char * topic, const char * message, bool force) {
bool useJson = force ? false : _mqtt_use_json;
// Equeue message
if (useJson) {
// Set default queue topic
mqttQueueTopic(MQTT_TOPIC_JSON);
// Enqueue new message
mqttEnqueue(topic, message);
// Reset flush timer
_mqtt_flush_ticker.once_ms(MQTT_USE_JSON_DELAY, mqttFlush);
// Send it right away
} else {
mqttSendRaw(mqttTopic(topic, false).c_str(), message);
if (NULL != message) {
element.message = strdup(message);
}
_mqtt_queue.push_back(element);
}
return index;
void mqttSend(const char * topic, const char * message) {
mqttSend(topic, message, false);
}
void mqttSend(const char * topic, unsigned int index, const char * message, bool force) {
char buffer[strlen(topic)+5];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), topic, index);
mqttSend(buffer, message, force);
}
void mqttSend(const char * topic, unsigned int index, const char * message) {
mqttSend(topic, index, message, false);
int8_t mqttEnqueue(const char * topic, const char * message) {
return mqttEnqueue(topic, message, 255);
}
// -----------------------------------------------------------------------------
@ -690,6 +750,8 @@ 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",
@ -748,6 +810,7 @@ void mqttSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_mqttWebSocketOnSend);
wsOnAfterParseRegister(_mqttConfigure);
wsOnReceiveRegister(_mqttWebSocketOnReceive);
#endif
#if TERMINAL_SUPPORT


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

@ -18,12 +18,20 @@ bool _nofussEnabled = false;
// NOFUSS
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _nofussWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "nofuss", 6) == 0);
}
void _nofussWebSocketOnSend(JsonObject& root) {
root["nofussVisible"] = 1;
root["nofussEnabled"] = getSetting("nofussEnabled", NOFUSS_ENABLED).toInt() == 1;
root["nofussServer"] = getSetting("nofussServer", NOFUSS_SERVER);
}
#endif
void _nofussConfigure() {
String nofussServer = getSetting("nofussServer", NOFUSS_SERVER);
@ -135,7 +143,7 @@ void nofussSetup() {
#if WEB_SUPPORT
wsSend_P(PSTR("{\"action\": \"reload\"}"));
#endif
delay(100);
nice_delay(100);
}
if (code == NOFUSS_END) {
@ -147,6 +155,7 @@ void nofussSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_nofussWebSocketOnSend);
wsOnAfterParseRegister(_nofussConfigure);
wsOnReceiveRegister(_nofussWebSocketOnReceive);
#endif
#if TERMINAL_SUPPORT


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

@ -21,21 +21,31 @@ bool _ntp_configure = false;
// NTP
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _ntpWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "ntp", 3) == 0);
}
void _ntpWebSocketOnSend(JsonObject& root) {
root["ntpVisible"] = 1;
root["ntpStatus"] = (timeStatus() == timeSet);
root["ntpServer"] = getSetting("ntpServer", NTP_SERVER);
root["ntpOffset"] = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
root["ntpDST"] = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1;
root["ntpRegion"] = getSetting("ntpRegion", NTP_DST_REGION).toInt();
if (ntpSynced()) root["now"] = now();
}
#endif
void _ntpStart() {
_ntp_start = 0;
NTP.begin(getSetting("ntpServer", NTP_SERVER));
NTP.setInterval(NTP_SYNC_INTERVAL, NTP_UPDATE_INTERVAL);
NTP.setNTPTimeout(NTP_TIMEOUT);
_ntpConfigure();
}
@ -65,6 +75,9 @@ void _ntpConfigure() {
NTP.setNtpServerName(server);
}
uint8_t dst_region = getSetting("ntpRegion", NTP_DST_REGION).toInt();
NTP.setDSTZone(dst_region);
}
void _ntpUpdate() {
@ -75,7 +88,11 @@ void _ntpUpdate() {
wsSend(_ntpWebSocketOnSend);
#endif
DEBUG_MSG_P(PSTR("[NTP] Time: %s\n"), (char *) ntpDateTime().c_str());
if (ntpSynced()) {
time_t t = now();
DEBUG_MSG_P(PSTR("[NTP] UTC Time : %s\n"), (char *) ntpDateTime(ntpLocal2UTC(t)).c_str());
DEBUG_MSG_P(PSTR("[NTP] Local Time: %s\n"), (char *) ntpDateTime(t).c_str());
}
}
@ -114,10 +131,8 @@ bool ntpSynced() {
return (year() > 2017);
}
String ntpDateTime() {
if (!ntpSynced()) return String();
String ntpDateTime(time_t t) {
char buffer[20];
time_t t = now();
snprintf_P(buffer, sizeof(buffer),
PSTR("%04d-%02d-%02d %02d:%02d:%02d"),
year(t), month(t), day(t), hour(t), minute(t), second(t)
@ -125,6 +140,17 @@ String ntpDateTime() {
return String(buffer);
}
String ntpDateTime() {
if (ntpSynced()) return ntpDateTime(now());
return String();
}
time_t ntpLocal2UTC(time_t local) {
int offset = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
if (NTP.isSummerTime()) offset += 60;
return local - offset * 60;
}
// -----------------------------------------------------------------------------
void ntpSetup() {
@ -152,6 +178,7 @@ void ntpSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_ntpWebSocketOnSend);
wsOnReceiveRegister(_ntpWebSocketOnReceive);
wsOnAfterParseRegister([]() { _ntp_configure = true; });
#endif


+ 11
- 1
code/espurna/ota.ino View File

@ -70,6 +70,7 @@ void _otaFrom(const char * host, unsigned int port, const char * url) {
#ifdef DEBUG_PORT
Update.printError(DEBUG_PORT);
#endif
eepromRotate(true);
}
DEBUG_MSG_P(PSTR("[OTA] Disconnected\n"));
@ -133,6 +134,9 @@ void _otaFrom(const char * host, unsigned int port, const char * url) {
}
#endif
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
eepromRotate(false);
DEBUG_MSG_P(PSTR("[OTA] Downloading %s\n"), _ota_url);
char buffer[strlen_P(OTA_REQUEST_TEMPLATE) + strlen(_ota_url) + strlen(_ota_host)];
snprintf_P(buffer, sizeof(buffer), OTA_REQUEST_TEMPLATE, _ota_url, _ota_host);
@ -140,7 +144,6 @@ void _otaFrom(const char * host, unsigned int port, const char * url) {
}, NULL);
#if ASYNC_TCP_SSL_ENABLED
bool connected = _ota_client->connect(host, port, 443 == port);
#else
@ -214,10 +217,16 @@ void otaSetup() {
// -------------------------------------------------------------------------
ArduinoOTA.onStart([]() {
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
eepromRotate(false);
DEBUG_MSG_P(PSTR("[OTA] Start\n"));
#if WEB_SUPPORT
wsSend_P(PSTR("{\"message\": 2}"));
#endif
});
ArduinoOTA.onEnd([]() {
@ -242,6 +251,7 @@ void otaSetup() {
else if (error == OTA_RECEIVE_ERROR) DEBUG_MSG_P(PSTR("Receive Failed\n"));
else if (error == OTA_END_ERROR) DEBUG_MSG_P(PSTR("End Failed\n"));
#endif
eepromRotate(true);
});
ArduinoOTA.begin();


+ 147
- 46
code/espurna/relay.ino View File

@ -6,7 +6,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <EEPROM.h>
#include <EEPROM_Rotate.h>
#include <Ticker.h>
#include <ArduinoJson.h>
#include <vector>
@ -17,7 +17,7 @@ typedef struct {
// Configuration variables
unsigned char pin; // GPIO pin for the relay
unsigned char type; // RELAY_TYPE_NORMAL, RELAY_TYPE_INVERSE or RELAY_TYPE_LATCHED
unsigned char type; // RELAY_TYPE_NORMAL, RELAY_TYPE_INVERSE, RELAY_TYPE_LATCHED or RELAY_TYPE_LATCHED_INVERSE
unsigned char reset_pin; // GPIO to reset the relay if RELAY_TYPE_LATCHED
unsigned long delay_on; // Delay to turn relay ON
unsigned long delay_off; // Delay to turn relay OFF
@ -118,17 +118,18 @@ void _relayProviderStatus(unsigned char id, bool status) {
digitalWrite(_relays[id].pin, status);
} else if (_relays[id].type == RELAY_TYPE_INVERSE) {
digitalWrite(_relays[id].pin, !status);
} else if (_relays[id].type == RELAY_TYPE_LATCHED) {
digitalWrite(_relays[id].pin, LOW);
digitalWrite(_relays[id].reset_pin, LOW);
} else if (_relays[id].type == RELAY_TYPE_LATCHED || _relays[id].type == RELAY_TYPE_LATCHED_INVERSE) {
bool pulse = RELAY_TYPE_LATCHED ? HIGH : LOW;
digitalWrite(_relays[id].pin, !pulse);
digitalWrite(_relays[id].reset_pin, !pulse);
if (status) {
digitalWrite(_relays[id].pin, HIGH);
digitalWrite(_relays[id].pin, pulse);
} else {
digitalWrite(_relays[id].reset_pin, HIGH);
digitalWrite(_relays[id].reset_pin, pulse);
}
delay(RELAY_LATCHING_PULSE);
digitalWrite(_relays[id].pin, LOW);
digitalWrite(_relays[id].reset_pin, LOW);
nice_delay(RELAY_LATCHING_PULSE);
digitalWrite(_relays[id].pin, !pulse);
digitalWrite(_relays[id].reset_pin, !pulse);
}
#endif
@ -141,7 +142,7 @@ void _relayProviderStatus(unsigned char id, bool status) {
*/
void _relayProcess(bool mode) {
unsigned int current_time = millis();
unsigned long current_time = millis();
for (unsigned char id = 0; id < _relays.size(); id++) {
@ -208,6 +209,8 @@ void _relayProcess(bool mode) {
void relayPulse(unsigned char id) {
_relays[id].pulseTicker.detach();
byte mode = _relays[id].pulse;
if (mode == RELAY_PULSE_NONE) return;
unsigned long ms = _relays[id].pulse_ms;
@ -216,10 +219,12 @@ void relayPulse(unsigned char id) {
bool status = relayStatus(id);
bool pulseStatus = (mode == RELAY_PULSE_ON);
if (pulseStatus == status) {
_relays[id].pulseTicker.detach();
} else {
if (pulseStatus != status) {
DEBUG_MSG_P(PSTR("[RELAY] Scheduling relay #%d back in %lums (pulse)\n"), id, ms);
_relays[id].pulseTicker.once_ms(ms, relayToggle, id);
// Reconfigure after dynamic pulse
_relays[id].pulse = getSetting("relayPulse", id, RELAY_PULSE_MODE).toInt();
_relays[id].pulse_ms = 1000 * getSetting("relayTime", id, RELAY_PULSE_MODE).toFloat();
}
}
@ -355,9 +360,9 @@ void relaySave() {
if (relayStatus(i)) mask += bit;
bit += bit;
}
EEPROM.write(EEPROM_RELAY_STATUS, mask);
EEPROMr.write(EEPROM_RELAY_STATUS, mask);
DEBUG_MSG_P(PSTR("[RELAY] Saving mask: %d\n"), mask);
EEPROM.commit();
EEPROMr.commit();
}
void relayToggle(unsigned char id, bool report, bool group_report) {
@ -435,7 +440,7 @@ void _relayBoot() {
bool trigger_save = false;
// Get last statuses from EEPROM
unsigned char mask = EEPROM.read(EEPROM_RELAY_STATUS);
unsigned char mask = EEPROMr.read(EEPROM_RELAY_STATUS);
DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %d\n"), mask);
// Walk the relays
@ -462,7 +467,7 @@ void _relayBoot() {
}
_relays[i].current_status = !status;
_relays[i].target_status = status;
#ifdef RELAY_PROVIDER_STM
#if RELAY_PROVIDER == RELAY_PROVIDER_STM
_relays[i].change_time = millis() + 3000 + 1000 * i;
#else
_relays[i].change_time = millis();
@ -472,8 +477,8 @@ void _relayBoot() {
// Save if there is any relay in the RELAY_BOOT_TOGGLE mode
if (trigger_save) {
EEPROM.write(EEPROM_RELAY_STATUS, mask);
EEPROM.commit();
EEPROMr.write(EEPROM_RELAY_STATUS, mask);
EEPROMr.commit();
}
_relayRecursive = false;
@ -483,7 +488,9 @@ void _relayBoot() {
void _relayConfigure() {
for (unsigned int i=0; i<_relays.size(); i++) {
pinMode(_relays[i].pin, OUTPUT);
if (_relays[i].type == RELAY_TYPE_LATCHED) pinMode(_relays[i].reset_pin, OUTPUT);
if (_relays[i].type == RELAY_TYPE_LATCHED || _relays[i].type == RELAY_TYPE_LATCHED_INVERSE) {
pinMode(_relays[i].reset_pin, OUTPUT);
}
_relays[i].pulse = getSetting("relayPulse", i, RELAY_PULSE_MODE).toInt();
_relays[i].pulse_ms = 1000 * getSetting("relayTime", i, RELAY_PULSE_MODE).toFloat();
}
@ -495,6 +502,10 @@ void _relayConfigure() {
#if WEB_SUPPORT
bool _relayWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "relay", 5) == 0);
}
void _relayWebSocketUpdate(JsonObject& root) {
JsonArray& relay = root.createNestedArray("relayStatus");
for (unsigned char i=0; i<relayCount(); i++) {
@ -522,6 +533,7 @@ void _relayWebSocketOnStart(JsonObject& root) {
#if MQTT_SUPPORT
line["group"] = getSetting("mqttGroup", i, "");
line["group_inv"] = getSetting("mqttGroupInv", i, 0).toInt();
line["on_disc"] = getSetting("relayOnDisc", i, 0).toInt();
#endif
}
@ -573,6 +585,7 @@ void relaySetupWS() {
wsOnSendRegister(_relayWebSocketOnStart);
wsOnActionRegister(_relayWebSocketOnAction);
wsOnAfterParseRegister(_relayConfigure);
wsOnReceiveRegister(_relayWebSocketOnReceive);
}
#endif // WEB_SUPPORT
@ -588,12 +601,12 @@ void relaySetupAPI() {
// API entry points (protected with apikey)
for (unsigned int relayID=0; relayID<relayCount(); relayID++) {
char key[15];
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_RELAY, relayID);
char key[20];
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_RELAY, relayID);
apiRegister(key,
[relayID](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), relayStatus(relayID) ? 1 : 0);
snprintf_P(buffer, len, PSTR("%d"), _relays[relayID].target_status ? 1 : 0);
},
[relayID](const char * payload) {
@ -615,6 +628,30 @@ void relaySetupAPI() {
}
);
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_PULSE, relayID);
apiRegister(key,
[relayID](char * buffer, size_t len) {
dtostrf((double) _relays[relayID].pulse_ms / 1000, 1-len, 3, buffer);
},
[relayID](const char * payload) {
unsigned long pulse = 1000 * String(payload).toFloat();
if (0 == pulse) return;
if (RELAY_PULSE_NONE != _relays[relayID].pulse) {
DEBUG_MSG_P(PSTR("[RELAY] Overriding relay #%d pulse settings\n"), relayID);
}
_relays[relayID].pulse_ms = pulse;
_relays[relayID].pulse = relayStatus(relayID) ? RELAY_PULSE_ON : RELAY_PULSE_OFF;
relayToggle(relayID, true, false);
return;
}
);
}
}
@ -647,7 +684,6 @@ void relayMQTT(unsigned char id) {
mqttSendRaw(t.c_str(), status ? "1" : "0");
}
}
}
void relayMQTT() {
@ -657,13 +693,20 @@ void relayMQTT() {
}
void relayStatusWrap(unsigned char id, unsigned char value, bool is_group_topic) {
// Action to perform
if (value == 0) {
relayStatus(id, false, mqttForward(), !is_group_topic);
} else if (value == 1) {
relayStatus(id, true, mqttForward(), !is_group_topic);
} else if (value == 2) {
relayToggle(id, true, true);
switch (value) {
case 0:
relayStatus(id, false, mqttForward(), !is_group_topic);
break;
case 1:
relayStatus(id, true, mqttForward(), !is_group_topic);
break;
case 2:
relayToggle(id, true, true);
break;
default:
_relays[id].report = true;
relayMQTT(id);
break;
}
}
@ -677,9 +720,14 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
#endif
// Subscribe to own /set topic
char buffer[strlen(MQTT_TOPIC_RELAY) + 3];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RELAY);
mqttSubscribe(buffer);
char relay_topic[strlen(MQTT_TOPIC_RELAY) + 3];
snprintf_P(relay_topic, sizeof(relay_topic), PSTR("%s/+"), MQTT_TOPIC_RELAY);
mqttSubscribe(relay_topic);
// Subscribe to pulse topic
char pulse_topic[strlen(MQTT_TOPIC_PULSE) + 3];
snprintf_P(pulse_topic, sizeof(pulse_topic), PSTR("%s/+"), MQTT_TOPIC_PULSE);
mqttSubscribe(pulse_topic);
// Subscribe to group topics
for (unsigned int i=0; i < _relays.size(); i++) {
@ -691,26 +739,53 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
if (type == MQTT_MESSAGE_EVENT) {
// Check relay topic
String t = mqttMagnitude((char *) topic);
if (t.startsWith(MQTT_TOPIC_RELAY)) {
// Get value
unsigned char value = relayParsePayload(payload);
if (value == 0xFF) return;
// magnitude is relay/#/pulse
if (t.startsWith(MQTT_TOPIC_PULSE)) {
unsigned int id = t.substring(strlen(MQTT_TOPIC_PULSE)+1).toInt();
if (id >= relayCount()) {
DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), id);
return;
}
unsigned long pulse = 1000 * String(payload).toFloat();
if (0 == pulse) return;
if (RELAY_PULSE_NONE != _relays[id].pulse) {
DEBUG_MSG_P(PSTR("[RELAY] Overriding relay #%d pulse settings\n"), id);
}
_relays[id].pulse_ms = pulse;
_relays[id].pulse = relayStatus(id) ? RELAY_PULSE_ON : RELAY_PULSE_OFF;
relayToggle(id, true, false);
return;
}
// magnitude is relay/#
if (t.startsWith(MQTT_TOPIC_RELAY)) {
// Get relay ID
unsigned int id = t.substring(strlen(MQTT_TOPIC_RELAY)+1).toInt();
if (id >= relayCount()) {
DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), id);
} else {
relayStatusWrap(id, value, false);
return;
}
return;
// Get value
unsigned char value = relayParsePayload(payload);
if (value == 0xFF) return;
relayStatusWrap(id, value, false);
return;
}
// Check group topics
for (unsigned int i=0; i < _relays.size(); i++) {
@ -735,6 +810,20 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
}
if (type == MQTT_DISCONNECT_EVENT) {
for (unsigned int i=0; i < _relays.size(); i++){
int reaction = getSetting("relayOnDisc", i, 0).toInt();
if (1 == reaction) { // switch relay OFF
DEBUG_MSG_P(PSTR("[RELAY] Reset relay (%d) due to MQTT disconnection\n"), i);
relayStatusWrap(i, false, false);
} else if(2 == reaction) { // switch relay ON
DEBUG_MSG_P(PSTR("[RELAY] Set relay (%d) due to MQTT disconnection\n"), i);
relayStatusWrap(i, true, false);
}
}
}
}
void relaySetupMQTT() {
@ -768,8 +857,14 @@ void _relayInitCommands() {
settingsRegisterCommand(F("RELAY"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
int id = String(e->argv[1]).toInt();
if (id >= relayCount()) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong relayID (%d)\n"), id);
return;
}
if (e->argc > 2) {
int value = String(e->argv[2]).toInt();
if (value == 2) {
@ -778,7 +873,12 @@ void _relayInitCommands() {
relayStatus(id, value == 1);
}
}
DEBUG_MSG_P(PSTR("Status: %s\n"), relayStatus(id) ? "true" : "false");
DEBUG_MSG_P(PSTR("Status: %s\n"), _relays[id].target_status ? "true" : "false");
if (_relays[id].pulse != RELAY_PULSE_NONE) {
DEBUG_MSG_P(PSTR("Pulse: %s\n"), (_relays[id].pulse == RELAY_PULSE_ON) ? "ON" : "OFF");
DEBUG_MSG_P(PSTR("Pulse time: %d\n"), _relays[id].pulse_ms);
}
DEBUG_MSG_P(PSTR("+OK\n"));
});
@ -800,9 +900,10 @@ void relaySetup() {
// Dummy relays for AI Light, Magic Home LED Controller, H801,
// Sonoff Dual and Sonoff RF Bridge
#if DUMMY_RELAY_COUNT > 0
unsigned int _delay_on[8] = {RELAY1_DELAY_ON, RELAY2_DELAY_ON, RELAY3_DELAY_ON, RELAY4_DELAY_ON, RELAY5_DELAY_ON, RELAY6_DELAY_ON, RELAY7_DELAY_ON, RELAY8_DELAY_ON};
unsigned int _delay_off[8] = {RELAY1_DELAY_OFF, RELAY2_DELAY_OFF, RELAY3_DELAY_OFF, RELAY4_DELAY_OFF, RELAY5_DELAY_OFF, RELAY6_DELAY_OFF, RELAY7_DELAY_OFF, RELAY8_DELAY_OFF};
for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL});
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL,0,_delay_on[i], _delay_off[i]});
}
#else


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

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


+ 174
- 64
code/espurna/rfbridge.ino View File

@ -11,6 +11,10 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <queue>
#include <Ticker.h>
#if RFB_DIRECT
#include <RCSwitch.h>
#endif
// -----------------------------------------------------------------------------
// DEFINITIONS
// -----------------------------------------------------------------------------
@ -51,6 +55,11 @@ static std::queue<rfb_message_t> _rfb_message_queue;
Ticker _rfb_ticker;
bool _rfb_ticker_active = false;
#if RFB_DIRECT
RCSwitch * _rfModem;
bool _learning = false;
#endif
// -----------------------------------------------------------------------------
// PRIVATES
// -----------------------------------------------------------------------------
@ -105,24 +114,30 @@ void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject&
}
void _rfbAck() {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_ACK);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
#if not RFB_DIRECT
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_ACK);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
#endif
}
void _rfbLearn() {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_LEARN);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
#if RFB_DIRECT
DEBUG_MSG_P(PSTR("[RFBRIDGE] Entering LEARN mode\n"));
_learning = true;
#else
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_LEARN);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
#endif
#if WEB_SUPPORT
char buffer[100];
@ -139,13 +154,32 @@ void _rfbSendRaw(const byte *message, const unsigned char n = RF_MESSAGE_SIZE) {
}
void _rfbSend(byte * message) {
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_RFOUT);
_rfbSendRaw(message);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
#if RFB_DIRECT
unsigned int protocol = message[1];
unsigned int timing =
(message[2] << 8) |
(message[3] << 0) ;
unsigned int bitlength = message[4];
unsigned long rf_code =
(message[5] << 24) |
(message[6] << 16) |
(message[7] << 8) |
(message[8] << 0) ;
_rfModem->setProtocol(protocol);
if (timing > 0) {
_rfModem->setPulseLength(timing);
}
_rfModem->send(rf_code, bitlength);
_rfModem->resetAvailable();
#else
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_RFOUT);
_rfbSendRaw(message);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
#endif
}
void _rfbSend() {
@ -175,6 +209,9 @@ void _rfbSend() {
}
void _rfbSend(byte * code, unsigned char times) {
#if RFB_DIRECT
times = 1;
#endif
char buffer[RF_MESSAGE_SIZE];
_rfbToChar(code, buffer);
@ -204,7 +241,7 @@ void _rfbSendRawOnce(byte *code, unsigned char length) {
#endif // RF_RAW_SUPPORT
bool _rfbMatch(char * code, unsigned char& relayID, unsigned char& value) {
bool _rfbMatch(char* code, unsigned char& relayID, unsigned char& value, char* buffer = NULL) {
if (strlen(code) != 18) return false;
@ -220,6 +257,7 @@ bool _rfbMatch(char * code, unsigned char& relayID, unsigned char& value) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Match ON code for relay %d\n"), i);
value = 1;
found = true;
if (buffer) strcpy(buffer, code_on.c_str());
}
String code_off = rfbRetrieve(i, false);
@ -227,6 +265,7 @@ bool _rfbMatch(char * code, unsigned char& relayID, unsigned char& value) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Match OFF code for relay %d\n"), i);
if (found) value = 2;
found = true;
if (buffer) strcpy(buffer, code_off.c_str());
}
if (found) {
@ -258,12 +297,25 @@ void _rfbDecode() {
#endif
}
unsigned char id;
unsigned char status;
bool matched = false;
if (action == RF_CODE_LEARN_OK || action == RF_CODE_RFIN) {
_rfbAck();
_rfbToChar(&_uartbuf[1], buffer);
/* Look for the code, possibly replacing the code with the exact learned one on match
* we want to do this on learn too to be sure that the learned code is the same if it
* is equivalent
*/
DEBUG_MSG_P(PSTR("[RFBRIDGE] Received message '%s'\n"), buffer);
matched = _rfbMatch(buffer, id, status, buffer);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Matched message '%s'\n"), buffer);
#if MQTT_SUPPORT
_rfbToChar(&_uartbuf[1], buffer);
mqttSend(MQTT_TOPIC_RFIN, buffer);
#endif
_rfbAck();
}
if (action == RF_CODE_LEARN_OK) {
@ -281,13 +333,8 @@ void _rfbDecode() {
}
if (action == RF_CODE_RFIN) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Forward message '%s'\n"), buffer);
// Look for the code
unsigned char id;
unsigned char status = 0;
if (_rfbMatch(buffer, id, status)) {
if (matched) {
_rfbin = true;
if (status == 2) {
relayToggle(id);
@ -301,33 +348,77 @@ void _rfbDecode() {
}
void _rfbReceive() {
static bool receiving = false;
while (Serial.available()) {
yield();
byte c = Serial.read();
//DEBUG_MSG_P(PSTR("[RFBRIDGE] Received 0x%02X\n"), c);
if (receiving) {
if (c == RF_CODE_STOP && (_uartpos == 1 || _uartpos == RF_MESSAGE_SIZE + 1)) {
#if RFB_DIRECT
static long learn_start = 0;
if (!_learning && learn_start) {
learn_start = 0;
}
if (_learning) {
if (!learn_start) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] arming learn timeout\n"));
learn_start = millis();
}
if (learn_start > 0 && millis() - learn_start > RF_LEARN_TIMEOUT) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] learn timeout triggered\n"));
memset(_uartbuf, 0, sizeof(_uartbuf));
_uartbuf[0] = RF_CODE_LEARN_KO;
_rfbDecode();
receiving = false;
} else if (_uartpos <= RF_MESSAGE_SIZE) {
_uartbuf[_uartpos++] = c;
} else {
// wrong message, should have received a RF_CODE_STOP
receiving = false;
_learning = false;
}
} else if (c == RF_CODE_START) {
_uartpos = 0;
receiving = true;
}
}
if (_rfModem->available()) {
static unsigned long last = 0;
if (millis() - last > RF_DEBOUNCE) {
last = millis();
unsigned long rf_code = _rfModem->getReceivedValue();
if ( rf_code > 0) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Received code: %08X\n"), rf_code);
unsigned int timing = _rfModem->getReceivedDelay();
memset(_uartbuf, 0, sizeof(_uartbuf));
unsigned char *msgbuf = _uartbuf + 1;
_uartbuf[0] = _learning ? RF_CODE_LEARN_OK: RF_CODE_RFIN;
msgbuf[0] = 0xC0;
msgbuf[1] = _rfModem->getReceivedProtocol();
msgbuf[2] = timing >> 8;
msgbuf[3] = timing >> 0;
msgbuf[4] = _rfModem->getReceivedBitlength();
msgbuf[5] = rf_code >> 24;
msgbuf[6] = rf_code >> 16;
msgbuf[7] = rf_code >> 8;
msgbuf[8] = rf_code >> 0;
_rfbDecode();
_learning = false;
}
}
_rfModem->resetAvailable();
}
#else
static bool receiving = false;
while (Serial.available()) {
yield();
byte c = Serial.read();
//DEBUG_MSG_P(PSTR("[RFBRIDGE] Received 0x%02X\n"), c);
if (receiving) {
if (c == RF_CODE_STOP && (_uartpos == 1 || _uartpos == RF_MESSAGE_SIZE + 1)) {
_rfbDecode();
receiving = false;
} else if (_uartpos <= RF_MESSAGE_SIZE) {
_uartbuf[_uartpos++] = c;
} else {
// wrong message, should have received a RF_CODE_STOP
receiving = false;
}
} else if (c == RF_CODE_START) {
_uartpos = 0;
receiving = true;
}
}
#endif
}
bool _rfbCompare(const char * code1, const char * code2) {
@ -346,9 +437,9 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RFLEARN);
mqttSubscribe(buffer);
mqttSubscribe(MQTT_TOPIC_RFOUT);
#if RF_RAW_SUPPORT
mqttSubscribe(MQTT_TOPIC_RFRAW);
#endif
#if RF_RAW_SUPPORT
mqttSubscribe(MQTT_TOPIC_RFRAW);
#endif
}
if (type == MQTT_MESSAGE_EVENT) {
@ -443,37 +534,47 @@ String rfbRetrieve(unsigned char id, bool status) {
}
void rfbStatus(unsigned char id, bool status) {
String value = rfbRetrieve(id, status);
if (value.length() > 0) {
bool same = _rfbSameOnOff(id);
#if RF_RAW_SUPPORT
byte message[RF_MAX_MESSAGE_SIZE];
int len = _rfbToArray(value.c_str(), message, 0);
if (len == RF_MESSAGE_SIZE && // probably a standard msg
(message[0] != RF_CODE_START || // raw would start with 0xAA
message[1] != RF_CODE_RFOUT_BUCKET || // followed by 0xB0,
message[2] + 4 != len || // needs a valid length,
message[len-1] != RF_CODE_STOP)) { // and finish with 0x55
unsigned char times = RF_SEND_TIMES;
if (same) times = _rfbin ? 0 : 1;
_rfbSend(message, times);
if (!_rfbin) {
unsigned char times = same ? 1 : RF_SEND_TIMES;
_rfbSend(message, times);
}
} else {
_rfbSendRawOnce(message, len); // send a raw message
}
#else // RF_RAW_SUPPORT
byte message[RF_MESSAGE_SIZE];
_rfbToArray(value.c_str(), message);
unsigned char times = RF_SEND_TIMES;
if (same) times = _rfbin ? 0 : 1;
_rfbSend(message, times);
if (!_rfbin) {
byte message[RF_MESSAGE_SIZE];
_rfbToArray(value.c_str(), message);
unsigned char times = same ? 1 : RF_SEND_TIMES;
_rfbSend(message, times);
}
#endif // RF_RAW_SUPPORT
}
_rfbin = false;
}
void rfbLearn(unsigned char id, bool status) {
@ -512,7 +613,16 @@ void rfbSetup() {
wsOnActionRegister(_rfbWebSocketOnAction);
#endif
// Register oop
#if RFB_DIRECT
_rfModem = new RCSwitch();
_rfModem->enableReceive(RFB_RX_PIN);
_rfModem->enableTransmit(RFB_TX_PIN);
_rfModem->setRepeatTransmit(6);
DEBUG_MSG_P(PSTR("[RFBRIDGE] RF receiver on GPIO %u\n"), RFB_RX_PIN);
DEBUG_MSG_P(PSTR("[RFBRIDGE] RF transmitter on GPIO %u\n"), RFB_TX_PIN);
#endif
// Register loop
espurnaRegisterLoop(rfbLoop);
}


+ 71
- 31
code/espurna/scheduler.ino View File

@ -15,21 +15,32 @@ Adapted by Xose Pérez <xose dot perez at gmail dot com>
#if WEB_SUPPORT
bool _schWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "sch", 3) == 0);
}
void _schWebSocketOnSend(JsonObject &root){
root["schVisible"] = 1;
root["maxScheduled"] = SCHEDULER_MAX_SCHEDULES;
JsonArray &sch = root.createNestedArray("schedule");
for (byte i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
if (!hasSetting("schSwitch", i)) break;
JsonObject &scheduler = sch.createNestedObject();
scheduler["schEnabled"] = getSetting("schEnabled", i, 1).toInt() == 1;
scheduler["schSwitch"] = getSetting("schSwitch", i, 0).toInt();
scheduler["schAction"] = getSetting("schAction", i, 0).toInt();
scheduler["schHour"] = getSetting("schHour", i, 0).toInt();
scheduler["schMinute"] = getSetting("schMinute", i, 0).toInt();
scheduler["schWDs"] = getSetting("schWDs", i, "");
if (relayCount() > 0) {
root["schVisible"] = 1;
root["maxSchedules"] = SCHEDULER_MAX_SCHEDULES;
JsonArray &sch = root.createNestedArray("schedule");
for (byte i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
if (!hasSetting("schSwitch", i)) break;
JsonObject &scheduler = sch.createNestedObject();
scheduler["schEnabled"] = getSetting("schEnabled", i, 1).toInt() == 1;
scheduler["schSwitch"] = getSetting("schSwitch", i, 0).toInt();
scheduler["schAction"] = getSetting("schAction", i, 0).toInt();
scheduler["schType"] = getSetting("schType", i, 0).toInt();
scheduler["schHour"] = getSetting("schHour", i, 0).toInt();
scheduler["schMinute"] = getSetting("schMinute", i, 0).toInt();
scheduler["schUTC"] = getSetting("schUTC", i, 0).toInt() == 1;
scheduler["schWDs"] = getSetting("schWDs", i, "");
}
}
}
#endif // WEB_SUPPORT
@ -53,20 +64,26 @@ void _schConfigure() {
delSetting("schHour", i);
delSetting("schMinute", i);
delSetting("schWDs", i);
delSetting("schType", i);
delSetting("schUTC", i);
} else {
#if DEBUG_SUPPORT
int sch_enabled = getSetting("schEnabled", i, 1).toInt() == 1;
bool sch_enabled = getSetting("schEnabled", i, 1).toInt() == 1;
int sch_action = getSetting("schAction", i, 0).toInt();
int sch_hour = getSetting("schHour", i, 0).toInt();
int sch_minute = getSetting("schMinute", i, 0).toInt();
bool sch_utc = getSetting("schUTC", i, 0).toInt() == 1;
String sch_weekdays = getSetting("schWDs", i, "");
unsigned char sch_type = getSetting("schType", i, SCHEDULER_TYPE_SWITCH).toInt();
DEBUG_MSG_P(
PSTR("[SCH] Schedule #%d: %s switch #%d at %02d:%02d on %s%s\n"),
i, sch_action == 0 ? "turn OFF" : sch_action == 1 ? "turn ON" : "toggle", sch_switch,
sch_hour, sch_minute, (char *) sch_weekdays.c_str(),
PSTR("[SCH] Schedule #%d: %s #%d to %d at %02d:%02d %s on %s%s\n"),
i, SCHEDULER_TYPE_SWITCH == sch_type ? "switch" : "channel", sch_switch,
sch_action, sch_hour, sch_minute, sch_utc ? "UTC" : "local time",
(char *) sch_weekdays.c_str(),
sch_enabled ? "" : " (disabled)"
);
@ -78,24 +95,23 @@ void _schConfigure() {
}
bool _schIsThisWeekday(String weekdays){
bool _schIsThisWeekday(time_t t, String weekdays){
// Convert from Sunday to Monday as day 1
int w = weekday(now()) - 1;
if (w == 0) w = 7;
int w = weekday(t) - 1;
if (0 == w) w = 7;
char pch;
char * p = (char *) weekdays.c_str();
unsigned char position = 0;
while (pch = p[position++]) {
while ((pch = p[position++])) {
if ((pch - '0') == w) return true;
}
return false;
}
int _schMinutesLeft(unsigned char schedule_hour, unsigned char schedule_minute){
time_t t = now();
int _schMinutesLeft(time_t t, unsigned char schedule_hour, unsigned char schedule_minute){
unsigned char now_hour = hour(t);
unsigned char now_minute = minute(t);
return (schedule_hour - now_hour) * 60 + schedule_minute - now_minute;
@ -103,6 +119,9 @@ int _schMinutesLeft(unsigned char schedule_hour, unsigned char schedule_minute){
void _schCheck() {
time_t local_time = now();
time_t utc_time = ntpLocal2UTC(local_time);
// Check schedules
for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
@ -112,21 +131,41 @@ void _schCheck() {
// Skip disabled schedules
if (getSetting("schEnabled", i, 1).toInt() == 0) continue;
// Get the datetime used for the calculation
bool sch_utc = getSetting("schUTC", i, 0).toInt() == 1;
time_t t = sch_utc ? utc_time : local_time;
String sch_weekdays = getSetting("schWDs", i, "");
if (_schIsThisWeekday(sch_weekdays)) {
if (_schIsThisWeekday(t, sch_weekdays)) {
int sch_hour = getSetting("schHour", i, 0).toInt();
int sch_minute = getSetting("schMinute", i, 0).toInt();
int minutes_to_trigger = _schMinutesLeft(sch_hour, sch_minute);
int minutes_to_trigger = _schMinutesLeft(t, sch_hour, sch_minute);
if (minutes_to_trigger == 0) {
int sch_action = getSetting("schAction", i, 0).toInt();
if (sch_action == 2) {
relayToggle(sch_switch);
} else {
relayStatus(sch_switch, sch_action);
unsigned char sch_type = getSetting("schType", i, SCHEDULER_TYPE_SWITCH).toInt();
if (SCHEDULER_TYPE_SWITCH == sch_type) {
int sch_action = getSetting("schAction", i, 0).toInt();
DEBUG_MSG_P(PSTR("[SCH] Switching switch %d to %d\n"), sch_switch, sch_action);
if (sch_action == 2) {
relayToggle(sch_switch);
} else {
relayStatus(sch_switch, sch_action);
}
}
DEBUG_MSG_P(PSTR("[SCH] Schedule #%d TRIGGERED!!\n"), sch_switch);
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (SCHEDULER_TYPE_DIM == sch_type) {
int sch_brightness = getSetting("schAction", i, -1).toInt();
DEBUG_MSG_P(PSTR("[SCH] Set channel %d value to %d\n"), sch_switch, sch_brightness);
lightChannel(sch_switch, sch_brightness);
lightUpdate(true, true);
}
#endif
DEBUG_MSG_P(PSTR("[SCH] Schedule #%d TRIGGERED!!\n"), i);
// Show minutes to trigger every 15 minutes
// or every minute if less than 15 minutes to scheduled time.
@ -139,7 +178,7 @@ void _schCheck() {
if ((minutes_to_trigger % 15 == 0) || (minutes_to_trigger < 15)) {
DEBUG_MSG_P(
PSTR("[SCH] %d minutes to trigger schedule #%d\n"),
minutes_to_trigger, sch_switch
minutes_to_trigger, i
);
}
#endif
@ -176,6 +215,7 @@ void schSetup() {
// Update websocket clients
#if WEB_SUPPORT
wsOnSendRegister(_schWebSocketOnSend);
wsOnReceiveRegister(_schWebSocketOnReceive);
wsOnAfterParseRegister(_schConfigure);
#endif


+ 409
- 80
code/espurna/sensor.ino View File

@ -28,39 +28,87 @@ typedef struct {
std::vector<BaseSensor *> _sensors;
std::vector<sensor_magnitude_t> _magnitudes;
bool _sensors_ready = false;
unsigned char _counts[MAGNITUDE_MAX];
bool _sensor_realtime = API_REAL_TIME_VALUES;
unsigned long _sensor_read_interval = 1000 * SENSOR_READ_INTERVAL;
unsigned char _sensor_report_every = SENSOR_REPORT_EVERY;
unsigned char _sensor_power_units = SENSOR_POWER_UNITS;
unsigned char _sensor_energy_units = SENSOR_ENERGY_UNITS;
unsigned char _sensor_temperature_units = SENSOR_TEMPERATURE_UNITS;
double _sensor_temperature_correction = SENSOR_TEMPERATURE_CORRECTION;
double _sensor_humidity_correction = SENSOR_HUMIDITY_CORRECTION;
String _sensor_energy_reset_ts = String();
// -----------------------------------------------------------------------------
// Private
// -----------------------------------------------------------------------------
unsigned char _magnitudeDecimals(unsigned char type) {
// Hardcoded decimals (these should be linked to the unit, instead of the magnitude)
if (type == MAGNITUDE_ENERGY ||
type == MAGNITUDE_ENERGY_DELTA) {
if (_sensor_energy_units == ENERGY_KWH) return 3;
}
if (type == MAGNITUDE_POWER_ACTIVE ||
type == MAGNITUDE_POWER_APPARENT ||
type == MAGNITUDE_POWER_REACTIVE) {
if (_sensor_power_units == POWER_KILOWATTS) return 3;
}
if (type < MAGNITUDE_MAX) return pgm_read_byte(magnitude_decimals + type);
return 0;
}
double _magnitudeProcess(unsigned char type, double value) {
// Hardcoded conversions (these should be linked to the unit, instead of the magnitude)
if (type == MAGNITUDE_TEMPERATURE) {
if (_sensor_temperature_units == TMP_FAHRENHEIT) value = value * 1.8 + 32;
value = value + _sensor_temperature_correction;
}
if (type == MAGNITUDE_HUMIDITY) {
value = constrain(value + _sensor_humidity_correction, 0, 100);
}
if (type == MAGNITUDE_ENERGY ||
type == MAGNITUDE_ENERGY_DELTA) {
if (_sensor_energy_units == ENERGY_KWH) value = value / 3600000;
}
if (type == MAGNITUDE_POWER_ACTIVE ||
type == MAGNITUDE_POWER_APPARENT ||
type == MAGNITUDE_POWER_REACTIVE) {
if (_sensor_power_units == POWER_KILOWATTS) value = value / 1000;
}
return roundTo(value, _magnitudeDecimals(type));
}
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _sensorWebSocketOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, "pwr", 3) == 0) return true;
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;
return false;
}
void _sensorWebSocketSendData(JsonObject& root) {
char buffer[10];
bool hasTemperature = false;
bool hasHumidity = false;
JsonArray& list = root.createNestedArray("magnitudes");
for (unsigned char i=0; i<_magnitudes.size(); i++) {
@ -74,14 +122,22 @@ void _sensorWebSocketSendData(JsonObject& root) {
element["type"] = int(magnitude.type);
element["value"] = String(buffer);
element["units"] = magnitudeUnits(magnitude.type);
element["description"] = magnitude.sensor->slot(magnitude.local);
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;
} else {
element["description"] = magnitude.sensor->slot(magnitude.local);
}
if (magnitude.type == MAGNITUDE_TEMPERATURE) hasTemperature = true;
if (magnitude.type == MAGNITUDE_HUMIDITY) hasHumidity = true;
}
if (hasTemperature) root["temperatureVisible"] = 1;
if (hasHumidity) root["humidityVisible"] = 1;
}
@ -94,6 +150,7 @@ void _sensorWebSocketStart(JsonObject& root) {
#if EMON_ANALOG_SUPPORT
if (sensor->getID() == SENSOR_EMON_ANALOG_ID) {
root["emonVisible"] = 1;
root["pwrVisible"] = 1;
root["pwrVoltage"] = ((EmonAnalogSensor *) sensor)->getVoltage();
}
#endif
@ -101,6 +158,32 @@ void _sensorWebSocketStart(JsonObject& root) {
#if HLW8012_SUPPORT
if (sensor->getID() == SENSOR_HLW8012_ID) {
root["hlwVisible"] = 1;
root["pwrVisible"] = 1;
}
#endif
#if CSE7766_SUPPORT
if (sensor->getID() == SENSOR_CSE7766_ID) {
root["cseVisible"] = 1;
root["pwrVisible"] = 1;
}
#endif
#if V9261F_SUPPORT
if (sensor->getID() == SENSOR_V9261F_ID) {
root["pwrVisible"] = 1;
}
#endif
#if ECH1560_SUPPORT
if (sensor->getID() == SENSOR_ECH1560_ID) {
root["pwrVisible"] = 1;
}
#endif
#if PZEM004T_SUPPORT
if (sensor->getID() == SENSOR_PZEM004T_ID) {
root["pwrVisible"] = 1;
}
#endif
@ -109,8 +192,11 @@ void _sensorWebSocketStart(JsonObject& root) {
if (_magnitudes.size() > 0) {
root["sensorsVisible"] = 1;
//root["apiRealTime"] = _sensor_realtime;
root["pwrUnits"] = _sensor_power_units;
root["energyUnits"] = _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;
}
@ -199,11 +285,19 @@ void _sensorPost() {
}
}
void _sensorReset() {
#if NTP_SUPPORT
if (ntpSynced()) {
_sensor_energy_reset_ts = String(" (since ") + ntpDateTime() + String(")");
}
#endif
}
// -----------------------------------------------------------------------------
// Sensor initialization
// -----------------------------------------------------------------------------
void _sensorInit() {
void _sensorLoad() {
/*
@ -218,6 +312,14 @@ void _sensorInit() {
*/
#if AM2320_SUPPORT
{
AM2320Sensor * sensor = new AM2320Sensor();
sensor->setAddress(AM2320_ADDRESS);
_sensors.push_back(sensor);
}
#endif
#if ANALOG_SUPPORT
{
AnalogSensor * sensor = new AnalogSensor();
@ -242,6 +344,14 @@ void _sensorInit() {
}
#endif
#if CSE7766_SUPPORT
{
CSE7766Sensor * sensor = new CSE7766Sensor();
sensor->setRX(CSE7766_PIN);
_sensors.push_back(sensor);
}
#endif
#if DALLAS_SUPPORT
{
DallasSensor * sensor = new DallasSensor();
@ -340,6 +450,35 @@ void _sensorInit() {
}
#endif
#if GEIGER_SUPPORT
{
GeigerSensor * sensor = new GeigerSensor(); // Create instance of thr Geiger module.
sensor->setGPIO(GEIGER_PIN); // Interrupt pin of the attached geiger counter board.
sensor->setMode(GEIGER_PIN_MODE); // This pin is an input.
sensor->setDebounceTime(GEIGER_DEBOUNCE); // Debounce time 25ms, because https://github.com/Trickx/espurna/wiki/Geiger-counter
sensor->setInterruptMode(GEIGER_INTERRUPT_MODE); // Interrupt triggering: edge detection rising.
sensor->setCPM2SievertFactor(GEIGER_CPM2SIEVERT); // Conversion factor from counts per minute to µSv/h
_sensors.push_back(sensor);
}
#endif
#if GUVAS12SD_SUPPORT
{
GUVAS12SDSensor * sensor = new GUVAS12SDSensor();
sensor->setGPIO(GUVAS12SD_PIN);
_sensors.push_back(sensor);
}
#endif
#if HCSR04_SUPPORT
{
HCSR04Sensor * sensor = new HCSR04Sensor();
sensor->setTrigger(HCSR04_TRIGGER);
sensor->setEcho(HCSR04_ECHO);
_sensors.push_back(sensor);
}
#endif
#if HLW8012_SUPPORT
{
HLW8012Sensor * sensor = new HLW8012Sensor();
@ -360,11 +499,34 @@ void _sensorInit() {
}
#endif
#if SENSEAIR_SUPPORT
{
SenseAirSensor * sensor = new SenseAirSensor();
sensor->setRX(SENSEAIR_RX_PIN);
sensor->setTX(SENSEAIR_TX_PIN);
_sensors.push_back(sensor);
}
#endif
#if PMSX003_SUPPORT
{
PMSX003Sensor * sensor = new PMSX003Sensor();
sensor->setRX(PMS_RX_PIN);
sensor->setTX(PMS_TX_PIN);
sensor->setType(PMS_TYPE);
_sensors.push_back(sensor);
}
#endif
#if PZEM004T_SUPPORT
{
PZEM004TSensor * sensor = new PZEM004TSensor();
#if PZEM004T_USE_SOFT
sensor->setRX(PZEM004T_RX_PIN);
sensor->setTX(PZEM004T_TX_PIN);
#else
sensor->setSerial(& PZEM004T_HW_PORT);
#endif
_sensors.push_back(sensor);
}
#endif
@ -385,6 +547,14 @@ void _sensorInit() {
}
#endif
#if TMP3X_SUPPORT
{
TMP3XSensor * sensor = new TMP3XSensor();
sensor->setType(TMP3X_TYPE);
_sensors.push_back(sensor);
}
#endif
#if V9261F_SUPPORT
{
V9261FSensor * sensor = new V9261FSensor();
@ -397,13 +567,133 @@ void _sensorInit() {
}
void _sensorCallback(unsigned char i, unsigned char type, const char * payload) {
DEBUG_MSG_P(PSTR("[SENSOR] Sensor #%u callback, type %u, payload: '%s'\n"), i, type, payload);
}
void _sensorInit() {
_sensors_ready = true;
for (unsigned char i=0; i<_sensors.size(); i++) {
// Do not process an already initialized sensor
if (_sensors[i]->ready()) continue;
DEBUG_MSG_P(PSTR("[SENSOR] Initializing %s\n"), _sensors[i]->description().c_str());
// Force sensor to reload config
_sensors[i]->begin();
if (!_sensors[i]->ready()) {
if (_sensors[i]->error() != 0) DEBUG_MSG_P(PSTR("[SENSOR] -> ERROR %d\n"), _sensors[i]->error());
_sensors_ready = false;
continue;
}
// Initialize magnitudes
for (unsigned char k=0; k<_sensors[i]->count(); k++) {
unsigned char type = _sensors[i]->type(k);
sensor_magnitude_t new_magnitude;
new_magnitude.sensor = _sensors[i];
new_magnitude.local = k;
new_magnitude.type = type;
new_magnitude.global = _counts[type];
new_magnitude.current = 0;
new_magnitude.filtered = 0;
new_magnitude.reported = 0;
new_magnitude.min_change = 0;
if (type == MAGNITUDE_DIGITAL) {
new_magnitude.filter = new MaxFilter();
} else if (type == MAGNITUDE_EVENTS || type == MAGNITUDE_GEIGER_CPM|| type == MAGNITUDE_GEIGER_SIEVERT) { // 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]);
_counts[type] = _counts[type] + 1;
}
// Hook callback
_sensors[i]->onEvent([i](unsigned char type, const char * payload) {
_sensorCallback(i, type, payload);
});
// Custom initializations
#if EMON_ANALOG_SUPPORT
if (_sensors[i]->getID() == SENSOR_EMON_ANALOG_ID) {
EmonAnalogSensor * sensor = (EmonAnalogSensor *) _sensors[i];
sensor->setCurrentRatio(0, getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat());
sensor->setVoltage(getSetting("pwrVoltage", EMON_MAINS_VOLTAGE).toInt());
}
#endif // EMON_ANALOG_SUPPORT
#if HLW8012_SUPPORT
if (_sensors[i]->getID() == SENSOR_HLW8012_ID) {
HLW8012Sensor * sensor = (HLW8012Sensor *) _sensors[i];
double value;
value = getSetting("pwrRatioC", 0).toFloat();
if (value > 0) sensor->setCurrentRatio(value);
value = getSetting("pwrRatioV", 0).toFloat();
if (value > 0) sensor->setVoltageRatio(value);
value = getSetting("pwrRatioP", 0).toFloat();
if (value > 0) sensor->setPowerRatio(value);
}
#endif // HLW8012_SUPPORT
#if CSE7766_SUPPORT
if (_sensors[i]->getID() == SENSOR_CSE7766_ID) {
CSE7766Sensor * sensor = (CSE7766Sensor *) _sensors[i];
double value;
value = getSetting("pwrRatioC", 0).toFloat();
if (value > 0) sensor->setCurrentRatio(value);
value = getSetting("pwrRatioV", 0).toFloat();
if (value > 0) sensor->setVoltageRatio(value);
value = getSetting("pwrRatioP", 0).toFloat();
if (value > 0) sensor->setPowerRatio(value);
}
#endif // CSE7766_SUPPORT
}
}
void _sensorConfigure() {
// General sensor settings
_sensor_read_interval = 1000 * constrain(getSetting("snsRead", SENSOR_READ_INTERVAL).toInt(), SENSOR_READ_MIN_INTERVAL, SENSOR_READ_MAX_INTERVAL);
_sensor_report_every = constrain(getSetting("snsReport", SENSOR_REPORT_EVERY).toInt(), SENSOR_REPORT_MIN_EVERY, SENSOR_REPORT_MAX_EVERY);
_sensor_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
_sensor_power_units = getSetting("pwrUnits", SENSOR_POWER_UNITS).toInt();
_sensor_energy_units = getSetting("energyUnits", SENSOR_ENERGY_UNITS).toInt();
_sensor_temperature_units = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt();
_sensor_temperature_correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat();
_sensor_humidity_correction = getSetting("humCorrection", SENSOR_HUMIDITY_CORRECTION).toFloat();
// Specific sensor settings
for (unsigned char i=0; i<_sensors.size(); i++) {
#if EMON_ANALOG_SUPPORT
@ -413,10 +703,7 @@ void _sensorConfigure() {
double value;
EmonAnalogSensor * sensor = (EmonAnalogSensor *) _sensors[i];
if (value = (getSetting("pwrExpectedP", 0).toInt() == 0)) {
value = getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat();
if (value > 0) sensor->setCurrentRatio(0, value);
} else {
if (value = getSetting("pwrExpectedP", 0).toInt()) {
sensor->expectedPower(0, value);
setSetting("pwrRatioC", sensor->getCurrentRatio(0));
}
@ -426,20 +713,36 @@ void _sensorConfigure() {
delSetting("pwrRatioC");
}
sensor->setVoltage(getSetting("pwrVoltage", EMON_MAINS_VOLTAGE).toInt());
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
}
sensor->setVoltage(getSetting("pwrVoltage", EMON_MAINS_VOLTAGE).toInt());
}
#endif // EMON_ANALOG_SUPPORT
// Force sensor to reload config
_sensors[i]->begin();
#if EMON_ADC121_SUPPORT
if (_sensors[i]->getID() == SENSOR_EMON_ADC121_ID) {
EmonADC121Sensor * sensor = (EmonADC121Sensor *) _sensors[i];
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
}
}
#endif
// Hook callback
_sensors[i]->onEvent([i](unsigned char type, const char * payload) {
_sensorCallback(i, type, payload);
});
#if EMON_ADS1X15_SUPPORT
if (_sensors[i]->getID() == SENSOR_EMON_ADS1X15_ID) {
EmonADS1X15Sensor * sensor = (EmonADS1X15Sensor *) _sensors[i];
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
}
}
#endif
#if HLW8012_SUPPORT
@ -452,25 +755,21 @@ void _sensorConfigure() {
if (value = getSetting("pwrExpectedC", 0).toFloat()) {
sensor->expectedCurrent(value);
setSetting("pwrRatioC", sensor->getCurrentRatio());
} else {
value = getSetting("pwrRatioC", 0).toFloat();
if (value > 0) sensor->setCurrentRatio(value);
}
if (value = getSetting("pwrExpectedV", 0).toInt()) {
sensor->expectedVoltage(value);
setSetting("pwrRatioV", sensor->getVoltageRatio());
} else {
value = getSetting("pwrRatioV", 0).toFloat();
if (value > 0) sensor->setVoltageRatio(value);
}
if (value = getSetting("pwrExpectedP", 0).toInt()) {
sensor->expectedPower(value);
setSetting("pwrRatioP", sensor->getPowerRatio());
} else {
value = getSetting("pwrRatioP", 0).toFloat();
if (value > 0) sensor->setPowerRatio(value);
}
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
}
if (getSetting("pwrResetCalibration", 0).toInt() == 1) {
@ -484,69 +783,59 @@ void _sensorConfigure() {
#endif // HLW8012_SUPPORT
}
#if CSE7766_SUPPORT
// General sensor settings
_sensor_read_interval = 1000 * constrain(getSetting("snsRead", SENSOR_READ_INTERVAL).toInt(), SENSOR_READ_MIN_INTERVAL, SENSOR_READ_MAX_INTERVAL);
_sensor_report_every = constrain(getSetting("snsReport", SENSOR_REPORT_EVERY).toInt(), SENSOR_REPORT_MIN_EVERY, SENSOR_REPORT_MAX_EVERY);
_sensor_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
_sensor_temperature_units = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt();
_sensor_temperature_correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat();
// Update filter sizes
for (unsigned char i=0; i<_magnitudes.size(); i++) {
_magnitudes[i].filter->resize(_sensor_report_every);
}
// Save settings
delSetting("pwrExpectedP");
delSetting("pwrExpectedC");
delSetting("pwrExpectedV");
delSetting("pwrResetCalibration");
//saveSettings();
if (_sensors[i]->getID() == SENSOR_CSE7766_ID) {
}
void _magnitudesInit() {
double value;
CSE7766Sensor * sensor = (CSE7766Sensor *) _sensors[i];
for (unsigned char i=0; i<_sensors.size(); i++) {
if (value = getSetting("pwrExpectedC", 0).toFloat()) {
sensor->expectedCurrent(value);
setSetting("pwrRatioC", sensor->getCurrentRatio());
}
BaseSensor * sensor = _sensors[i];
if (value = getSetting("pwrExpectedV", 0).toInt()) {
sensor->expectedVoltage(value);
setSetting("pwrRatioV", sensor->getVoltageRatio());
}
DEBUG_MSG_P(PSTR("[SENSOR] %s\n"), sensor->description().c_str());
if (sensor->error() != 0) DEBUG_MSG_P(PSTR("[SENSOR] -> ERROR %d\n"), sensor->error());
if (value = getSetting("pwrExpectedP", 0).toInt()) {
sensor->expectedPower(value);
setSetting("pwrRatioP", sensor->getPowerRatio());
}
for (unsigned char k=0; k<sensor->count(); k++) {
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
}
unsigned char type = sensor->type(k);
if (getSetting("pwrResetCalibration", 0).toInt() == 1) {
sensor->resetRatios();
delSetting("pwrRatioC");
delSetting("pwrRatioV");
delSetting("pwrRatioP");
}
sensor_magnitude_t new_magnitude;
new_magnitude.sensor = sensor;
new_magnitude.local = k;
new_magnitude.type = type;
new_magnitude.global = _counts[type];
new_magnitude.current = 0;
new_magnitude.filtered = 0;
new_magnitude.reported = 0;
new_magnitude.min_change = 0;
if (type == MAGNITUDE_DIGITAL) {
new_magnitude.filter = new MaxFilter();
} else if (type == MAGNITUDE_EVENTS) {
new_magnitude.filter = new MovingAverageFilter();
} else {
new_magnitude.filter = new MedianFilter();
}
new_magnitude.filter->resize(_sensor_report_every);
_magnitudes.push_back(new_magnitude);
DEBUG_MSG_P(PSTR("[SENSOR] -> %s:%d\n"), magnitudeTopic(type).c_str(), _counts[type]);
#endif // CSE7766_SUPPORT
_counts[type] = _counts[type] + 1;
}
}
// Update filter sizes
for (unsigned char i=0; i<_magnitudes.size(); i++) {
_magnitudes[i].filter->resize(_sensor_report_every);
}
// Save settings
delSetting("pwrExpectedP");
delSetting("pwrExpectedC");
delSetting("pwrExpectedV");
delSetting("pwrResetCalibration");
delSetting("pwrResetE");
saveSettings();
}
// -----------------------------------------------------------------------------
@ -608,6 +897,14 @@ String magnitudeUnits(unsigned char type) {
if (type < MAGNITUDE_MAX) {
if ((type == MAGNITUDE_TEMPERATURE) && (_sensor_temperature_units == TMP_FAHRENHEIT)) {
strncpy_P(buffer, magnitude_fahrenheit, sizeof(buffer));
} else if (
(type == MAGNITUDE_ENERGY || type == MAGNITUDE_ENERGY_DELTA) &&
(_sensor_energy_units == ENERGY_KWH)) {
strncpy_P(buffer, magnitude_kwh, sizeof(buffer));
} else if (
(type == MAGNITUDE_POWER_ACTIVE || type == MAGNITUDE_POWER_APPARENT || type == MAGNITUDE_POWER_REACTIVE) &&
(_sensor_power_units == POWER_KILOWATTS)) {
strncpy_P(buffer, magnitude_kw, sizeof(buffer));
} else {
strncpy_P(buffer, magnitude_units[type], sizeof(buffer));
}
@ -619,19 +916,21 @@ String magnitudeUnits(unsigned char type) {
void sensorSetup() {
// Backwards compatibility
moveSetting("powerUnits", "pwrUnits");
// Load sensors
_sensorLoad();
_sensorInit();
// Configure stored values
_sensorConfigure();
// Load magnitudes
_magnitudesInit();
#if WEB_SUPPORT
// Websockets
wsOnSendRegister(_sensorWebSocketStart);
wsOnReceiveRegister(_sensorWebSocketOnReceive);
wsOnSendRegister(_sensorWebSocketSendData);
wsOnAfterParseRegister(_sensorConfigure);
@ -651,8 +950,14 @@ void sensorSetup() {
void sensorLoop() {
static unsigned long last_update = 0;
static unsigned long report_count = 0;
// Check if we still have uninitialized sensors
static unsigned long last_init = 0;
if (!_sensors_ready) {
if (millis() - last_init > SENSOR_INIT_INTERVAL) {
last_init = millis();
_sensorInit();
}
}
if (_magnitudes.size() == 0) return;
@ -660,6 +965,8 @@ void sensorLoop() {
_sensorTick();
// Check if we should read new data
static unsigned long last_update = 0;
static unsigned long report_count = 0;
if (millis() - last_update > _sensor_read_interval) {
last_update = millis();
@ -672,6 +979,11 @@ void sensorLoop() {
// Pre-read hook
_sensorPre();
// Get the first relay state
#if SENSOR_POWER_CHECK_STATUS
bool relay_off = (relayCount() > 0) && (relayStatus(0) == 0);
#endif
// Get readings
for (unsigned char i=0; i<_magnitudes.size(); i++) {
@ -679,17 +991,34 @@ void sensorLoop() {
if (magnitude.sensor->status()) {
unsigned char decimals = _magnitudeDecimals(magnitude.type);
current = magnitude.sensor->value(magnitude.local);
// Completely remove spurious values if relay is OFF
#if SENSOR_POWER_CHECK_STATUS
if (relay_off) {
if (magnitude.type == MAGNITUDE_POWER_ACTIVE ||
magnitude.type == MAGNITUDE_POWER_REACTIVE ||
magnitude.type == MAGNITUDE_POWER_APPARENT ||
magnitude.type == MAGNITUDE_CURRENT ||
magnitude.type == MAGNITUDE_ENERGY_DELTA
) {
current = 0;
}
}
#endif
magnitude.filter->add(current);
// Special case
if (magnitude.type == MAGNITUDE_EVENTS) current = magnitude.filter->result();
if (magnitude.type == MAGNITUDE_EVENTS) {
current = magnitude.filter->result();
}
current = _magnitudeProcess(magnitude.type, current);
_magnitudes[i].current = current;
unsigned char decimals = _magnitudeDecimals(magnitude.type);
// Debug
#if SENSOR_DEBUG
{


+ 198
- 0
code/espurna/sensors/AM2320Sensor.h View File

@ -0,0 +1,198 @@
// -----------------------------------------------------------------------------
// AM2320 Humidity & Temperature sensor over I2C
// Copyright (C) 2018 by Mustafa Tufan
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && AM2320_SUPPORT
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
// https://akizukidenshi.com/download/ds/aosong/AM2320.pdf
#define AM2320_I2C_READ_REGISTER_DATA 0x03 // Read one or more data registers
#define AM2320_I2C_WRITE_MULTIPLE_REGISTERS 0x10 // Multiple sets of binary data to write multiple registers
/*
Register | Address | Register | Address | Register | Address | Register | Address
-----------------+---------+--------------------+---------+-------------------------+---------+-----------+--------
High humidity | 0x00 | Model High | 0x08 | Users register a high | 0x10 | Retention | 0x18
Low humidity | 0x01 | Model Low | 0x09 | Users register a low | 0x11 | Retention | 0x19
High temperature | 0x02 | The version number | 0x0A | Users register 2 high | 0x12 | Retention | 0x1A
Low temperature | 0x03 | Device ID(24-31)Bit| 0x0B | Users register 2 low | 0x13 | Retention | 0x1B
Retention | 0x04 | Device ID(24-31)Bit| 0x0C | Retention | 0x14 | Retention | 0x1C
Retention | 0x05 | Device ID(24-31)Bit| 0x0D | Retention | 0x15 | Retention | 0x1D
Retention | 0x06 | Device ID(24-31)Bit| 0x0E | Retention | 0x16 | Retention | 0x1E
Retention | 0x07 | Status Register | 0x0F | Retention | 0x17 | Retention | 0x1F
*/
class AM2320Sensor : public I2CSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
AM2320Sensor(): I2CSensor() {
_count = 2;
_sensor_id = SENSOR_AM2320_ID;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
// I2C auto-discover
unsigned char addresses[] = {0x23, 0x5C, 0xB8};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[25];
snprintf(buffer, sizeof(buffer), "AM2320 @ I2C (0x%02X)", _address);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
_read();
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _temperature;
if (index == 1) return _humidity;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
/*
// Get device model, version, device_id
void _init() {
i2c_wakeup(_address);
delayMicroseconds(800);
unsigned char _buffer[11];
// 0x08 = read address
// 7 = number of bytes to read
if (i2c_write_uint8(_address, AM2320_I2C_READ_REGISTER_DATA, 0x08, 7) != I2C_TRANS_SUCCESS) {
_error = SENSOR_ERROR_TIMEOUT;
return false;
}
uint16_t model = (_buffer[2] << 8) | _buffer[3];
uint8_t version = _buffer[4];
uint32_t device_id = _buffer[8] << 24 | _buffer[7] << 16 | _buffer[6] << 8 | _buffer[5];
}
*/
void _read() {
i2c_wakeup(_address);
// waiting time of at least 800 μs, the maximum 3000 μs
delayMicroseconds(800); // just to be on safe side
// 0x00 = read address
// 4 = number of bytes to read
if (i2c_write_uint8(_address, AM2320_I2C_READ_REGISTER_DATA, 0x00, 4) != I2C_TRANS_SUCCESS) {
_error = SENSOR_ERROR_TIMEOUT;
return;
}
unsigned char _buffer[8];
// waiting time of at least 800 μs, the maximum 3000 μs
delayMicroseconds(800 + ((3000-800)/2) );
i2c_read_buffer(_address, _buffer, 8);
// Humidity : 01F4 = (1×256)+(F×16)+4 = 500 => humidity = 500÷10 = 50.0 %
// 0339 = (3×256)+(3×16)+9 = 825 => humidity = 825÷10 = 82.5 %
// Temperature: 00FA = (F×16)+A = 250 => temperature = 250÷10 = 25.0 C
// 0115 = (1×256)+(1×16)+5 = 277 => temperature = 277÷10 = 27.7 C
// Temperature resolution is 16Bit, temperature highest bit (Bit 15) is equal to 1 indicates a negative temperature
// _buffer 0 = function code
// _buffer 1 = number of bytes
// _buffer 2-3 = high/low humidity
// _buffer 4-5 = high/low temperature
// _buffer 6-7 = CRC low/high
unsigned int responseCRC = 0;
responseCRC = ((responseCRC | _buffer[7]) << 8 | _buffer[6]);
if (responseCRC == _CRC16(_buffer)) {
int foo = (_buffer[2] << 8) | _buffer[3];
_humidity = foo / 10.0;
foo = ((_buffer[4] & 0x7F) << 8) | _buffer[5]; // clean bit 15 and merge
_temperature = foo / 10.0;
if (_buffer[4] & 0x80) { // is bit 15 == 1
_temperature = _temperature * -1; // negative temperature
}
_error = SENSOR_ERROR_OK;
} else {
_error = SENSOR_ERROR_CRC;
return;
}
}
unsigned int _CRC16(unsigned char buffer[]) {
unsigned int crc16 = 0xFFFF;
for (unsigned int i = 0; i < 6; i++) {
crc16 ^= buffer[i];
for (unsigned int b = 8; b != 0; b--) {
if (crc16 & 0x01) { // is lsb set
crc16 >>= 1;
crc16 ^= 0xA001;
} else {
crc16 >>= 1;
}
}
}
return crc16;
}
double _temperature = 0;
double _humidity = 0;
};
#endif // SENSOR_SUPPORT && AM2320_SUPPORT

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

@ -7,6 +7,10 @@
#pragma once
// Set ADC to TOUT pin
#undef ADC_MODE_VALUE
#define ADC_MODE_VALUE ADC_TOUT
#include "Arduino.h"
#include "BaseSensor.h"
@ -30,11 +34,12 @@ class AnalogSensor : public BaseSensor {
// Initialization method, must be idempotent
void begin() {
pinMode(0, INPUT);
_ready = true;
}
// Descriptive name of the sensor
String description() {
return String("ANALOG @ GPIO0");
return String("ANALOG @ TOUT");
}
// Descriptive name of the slot # index


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

@ -7,6 +7,9 @@
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
@ -55,7 +58,6 @@ class BH1750Sensor : public I2CSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
// I2C auto-discover
unsigned char addresses[] = {0x23, 0x5C};
@ -64,6 +66,8 @@ class BH1750Sensor : public I2CSensor {
// Run configuration on next update
_run_configure = true;
_ready = true;
_dirty = false;
}


+ 23
- 12
code/espurna/sensors/BMX280Sensor.h View File

@ -7,6 +7,9 @@
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
@ -67,18 +70,9 @@ class BMX280Sensor : public I2CSensor {
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_dirty = false;
_chip = 0;
// I2C auto-discover
_address = _begin_i2c(_address, sizeof(BMX280Sensor::addresses), BMX280Sensor::addresses);
if (_address == 0) return;
// Init
_init();
_dirty = !_ready;
}
// Descriptive name of the sensor
@ -192,15 +186,31 @@ class BMX280Sensor : public I2CSensor {
void _init() {
// Make sure sensor had enough time to turn on. BMX280 requires 2ms to start up
delay(10);
nice_delay(10);
// No chip ID by default
_chip = 0;
// I2C auto-discover
_address = _begin_i2c(_address, sizeof(BMX280Sensor::addresses), BMX280Sensor::addresses);
if (_address == 0) return;
// Check sensor correctly initialized
_chip = i2c_read_uint8(_address, BMX280_REGISTER_CHIPID);
if ((_chip != BMX280_CHIP_BME280) && (_chip != BMX280_CHIP_BMP280)) {
_chip = 0;
i2cReleaseLock(_address);
_previous_address = 0;
_error = SENSOR_ERROR_UNKNOWN_ID;
// Setting _address to 0 forces auto-discover
// This might be necessary at this stage if there is a
// different sensor in the hardcoded address
_address = 0;
return;
}
_count = 0;
@ -233,6 +243,7 @@ class BMX280Sensor : public I2CSensor {
_measurement_delay = _measurementTime();
_run_init = false;
_ready = true;
}
@ -293,7 +304,7 @@ class BMX280Sensor : public I2CSensor {
value = (value & 0xFC) + 0x01;
i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, value);
delay(_measurement_delay);
nice_delay(_measurement_delay);
}


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

@ -18,6 +18,8 @@
#define SENSOR_ERROR_CRC 5 // Sensor data corrupted
#define SENSOR_ERROR_I2C 6 // Wrong or locked I2C address
#define SENSOR_ERROR_GPIO_USED 7 // The GPIO is already in use
#define SENSOR_ERROR_CALIBRATION 8 // Calibration error or Not calibrated
#define SENSOR_ERROR_OTHER 99 // Any other error
typedef std::function<void(unsigned char, const char *)> TSensorCallback;
@ -70,8 +72,11 @@ class BaseSensor {
// Sensor ID
unsigned char getID() { return _sensor_id; };
// Return sensor status (true for ready)
bool status() { return _error == 0; }
// Return status (true if no errors)
bool status() { return 0 == _error; }
// Return ready status (true for ready)
bool ready() { return _ready; }
// Return sensor last internal error
int error() { return _error; }
@ -89,6 +94,7 @@ class BaseSensor {
int _error = 0;
bool _dirty = true;
unsigned char _count = 0;
bool _ready = false;
};


+ 378
- 0
code/espurna/sensors/CSE7766Sensor.h View File

@ -0,0 +1,378 @@
// -----------------------------------------------------------------------------
// CSE7766 based power monitor
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// http://www.chipsea.com/UploadFiles/2017/08/11144342F01B5662.pdf
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && CSE7766_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <SoftwareSerial.h>
class CSE7766Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
CSE7766Sensor(): BaseSensor(), _data() {
_count = 4;
_sensor_id = SENSOR_CSE7766_ID;
}
~CSE7766Sensor() {
if (_serial) delete _serial;
}
// ---------------------------------------------------------------------
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setInverted(bool inverted) {
if (_inverted == inverted) return;
_inverted = inverted;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
bool getInverted() {
return _inverted;
}
// ---------------------------------------------------------------------
void expectedCurrent(double expected) {
if ((expected > 0) && (_current > 0)) {
_ratioC = _ratioC * (expected / _current);
}
}
void expectedVoltage(unsigned int expected) {
if ((expected > 0) && (_voltage > 0)) {
_ratioV = _ratioV * (expected / _voltage);
}
}
void expectedPower(unsigned int expected) {
if ((expected > 0) && (_active > 0)) {
_ratioP = _ratioP * (expected / _active);
}
}
void setCurrentRatio(double value) {
_ratioC = value;
};
void setVoltageRatio(double value) {
_ratioV = value;
};
void setPowerRatio(double value) {
_ratioP = value;
};
double getCurrentRatio() {
return _ratioC;
};
double getVoltageRatio() {
return _ratioV;
};
double getPowerRatio() {
return _ratioP;
};
void resetRatios() {
_ratioC = _ratioV = _ratioP = 1.0;
}
void resetEnergy() {
_energy = 0;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_serial) delete _serial;
if (1 == _pin_rx) {
Serial.begin(CSE7766_BAUDRATE);
} else {
_serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32);
_serial->enableIntTx(false);
_serial->begin(CSE7766_BAUDRATE);
}
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
if (1 == _pin_rx) {
snprintf(buffer, sizeof(buffer), "CSE7766 @ HwSerial");
} else {
snprintf(buffer, sizeof(buffer), "CSE7766 @ SwSerial(%u,NULL)", _pin_rx);
}
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_pin_rx);
}
// Loop-like method, call it in your main loop
void tick() {
_read();
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
if (index == 3) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _current;
if (index == 1) return _voltage;
if (index == 2) return _active;
if (index == 3) return _energy;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
/**
* "
* Checksum is the sum of all data
* except for packet header and packet tail lowering by 8bit (...)
* "
* @return bool
*/
bool _checksum() {
unsigned char checksum = 0;
for (unsigned char i = 2; i < 23; i++) {
checksum += _data[i];
}
return checksum == _data[23];
}
void _process() {
// Sample data:
// 55 5A 02 E9 50 00 03 31 00 3E 9E 00 0D 30 4F 44 F8 00 12 65 F1 81 76 72 (w/ load)
// F2 5A 02 E9 50 00 03 2B 00 3E 9E 02 D7 7C 4F 44 F8 CF A5 5D E1 B3 2A B4 (w/o load)
#if SENSOR_DEBUG
DEBUG_MSG("[SENSOR] CSE7766: _process: ");
for (byte i=0; i<24; i++) DEBUG_MSG("%02X ", _data[i]);
DEBUG_MSG("\n");
#endif
// Checksum
if (!_checksum()) {
_error = SENSOR_ERROR_CRC;
#if SENSOR_DEBUG
DEBUG_MSG("[SENSOR] CSE7766: Checksum error\n");
#endif
return;
}
// Calibration
if (0xAA == _data[0]) {
_error = SENSOR_ERROR_CALIBRATION;
#if SENSOR_DEBUG
DEBUG_MSG("[SENSOR] CSE7766: Chip not calibrated\n");
#endif
return;
}
if ((_data[0] & 0xFC) > 0xF0) {
_error = SENSOR_ERROR_OTHER;
#if SENSOR_DEBUG
if (0xF1 == _data[0] & 0xF1) DEBUG_MSG("[SENSOR] CSE7766: Abnormal coefficient storage area\n");
if (0xF2 == _data[0] & 0xF2) DEBUG_MSG("[SENSOR] CSE7766: Power cycle exceeded range\n");
if (0xF4 == _data[0] & 0xF4) DEBUG_MSG("[SENSOR] CSE7766: Current cycle exceeded range\n");
if (0xF8 == _data[0] & 0xF8) DEBUG_MSG("[SENSOR] CSE7766: Voltage cycle exceeded range\n");
#endif
return;
}
// Calibration coefficients
unsigned long _coefV = (_data[2] << 16 | _data[3] << 8 | _data[4] ); // 190770
unsigned long _coefC = (_data[8] << 16 | _data[9] << 8 | _data[10]); // 16030
unsigned long _coefP = (_data[14] << 16 | _data[15] << 8 | _data[16]); // 5195000
// Adj: this looks like a sampling report
uint8_t adj = _data[20]; // F1 11110001
// Calculate voltage
_voltage = 0;
if ((adj & 0x40) == 0x40) {
unsigned long voltage_cycle = _data[5] << 16 | _data[6] << 8 | _data[7]; // 817
_voltage = _ratioV * _coefV / voltage_cycle / CSE7766_V2R; // 190700 / 817 = 233.41
}
// Calculate power
_active = 0;
if ((adj & 0x10) == 0x10) {
if ((_data[0] & 0xF2) != 0xF2) {
unsigned long power_cycle = _data[17] << 16 | _data[18] << 8 | _data[19]; // 4709
_active = _ratioP * _coefP / power_cycle / CSE7766_V1R / CSE7766_V2R; // 5195000 / 4709 = 1103.20
}
}
// Calculate current
_current = 0;
if ((adj & 0x20) == 0x20) {
if (_active > 0) {
unsigned long current_cycle = _data[11] << 16 | _data[12] << 8 | _data[13]; // 3376
_current = _ratioC * _coefC / current_cycle / CSE7766_V1R; // 16030 / 3376 = 4.75
}
}
// Calculate energy
unsigned int difference;
static unsigned int cf_pulses_last = 0;
unsigned int cf_pulses = _data[21] << 8 | _data[22];
if (0 == cf_pulses_last) cf_pulses_last = cf_pulses;
if (cf_pulses < cf_pulses_last) {
difference = cf_pulses + (0xFFFF - cf_pulses_last) + 1;
} else {
difference = cf_pulses - cf_pulses_last;
}
_energy += difference * (float) _coefP / 1000000.0;
cf_pulses_last = cf_pulses;
}
void _read() {
_error = SENSOR_ERROR_OK;
static unsigned char index = 0;
static unsigned long last = millis();
while (_serial_available()) {
// A 24 bytes message takes ~55ms to go through at 4800 bps
// Reset counter if more than 1000ms have passed since last byte.
if (millis() - last > CSE7766_SYNC_INTERVAL) index = 0;
last = millis();
uint8_t byte = _serial_read();
// first byte must be 0x55 or 0xF?
if (0 == index) {
if ((0x55 != byte) && (byte < 0xF0)) {
continue;
}
// second byte must be 0x5A
} else if (1 == index) {
if (0x5A != byte) {
index = 0;
continue;
}
}
_data[index++] = byte;
if (index > 23) {
_serial_flush();
break;
}
}
// Process packet
if (24 == index) {
_process();
index = 0;
}
}
// ---------------------------------------------------------------------
bool _serial_available() {
if (1 == _pin_rx) {
return Serial.available();
} else {
return _serial->available();
}
}
void _serial_flush() {
if (1 == _pin_rx) {
return Serial.flush();
} else {
return _serial->flush();
}
}
uint8_t _serial_read() {
if (1 == _pin_rx) {
return Serial.read();
} else {
return _serial->read();
}
}
// ---------------------------------------------------------------------
unsigned int _pin_rx = CSE7766_PIN;
bool _inverted = CSE7766_PIN_INVERSE;
SoftwareSerial * _serial = NULL;
double _active = 0;
double _voltage = 0;
double _current = 0;
double _energy = 0;
double _ratioV = 1.0;
double _ratioC = 1.0;
double _ratioP = 1.0;
unsigned char _data[24];
};
#endif // SENSOR_SUPPORT && CSE7766_SUPPORT

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

@ -75,6 +75,7 @@ class DHTSensor : public BaseSensor {
_previous = _gpio;
_count = 2;
_ready = true;
}
@ -139,13 +140,13 @@ class DHTSensor : public BaseSensor {
if (++_errors > DHT_MAX_ERRORS) {
_errors = 0;
digitalWrite(_gpio, HIGH);
delay(250);
nice_delay(250);
}
pinMode(_gpio, OUTPUT);
noInterrupts();
digitalWrite(_gpio, LOW);
if (_type == DHT_CHIP_DHT11) {
delay(20);
nice_delay(20);
} else {
delayMicroseconds(500);
}


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

@ -64,7 +64,6 @@ class DallasSensor : public BaseSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
// Manage GPIO lock
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
@ -93,6 +92,8 @@ class DallasSensor : public BaseSensor {
} else {
_previous = _gpio;
}
_ready = true;
_dirty = false;
}
@ -132,15 +133,6 @@ class DallasSensor : public BaseSensor {
data[i] = _wire->read();
}
#if false
Serial.printf("[DS18B20] Data = ");
for (unsigned char i = 0; i < DS_DATA_SIZE; i++) {
Serial.printf("%02X ", data[i]);
}
Serial.printf(" CRC = %02X\n", OneWire::crc8(data, DS_DATA_SIZE-1));
#endif
if (_wire->reset() != 1) {
// Force a CRC check error
_devices[index].data[0] = _devices[index].data[0] + 1;


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

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


+ 25
- 14
code/espurna/sensors/ECH1560Sensor.h View File

@ -67,18 +67,25 @@ class ECH1560Sensor : public BaseSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
pinMode(_clk, INPUT);
pinMode(_miso, INPUT);
_enableInterrupts(true);
_dirty = false;
_ready = true;
}
// Loop-like method, call it in your main loop
void tick() {
if (_dosync) _sync();
}
// Descriptive name of the sensor
String description() {
char buffer[25];
snprintf(buffer, sizeof(buffer), "ECH1560 @ GPIO(%u,%u)", _clk, _miso);
char buffer[35];
snprintf(buffer, sizeof(buffer), "ECH1560 (CLK,SDO) @ GPIO(%u,%u)", _clk, _miso);
return String(buffer);
}
@ -119,7 +126,7 @@ class ECH1560Sensor : public BaseSensor {
_clk_count = 0;
// register how long the ClkHigh is high to evaluate if we are at the part wher clk goes high for 1-2 ms
// register how long the ClkHigh is high to evaluate if we are at the part where clk goes high for 1-2 ms
while (digitalRead(_clk) == HIGH) {
_clk_count += 1;
delayMicroseconds(30); //can only use delayMicroseconds in an interrupt.
@ -181,17 +188,17 @@ class ECH1560Sensor : public BaseSensor {
while (_bits_count < 40); // skip the uninteresting 5 first bytes
_bits_count = 0;
while (_bits_count < 24) { // loop through the next 3 Bytes (6-8) and save byte 6 and 7 in Ba and Bb
while (_bits_count < 24) { // loop through the next 3 Bytes (6-8) and save byte 6 and 7 in byte1 and byte2
if (_nextbit) {
if (_bits_count < 9) { // first Byte/8 bits in Ba
if (_bits_count < 9) { // first Byte/8 bits in byte1
byte1 = byte1 << 1;
if (digitalRead(_miso) == HIGH) byte1 |= 1;
_nextbit = false;
} else if (_bits_count < 17) { // bit 9-16 is byte 7, stor in Bb
} else if (_bits_count < 17) { // bit 9-16 is byte 7, store in byte2
byte2 = byte2 << 1;
if (digitalRead(_miso) == HIGH) byte2 |= 1;
@ -203,9 +210,9 @@ class ECH1560Sensor : public BaseSensor {
}
if (byte2 != 3) { // if bit Bb is not 3, we have reached the important part, U is allready in Ba and Bb and next 8 Bytes will give us the Power.
if (byte2 != 3) { // if bit byte2 is not 3, we have reached the important part, U is allready in byte1 and byte2 and next 8 Bytes will give us the Power.
// voltage = 2 * (Ba + Bb / 255)
// voltage = 2 * (byte1 + byte2 / 255)
_voltage = 2.0 * ((float) byte1 + (float) byte2 / 255.0);
// power:
@ -217,7 +224,7 @@ class ECH1560Sensor : public BaseSensor {
byte2 = 0;
byte3 = 0;
while (_bits_count < 24) { //store byte 6, 7 and 8 in Ba and Bb & Bc.
while (_bits_count < 24) { //store byte 6, 7 and 8 in byte1 and byte2 & byte3.
if (_nextbit) {
@ -249,7 +256,7 @@ class ECH1560Sensor : public BaseSensor {
byte3 = 255 - byte3;
}
// power = (Ba*255+Bb+Bc/255)/2
// power = (byte1*255+byte2+byte3/255)/2
_apparent = ( (float) byte1 * 255 + (float) byte2 + (float) byte3 / 255.0) / 2;
_current = _apparent / _voltage;
@ -257,9 +264,13 @@ class ECH1560Sensor : public BaseSensor {
}
// If Bb is not 3 or something else than 0, something is wrong!
if (byte2 == 0) _dosync = false;
// If byte2 is not 3 or something else than 0, something is wrong!
if (byte2 == 0) {
_dosync = false;
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("Nothing connected, or out of sync!\n"));
#endif
}
}
// ---------------------------------------------------------------------


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

@ -147,7 +147,6 @@ class EmonADS1X15Sensor : public EmonSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
// Discover
unsigned char addresses[] = {0x48, 0x49, 0x4A, 0x4B};
@ -302,10 +301,6 @@ class EmonADS1X15Sensor : public EmonSensor {
}
config |= ((channel + 4) << 12); // Set single-ended input channel (0x4000 - 0x7000)
#if SENSOR_DEBUG
//Serial.printf("[EMON] ADS1X115 Config Registry: %04X\n", config);
#endif
// Write config register to the ADC
i2c_write_uint16(_address, ADS1X15_REG_POINTER_CONFIG, config);
@ -319,7 +314,7 @@ class EmonADS1X15Sensor : public EmonSensor {
setConfigRegistry(channel, true, false);
setConfigRegistry(channel, false, false);
setConfigRegistry(channel, false, true);
delay(10);
nice_delay(10);
readADC(channel);
previous = channel;
}


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

@ -7,6 +7,10 @@
#pragma once
// Set ADC to TOUT pin
#undef ADC_MODE_VALUE
#define ADC_MODE_VALUE ADC_TOUT
#include "Arduino.h"
#include "EmonSensor.h"


+ 28
- 7
code/espurna/sensors/EmonSensor.h View File

@ -7,8 +7,14 @@
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
extern "C" {
#include "libs/fs_math.h"
}
class EmonSensor : public I2CSensor {
@ -39,9 +45,16 @@ class EmonSensor : public I2CSensor {
if (actual == 0) return;
if (expected == actual) return;
_current_ratio[channel] = _current_ratio[channel] * ((double) expected / (double) actual);
calculateFactors(channel);
_dirty = true;
}
void resetEnergy() {
for (unsigned char i=0; i<_channels; i++) {
_energy[i] = 0;
}
}
// ---------------------------------------------------------------------
void setVoltage(double voltage) {
@ -60,6 +73,7 @@ class EmonSensor : public I2CSensor {
if (channel >= _channels) return;
if (_current_ratio[channel] == current_ratio) return;
_current_ratio[channel] = current_ratio;
calculateFactors(channel);
_dirty = true;
}
@ -95,8 +109,7 @@ class EmonSensor : public I2CSensor {
for (unsigned char i=0; i<_channels; i++) {
_energy[i] = _current[i] = 0;
_pivot[i] = _adc_counts >> 1;
_current_factor[i] = _current_ratio[i] * _reference / _adc_counts;
_multiplier[i] = calculateMultiplier(_current_factor[i]);
calculateFactors(i);
}
#if SENSOR_DEBUG
@ -109,6 +122,10 @@ class EmonSensor : public I2CSensor {
}
#endif
_ready = true;
_dirty = false;
}
protected:
@ -131,18 +148,22 @@ class EmonSensor : public I2CSensor {
virtual unsigned int readADC(unsigned char channel) {}
unsigned int calculateMultiplier(double current_factor) {
void calculateFactors(unsigned char channel) {
_current_factor[channel] = _current_ratio[channel] * _reference / _adc_counts;
unsigned int s = 1;
unsigned int i = 1;
unsigned int m = s * i;
unsigned int multiplier;
while (m * current_factor < 1) {
while (m * _current_factor[channel] < 1) {
multiplier = m;
i = (i == 1) ? 2 : (i == 2) ? 5 : 1;
if (i == 1) s *= 10;
m = s * i;
}
return multiplier;
_multiplier[channel] = multiplier;
}
double read(unsigned char channel) {
@ -178,7 +199,7 @@ class EmonSensor : public I2CSensor {
}
// Calculate current
double rms = _samples > 0 ? sqrt(sum / _samples) : 0;
double rms = _samples > 0 ? fs_sqrt(sum / _samples) : 0;
double current = _current_factor[channel] * rms;
current = (double) (int(current * _multiplier[channel]) - 1) / _multiplier[channel];
if (current < 0) current = 0;
@ -192,7 +213,7 @@ class EmonSensor : public I2CSensor {
DEBUG_MSG("[EMON] Min value: %d\n", min);
DEBUG_MSG("[EMON] Midpoint value: %d\n", int(_pivot[channel]));
DEBUG_MSG("[EMON] RMS value: %d\n", int(rms));
DEBUG_MSG("[EMON] Current (mA): %d\n", int(current));
DEBUG_MSG("[EMON] Current (mA): %d\n", int(1000 * current));
#endif
// Check timing


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

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


+ 174
- 0
code/espurna/sensors/GUVAS12SDSensor.h View File

@ -0,0 +1,174 @@
// -----------------------------------------------------------------------------
// GUVA-S12SD UV Sensor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// by Mustafa Tufan
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && GUVAS12SD_SUPPORT
#pragma once
// Set ADC to TOUT pin
#undef ADC_MODE_VALUE
#define ADC_MODE_VALUE ADC_TOUT
#include "Arduino.h"
#include "BaseSensor.h"
// http://www.eoc-inc.com/genicom/GUVA-S12SD.pdf
//
// GUVA-S12D has a wide spectral range of 200nm-400nm
// The output voltage and the UV index is linear, illumination intensity = 307 * Vsig where: Vsig is the value of voltage measured from the SIG pin of the interface, unit V.
// illumination intensity unit: mW/m2 for the combination strength of UV light with wavelength range: 200nm-400nm
// UV Index = illumination intensity / 200
//
// UV Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 10+
// -----------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+--------
// mV | <50 | 227 | 318 | 408 | 503 | 606 | 696 | 795 | 881 | 976 | 1079 | 1170+
// analog val | <10 | 46 | 65 | 83 | 103 | 124 | 142 | 162 | 180 | 200 | 221 | 240+
//
#define UV_SAMPLE_RATE 1
class GUVAS12SDSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
GUVAS12SDSensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_GUVAS12SD_ID;
}
~GUVAS12SDSensor() {
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
_gpio = gpio;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
// Manage GPIO lock
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
_previous = GPIO_NONE;
if (!gpioGetLock(_gpio)) {
_error = SENSOR_ERROR_GPIO_USED;
return;
}
_previous = _gpio;
_ready = true;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
_read();
}
// Descriptive name of the sensor
String description() {
char buffer[18];
snprintf(buffer, sizeof(buffer), "GUVAS12SD @ GPIO%d", _gpio);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_UV;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _uvindex;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _read() {
int _average = 0;
#if UV_SAMPLE_RATE == 1
_average = analogRead(0);
#else
for (unsigned int i=0; i < UV_SAMPLE_RATE; i++) {
_average += analogRead(0);
nice_delay(2);
}
_average = (_average / UV_SAMPLE_RATE);
#endif
// _sensormV = _average / 1023*3.3;
if (_average < 10) {
_uvindex = 0;
} else if (_average < 46) {
_uvindex = (_average - 10) / (46-10);
} else if (_average < 65) {
_uvindex = 1 + ((_average - 46) / (65-46));
} else if (_average < 83) {
_uvindex = 2 + ((_average - 65) / (83-65));
} else if (_average < 103) {
_uvindex = 3 + ((_average - 83) / (103- 83));
} else if (_average < 124) {
_uvindex = 4 + ((_average - 103) / (124-103));
} else if (_average < 142) {
_uvindex = 5 + ((_average - 124) / (142-124));
} else if (_average < 162) {
_uvindex = 6 + ((_average - 142) / (162-142));
} else if (_average < 180) {
_uvindex = 7 + ((_average - 162) / (180-162));
} else if (_average < 200) {
_uvindex = 8 + ((_average - 180) / (200-180));
} else if (_average < 221) {
_uvindex = 9 + ((_average - 200) / (221-200));
} else {
_uvindex = 10;
}
}
unsigned char _gpio = GPIO_NONE;
unsigned char _previous = GPIO_NONE;
double _uvindex = 0;
};
#endif // SENSOR_SUPPORT && GUVAS12SD_SUPPORT

+ 298
- 0
code/espurna/sensors/GeigerSensor.h View File

@ -0,0 +1,298 @@
// -----------------------------------------------------------------------------
// Geiger Sensor based on Event Counter Sensor
// Copyright (C) 2018 by Sven Kopetzki <skopetzki at web dot de>
// Documentation: https://github.com/Trickx/espurna/wiki/Geiger-counter
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && GEIGER_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
class GeigerSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
GeigerSensor() : BaseSensor() {
_count = 2;
_sensor_id = SENSOR_GEIGER_ID;
}
~GeigerSensor() {
_enableInterrupts(false);
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
_gpio = gpio;
}
void setMode(unsigned char mode) {
_mode = mode;
}
void setInterruptMode(unsigned char mode) {
_interrupt_mode = mode;
}
void setDebounceTime(unsigned long debounce) {
_debounce = debounce;
}
void setCPM2SievertFactor(unsigned int cpm2sievert) {
_cpm2sievert = cpm2sievert;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
unsigned char getMode() {
return _mode;
}
unsigned char getInterruptMode() {
return _interrupt_mode;
}
unsigned long getDebounceTime() {
return _debounce;
}
unsigned long getCPM2SievertFactor() {
return _cpm2sievert;
}
// ---------------------------------------------------------------------
// Sensors API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
// Defined outside the class body
void begin() {
pinMode(_gpio, _mode);
_enableInterrupts(true);
_ready = true;
}
// Descriptive name of the sensor
String description() {
char buffer[20];
snprintf(buffer, sizeof(buffer), "µSv/h @ GPIO%d", _gpio);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
char buffer[30];
unsigned char i=0;
#if GEIGER_REPORT_CPM
if (index == i++) {
snprintf(buffer, sizeof(buffer), "Counts per Minute @ GPIO%d", _gpio);
return String(buffer);
}
#endif
#if GEIGER_REPORT_SIEVERTS
if (index == i++) {
snprintf(buffer, sizeof(buffer), "CPM / %d = µSv/h", _cpm2sievert);
return String(buffer);
}
#endif
snprintf(buffer, sizeof(buffer), "Events @ GPIO%d", _gpio);
return String(buffer);
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
unsigned char i=0;
#if GEIGER_REPORT_CPM
if (index == i++) return MAGNITUDE_GEIGER_CPM;
#endif
#if GEIGER_REPORT_SIEVERTS
if (index == i++) return MAGNITUDE_GEIGER_SIEVERT;
#endif
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
unsigned char i=0;
#if GEIGER_REPORT_CPM
if (index == i++) {
unsigned long _period_begin = _lastreport_cpm;
_lastreport_cpm = millis();
double value = _events * 60000;
value = value / (_lastreport_cpm-_period_begin);
#if SENSOR_DEBUG
char data[128]; char buffer[10];
dtostrf(value, 1-sizeof(buffer), 4, buffer);
snprintf(data, sizeof(data), "Ticks: %u | Interval: %u | CPM: %s", _ticks, (_lastreport_cpm-_period_begin), buffer);
DEBUG_MSG("[GEIGER] %s\n", data);
#endif
_events = 0;
return value;
}
#endif
#if GEIGER_REPORT_SIEVERTS
if (index == i++) {
unsigned long _period_begin = _lastreport_sv;
_lastreport_sv = millis();
double value = _ticks * 60000 / _cpm2sievert;
value = value / (_lastreport_sv-_period_begin);
#if SENSOR_DEBUG
char data[128]; char buffer[10];
dtostrf(value, 1-sizeof(buffer), 4, buffer);
snprintf(data, sizeof(data), "Ticks: %u | Interval: %u | µSievert: %s", _ticks, (_lastreport_sv-_period_begin), buffer);
DEBUG_MSG("[GEIGER] %s\n", data);
#endif
_ticks = 0;
return value;
}
#endif
return 0;
}
// Handle interrupt calls
void handleInterrupt(unsigned char gpio) {
(void) gpio;
static unsigned long last = 0;
if (millis() - last > _debounce) {
_events = _events + 1;
_ticks = _ticks + 1;
last = millis();
}
}
protected:
// ---------------------------------------------------------------------
// Interrupt management
// ---------------------------------------------------------------------
void _attach(GeigerSensor * instance, unsigned char gpio, unsigned char mode);
void _detach(unsigned char gpio);
void _enableInterrupts(bool value) {
static unsigned char _interrupt_gpio = GPIO_NONE;
if (value) {
if (_interrupt_gpio != GPIO_NONE) _detach(_interrupt_gpio);
_attach(this, _gpio, _interrupt_mode);
_interrupt_gpio = _gpio;
} else if (_interrupt_gpio != GPIO_NONE) {
_detach(_interrupt_gpio);
_interrupt_gpio = GPIO_NONE;
}
}
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
volatile unsigned long _events = 0;
volatile unsigned long _ticks = 0;
unsigned long _debounce = GEIGER_DEBOUNCE;
unsigned int _cpm2sievert = GEIGER_CPM2SIEVERT;
unsigned char _gpio;
unsigned char _mode;
unsigned char _interrupt_mode;
// Added for µSievert calculations
unsigned long _lastreport_cpm = millis();
unsigned long _lastreport_sv = _lastreport_cpm;
};
// -----------------------------------------------------------------------------
// Interrupt helpers
// -----------------------------------------------------------------------------
GeigerSensor * _geiger_sensor_instance[10] = {NULL};
void ICACHE_RAM_ATTR _geiger_sensor_isr(unsigned char gpio) {
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_geiger_sensor_instance[index]) {
_geiger_sensor_instance[index]->handleInterrupt(gpio);
}
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_0() {
_geiger_sensor_isr(0);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_1() {
_geiger_sensor_isr(1);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_2() {
_geiger_sensor_isr(2);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_3() {
_geiger_sensor_isr(3);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_4() {
_geiger_sensor_isr(4);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_5() {
_geiger_sensor_isr(5);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_12() {
_geiger_sensor_isr(12);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_13() {
_geiger_sensor_isr(13);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_14() {
_geiger_sensor_isr(14);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_15() {
_geiger_sensor_isr(15);
}
static void (*_geiger_sensor_isr_list[10])() = {
_geiger_sensor_isr_0, _geiger_sensor_isr_1, _geiger_sensor_isr_2,
_geiger_sensor_isr_3, _geiger_sensor_isr_4, _geiger_sensor_isr_5,
_geiger_sensor_isr_12, _geiger_sensor_isr_13, _geiger_sensor_isr_14,
_geiger_sensor_isr_15
};
void GeigerSensor::_attach(GeigerSensor * instance, unsigned char gpio, unsigned char mode) {
if (!gpioValid(gpio)) return;
_detach(gpio);
unsigned char index = gpio > 5 ? gpio-6 : gpio;
_geiger_sensor_instance[index] = instance;
attachInterrupt(gpio, _geiger_sensor_isr_list[index], mode);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[GEIGER] GPIO%d interrupt attached to %s\n"), gpio, instance->description().c_str());
#endif
}
void GeigerSensor::_detach(unsigned char gpio) {
if (!gpioValid(gpio)) return;
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_geiger_sensor_instance[index]) {
detachInterrupt(gpio);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[GEIGER] GPIO%d interrupt detached from %s\n"), gpio, _geiger_sensor_instance[index]->description().c_str());
#endif
_geiger_sensor_instance[index] = NULL;
}
}
#endif // SENSOR_SUPPORT && GEIGER_SUPPORT

+ 119
- 0
code/espurna/sensors/HCSR04Sensor.h View File

@ -0,0 +1,119 @@
// -----------------------------------------------------------------------------
// HC-SR04 Ultrasonic sensor
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && HCSR04_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
class HCSR04Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
HCSR04Sensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_HCSR04_ID;
}
// ---------------------------------------------------------------------
void setEcho(unsigned char echo) {
_echo = echo;
}
void setTrigger(unsigned char trigger) {
_trigger = trigger;
}
// ---------------------------------------------------------------------
unsigned char getEcho() {
return _echo;
}
unsigned char getTrigger() {
return _trigger;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
pinMode(_echo, INPUT);
pinMode(_trigger, OUTPUT);
digitalWrite(_trigger, LOW);
_ready = true;
}
// Descriptive name of the sensor
String description() {
char buffer[24];
snprintf(buffer, sizeof(buffer), "HCSR04 @ GPIO(%u, %u)", _trigger, _echo);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_trigger);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_DISTANCE;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) {
// Trigger pulse
digitalWrite(_trigger, HIGH);
delayMicroseconds(10);
digitalWrite(_trigger, LOW);
// Wait for echo pulse low-high-low
while ( digitalRead(_echo) == 0 ) yield();
unsigned long start = micros();
while ( digitalRead(_echo) == 1 ) yield();
unsigned long travel_time = micros() - start;
// Assuming a speed of sound of 340m/s
// Dividing by 2 since it is a round trip
return 340.0 * (double) travel_time / 1000000.0 / 2;
}
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
unsigned char _trigger;
unsigned char _echo;
};
#endif // SENSOR_SUPPORT && HCSR04_SUPPORT

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

@ -48,6 +48,10 @@ class HLW8012Sensor : public BaseSensor {
_hlw8012->resetMultipliers();
}
void resetEnergy() {
_hlw8012->resetEnergy();
}
// ---------------------------------------------------------------------
void setSEL(unsigned char sel) {
@ -153,6 +157,8 @@ class HLW8012Sensor : public BaseSensor {
});
#endif
_ready = true;
}
// Descriptive name of the sensor


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

@ -5,6 +5,24 @@
#if SENSOR_SUPPORT && ( I2C_SUPPORT || EMON_ANALOG_SUPPORT )
#if I2C_USE_BRZO
#define I2C_TRANS_SUCCESS 0 // All i2c commands were executed without errors
#define I2C_TRANS_ERROR_BUS_NOT_FREE 1 // Bus not free, i.e. either SDA or SCL is low
#define I2C_TRANS_ERROR_NACK_WRITE 2 // Not ACK ("NACK") by slave during write:
// Either the slave did not respond to the given slave address; or the slave did not ACK a byte transferred by the master.
#define I2C_TRANS_ERROR_NACK_READ 4 // Not ACK ("NACK") by slave during read,
// i.e. slave did not respond to the given slave address
#define I2C_TRANS_ERROR_CLOCK 8 // Clock Stretching by slave exceeded maximum clock stretching time. Most probably, there is a bus stall now!
#define I2C_TRANS_ERROR_READ_NULL 16 // Read was called with 0 bytes to be read by the master. Command not sent to the slave, since this could yield to a bus stall
#define I2C_TRANS_ERROR_TIMEOUT 32 // ACK Polling timeout exceeded
#else // Wire
#define I2C_TRANS_SUCCESS 0 // success
#define I2C_TRANS_ERROR_BUFFER_OVERLOW 1 // data too long to fit in transmit buffer
#define I2C_TRANS_ERROR_NACK_ADDRESS 2 // received NACK on transmit of address
#define I2C_TRANS_ERROR_NACK_DATA 3 // received NACK on transmit of data
#define I2C_TRANS_ERROR_OTHER 4 // other error
#endif
#pragma once
#include "BaseSensor.h"
@ -40,28 +58,32 @@ class I2CSensor : public BaseSensor {
// Specific for I2C sensors
unsigned char _begin_i2c(unsigned char address, size_t size, unsigned char * addresses) {
// If we have already locked this address for this sensor quit
if ((address > 0) && (address == _previous_address)) {
return _previous_address;
}
// Check if we should release a previously locked address
if (_previous_address != address) {
if ((_previous_address > 0) && (_previous_address != address)) {
i2cReleaseLock(_previous_address);
_previous_address = 0;
}
// If we have already an address, check it is not locked
if (address && !i2cGetLock(address)) {
_error = SENSOR_ERROR_I2C;
// If we don't have an address...
} else {
// Trigger auto-discover
address = i2cFindAndLock(size, addresses);
// If requesting a specific address, try to ger a lock to it
if ((0 < address) && i2cGetLock(address)) {
_previous_address = address;
return _previous_address;
}
// If still nothing exit with error
if (address == 0) _error = SENSOR_ERROR_I2C;
// If everything else fails, perform an auto-discover
_previous_address = i2cFindAndLock(size, addresses);
// Flag error
if (0 == _previous_address) {
_error = SENSOR_ERROR_I2C;
}
_previous_address = address;
return address;
return _previous_address;
}


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

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


+ 210
- 35
code/espurna/sensors/PMSX003Sensor.h View File

@ -1,7 +1,8 @@
// -----------------------------------------------------------------------------
// PMSX003 Dust Sensor
// PMS Dust Sensor
// Uses SoftwareSerial library
// Contribution by Òscar Rovira López
// Refine to support PMS5003T/PMS5003ST by Yonsm Guo
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && PMSX003_SUPPORT
@ -11,10 +12,141 @@
#include "Arduino.h"
#include "BaseSensor.h"
#include <PMS.h>
#include <SoftwareSerial.h>
class PMSX003Sensor : public BaseSensor {
// Type of sensor
#define PMS_TYPE_X003 0
#define PMS_TYPE_X003_9 1
#define PMS_TYPE_5003T 2
#define PMS_TYPE_5003ST 3
// Sensor type specified data
#define PMS_SLOT_MAX 4
#define PMS_DATA_MAX 17
const static struct {
const char *name;
unsigned char data_count;
unsigned char slot_count;
unsigned char slot_types[PMS_SLOT_MAX];
} pms_specs[] = {
{"PMSX003", 13, 3, {MAGNITUDE_PM1dot0, MAGNITUDE_PM2dot5, MAGNITUDE_PM10}},
{"PMSX003_9", 9, 3, {MAGNITUDE_PM1dot0, MAGNITUDE_PM2dot5, MAGNITUDE_PM10}},
{"PMS5003T", 13, 3, {MAGNITUDE_PM2dot5, MAGNITUDE_TEMPERATURE, MAGNITUDE_HUMIDITY}},
{"PMS5003ST", 17, 4, {MAGNITUDE_PM2dot5, MAGNITUDE_TEMPERATURE, MAGNITUDE_HUMIDITY, MAGNITUDE_HCHO}}
};
// [MAGIC][LEN][DATA9|13|17][SUM]
#define PMS_PACKET_SIZE(data_count) ((data_count + 3) * 2)
#define PMS_PAYLOAD_SIZE(data_count) ((data_count + 1) * 2)
// PMS sensor utils
// Command functions copied from: https://github.com/fu-hsi/PMS/blob/master/src/PMS.cpp
// Reading function is rewrited to support flexible reading for PMS5003T/PMS5003ST
class PMSX003 {
protected:
SoftwareSerial *_serial = NULL; // Should initialized by child class
public:
// Standby mode. For low power consumption and prolong the life of the sensor.
inline void sleep() {
uint8_t command[] = { 0x42, 0x4D, 0xE4, 0x00, 0x00, 0x01, 0x73 };
_serial->write(command, sizeof(command));
}
// Operating mode. Stable data should be got at least 30 seconds after the sensor wakeup from the sleep mode because of the fan's performance.
inline void wakeUp() {
uint8_t command[] = { 0x42, 0x4D, 0xE4, 0x00, 0x01, 0x01, 0x74 };
_serial->write(command, sizeof(command));
}
// Active mode. Default mode after power up. In this mode sensor would send serial data to the host automatically.
inline void activeMode() {
uint8_t command[] = { 0x42, 0x4D, 0xE1, 0x00, 0x01, 0x01, 0x71 };
_serial->write(command, sizeof(command));
}
// Passive mode. In this mode, sensor would send serial data to the host only for request.
inline void passiveMode() {
uint8_t command[] = { 0x42, 0x4D, 0xE1, 0x00, 0x00, 0x01, 0x70 };
_serial->write(command, sizeof(command));
}
// Request read, ONLY needed in Passive Mode!!
inline void requestRead() {
uint8_t command[] = { 0x42, 0x4D, 0xE2, 0x00, 0x00, 0x01, 0x71 };
_serial->write(command, sizeof(command));
}
// Read sensor's data
bool readData(uint16_t data[], unsigned char data_count) {
do {
int avail = _serial->available();
#if SENSOR_DEBUG
//debugSend("[SENSOR] PMS: Packet available = %d\n", avail);
#endif
if (avail < PMS_PACKET_SIZE(data_count)) {
break;
}
if (_serial->read() == 0x42 && _serial->read() == 0x4D) {
uint16_t sum = 0x42 + 0x4D;
uint16_t size = read16(sum);
if (size != PMS_PAYLOAD_SIZE(data_count)) {
#if SENSOR_DEBUG
debugSend(("[SENSOR] PMS: Payload size: %d != %d.\n"), size, PMS_PAYLOAD_SIZE(data_count));
#endif
break;
}
for (int i = 0; i < data_count; i++) {
data[i] = read16(sum);
#if SENSOR_DEBUG
//debugSend(("[SENSOR] PMS: data[%d] = %d\n"), i, data[i]);
#endif
}
uint16_t checksum = read16();
if (sum == checksum) {
return true;
} else {
#if SENSOR_DEBUG
debugSend(("[SENSOR] PMS checksum: %04X != %04X\n"), sum, checksum);
#endif
}
break;
}
} while (true);
return false;
}
private:
// Read 16-bit
inline uint16_t read16() {
return ((uint16_t) _serial->read()) << 8 | _serial->read();
}
// Read 16-bit and calculate checksum
uint16_t read16(uint16_t &checksum) {
uint8_t high = _serial->read();
uint8_t low = _serial->read();
checksum += high;
checksum += low;
return ((uint16_t) high) << 8 | low;
}
};
class PMSX003Sensor : public BaseSensor, PMSX003 {
public:
@ -23,13 +155,12 @@ class PMSX003Sensor : public BaseSensor {
// ---------------------------------------------------------------------
PMSX003Sensor(): BaseSensor() {
_count = 3;
_count = pms_specs[_type].slot_count;
_sensor_id = SENSOR_PMSX003_ID;
}
~PMSX003Sensor() {
if (_serial) delete _serial;
if (_pms) delete _pms;
}
void setRX(unsigned char pin_rx) {
@ -44,6 +175,12 @@ class PMSX003Sensor : public BaseSensor {
_dirty = true;
}
// Should call setType after constrcutor immediately to enable corresponding slot count
void setType(unsigned char type) {
_type = type;
_count = pms_specs[_type].slot_count;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
@ -54,6 +191,10 @@ class PMSX003Sensor : public BaseSensor {
return _pin_tx;
}
unsigned char getType() {
return _type;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
@ -62,33 +203,31 @@ class PMSX003Sensor : public BaseSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
if (_serial) delete _serial;
if (_pms) delete _pms;
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32);
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 64);
_serial->enableIntTx(false);
_serial->begin(9600);
_pms = new PMS(* _serial);
_pms->passiveMode();
passiveMode();
_startTime = millis();
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
snprintf(buffer, sizeof(buffer), "%s @ SwSerial(%u,%u)", pms_specs[_type].name, _pin_rx, _pin_tx);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
char buffer[36] = {0};
if (index == 0) snprintf(buffer, sizeof(buffer), "PM1.0 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
if (index == 1) snprintf(buffer, sizeof(buffer), "PM2.5 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
if (index == 2) snprintf(buffer, sizeof(buffer), "PM10 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
snprintf(buffer, sizeof(buffer), "%d @ %s @ SwSerial(%u,%u)", int(index + 1), pms_specs[_type].name, _pin_rx, _pin_tx);
return String(buffer);
}
@ -101,10 +240,7 @@ class PMSX003Sensor : public BaseSensor {
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_PM1dot0;
if (index == 1) return MAGNITUDE_PM2dot5;
if (index == 2) return MAGNITUDE_PM10;
return MAGNITUDE_NONE;
return pms_specs[_type].slot_types[index];
}
void pre() {
@ -116,35 +252,74 @@ class PMSX003Sensor : public BaseSensor {
_error = SENSOR_ERROR_OK;
if(_pms->read(_data)) {
_pm1dot0 = _data.PM_AE_UG_1_0;
_pm2dot5 = _data.PM_AE_UG_2_5;
_pm10 = _data.PM_AE_UG_10_0;
#if PMS_SMART_SLEEP
unsigned int readCycle;
if (_readCount++ > 30) {
readCycle = _readCount % 30;
if (readCycle == 0) {
#if SENSOR_DEBUG
debugSend("[SENSOR] %s: Wake up: %d\n", pms_specs[_type].name, _readCount);
#endif
wakeUp();
return;
} else if (readCycle == 1) {
requestRead();
} else if (readCycle > 6) {
return;
}
} else {
readCycle = -1;
}
#endif
uint16_t data[PMS_DATA_MAX];
if (readData(data, pms_specs[_type].data_count)) {
if (_type == PMS_TYPE_5003ST) {
_slot_values[0] = data[4];
_slot_values[1] = (double)data[13] / 10;
_slot_values[2] = (double)data[14] / 10;
_slot_values[3] = (double)data[12] / 1000;
} else if (_type == PMS_TYPE_5003T) {
_slot_values[0] = data[4];
_slot_values[1] = (double)data[10] / 10;
_slot_values[2] = (double)data[11] / 10;
} else {
_slot_values[0] = data[3];
_slot_values[1] = data[4];
_slot_values[2] = data[5];
}
}
_pms->requestRead();
#if PMS_SMART_SLEEP
if (readCycle == 6) {
sleep();
#if SENSOR_DEBUG
debugSend("[SENSOR] %s: Enter sleep mode: %d\n", pms_specs[_type].name, _readCount);
#endif
return;
}
#endif
requestRead();
}
// Current value for slot # index
double value(unsigned char index) {
if(index == 0) return _pm1dot0;
if(index == 1) return _pm2dot5;
if(index == 2) return _pm10;
return 0;
return _slot_values[index];
}
protected:
unsigned int _pm1dot0;
unsigned int _pm2dot5;
unsigned int _pm10;
unsigned int _pin_rx;
unsigned int _pin_tx;
unsigned long _startTime;
SoftwareSerial * _serial = NULL;
PMS * _pms = NULL;
PMS::DATA _data;
unsigned char _type = PMS_TYPE_X003;
double _slot_values[PMS_SLOT_MAX] = {0};
#if PMS_SMART_SLEEP
unsigned int _readCount = 0;
#endif
};
#endif // SENSOR_SUPPORT && PMSX003_SUPPORT
#endif // SENSOR_SUPPORT && PMS_SUPPORT

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

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

+ 8
- 2
code/espurna/sensors/SHT3XI2CSensor.h View File

@ -7,6 +7,10 @@
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
@ -31,13 +35,15 @@ class SHT3XI2CSensor : public I2CSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
// I2C auto-discover
unsigned char addresses[] = {0x45};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
@ -61,7 +67,7 @@ class SHT3XI2CSensor : public I2CSensor {
unsigned char buffer[6];
i2c_write_uint8(_address, 0x2C, 0x06);
delay(500);
nice_delay(500);
i2c_read_buffer(_address, buffer, 6);
// cTemp msb, cTemp lsb, cTemp crc, humidity msb, humidity lsb, humidity crc


+ 23
- 12
code/espurna/sensors/SI7021Sensor.h View File

@ -7,6 +7,11 @@
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
@ -41,18 +46,9 @@ class SI7021Sensor : public I2CSensor {
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_dirty = false;
// I2C auto-discover
unsigned char addresses[] = {0x40};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Initialize sensor
_init();
_dirty = !_ready;
}
// Descriptive name of the sensor
@ -116,18 +112,33 @@ class SI7021Sensor : public I2CSensor {
void _init() {
// I2C auto-discover
unsigned char addresses[] = {0x40};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Check device
i2c_write_uint8(_address, 0xFC, 0xC9);
_chip = i2c_read_uint8(_address);
if ((_chip != SI7021_CHIP_SI7021) & (_chip != SI7021_CHIP_HTU21D)) {
_count = 0;
i2cReleaseLock(_address);
_previous_address = 0;
_error = SENSOR_ERROR_UNKNOWN_ID;
_count = 0;
// Setting _address to 0 forces auto-discover
// This might be necessary at this stage if there is a
// different sensor in the hardcoded address
_address = 0;
} else {
_count = 2;
}
_ready = true;
}
unsigned int _read(uint8_t command) {
@ -139,7 +150,7 @@ class SI7021Sensor : public I2CSensor {
// is needed to wait for the measurement.
// According to datasheet the max. conversion time is ~22ms
unsigned long start = millis();
while (millis() - start < 50) delay(1);
nice_delay(50);
// Clear the last to bits of LSB to 00.
// According to datasheet LSB of RH is always xxxxxx10


+ 233
- 0
code/espurna/sensors/SenseAirSensor.h View File

@ -0,0 +1,233 @@
// -----------------------------------------------------------------------------
// SenseAir S8 CO2 Sensor
// Uses SoftwareSerial library
// Contribution by Yonsm Guo
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && SENSEAIR_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <SoftwareSerial.h>
// SenseAir sensor utils
class SenseAir
{
protected:
SoftwareSerial *_serial; // Should initialized by child class
public:
int sendCommand(byte command[]) {
byte recv_buf[7] = {0xff};
byte data_buf[2] = {0xff};
long value = -1;
_serial->write(command, 8); //Send the byte array
delay(50);
// Read answer from sensor
int ByteCounter = 0;
while(_serial->available()) {
recv_buf[ByteCounter] = _serial->read();
ByteCounter++;
}
data_buf[0] = recv_buf[3];
data_buf[1] = recv_buf[4];
value = (data_buf[0] << 8) | (data_buf[1]);
return value;
}
int readCo2(void) {
int co2 = 0;
byte frame[8] = {0};
buildFrame(0xFE, 0x04, 0x03, 1, frame);
co2 = sendCommand(frame);
return co2;
}
private:
// Compute the MODBUS RTU CRC
static unsigned int modRTU_CRC(byte buf[], int len, byte checkSum[2]) {
unsigned int crc = 0xFFFF;
for (int pos = 0; pos < len; pos++) {
crc ^= (unsigned int)buf[pos]; // XOR byte into least sig. byte of crc
for (int i = 8; i != 0; i--) { // Loop over each bit
if ((crc & 0x0001) != 0) { // If the LSB is set
crc >>= 1; // Shift right and XOR 0xA001
crc ^= 0xA001;
}
else // Else LSB is not set
crc >>= 1; // Just shift right
}
}
// Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes)
checkSum[1] = (byte)((crc >> 8) & 0xFF);
checkSum[0] = (byte)(crc & 0xFF);
return crc;
}
static int getBitOfInt(int reg, int pos) {
// Create a mask
int mask = 0x01 << pos;
// Mask the status register
int masked_register = mask & reg;
// Shift the result of masked register back to position 0
int result = masked_register >> pos;
return result;
}
static void buildFrame(byte slaveAddress,
byte functionCode,
short startAddress,
short numberOfRegisters,
byte frame[8]) {
frame[0] = slaveAddress;
frame[1] = functionCode;
frame[2] = (byte)(startAddress >> 8);
frame[3] = (byte)(startAddress);
frame[4] = (byte)(numberOfRegisters >> 8);
frame[5] = (byte)(numberOfRegisters);
// CRC-calculation
byte checkSum[2] = {0};
modRTU_CRC(frame, 6, checkSum);
frame[6] = checkSum[0];
frame[7] = checkSum[1];
}
};
//
class SenseAirSensor : public BaseSensor, SenseAir {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
SenseAirSensor(): BaseSensor() {
_count = 1;
_co2 = 0;
_lastCo2 = 0;
_serial = NULL;
_sensor_id = SENSOR_SENSEAIR_ID;
}
~SenseAirSensor() {
if (_serial) delete _serial;
_serial = NULL;
}
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, false, 64);
_serial->enableIntTx(false);
_serial->begin(9600);
_serial->enableRx(true);
_startTime = 0;
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "SenseAir S8 @ 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) {
return MAGNITUDE_CO2;
}
void pre() {
if (millis() - _startTime < 20000) {
_error = SENSOR_ERROR_WARM_UP;
return;
}
_error = SENSOR_ERROR_OK;
unsigned int co2 = readCo2();
if (co2 >= 5000 || co2 < 100)
{
_co2 = _lastCo2;
}
else
{
_co2 = (co2 > _lastCo2 + 2000) ? _lastCo2 : co2;
_lastCo2 = co2;
}
}
// Current value for slot # index
double value(unsigned char index) {
return _co2;
}
protected:
unsigned int _pin_rx;
unsigned int _pin_tx;
unsigned long _startTime;
unsigned int _co2;
unsigned int _lastCo2;
};
#endif // SENSOR_SUPPORT && SENSEAIR_SUPPORT

+ 94
- 0
code/espurna/sensors/TMP3XSensor.h View File

@ -0,0 +1,94 @@
// -----------------------------------------------------------------------------
// TMP3X Temperature Analog Sensor
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && TMP3X_SUPPORT
#pragma once
// Set ADC to TOUT pin
#undef ADC_MODE_VALUE
#define ADC_MODE_VALUE ADC_TOUT
#include "Arduino.h"
#include "BaseSensor.h"
#define TMP3X_TMP35 35
#define TMP3X_TMP36 36
#define TMP3X_TMP37 37
class TMP3XSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
TMP3XSensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_TMP3X_ID;
}
void setType(unsigned char type) {
if (35 <= type && type <= 37) {
_type = type;
}
}
unsigned char getType() {
return _type;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
pinMode(0, INPUT);
_ready = true;
}
// Descriptive name of the sensor
String description() {
char buffer[14];
snprintf(buffer, sizeof(buffer), "TMP%d @ TOUT", _type);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String("0");
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_TEMPERATURE;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) {
double mV = 3300.0 * analogRead(0) / 1024.0;
if (_type == TMP3X_TMP35) return mV / 10.0;
if (_type == TMP3X_TMP36) return mV / 10.0 - 50.0;
if (_type == TMP3X_TMP37) return mV / 20.0;
}
return 0;
}
private:
unsigned char _type = TMP3X_TMP35;
};
#endif // SENSOR_SUPPORT && TMP3X_SUPPORT

+ 7
- 2
code/espurna/sensors/V9261FSensor.h View File

@ -9,6 +9,9 @@
#include "Arduino.h"
#include "BaseSensor.h"
extern "C" {
#include "libs/fs_math.h"
}
extern "C" {
#include "libs/softuart.h"
@ -59,11 +62,13 @@ class V9261FSensor : public BaseSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
Softuart_SetPinRx(&_serial, _pin_rx);
Softuart_Init(&_serial, V9261F_BAUDRATE, _inverted);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
@ -200,7 +205,7 @@ class V9261FSensor : public BaseSensor {
if (_voltage < 0) _voltage = 0;
if (_current < 0) _current = 0;
_apparent = sqrt(_reactive * _reactive + _active * _active);
_apparent = fs_sqrt(_reactive * _reactive + _active * _active);
}


+ 90
- 59
code/espurna/settings.ino View File

@ -6,25 +6,14 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <EEPROM.h>
#include <EEPROM_Rotate.h>
#include <vector>
#include "libs/EmbedisWrap.h"
#include <Stream.h>
#include "libs/StreamInjector.h"
#ifdef DEBUG_PORT
#define EMBEDIS_PORT DEBUG_PORT
#else
#define EMBEDIS_PORT Serial
#endif
#if TELNET_SUPPORT
#include "libs/StreamInjector.h"
StreamInjector _serial = StreamInjector(EMBEDIS_PORT, TERMINAL_BUFFER_SIZE);
#undef EMBEDIS_PORT
#define EMBEDIS_PORT _serial
#endif
EmbedisWrap embedis(EMBEDIS_PORT, TERMINAL_BUFFER_SIZE);
StreamInjector _serial = StreamInjector(TERMINAL_BUFFER_SIZE);
EmbedisWrap embedis(_serial, TERMINAL_BUFFER_SIZE);
#if TERMINAL_SUPPORT
#if SERIAL_RX_ENABLED
@ -41,7 +30,7 @@ bool _settings_save = false;
unsigned long settingsSize() {
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
while (size_t len = EEPROMr.read(pos)) {
pos = pos - len - 2;
}
return SPI_FLASH_SEC_SIZE - pos;
@ -52,9 +41,9 @@ unsigned long settingsSize() {
unsigned int _settingsKeyCount() {
unsigned count = 0;
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
while (size_t len = EEPROMr.read(pos)) {
pos = pos - len - 2;
len = EEPROM.read(pos);
len = EEPROMr.read(pos);
pos = pos - len - 2;
count ++;
}
@ -67,17 +56,17 @@ String _settingsKeyName(unsigned int index) {
unsigned count = 0;
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
while (size_t len = EEPROMr.read(pos)) {
pos = pos - len - 2;
if (count == index) {
s.reserve(len);
for (unsigned char i = 0 ; i < len; i++) {
s += (char) EEPROM.read(pos + i + 1);
s += (char) EEPROMr.read(pos + i + 1);
}
break;
}
count++;
len = EEPROM.read(pos);
len = EEPROMr.read(pos);
pos = pos - len - 2;
}
@ -162,32 +151,21 @@ void _settingsKeysCommand() {
DEBUG_MSG_P(PSTR("Current settings:\n"));
for (unsigned int i=0; i<keys.size(); i++) {
String value = getSetting(keys[i]);
DEBUG_MSG_P(PSTR("> %s => %s\n"), (keys[i]).c_str(), value.c_str());
DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), (keys[i]).c_str(), value.c_str());
}
unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize();
DEBUG_MSG_P(PSTR("Number of keys: %d\n"), keys.size());
DEBUG_MSG_P(PSTR("Current EEPROM sector: %u\n"), EEPROMr.current());
DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
}
void _settingsFactoryResetCommand() {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF);
}
EEPROM.commit();
}
void _settingsDumpCommand(bool ascii) {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
if (i % 16 == 0) DEBUG_MSG_P(PSTR("\n[%04X] "), i);
byte c = EEPROM.read(i);
if (ascii && 32 <= c && c <= 126) {
DEBUG_MSG_P(PSTR(" %c "), c);
} else {
DEBUG_MSG_P(PSTR("%02X "), c);
}
EEPROMr.write(i, 0xFF);
}
EEPROMr.commit();
}
void _settingsInitCommands() {
@ -195,6 +173,7 @@ void _settingsInitCommands() {
#if DEBUG_SUPPORT
settingsRegisterCommand(F("CRASH"), [](Embedis* e) {
debugDumpCrashInfo();
debugClearCrashInfo();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
@ -204,13 +183,6 @@ void _settingsInitCommands() {
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) {
bool ascii = false;
if (e->argc == 2) ascii = String(e->argv[1]).toInt() == 1;
_settingsDumpCommand(ascii);
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
settingsRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
resetReason(CUSTOM_RESET_TERMINAL);
@ -218,6 +190,20 @@ void _settingsInitCommands() {
*((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494
});
#if I2C_SUPPORT
settingsRegisterCommand(F("I2C.SCAN"), [](Embedis* e) {
i2cScan();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("I2C.CLEAR"), [](Embedis* e) {
i2cClearBus();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("FACTORY.RESET"), [](Embedis* e) {
_settingsFactoryResetCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
@ -253,7 +239,7 @@ void _settingsInitCommands() {
settingsRegisterCommand(F("INFO"), [](Embedis* e) {
info();
wifiStatus();
wifiDebug();
//StreamString s;
//WiFi.printDiag(s);
//DEBUG_MSG(s.c_str());
@ -265,11 +251,44 @@ void _settingsInitCommands() {
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("GET"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
for (unsigned char i = 1; i < e->argc; i++) {
String key = String(e->argv[i]);
String value;
if (!Embedis::get(key, value)) {
DEBUG_MSG_P(PSTR("> %s =>\n"), key.c_str());
continue;
}
DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), key.c_str(), value.c_str());
}
DEBUG_MSG_P(PSTR("+OK\n"));
});
#if WEB_SUPPORT
settingsRegisterCommand(F("RELOAD"), [](Embedis* e) {
wsReload();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("RESET"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
settingsRegisterCommand(F("RESET.SAFE"), [](Embedis* e) {
EEPROMr.write(EEPROM_CRASH_COUNTER, SYSTEM_CHECK_MAX);
DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
settingsRegisterCommand(F("UPTIME"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime());
DEBUG_MSG_P(PSTR("+OK\n"));
@ -339,11 +358,13 @@ void resetSettings() {
// Settings
// -----------------------------------------------------------------------------
#if TELNET_SUPPORT
void settingsInject(void *data, size_t len) {
_serial.inject((char *) data, len);
}
#endif
void settingsInject(void *data, size_t len) {
_serial.inject((char *) data, len);
}
Stream & settingsSerial() {
return (Stream &) _serial;
}
size_t settingsMaxSize() {
size_t size = EEPROM_SIZE;
@ -358,7 +379,7 @@ bool settingsRestoreJson(JsonObject& data) {
if (strcmp(app, APP_NAME) != 0) return false;
for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF);
EEPROMr.write(i, 0xFF);
}
for (auto element : data) {
@ -374,7 +395,7 @@ bool settingsRestoreJson(JsonObject& data) {
}
bool settingsGetJson(JsonObject& root) {
void settingsGetJson(JsonObject& root) {
// Get sorted list of keys
std::vector<String> keys = _settingsKeys();
@ -397,18 +418,21 @@ void settingsRegisterCommand(const String& name, void (*call)(Embedis*)) {
void settingsSetup() {
EEPROM.begin(SPI_FLASH_SEC_SIZE);
EEPROMr.begin(SPI_FLASH_SEC_SIZE);
#if TELNET_SUPPORT
_serial.callback([](uint8_t ch) {
_serial.callback([](uint8_t ch) {
#if TELNET_SUPPORT
telnetWrite(ch);
});
#endif
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.write(ch);
#endif
});
Embedis::dictionary( F("EEPROM"),
SPI_FLASH_SEC_SIZE,
[](size_t pos) -> char { return EEPROM.read(pos); },
[](size_t pos, char value) { EEPROM.write(pos, value); },
[](size_t pos) -> char { return EEPROMr.read(pos); },
[](size_t pos, char value) { EEPROMr.write(pos, value); },
#if SETTINGS_AUTOSAVE
[]() { _settings_save = true; }
#else
@ -432,12 +456,19 @@ void settingsSetup() {
void settingsLoop() {
if (_settings_save) {
EEPROM.commit();
EEPROMr.commit();
_settings_save = false;
}
#if TERMINAL_SUPPORT
#if DEBUG_SERIAL_SUPPORT
while (DEBUG_PORT.available()) {
_serial.inject(DEBUG_PORT.read());
}
#endif
embedis.process();
#if SERIAL_RX_ENABLED


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


+ 71
- 23
code/espurna/system.ino View File

@ -6,13 +6,16 @@ Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <EEPROM.h>
#include <EEPROM_Rotate.h>
// -----------------------------------------------------------------------------
unsigned long _loopDelay = 0;
unsigned long _loop_delay = 0;
bool _system_send_heartbeat = false;
// Calculated load average 0 to 100;
unsigned short int _load_average = 100;
// -----------------------------------------------------------------------------
#if SYSTEM_CHECK_ENABLED
@ -27,7 +30,7 @@ bool _system_send_heartbeat = false;
bool _systemStable = true;
void systemCheck(bool stable) {
unsigned char value = EEPROM.read(EEPROM_CRASH_COUNTER);
unsigned char value = EEPROMr.read(EEPROM_CRASH_COUNTER);
if (stable) {
value = 0;
DEBUG_MSG_P(PSTR("[MAIN] System OK\n"));
@ -38,8 +41,8 @@ void systemCheck(bool stable) {
DEBUG_MSG_P(PSTR("[MAIN] System UNSTABLE\n"));
}
}
EEPROM.write(EEPROM_CRASH_COUNTER, value);
EEPROM.commit();
EEPROMr.write(EEPROM_CRASH_COUNTER, value);
EEPROMr.commit();
}
bool systemCheck() {
@ -63,41 +66,88 @@ void systemSendHeartbeat() {
_system_send_heartbeat = true;
}
unsigned long systemLoopDelay() {
return _loop_delay;
}
unsigned long systemLoadAverage() {
return _load_average;
}
void systemLoop() {
// -------------------------------------------------------------------------
// Check system stability
// -------------------------------------------------------------------------
#if SYSTEM_CHECK_ENABLED
systemCheckLoop();
#endif
// -------------------------------------------------------------------------
// Heartbeat
// -------------------------------------------------------------------------
#if HEARTBEAT_ENABLED
// Heartbeat
static unsigned long last = 0;
if (_system_send_heartbeat || (last == 0) || (millis() - last > HEARTBEAT_INTERVAL)) {
static unsigned long last_hbeat = 0;
if (_system_send_heartbeat || (last_hbeat == 0) || (millis() - last_hbeat > HEARTBEAT_INTERVAL)) {
_system_send_heartbeat = false;
last = millis();
last_hbeat = millis();
heartbeat();
}
#endif // HEARTBEAT_ENABLED
// -------------------------------------------------------------------------
// Load Average calculation
// -------------------------------------------------------------------------
static unsigned long last_loadcheck = 0;
static unsigned long load_counter_temp = 0;
load_counter_temp++;
if (millis() - last_loadcheck > LOADAVG_INTERVAL) {
static unsigned long load_counter = 0;
static unsigned long load_counter_max = 1;
load_counter = load_counter_temp;
load_counter_temp = 0;
if (load_counter > load_counter_max) {
load_counter_max = load_counter;
}
_load_average = 100 - (100 * load_counter / load_counter_max);
last_loadcheck = millis();
}
// -------------------------------------------------------------------------
// Power saving delay
delay(_loopDelay);
// -------------------------------------------------------------------------
delay(_loop_delay);
}
void systemSetup() {
void _systemSetupSpecificHardware() {
EEPROM.begin(EEPROM_SIZE);
//The ESPLive has an ADC MUX which needs to be configured.
#if defined(MANCAVEMADE_ESPLIVE)
pinMode(16, OUTPUT);
digitalWrite(16, HIGH); //Defualt CT input (pin B, solder jumper B)
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.begin(SERIAL_BAUDRATE);
#if DEBUG_ESP_WIFI
DEBUG_PORT.setDebugOutput(true);
#endif
#elif defined(SERIAL_BAUDRATE)
// These devices use the hardware UART
// to communicate to secondary microcontrollers
#if defined(ITEAD_SONOFF_RFBRIDGE) || defined(ITEAD_SONOFF_DUAL) || defined(STM_RELAY)
Serial.begin(SERIAL_BAUDRATE);
#endif
}
void systemSetup() {
#if SPIFFS_SUPPORT
SPIFFS.begin();
#endif
@ -107,14 +157,12 @@ void systemSetup() {
systemCheck(false);
#endif
#if defined(ESPLIVE)
//The ESPLive has an ADC MUX which needs to be configured.
pinMode(16, OUTPUT);
digitalWrite(16, HIGH); //Defualt CT input (pin B, solder jumper B)
#endif
// Init device-specific hardware
_systemSetupSpecificHardware();
// Cache loop delay value to speed things (recommended max 250ms)
_loopDelay = atol(getSetting("loopDelay", LOOP_DELAY_TIME).c_str());
_loop_delay = atol(getSetting("loopDelay", LOOP_DELAY_TIME).c_str());
_loop_delay = constrain(_loop_delay, 0, 300);
// Register Loop
espurnaRegisterLoop(systemLoop);


+ 27
- 0
code/espurna/telnet.ino View File

@ -20,11 +20,19 @@ bool _telnetFirst = true;
// Private methods
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _telnetWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "telnet", 6) == 0);
}
void _telnetWebSocketOnSend(JsonObject& root) {
root["telnetVisible"] = 1;
root["telnetSTA"] = getSetting("telnetSTA", TELNET_STA).toInt() == 1;
}
#endif
void _telnetDisconnect(unsigned char clientId) {
_telnetClients[clientId]->free();
_telnetClients[clientId] = NULL;
@ -58,6 +66,15 @@ void _telnetData(unsigned char clientId, void *data, size_t len) {
// Capture close connection
char * p = (char *) data;
// C-d is sent as two bytes (sometimes repeating)
if (len >= 2) {
if ((p[0] == 0xFF) && (p[1] == 0xEC)) {
_telnetClients[clientId]->close(true);
return;
}
}
if ((strncmp(p, "close", 5) == 0) || (strncmp(p, "quit", 4) == 0)) {
_telnetClients[clientId]->close();
return;
@ -117,6 +134,15 @@ void _telnetNewClient(AsyncClient *client) {
}, 0);
DEBUG_MSG_P(PSTR("[TELNET] Client #%u connected\n"), i);
// If there is no terminal support automatically dump info and crash data
#if TERMINAL_SUPPORT == 0
info();
wifiDebug();
debugDumpCrashInfo();
debugClearCrashInfo();
#endif
_telnetFirst = true;
wifiReconnectCheck();
return;
@ -160,6 +186,7 @@ void telnetSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_telnetWebSocketOnSend);
wsOnReceiveRegister(_telnetWebSocketOnReceive);
#endif
DEBUG_MSG_P(PSTR("[TELNET] Listening on port %d\n"), TELNET_PORT);


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

@ -32,6 +32,12 @@ unsigned long _tspk_last_flush = 0;
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _tspkWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "tspk", 4) == 0);
}
void _tspkWebSocketOnSend(JsonObject& root) {
unsigned char visible = 0;
@ -61,6 +67,8 @@ void _tspkWebSocketOnSend(JsonObject& root) {
}
#endif
void _tspkConfigure() {
_tspk_enabled = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
if (_tspk_enabled && (getSetting("tspkKey").length() == 0)) {
@ -184,7 +192,7 @@ void _tspkPost(String data) {
#endif // THINGSPEAK_USE_ASYNC
bool _tspkEnqueue(unsigned char index, char * payload) {
void _tspkEnqueue(unsigned char index, char * payload) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Enqueuing field #%d with value %s\n"), index, payload);
--index;
if (_tspk_queue[index] != NULL) free(_tspk_queue[index]);
@ -222,7 +230,9 @@ bool tspkEnqueueRelay(unsigned char index, unsigned char status) {
char payload[3] = {0};
itoa(status ? 1 : 0, payload, 10);
_tspkEnqueue(id, payload);
return true;
}
return false;
}
bool tspkEnqueueMeasurement(unsigned char index, char * payload) {
@ -230,7 +240,9 @@ bool tspkEnqueueMeasurement(unsigned char index, char * payload) {
unsigned char id = getSetting("tspkMagnitude", index, 0).toInt();
if (id > 0) {
_tspkEnqueue(id, payload);
return true;
}
return false;
}
void tspkFlush() {
@ -248,6 +260,7 @@ void tspkSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_tspkWebSocketOnSend);
wsOnAfterParseRegister(_tspkConfigure);
wsOnReceiveRegister(_tspkWebSocketOnReceive);
#endif
DEBUG_MSG_P(PSTR("[THINGSPEAK] Async %s, SSL %s\n"),


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

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

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

@ -11,10 +11,18 @@ Ticker _defer_reset;
String getIdentifier() {
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%s_%06X"), APP_NAME, ESP.getChipId());
snprintf_P(buffer, sizeof(buffer), PSTR("%s-%06X"), APP_NAME, ESP.getChipId());
return String(buffer);
}
void setDefaultHostname() {
if (strlen(HOSTNAME) > 0) {
setSetting("hostname", HOSTNAME);
} else {
setSetting("hostname", getIdentifier());
}
}
void setBoardName() {
#ifndef ESPURNA_CORE
setSetting("boardName", DEVICE_NAME);
@ -32,6 +40,7 @@ String getCoreVersion() {
version = String(ARDUINO_ESP8266_RELEASE);
}
#endif
version.replace("_", ".");
return version;
}
@ -43,6 +52,10 @@ 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...
@ -51,6 +64,16 @@ unsigned int getFreeHeap() {
return ESP.getFreeHeap();
}
String getEspurnaModules() {
return FPSTR(espurna_modules);
}
#if SENSOR_SUPPORT
String getEspurnaSensors() {
return FPSTR(espurna_sensors);
}
#endif
String buildTime() {
const char time_now[] = __TIME__; // hh:mm:ss
@ -114,7 +137,7 @@ void heartbeat() {
if (serial) {
DEBUG_MSG_P(PSTR("[MAIN] Uptime: %lu seconds\n"), uptime_seconds);
DEBUG_MSG_P(PSTR("[MAIN] Free heap: %lu bytes\n"), free_heap);
#if ADC_VCC_ENABLED
#if ADC_MODE_VALUE == ADC_VCC
DEBUG_MSG_P(PSTR("[MAIN] Power: %lu mV\n"), ESP.getVcc());
#endif
#if NTP_SUPPORT
@ -137,6 +160,9 @@ void heartbeat() {
#if (HEARTBEAT_REPORT_VERSION)
mqttSend(MQTT_TOPIC_VERSION, APP_VERSION);
#endif
#if (HEARTBEAT_REPORT_BOARD)
mqttSend(MQTT_TOPIC_BOARD, getBoardName().c_str());
#endif
#if (HEARTBEAT_REPORT_HOSTNAME)
mqttSend(MQTT_TOPIC_HOSTNAME, getSetting("hostname").c_str());
#endif
@ -165,13 +191,16 @@ void heartbeat() {
lightMQTT();
#endif
#if (HEARTBEAT_REPORT_VCC)
#if ADC_VCC_ENABLED
#if ADC_MODE_VALUE == ADC_VCC
mqttSend(MQTT_TOPIC_VCC, String(ESP.getVcc()).c_str());
#endif
#endif
#if (HEARTBEAT_REPORT_STATUS)
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
#endif
#if (LOADAVG_REPORT)
mqttSend(MQTT_TOPIC_LOADAVG, String(systemLoadAverage()).c_str());
#endif
}
#endif
@ -192,10 +221,42 @@ void heartbeat() {
#endif /// HEARTBEAT_ENABLED
unsigned int sectors(size_t size) {
// -----------------------------------------------------------------------------
// INFO
// -----------------------------------------------------------------------------
extern "C" uint32_t _SPIFFS_start;
extern "C" uint32_t _SPIFFS_end;
unsigned int info_bytes2sectors(size_t size) {
return (int) (size + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE;
}
unsigned long info_ota_space() {
return (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
}
unsigned long info_filesystem_space() {
return ((uint32_t)&_SPIFFS_end - (uint32_t)&_SPIFFS_start);
}
unsigned long info_eeprom_space() {
return EEPROMr.reserved() * SPI_FLASH_SEC_SIZE;
}
void _info_print_memory_layout_line(const char * name, unsigned long bytes, bool reset) {
static unsigned long index = 0;
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);
index += _sectors;
}
void _info_print_memory_layout_line(const char * name, unsigned long bytes) {
_info_print_memory_layout_line(name, bytes, false);
}
void info() {
DEBUG_MSG_P(PSTR("\n\n"));
@ -216,29 +277,27 @@ void info() {
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("\n"));
DEBUG_MSG_P(PSTR("[INIT] Flash sector size: %8u bytes\n"), SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("[INIT] Flash size (CHIP): %8u bytes\n"), ESP.getFlashChipRealSize());
DEBUG_MSG_P(PSTR("[INIT] Flash size (SDK): %8u bytes / %4d sectors\n"), ESP.getFlashChipSize(), sectors(ESP.getFlashChipSize()));
DEBUG_MSG_P(PSTR("[INIT] Firmware size: %8u bytes / %4d sectors\n"), ESP.getSketchSize(), sectors(ESP.getSketchSize()));
DEBUG_MSG_P(PSTR("[INIT] OTA size: %8u bytes / %4d sectors\n"), ESP.getFreeSketchSpace(), sectors(ESP.getFreeSketchSpace()));
#if SPIFFS_SUPPORT
FSInfo fs_info;
bool fs = SPIFFS.info(fs_info);
if (fs) {
DEBUG_MSG_P(PSTR("[INIT] SPIFFS size: %8u bytes / %4d sectors\n"), fs_info.totalBytes, sectors(fs_info.totalBytes));
}
#else
DEBUG_MSG_P(PSTR("[INIT] SPIFFS size: %8u bytes / %4d sectors\n"), 0, 0);
#endif
DEBUG_MSG_P(PSTR("[INIT] EEPROM size: %8u bytes / %4d sectors\n"), settingsMaxSize(), sectors(settingsMaxSize()));
DEBUG_MSG_P(PSTR("[INIT] Empty space: %8u bytes / 4 sectors\n"), 4 * SPI_FLASH_SEC_SIZE);
_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);
_info_print_memory_layout_line("Firmware size", ESP.getSketchSize());
_info_print_memory_layout_line("Max OTA size", info_ota_space());
_info_print_memory_layout_line("SPIFFS size", info_filesystem_space());
_info_print_memory_layout_line("EEPROM size", info_eeprom_space());
_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\n"), fs_info.totalBytes);
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);
@ -252,141 +311,12 @@ void info() {
// -------------------------------------------------------------------------
#ifdef APP_BUILD_FLAGS
DEBUG_MSG_P(PSTR("[INIT] BUILD_FLAGS: %s\n"), APP_BUILD_FLAGS);
#endif
DEBUG_MSG_P(PSTR("[INIT] BOARD: %s\n"), getBoardName().c_str());
DEBUG_MSG_P(PSTR("[INIT] SUPPORT:"));
#if ALEXA_SUPPORT
DEBUG_MSG_P(PSTR(" ALEXA"));
#endif
#if BROKER_SUPPORT
DEBUG_MSG_P(PSTR(" BROKER"));
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_SERIAL"));
#endif
#if DEBUG_TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_TELNET"));
#endif
#if DEBUG_UDP_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_UDP"));
#endif
#if DOMOTICZ_SUPPORT
DEBUG_MSG_P(PSTR(" DOMOTICZ"));
#endif
#if HOMEASSISTANT_SUPPORT
DEBUG_MSG_P(PSTR(" HOMEASSISTANT"));
#endif
#if I2C_SUPPORT
DEBUG_MSG_P(PSTR(" I2C"));
#endif
#if INFLUXDB_SUPPORT
DEBUG_MSG_P(PSTR(" INFLUXDB"));
#endif
#if LLMNR_SUPPORT
DEBUG_MSG_P(PSTR(" LLMNR"));
#endif
#if MDNS_SERVER_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS_SERVER"));
#endif
#if MDNS_CLIENT_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS_CLIENT"));
#endif
#if NETBIOS_SUPPORT
DEBUG_MSG_P(PSTR(" NETBIOS"));
#endif
#if NOFUSS_SUPPORT
DEBUG_MSG_P(PSTR(" NOFUSS"));
#endif
#if NTP_SUPPORT
DEBUG_MSG_P(PSTR(" NTP"));
#endif
#if RF_SUPPORT
DEBUG_MSG_P(PSTR(" RF"));
#endif
#if SCHEDULER_SUPPORT
DEBUG_MSG_P(PSTR(" SCHEDULER"));
#endif
DEBUG_MSG_P(PSTR("[INIT] SUPPORT: %s\n"), getEspurnaModules().c_str());
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR(" SENSOR"));
#endif
#if SPIFFS_SUPPORT
DEBUG_MSG_P(PSTR(" SPIFFS"));
#endif
#if SSDP_SUPPORT
DEBUG_MSG_P(PSTR(" SSDP"));
#endif
#if TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" TELNET"));
#endif
#if TERMINAL_SUPPORT
DEBUG_MSG_P(PSTR(" TERMINAL"));
#endif
#if THINGSPEAK_SUPPORT
DEBUG_MSG_P(PSTR(" THINGSPEAK"));
#endif
#if WEB_SUPPORT
DEBUG_MSG_P(PSTR(" WEB"));
#endif
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n[INIT] SENSORS:"));
#if ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" ANALOG"));
#endif
#if BMX280_SUPPORT
DEBUG_MSG_P(PSTR(" BMX280"));
#endif
#if DALLAS_SUPPORT
DEBUG_MSG_P(PSTR(" DALLAS"));
#endif
#if DHT_SUPPORT
DEBUG_MSG_P(PSTR(" DHTXX"));
#endif
#if DIGITAL_SUPPORT
DEBUG_MSG_P(PSTR(" DIGITAL"));
#endif
#if ECH1560_SUPPORT
DEBUG_MSG_P(PSTR(" ECH1560"));
#endif
#if EMON_ADC121_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ADC121"));
#endif
#if EMON_ADS1X15_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ADX1X15"));
#endif
#if EMON_ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ANALOG"));
#endif
#if EVENTS_SUPPORT
DEBUG_MSG_P(PSTR(" EVENTS"));
#endif
#if HLW8012_SUPPORT
DEBUG_MSG_P(PSTR(" HLW8012"));
#endif
#if MHZ19_SUPPORT
DEBUG_MSG_P(PSTR(" MHZ19"));
#endif
#if PMSX003_SUPPORT
DEBUG_MSG_P(PSTR(" PMSX003"));
#endif
#if SHT3X_I2C_SUPPORT
DEBUG_MSG_P(PSTR(" SHT3X_I2C"));
#endif
#if SI7021_SUPPORT
DEBUG_MSG_P(PSTR(" SI7021"));
#endif
#if V9261F_SUPPORT
DEBUG_MSG_P(PSTR(" V9261F"));
#endif
DEBUG_MSG_P(PSTR("[INIT] SENSORS: %s\n"), getEspurnaSensors().c_str());
#endif // SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n\n"));
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
@ -401,11 +331,11 @@ void info() {
DEBUG_MSG_P(PSTR("[INIT] Settings size: %u bytes\n"), settingsSize());
DEBUG_MSG_P(PSTR("[INIT] Free heap: %u bytes\n"), getFreeHeap());
#if ADC_VCC_ENABLED
#if ADC_MODE_VALUE == ADC_VCC
DEBUG_MSG_P(PSTR("[INIT] Power: %u mV\n"), ESP.getVcc());
#endif
DEBUG_MSG_P(PSTR("[INIT] Power saving delay value: %lu ms\n"), _loopDelay);
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"));
@ -465,7 +395,7 @@ bool sslFingerPrintChar(const char * fingerprint, char * destination) {
unsigned char resetReason() {
static unsigned char status = 255;
if (status == 255) {
status = EEPROM.read(EEPROM_CUSTOM_RESET);
status = EEPROMr.read(EEPROM_CUSTOM_RESET);
if (status > 0) resetReason(0);
if (status > CUSTOM_RESET_MAX) status = 0;
}
@ -473,8 +403,8 @@ unsigned char resetReason() {
}
void resetReason(unsigned char reason) {
EEPROM.write(EEPROM_CUSTOM_RESET, reason);
EEPROM.commit();
EEPROMr.write(EEPROM_CUSTOM_RESET, reason);
EEPROMr.commit();
}
void reset(unsigned char reason) {
@ -504,3 +434,24 @@ void nice_delay(unsigned long ms) {
unsigned long start = millis();
while (millis() - start < ms) delay(1);
}
// This method is called by the SDK to know where to connect the ADC
int __get_adc_mode() {
return (int) (ADC_MODE_VALUE);
}
bool isNumber(const char * s) {
unsigned char len = strlen(s);
bool decimal = false;
for (unsigned char i=0; i<len; i++) {
if (s[i] == '-') {
if (i>0) return false;
} else if (s[i] == '.') {
if (decimal) return false;
decimal = true;
} else if (!isdigit(s[i])) {
return false;
}
}
return true;
}

+ 34
- 7
code/espurna/web.ino View File

@ -43,7 +43,9 @@ void _onReset(AsyncWebServerRequest *request) {
void _onGetConfig(AsyncWebServerRequest *request) {
webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (!webAuthenticate(request)) {
return request->requestAuthentication(getSetting("hostname").c_str());
}
AsyncResponseStream *response = request->beginResponseStream("text/json");
@ -53,18 +55,23 @@ void _onGetConfig(AsyncWebServerRequest *request) {
root["version"] = APP_VERSION;
settingsGetJson(root);
root.prettyPrintTo(*response);
jsonBuffer.clear();
char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("attachment; filename=\"%s-backup.json\""), (char *) getSetting("hostname").c_str());
response->addHeader("Content-Disposition", buffer);
response->addHeader("X-XSS-Protection", "1; mode=block");
response->addHeader("X-Content-Type-Options", "nosniff");
response->addHeader("X-Frame-Options", "deny");
request->send(response);
}
void _onPostConfig(AsyncWebServerRequest *request) {
webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (!webAuthenticate(request)) {
return request->requestAuthentication(getSetting("hostname").c_str());
}
request->send(_webConfigSuccess ? 200 : 400);
}
@ -112,7 +119,9 @@ void _onPostConfigData(AsyncWebServerRequest *request, String filename, size_t i
void _onHome(AsyncWebServerRequest *request) {
webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (!webAuthenticate(request)) {
return request->requestAuthentication(getSetting("hostname").c_str());
}
if (request->header("If-Modified-Since").equals(_last_modified)) {
@ -151,6 +160,9 @@ void _onHome(AsyncWebServerRequest *request) {
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Last-Modified", _last_modified);
response->addHeader("X-XSS-Protection", "1; mode=block");
response->addHeader("X-Content-Type-Options", "nosniff");
response->addHeader("X-Frame-Options", "deny");
request->send(response);
}
@ -212,7 +224,9 @@ int _onCertificate(void * arg, const char *filename, uint8_t **buf) {
void _onUpgrade(AsyncWebServerRequest *request) {
webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (!webAuthenticate(request)) {
return request->requestAuthentication(getSetting("hostname").c_str());
}
char buffer[10];
if (!Update.hasError()) {
@ -223,7 +237,12 @@ void _onUpgrade(AsyncWebServerRequest *request) {
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", buffer);
response->addHeader("Connection", "close");
if (!Update.hasError()) {
response->addHeader("X-XSS-Protection", "1; mode=block");
response->addHeader("X-Content-Type-Options", "nosniff");
response->addHeader("X-Frame-Options", "deny");
if (Update.hasError()) {
eepromRotate(true);
} else {
deferredReset(100, CUSTOM_RESET_UPGRADE);
}
request->send(response);
@ -231,7 +250,12 @@ void _onUpgrade(AsyncWebServerRequest *request) {
}
void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
if (!index) {
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
eepromRotate(false);
DEBUG_MSG_P(PSTR("[UPGRADE] Start: %s\n"), filename.c_str());
Update.runAsync(true);
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
@ -239,7 +263,9 @@ void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t inde
Update.printError(DEBUG_PORT);
#endif
}
}
if (!Update.hasError()) {
if (Update.write(data, len) != len) {
#ifdef DEBUG_PORT
@ -247,6 +273,7 @@ void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t inde
#endif
}
}
if (final) {
if (Update.end(true)){
DEBUG_MSG_P(PSTR("[UPGRADE] Success: %u bytes\n"), index + len);
@ -262,7 +289,7 @@ void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t inde
// -----------------------------------------------------------------------------
bool _authenticate(AsyncWebServerRequest *request) {
bool webAuthenticate(AsyncWebServerRequest *request) {
#if USE_PASSWORD
String password = getSetting("adminPass", ADMIN_PASS);
char httpPassword[password.length() + 1];


+ 259
- 60
code/espurna/wifi.ino View File

@ -10,11 +10,26 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <Ticker.h>
uint32_t _wifi_scan_client_id = 0;
bool _wifi_wps_running = false;
bool _wifi_smartconfig_running = false;
uint8_t _wifi_ap_mode = WIFI_AP_FALLBACK;
// -----------------------------------------------------------------------------
// PRIVATE
// -----------------------------------------------------------------------------
void _wifiCheckAP() {
if ((WIFI_AP_FALLBACK == _wifi_ap_mode) &&
(jw.connected()) &&
((WiFi.getMode() & WIFI_AP) > 0) &&
(WiFi.softAPgetStationNum() == 0)
) {
jw.enableAP(false);
}
}
void _wifiConfigure() {
jw.setHostname(getSetting("hostname").c_str());
@ -25,9 +40,11 @@ void _wifiConfigure() {
#endif
jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
wifiReconnectCheck();
jw.setAPMode(WIFI_AP_MODE);
jw.enableAPFallback(true);
jw.cleanNetworks();
_wifi_ap_mode = getSetting("apmode", WIFI_AP_FALLBACK).toInt();
// If system is flagged unstable we do not init wifi networks
#if SYSTEM_CHECK_ENABLED
if (!systemCheck()) return;
@ -56,7 +73,7 @@ void _wifiConfigure() {
}
}
jw.scanNetworks(getSetting("wifiScan", WIFI_SCAN_NETWORKS).toInt() == 1);
jw.enableScan(getSetting("wifiScan", WIFI_SCAN_NETWORKS).toInt() == 1);
}
@ -196,9 +213,75 @@ void _wifiInject() {
}
}
void _wifiCallback(justwifi_messages_t code, char * parameter) {
if (MESSAGE_WPS_START == code) {
_wifi_wps_running = true;
}
if (MESSAGE_SMARTCONFIG_START == code) {
_wifi_smartconfig_running = true;
}
if (MESSAGE_WPS_ERROR == code || MESSAGE_SMARTCONFIG_ERROR == code) {
_wifi_wps_running = false;
_wifi_smartconfig_running = false;
}
if (MESSAGE_WPS_SUCCESS == code || MESSAGE_SMARTCONFIG_SUCCESS == code) {
String ssid = WiFi.SSID();
String pass = WiFi.psk();
// Look for the same SSID
uint8_t count = 0;
while (count < WIFI_MAX_NETWORKS) {
if (!hasSetting("ssid", count)) break;
if (ssid.equals(getSetting("ssid", count, ""))) break;
count++;
}
// If we have reached the max we overwrite the first one
if (WIFI_MAX_NETWORKS == count) count = 0;
setSetting("ssid", count, ssid);
setSetting("pass", count, pass);
_wifi_wps_running = false;
_wifi_smartconfig_running = false;
}
}
#if WIFI_AP_CAPTIVE
#include "DNSServer.h"
DNSServer _wifi_dnsServer;
void _wifiCaptivePortal(justwifi_messages_t code, char * parameter) {
if (MESSAGE_ACCESSPOINT_CREATED == code) {
_wifi_dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
_wifi_dnsServer.start(53, "*", WiFi.softAPIP());
DEBUG_MSG_P(PSTR("[WIFI] Captive portal enabled\n"));
}
if (MESSAGE_CONNECTED == code) {
_wifi_dnsServer.stop();
DEBUG_MSG_P(PSTR("[WIFI] Captive portal disabled\n"));
}
}
#endif // WIFI_AP_CAPTIVE
#if DEBUG_SUPPORT
void _wifiDebug(justwifi_messages_t code, char * parameter) {
void _wifiDebugCallback(justwifi_messages_t code, char * parameter) {
// -------------------------------------------------------------------------
if (code == MESSAGE_SCANNING) {
DEBUG_MSG_P(PSTR("[WIFI] Scanning\n"));
@ -220,6 +303,8 @@ void _wifiDebug(justwifi_messages_t code, char * parameter) {
DEBUG_MSG_P(PSTR("[WIFI] %s\n"), parameter);
}
// -------------------------------------------------------------------------
if (code == MESSAGE_CONNECTING) {
DEBUG_MSG_P(PSTR("[WIFI] Connecting to %s\n"), parameter);
}
@ -233,25 +318,59 @@ void _wifiDebug(justwifi_messages_t code, char * parameter) {
}
if (code == MESSAGE_CONNECTED) {
wifiStatus();
}
if (code == MESSAGE_ACCESSPOINT_CREATED) {
wifiStatus();
wifiDebug(WIFI_STA);
}
if (code == MESSAGE_DISCONNECTED) {
DEBUG_MSG_P(PSTR("[WIFI] Disconnected\n"));
}
// -------------------------------------------------------------------------
if (code == MESSAGE_ACCESSPOINT_CREATING) {
DEBUG_MSG_P(PSTR("[WIFI] Creating access point\n"));
}
if (code == MESSAGE_ACCESSPOINT_CREATED) {
wifiDebug(WIFI_AP);
}
if (code == MESSAGE_ACCESSPOINT_FAILED) {
DEBUG_MSG_P(PSTR("[WIFI] Could not create access point\n"));
}
if (code == MESSAGE_ACCESSPOINT_DESTROYED) {
DEBUG_MSG_P(PSTR("[WIFI] Access point destroyed\n"));
}
// -------------------------------------------------------------------------
if (code == MESSAGE_WPS_START) {
DEBUG_MSG_P(PSTR("[WIFI] WPS started\n"));
}
if (code == MESSAGE_WPS_SUCCESS) {
DEBUG_MSG_P(PSTR("[WIFI] WPS succeded!\n"));
}
if (code == MESSAGE_WPS_ERROR) {
DEBUG_MSG_P(PSTR("[WIFI] WPS failed\n"));
}
// ------------------------------------------------------------------------
if (code == MESSAGE_SMARTCONFIG_START) {
DEBUG_MSG_P(PSTR("[WIFI] Smart Config started\n"));
}
if (code == MESSAGE_SMARTCONFIG_SUCCESS) {
DEBUG_MSG_P(PSTR("[WIFI] Smart Config succeded!\n"));
}
if (code == MESSAGE_SMARTCONFIG_ERROR) {
DEBUG_MSG_P(PSTR("[WIFI] Smart Config failed\n"));
}
}
#endif // DEBUG_SUPPORT
@ -271,10 +390,24 @@ void _wifiInitCommands() {
});
settingsRegisterCommand(F("WIFI.AP"), [](Embedis* e) {
createAP();
wifiStartAP();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#if defined(JUSTWIFI_ENABLE_WPS)
settingsRegisterCommand(F("WIFI.WPS"), [](Embedis* e) {
wifiStartWPS();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
settingsRegisterCommand(F("WIFI.SMARTCONFIG"), [](Embedis* e) {
wifiStartSmartConfig();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
settingsRegisterCommand(F("WIFI.SCAN"), [](Embedis* e) {
_wifiScan();
DEBUG_MSG_P(PSTR("+OK\n"));
@ -290,6 +423,17 @@ void _wifiInitCommands() {
#if WEB_SUPPORT
bool _wifiWebSocketOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, "wifi", 4) == 0) return true;
if (strncmp(key, "ssid", 4) == 0) return true;
if (strncmp(key, "pass", 4) == 0) return true;
if (strncmp(key, "ip", 2) == 0) return true;
if (strncmp(key, "gw", 2) == 0) return true;
if (strncmp(key, "mask", 4) == 0) return true;
if (strncmp(key, "dns", 3) == 0) return true;
return false;
}
void _wifiWebSocketOnSend(JsonObject& root) {
root["maxNetworks"] = WIFI_MAX_NETWORKS;
root["wifiScan"] = getSetting("wifiScan", WIFI_SCAN_NETWORKS).toInt() == 1;
@ -312,6 +456,59 @@ void _wifiWebSocketOnAction(uint32_t client_id, const char * action, JsonObject&
#endif
// -----------------------------------------------------------------------------
// INFO
// -----------------------------------------------------------------------------
void wifiDebug(WiFiMode_t modes) {
bool footer = false;
if (((modes & WIFI_STA) > 0) && ((WiFi.getMode() & WIFI_STA) > 0)) {
uint8_t * bssid = WiFi.BSSID();
DEBUG_MSG_P(PSTR("[WIFI] ------------------------------------- MODE STA\n"));
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), WiFi.SSID().c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.localIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.macAddress().c_str());
DEBUG_MSG_P(PSTR("[WIFI] GW %s\n"), WiFi.gatewayIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] DNS %s\n"), WiFi.dnsIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MASK %s\n"), WiFi.subnetMask().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] HOST http://%s.local\n"), WiFi.hostname().c_str());
DEBUG_MSG_P(PSTR("[WIFI] BSSID %02X:%02X:%02X:%02X:%02X:%02X\n"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], bssid[6]
);
DEBUG_MSG_P(PSTR("[WIFI] CH %d\n"), WiFi.channel());
DEBUG_MSG_P(PSTR("[WIFI] RSSI %d\n"), WiFi.RSSI());
footer = true;
}
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] IP %s\n"), WiFi.softAPIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.softAPmacAddress().c_str());
footer = true;
}
if (WiFi.getMode() == 0) {
DEBUG_MSG_P(PSTR("[WIFI] ------------------------------------- MODE OFF\n"));
DEBUG_MSG_P(PSTR("[WIFI] No connection\n"));
footer = true;
}
if (footer) {
DEBUG_MSG_P(PSTR("[WIFI] ----------------------------------------------\n"));
}
}
void wifiDebug() {
wifiDebug(WIFI_AP_STA);
}
// -----------------------------------------------------------------------------
// API
// -----------------------------------------------------------------------------
@ -330,11 +527,6 @@ String getNetwork() {
return WiFi.SSID();
}
double wifiDistance(int rssi) {
double exponent = (double) (WIFI_RSSI_1M - rssi) / WIFI_PROPAGATION_CONST / 10.0;
return round(pow(10, exponent));
}
bool wifiConnected() {
return jw.connected();
}
@ -343,11 +535,30 @@ void wifiDisconnect() {
jw.disconnect();
}
bool createAP() {
jw.disconnect();
jw.resetReconnectTimeout();
return jw.createAP();
void wifiStartAP(bool only) {
if (only) {
jw.enableSTA(false);
jw.disconnect();
jw.resetReconnectTimeout();
}
jw.enableAP(true);
}
void wifiStartAP() {
wifiStartAP(true);
}
#if defined(JUSTWIFI_ENABLE_WPS)
void wifiStartWPS() {
jw.startWPS();
}
#endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
void wifiStartSmartConfig() {
jw.startSmartConfig();
}
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
void wifiReconnectCheck() {
bool connected = false;
@ -360,44 +571,13 @@ void wifiReconnectCheck() {
jw.setReconnectTimeout(connected ? 0 : WIFI_RECONNECT_INTERVAL);
}
void wifiStatus() {
if (WiFi.getMode() == WIFI_AP_STA) {
DEBUG_MSG_P(PSTR("[WIFI] MODE AP + STA --------------------------------\n"));
} else if (WiFi.getMode() == WIFI_AP) {
DEBUG_MSG_P(PSTR("[WIFI] MODE AP --------------------------------------\n"));
} else if (WiFi.getMode() == WIFI_STA) {
DEBUG_MSG_P(PSTR("[WIFI] MODE STA -------------------------------------\n"));
} else {
DEBUG_MSG_P(PSTR("[WIFI] MODE OFF -------------------------------------\n"));
DEBUG_MSG_P(PSTR("[WIFI] No connection\n"));
}
if ((WiFi.getMode() & WIFI_AP) == WIFI_AP) {
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), jw.getAPSSID().c_str());
DEBUG_MSG_P(PSTR("[WIFI] PASS %s\n"), getSetting("adminPass", ADMIN_PASS).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());
}
if ((WiFi.getMode() & WIFI_STA) == WIFI_STA) {
uint8_t * bssid = WiFi.BSSID();
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), WiFi.SSID().c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.localIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.macAddress().c_str());
DEBUG_MSG_P(PSTR("[WIFI] GW %s\n"), WiFi.gatewayIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] DNS %s\n"), WiFi.dnsIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MASK %s\n"), WiFi.subnetMask().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] HOST %s\n"), WiFi.hostname().c_str());
DEBUG_MSG_P(PSTR("[WIFI] BSSID %02X:%02X:%02X:%02X:%02X:%02X\n"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], bssid[6]
);
DEBUG_MSG_P(PSTR("[WIFI] CH %d\n"), WiFi.channel());
DEBUG_MSG_P(PSTR("[WIFI] RSSI %d\n"), WiFi.RSSI());
}
DEBUG_MSG_P(PSTR("[WIFI] ----------------------------------------------\n"));
uint8_t wifiState() {
uint8_t state = 0;
if (jw.connected()) state += WIFI_STATE_STA;
if (jw.connectable()) state += WIFI_STATE_AP;
if (_wifi_wps_running) state += WIFI_STATE_WPS;
if (_wifi_smartconfig_running) state += WIFI_STATE_SMARTCONFIG;
return state;
}
void wifiRegister(wifi_callback_f callback) {
@ -410,20 +590,23 @@ void wifiRegister(wifi_callback_f callback) {
void wifiSetup() {
#if WIFI_SLEEP_ENABLED
wifi_set_sleep_type(LIGHT_SLEEP_T);
#endif
WiFi.setSleepMode(WIFI_SLEEP_MODE);
_wifiInject();
_wifiConfigure();
// Message callbacks
wifiRegister(_wifiCallback);
#if WIFI_AP_CAPTIVE
wifiRegister(_wifiCaptivePortal);
#endif
#if DEBUG_SUPPORT
wifiRegister(_wifiDebug);
wifiRegister(_wifiDebugCallback);
#endif
#if WEB_SUPPORT
wsOnSendRegister(_wifiWebSocketOnSend);
wsOnReceiveRegister(_wifiWebSocketOnReceive);
wsOnAfterParseRegister(_wifiConfigure);
wsOnActionRegister(_wifiWebSocketOnAction);
#endif
@ -439,11 +622,27 @@ void wifiSetup() {
void wifiLoop() {
// Main wifi loop
jw.loop();
// Process captrive portal DNS queries if in AP mode only
#if WIFI_AP_CAPTIVE
if ((WiFi.getMode() & WIFI_AP) == WIFI_AP) {
_wifi_dnsServer.processNextRequest();
}
#endif
// Do we have a pending scan?
if (_wifi_scan_client_id > 0) {
_wifiScan(_wifi_scan_client_id);
_wifi_scan_client_id = 0;
}
// Check if we should disable AP
static unsigned long last = 0;
if (millis() - last > 60000) {
last = millis();
_wifiCheckAP();
}
}

+ 124
- 15
code/espurna/ws.ino View File

@ -21,11 +21,63 @@ 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;
// -----------------------------------------------------------------------------
// Private methods
// -----------------------------------------------------------------------------
typedef struct {
IPAddress ip;
unsigned long timestamp = 0;
} ws_ticket_t;
ws_ticket_t _ticket[WS_BUFFER_SIZE];
void _onAuth(AsyncWebServerRequest *request) {
webLog(request);
if (!webAuthenticate(request)) return request->requestAuthentication();
IPAddress ip = request->client()->remoteIP();
unsigned long now = millis();
unsigned short index;
for (index = 0; index < WS_BUFFER_SIZE; index++) {
if (_ticket[index].ip == ip) break;
if (_ticket[index].timestamp == 0) break;
if (now - _ticket[index].timestamp > WS_TIMEOUT) break;
}
if (index == WS_BUFFER_SIZE) {
request->send(429);
} else {
_ticket[index].ip = ip;
_ticket[index].timestamp = now;
request->send(200, "text/plain", "OK");
}
}
bool _wsAuth(AsyncWebSocketClient * client) {
IPAddress ip = client->remoteIP();
unsigned long now = millis();
unsigned short index = 0;
for (index = 0; index < WS_BUFFER_SIZE; index++) {
if ((_ticket[index].ip == ip) && (now - _ticket[index].timestamp < WS_TIMEOUT)) break;
}
if (index == WS_BUFFER_SIZE) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] Validation check failed\n"));
wsSend_P(client->id(), PSTR("{\"message\": 10}"));
return false;
}
return true;
}
// -----------------------------------------------------------------------------
#if MQTT_SUPPORT
void _wsMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) wsSend_P(PSTR("{\"mqttStatus\": true}"));
@ -93,8 +145,22 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] Requested action: %s\n"), action);
if (strcmp(action, "reboot") == 0) deferredReset(100, CUSTOM_RESET_WEB);
if (strcmp(action, "reconnect") == 0) _web_defer.once_ms(100, wifiDisconnect);
if (strcmp(action, "reboot") == 0) {
deferredReset(100, CUSTOM_RESET_WEB);
return;
}
if (strcmp(action, "reconnect") == 0) {
_web_defer.once_ms(100, wifiDisconnect);
return;
}
if (strcmp(action, "factory_reset") == 0) {
DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
resetSettings();
deferredReset(100, CUSTOM_RESET_FACTORY);
return;
}
JsonObject& data = root["data"];
if (data.success()) {
@ -113,6 +179,8 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
}
}
return;
}
};
@ -154,6 +222,19 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
continue;
}
// Check if key has to be processed
bool found = false;
for (unsigned char i = 0; i < _ws_on_receive_callbacks.size(); i++) {
found |= (_ws_on_receive_callbacks[i])(key.c_str(), value);
// TODO: remove this to call all OnReceiveCallbacks with the
// current key/value
if (found) break;
}
if (!found) {
delSetting(key);
continue;
}
// Store values
if (value.is<JsonArray&>()) {
if (_wsStore(key, value.as<JsonArray&>())) changed = true;
@ -175,9 +256,7 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
if (save) {
// Callbacks
for (unsigned char i = 0; i < _ws_on_after_parse_callbacks.size(); i++) {
(_ws_on_after_parse_callbacks[i])();
}
wsReload();
// This should got to callback as well
// but first change management has to be in place
@ -204,12 +283,23 @@ void _wsUpdate(JsonObject& root) {
root["heap"] = getFreeHeap();
root["uptime"] = getUptime();
root["rssi"] = WiFi.RSSI();
root["distance"] = wifiDistance(WiFi.RSSI());
root["loadaverage"] = systemLoadAverage();
#if ADC_MODE_VALUE == ADC_VCC
root["vcc"] = ESP.getVcc();
#endif
#if NTP_SUPPORT
if (ntpSynced()) root["now"] = now();
#endif
}
bool _wsOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, "ws", 2) == 0) return true;
if (strncmp(key, "admin", 5) == 0) return true;
if (strncmp(key, "hostname", 8) == 0) return true;
if (strncmp(key, "webPort", 7) == 0) return true;
return false;
}
void _wsOnStart(JsonObject& root) {
#if USE_PASSWORD && WEB_FORCE_PASS_CHANGE
@ -239,6 +329,7 @@ void _wsOnStart(JsonObject& root) {
root["app_name"] = APP_NAME;
root["app_version"] = APP_VERSION;
root["app_build"] = buildTime();
root["app_revision"] = APP_REVISION;
root["manufacturer"] = MANUFACTURER;
root["chipid"] = String(chipid);
root["mac"] = WiFi.macAddress();
@ -250,13 +341,17 @@ void _wsOnStart(JsonObject& root) {
root["deviceip"] = getIP();
root["sketch_size"] = ESP.getSketchSize();
root["free_size"] = ESP.getFreeSketchSpace();
root["sdk"] = ESP.getSdkVersion();
root["core"] = getCoreVersion();
_wsUpdate(root);
root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt();
root["webPort"] = getSetting("webPort", WEB_PORT).toInt();
root["tmpUnits"] = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt();
root["tmpCorrection"] = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat();
root["wsAuth"] = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1;
#if TERMINAL_SUPPORT
root["cmdVisible"] = 1;
#endif
}
@ -271,6 +366,11 @@ void _wsStart(uint32_t client_id) {
void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
if (type == WS_EVT_CONNECT) {
#ifndef NOWSAUTH
if (!_wsAuth(client)) return;
#endif
IPAddress ip = client->remoteIP();
DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u connected, ip: %d.%d.%d.%d, url: %s\n"), client->id(), ip[0], ip[1], ip[2], ip[3], server->url());
_wsStart(client->id());
@ -310,7 +410,7 @@ void _wsLoop() {
}
// -----------------------------------------------------------------------------
// Piblic API
// Public API
// -----------------------------------------------------------------------------
bool wsConnected() {
@ -321,6 +421,10 @@ void wsOnSendRegister(ws_on_send_callback_f callback) {
_ws_on_send_callbacks.push_back(callback);
}
void wsOnReceiveRegister(ws_on_receive_callback_f callback) {
_ws_on_receive_callbacks.push_back(callback);
}
void wsOnActionRegister(ws_on_action_callback_f callback) {
_ws_on_action_callbacks.push_back(callback);
}
@ -336,6 +440,7 @@ void wsSend(ws_on_send_callback_f callback) {
callback(root);
String output;
root.printTo(output);
jsonBuffer.clear();
_ws.textAll((char *) output.c_str());
}
}
@ -360,6 +465,7 @@ void wsSend(uint32_t client_id, ws_on_send_callback_f callback) {
callback(root);
String output;
root.printTo(output);
jsonBuffer.clear();
_ws.text(client_id, (char *) output.c_str());
}
@ -373,21 +479,24 @@ void wsSend_P(uint32_t client_id, PGM_P payload) {
_ws.text(client_id, buffer);
}
void wsConfigure() {
#if USE_PASSWORD
_ws.setAuthentication(WEB_USERNAME, (const char *) getSetting("adminPass", ADMIN_PASS).c_str());
#endif
// 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);
wsConfigure();
webServer()->addHandler(&_ws);
webServer()->on("/auth", HTTP_GET, _onAuth);
#if MQTT_SUPPORT
mqttRegister(_wsMQTTCallback);
#endif
wsOnSendRegister(_wsOnStart);
wsOnAfterParseRegister(wsConfigure);
wsOnReceiveRegister(_wsOnReceive);
espurnaRegisterLoop(_wsLoop);
}


+ 22
- 13
code/extra_scripts.py View File

@ -1,5 +1,8 @@
#!/usr/bin/env python
from subprocess import call
import os
import time
Import("env")
# ------------------------------------------------------------------------------
@ -31,6 +34,22 @@ def clr(color, text):
# Callbacks
# ------------------------------------------------------------------------------
def remove_float_support():
flags = " ".join(env['LINKFLAGS'])
flags = flags.replace("-u _printf_float", "")
flags = flags.replace("-u _scanf_float", "")
newflags = flags.split()
env.Replace(
LINKFLAGS = newflags
)
def cpp_check(source, target, env):
print("Started cppcheck...\n")
call(["cppcheck", os.getcwd()+"/espurna", "--force", "--enable=all"])
print("Finished cppcheck...\n")
def check_size(source, target, env):
time.sleep(2)
size = target[0].get_size()
@ -39,21 +58,11 @@ def check_size(source, target, env):
# print clr(Color.LIGHT_RED, "File too large for OTA!")
# Exit(1)
def add_build_flags(source, target, env):
build_h = "espurna/config/build.h"
build_flags = env['BUILD_FLAGS'][0]
lines = open(build_h).readlines()
with open(build_h, "w") as fh:
for line in lines:
if "APP_BUILD_FLAGS" in line:
fh.write("#define APP_BUILD_FLAGS \"%s\"" % build_flags)
else:
fh.write(line)
# ------------------------------------------------------------------------------
# Hooks
# ------------------------------------------------------------------------------
env.AddPreAction("$BUILD_DIR/src/espurna.ino.o", add_build_flags)
remove_float_support()
#env.AddPreAction("buildprog", cpp_check)
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", check_size)

+ 13
- 8
code/gulpfile.js View File

@ -29,15 +29,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
const fs = require('fs');
const gulp = require('gulp');
const htmlmin = require('gulp-htmlmin');
const cleancss = require('gulp-clean-css');
const uglify = require('gulp-uglify');
const gzip = require('gulp-gzip');
const inline = require('gulp-inline');
const inlineImages = require('gulp-css-base64');
const favicon = require('gulp-base64-favicon');
const htmllint = require('gulp-htmllint');
const gutil = require('gulp-util');
const log = require('fancy-log');
const csslint = require('gulp-csslint');
const crass = require('gulp-crass');
const replace = require('gulp-replace');
const dataFolder = 'espurna/data/';
const staticFolder = 'espurna/static/';
@ -50,7 +51,7 @@ var toHeader = function(filename) {
var wstream = fs.createWriteStream(destination);
wstream.on('error', function (err) {
console.log(err);
log.error(err);
});
var data = fs.readFileSync(source);
@ -76,10 +77,13 @@ var toHeader = function(filename) {
var htmllintReporter = function(filepath, issues) {
if (issues.length > 0) {
issues.forEach(function (issue) {
gutil.log(
gutil.colors.cyan('[gulp-htmllint] ') +
gutil.colors.white(filepath + ' [' + issue.line + ',' + issue.column + ']: ') +
gutil.colors.red('(' + issue.code + ') ' + issue.msg)
log.info(
'[gulp-htmllint] ' +
filepath + ' [' +
issue.line + ',' +
issue.column + ']: ' +
'(' + issue.code + ') ' +
issue.msg
);
});
process.exitCode = 1;
@ -114,7 +118,7 @@ gulp.task('buildfs_inline', function() {
pipe(inline({
base: 'html/',
js: [uglify],
css: [cleancss, inlineImages],
css: [crass, inlineImages],
disabledTypes: ['svg', 'img']
})).
pipe(htmlmin({
@ -123,6 +127,7 @@ gulp.task('buildfs_inline', function() {
minifyCSS: true,
minifyJS: true
})).
pipe(replace('pure-', 'p-')).
pipe(gzip()).
pipe(gulp.dest(dataFolder));
});


+ 42
- 5
code/html/custom.css View File

@ -5,6 +5,8 @@
#menu .pure-menu-heading {
font-size: 100%;
padding: .5em .5em;
white-space: normal;
text-transform: initial;
}
.pure-g {
@ -29,6 +31,13 @@
color: #777777;
}
@media screen and (max-width: 32em) {
.header > h1 {
line-height: 100%;
font-size: 2em;
}
}
h2 {
font-size: 1em;
}
@ -120,6 +129,15 @@ div.state {
text-align: right;
}
.pure-g span.terminal,
.pure-g textarea.terminal {
font-family: 'Courier New', monospace;
font-size: 80%;
line-height: 100%;
background-color: #000;
color: #0F0;
}
/* -----------------------------------------------------------------------------
Buttons
-------------------------------------------------------------------------- */
@ -147,23 +165,32 @@ div.state {
.button-rfb-forget,
.button-del-network,
.button-del-schedule,
.button-upgrade {
.button-dbg-clear,
.button-upgrade,
.button-settings-factory {
background: rgb(192, 0, 0); /* redish */
}
.button-update,
.button-update-password,
.button-add-network,
.button-add-schedule,
.button-rfb-learn,
.button-upgrade-browse,
.button-ha-add,
.button-ha-config,
.button-settings-backup,
.button-settings-restore,
.button-dbgcmd,
.button-apikey {
background: rgb(0, 192, 0); /* green */
}
.button-add-switch-schedule,
.button-add-light-schedule {
background: rgb(0, 192, 0); /* green */
display: none;
}
.button-more-network,
.button-more-schedule,
.button-wifi-scan,
@ -172,6 +199,7 @@ div.state {
}
.button-upgrade-browse,
.button-dbgcmd,
.button-ha-add,
.button-apikey,
.button-upgrade {
@ -276,9 +304,18 @@ span.slider {
display: none;
}
#haConfig,
#scanResult {
color: #888;
font-family: 'Courier New', monospace;
font-size: 60%;
margin-top: 10px;
display: none;
padding: 10px;
}
/* -----------------------------------------------------------------------------
Logs
-------------------------------------------------------------------------- */
#weblog {
height: 400px;
margin-bottom: 10px;
}

+ 226
- 52
code/html/custom.js View File

@ -4,7 +4,8 @@ var maxNetworks;
var maxSchedules;
var messages = [];
var free_size = 0;
var webhost;
var urls = {};
var numChanged = 0;
var numReboot = 0;
@ -12,6 +13,7 @@ var numReconnect = 0;
var numReload = 0;
var useWhite = false;
var useCCT = false;
var now = 0;
var ago = 0;
@ -37,7 +39,8 @@ function sensorName(id) {
"DHT", "Dallas", "Emon Analog", "Emon ADC121", "Emon ADS1X15",
"HLW8012", "V9261F", "ECH1560", "Analog", "Digital",
"Events", "PMSX003", "BMX280", "MHZ19", "SI7021",
"SHT3X I2C", "BH1750"
"SHT3X I2C", "BH1750", "PZEM004T", "AM2320 I2C", "GUVAS12SD",
"TMP3X", "HC-SR04", "SenseAir", "GeigerTicks", "GeigerCPM"
];
if (1 <= id && id <= names.length) {
return names[id - 1];
@ -51,7 +54,8 @@ function magnitudeType(type) {
"Current", "Voltage", "Active Power", "Apparent Power",
"Reactive Power", "Power Factor", "Energy", "Energy (delta)",
"Analog", "Digital", "Events",
"PM1.0", "PM2.5", "PM10", "CO2", "Lux"
"PM1.0", "PM2.5", "PM10", "CO2", "Lux", "UV", "Distance" , "HCHO",
"Local Dose Rate", "Local Dose Rate"
];
if (1 <= type && type <= types.length) {
return types[type - 1];
@ -62,7 +66,7 @@ function magnitudeType(type) {
function magnitudeError(error) {
var errors = [
"OK", "Out of Range", "Warming Up", "Timeout", "Wrong ID",
"Data Error", "I2C Error", "GPIO Error"
"Data Error", "I2C Error", "GPIO Error", "Calibration error"
];
if (0 <= error && error < errors.length) {
return errors[error];
@ -74,23 +78,29 @@ function magnitudeError(error) {
// Utils
// -----------------------------------------------------------------------------
$.fn.enterKey = function (fnc) {
return this.each(function () {
$(this).keypress(function (ev) {
var keycode = parseInt(ev.keyCode ? ev.keyCode : ev.which, 10);
if (13 === keycode) {
return fnc.call(this, ev);
}
});
});
};
function keepTime() {
$("span[name='ago']").html(ago);
ago++;
if (0 === now) { return; }
var date = new Date(now * 1000);
var text = date.toISOString().substring(0, 19).replace("T", " ");
$("input[name='now']").val(text);
$("span[name='now']").html(text);
$("span[name='ago']").html(ago);
now++;
ago++;
}
// http://www.the-art-of-web.com/javascript/validate-password/
function checkPassword(str) {
// at least one lowercase and one uppercase letter or number
// at least five characters (letters, numbers or special characters)
var re = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{5,}$/;
return re.test(str);
}
function zeroPad(number, positions) {
@ -130,9 +140,14 @@ function loadTimeZones() {
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,}$/;
// password
var adminPass1 = $("input[name='adminPass']", form).first().val();
if (adminPass1.length > 0 && !checkPassword(adminPass1)) {
if (adminPass1.length > 0 && !re_password.test(adminPass1)) {
alert("The password you have entered is not valid, it must have at least 5 characters, 1 lowercase and 1 uppercase or number!");
return false;
}
@ -143,6 +158,23 @@ function validateForm(form) {
return false;
}
// RFCs mandate that a hostname's labels may contain only
// the ASCII letters 'a' through 'z' (case-insensitive),
// the digits '0' through '9', and the hyphen.
// Hostname labels cannot begin or end with a hyphen.
// 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-]{0,31}[A-Za-z0-9]$');
var hostname = $("input[name='hostname']", form).val();
if (!re_hostname.test(hostname)) {
alert("Hostname cannot be empty and may only contain the ASCII letters ('A' through 'Z' and 'a' through 'z'), the digits '0' through '9', and the hyphen ('-')! They can neither start or end with an hyphen.");
return false;
}
return true;
}
@ -150,9 +182,9 @@ function validateForm(form) {
function getValue(element) {
if ($(element).attr("type") === "checkbox") {
return $(element).is(":checked") ? 1 : 0;
return $(element).prop("checked") ? 1 : 0;
} else if ($(element).attr("type") === "radio") {
if (!$(element).is(":checked")) {
if (!$(element).prop("checked")) {
return null;
}
}
@ -166,9 +198,9 @@ function addValue(data, name, value) {
// These fields will always be a list of values
var is_group = [
"ssid", "pass", "gw", "mask", "ip", "dns",
"schEnabled", "schSwitch","schAction","schHour","schMinute","schWDs",
"schEnabled", "schSwitch","schAction","schType","schHour","schMinute","schWDs","schUTC",
"relayBoot", "relayPulse", "relayTime",
"mqttGroup", "mqttGroupInv",
"mqttGroup", "mqttGroupInv", "relayOnDisc",
"dczRelayIdx", "dczMagnitude",
"tspkRelay", "tspkMagnitude",
"ledMode",
@ -311,7 +343,7 @@ function doUpgrade() {
$.ajax({
// Your server script to process the upload
url: webhost + "upgrade",
url: urls.upgrade.href,
type: "POST",
// Form data
@ -423,6 +455,9 @@ function doUpdate() {
$("input[name='pwrResetCalibration']").
prop("checked", false).
iphoneStyle("refresh");
$("input[name='pwrResetE']").
prop("checked", false).
iphoneStyle("refresh");
// Change handling
numChanged = 0;
@ -452,7 +487,7 @@ function doUpdate() {
}
function doBackup() {
document.getElementById("downloader").src = webhost + "config";
document.getElementById("downloader").src = urls.config.href;
return false;
}
@ -476,7 +511,7 @@ function onFileUpload(event) {
if (data) {
sendAction("restore", data);
} else {
alert(messages[4]);
window.alert(messages[4]);
}
};
reader.readAsText(inputFile);
@ -494,6 +529,16 @@ function doRestore() {
return false;
}
function doFactoryReset() {
var response = window.confirm("Are you sure you want to restore to factory settings?");
if (response === false) {
return false;
}
websock.send(JSON.stringify({"action": "factory_reset"}));
doReload(5000);
return false;
}
function doToggle(element, value) {
var id = parseInt(element.attr("data"), 10);
sendAction("relay", {id: id, status: value ? 1 : 0 });
@ -507,6 +552,25 @@ function doScan() {
return false;
}
function doHAConfig() {
$("#haConfig").html("");
sendAction("haconfig", {});
return false;
}
function doDebugCommand() {
var el = $("input[name='dbgcmd']");
var command = el.val();
el.val("");
sendAction("dbgcmd", {command: command});
return false;
}
function doDebugClear() {
$("#weblog").text("");
return false;
}
// -----------------------------------------------------------------------------
// Visualization
// -----------------------------------------------------------------------------
@ -519,9 +583,9 @@ function toggleMenu() {
function showPanel() {
$(".panel").hide();
$("#" + $(this).attr("data")).show();
if ($("#layout").hasClass("active")) { toggleMenu(); }
$("input[type='checkbox']").
$("#" + $(this).attr("data")).show().
find("input[type='checkbox']").
iphoneStyle("calculateDimensions").
iphoneStyle("refresh");
}
@ -612,7 +676,7 @@ function moreSchedule() {
$("div.more", parent).toggle();
}
function addSchedule() {
function addSchedule(event) {
var numSchedules = $("#schedules > div").length;
if (numSchedules >= maxSchedules) {
alert("Max number of schedules reached");
@ -621,6 +685,13 @@ function addSchedule() {
var tabindex = 200 + numSchedules * 10;
var template = $("#scheduleTemplate").children();
var line = $(template).clone();
var type = (1 === event.data.schType) ? "switch" : "light";
template = $("#" + type + "ActionTemplate").children();
var actionLine = template.clone();
$(line).find("#schActionDiv").append(actionLine);
$(line).find("input").each(function() {
$(this).attr("tabindex", tabindex);
tabindex++;
@ -628,6 +699,12 @@ function addSchedule() {
$(line).find(".button-del-schedule").on("click", delSchedule);
$(line).find(".button-more-schedule").on("click", moreSchedule);
line.appendTo("#schedules");
$(line).find("input[type='checkbox']").
prop("checked", false).
iphoneStyle("calculateDimensions").
iphoneStyle("refresh");
return line;
}
@ -648,7 +725,7 @@ function initRelays(data) {
$(".id", line).html(i);
$("input", line).attr("data", i);
line.appendTo("#relays");
$(":checkbox", line).iphoneStyle({
$("input[type='checkbox']", line).iphoneStyle({
onChange: doToggle,
resizeContainer: true,
resizeHandle: true,
@ -681,6 +758,7 @@ function initRelayConfig(data) {
$("input[name='relayTime']", line).val(relay.pulse_ms);
$("input[name='mqttGroup']", line).val(relay.group);
$("select[name='mqttGroupInv']", line).val(relay.group_inv);
$("select[name='relayOnDisc']", line).val(relay.on_disc);
line.appendTo("#relayConfig");
}
@ -742,6 +820,22 @@ function initColorRGB() {
}
function initCCT() {
// check if already initialized
var done = $("#cct > div").length;
if (done > 0) { return; }
$("#miredsTemplate").children().clone().appendTo("#cct");
$("#mireds").on("change", function() {
var value = $(this).val();
var parent = $(this).parents(".pure-g");
$("span", parent).html(value);
sendAction("mireds", {mireds: value});
});
}
function initColorHSV() {
// check if already initialized
@ -779,6 +873,9 @@ function initChannels(num) {
max = num % 3;
if ((max > 0) & useWhite) {
max--;
if (useCCT) {
max--;
}
}
}
var start = num - max;
@ -792,19 +889,25 @@ function initChannels(num) {
};
// add templates
var i = 0;
var template = $("#channelTemplate").children();
for (var i=0; i<max; i++) {
for (i=0; i<max; i++) {
var channel_id = start + i;
var line = $(template).clone();
$("span.slider", line).attr("data", channel_id);
$("input.slider", line).attr("data", channel_id).on("change", onChannelSliderChange);
$("label", line).html("Channel " + (channel_id + 1));
$("label", line).html("Channel #" + channel_id);
line.appendTo("#channels");
}
for (i=0; i<num; i++) {
$("select.islight").append(
$("<option></option>").attr("value",i).text("Channel #" + i));
}
}
// -----------------------------------------------------------------------------
@ -954,10 +1057,21 @@ function processData(data) {
return;
}
if ("mireds" === key) {
$("#mireds").val(value);
$("span.mireds").html(value);
return;
}
if ("useWhite" === key) {
useWhite = value;
}
if ("useCCT" === key) {
initCCT();
useCCT = value;
}
// ---------------------------------------------------------------------
// Sensors & Magnitudes
// ---------------------------------------------------------------------
@ -970,7 +1084,9 @@ function processData(data) {
var text = (0 === error) ?
magnitude.value + magnitude.units :
magnitudeError(error);
$("input[name='magnitude'][data='" + i + "']").val(text);
var element = $("input[name='magnitude'][data='" + i + "']");
element.val(text);
$("div.hint", element.parent().parent()).html(magnitude.description);
}
return;
}
@ -997,6 +1113,15 @@ function processData(data) {
if ("scanResult" === key) {
$("div.scan.loading").hide();
$("#scanResult").show();
}
// -----------------------------------------------------------------------------
// Home Assistant
// -----------------------------------------------------------------------------
if ("haConfig" === key) {
$("#haConfig").show();
}
// -----------------------------------------------------------------------------
@ -1011,12 +1136,13 @@ function processData(data) {
if ("schedule" === key) {
for (i in value) {
var schedule = value[i];
var sch_line = addSchedule();
var sch_line = addSchedule({ data: {schType: schedule["schType"] }});
Object.keys(schedule).forEach(function(key) {
var sch_value = schedule[key];
$("input[name='" + key + "']", sch_line).val(sch_value);
$("select[name='" + key + "']", sch_line).prop("value", sch_value);
$(":checkbox[name='" + key + "']", sch_line).
$("input[type='checkbox'][name='" + key + "']", sch_line).
prop("checked", sch_value).
iphoneStyle("refresh");
});
@ -1089,17 +1215,29 @@ function processData(data) {
return;
}
// Web log
if ("weblog" === key) {
$("#weblog").append(new Text(value));
$("#weblog").scrollTop($("#weblog")[0].scrollHeight - $("#weblog").height());
return;
}
// Enable options
var position = key.indexOf("Visible");
if (position > 0 && position === key.length - 7) {
var module = key.slice(0,-7);
$(".module-" + module).show();
$(".module-" + module).css("display", "inherit");
return;
}
if ("deviceip" === key) {
var a_href = $("span[name='" + key + "']").parent();
a_href.attr("href", "http://" + value);
a_href.next().attr("href", "telnet://" + value);
}
if ("now" === key) {
now = value;
ago = 0;
return;
}
@ -1108,9 +1246,6 @@ function processData(data) {
}
// Pre-process
if ("network" === key) {
value = value.toUpperCase();
}
if ("mqttStatus" === key) {
value = value ? "CONNECTED" : "NOT CONNECTED";
}
@ -1118,6 +1253,7 @@ function processData(data) {
value = value ? "SYNC'D" : "NOT SYNC'D";
}
if ("uptime" === key) {
ago = 0;
var uptime = parseInt(value, 10);
var seconds = uptime % 60; uptime = parseInt(uptime / 60, 10);
var minutes = uptime % 60; uptime = parseInt(uptime / 60, 10);
@ -1214,26 +1350,56 @@ function hasChanged() {
// Init & connect
// -----------------------------------------------------------------------------
function connect(host) {
function initUrls(root) {
var paths = ["ws", "upgrade", "config", "auth"];
if (typeof host === "undefined") {
host = window.location.href.replace("#", "");
urls["root"] = root;
paths.forEach(function(path) {
urls[path] = new URL(path, root);
urls[path].protocol = root.protocol;
});
if (root.protocol == "https:") {
urls.ws.protocol = "wss:";
} else {
if (host.indexOf("http") !== 0) {
host = "http://" + host + "/";
}
urls.ws.protocol = "ws:";
}
if (host.indexOf("http") !== 0) { return; }
webhost = host;
wshost = host.replace("http", "ws") + "ws";
}
if (websock) { websock.close(); }
websock = new WebSocket(wshost);
websock.onmessage = function(evt) {
var data = getJson(evt.data);
if (data) { processData(data); }
};
function connectToURL(url) {
initUrls(url);
$.ajax({
'method': 'GET',
'url': urls.auth.href,
'xhrFields': { 'withCredentials': true }
}).done(function(data) {
if (websock) { websock.close(); }
websock = new WebSocket(urls.ws.href);
websock.onmessage = function(evt) {
var data = getJson(evt.data.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"));
if (data) {
processData(data);
}
};
}).fail(function() {
// Nothing to do, reload page and retry
});
}
function connect(host) {
if (!host.startsWith("http:") && !host.startsWith("https:")) {
host = "http://" + host;
}
connectToURL(new URL(host));
}
function connectToCurrentURL() {
connectToURL(new URL(window.location));
}
$(function() {
@ -1251,8 +1417,13 @@ $(function() {
$(".button-reboot").on("click", doReboot);
$(".button-reconnect").on("click", doReconnect);
$(".button-wifi-scan").on("click", doScan);
$(".button-ha-config").on("click", doHAConfig);
$(".button-dbgcmd").on("click", doDebugCommand);
$("input[name='dbgcmd']").enterKey(doDebugCommand);
$(".button-dbg-clear").on("click", doDebugClear);
$(".button-settings-backup").on("click", doBackup);
$(".button-settings-restore").on("click", doRestore);
$(".button-settings-factory").on("click", doFactoryReset);
$("#uploader").on("change", onFileUpload);
$(".button-upgrade").on("click", doUpgrade);
@ -1268,11 +1439,14 @@ $(function() {
$(".button-add-network").on("click", function() {
$(".more", addNetwork()).toggle();
});
$(".button-add-schedule").on("click", addSchedule);
$(".button-add-switch-schedule").on("click", { schType: 1 }, addSchedule);
$(".button-add-light-schedule").on("click", { schType: 2 }, addSchedule);
$(document).on("change", "input", hasChanged);
$(document).on("change", "select", hasChanged);
connect();
// don't autoconnect when opening from filesystem
if (window.location.protocol === "file:") { return; }
connectToCurrentURL();
});

BIN
code/html/favicon.ico View File

Before After

+ 320
- 119
code/html/index.html View File

@ -66,7 +66,7 @@
<div id="layout" class="webmode">
<a href="#menu" id="menuLink" class="menu-link">
<a id="menuLink" class="menu-link">
<span></span>
</a>
@ -82,12 +82,24 @@
<a href="#" class="pure-menu-link" data="panel-status">STATUS</a>
</li>
<li class="pure-menu-item">
<li class="pure-menu-item menu-item-divided">
<a href="#" class="pure-menu-link" data="panel-general">GENERAL</a>
</li>
<li class="pure-menu-item">
<a href="#" class="pure-menu-link" data="panel-wifi">WIFI</a>
<li class="pure-menu-item module module-dcz">
<a href="#" class="pure-menu-link" data="panel-domoticz">DOMOTICZ</a>
</li>
<li class="pure-menu-item module module-ha">
<a href="#" class="pure-menu-link" data="panel-ha">HASS</a>
</li>
<li class="pure-menu-item module module-idb">
<a href="#" class="pure-menu-link" data="panel-idb">INFLUXDB</a>
</li>
<li class="pure-menu-item module module-color">
<a href="#" class="pure-menu-link" data="panel-color">LIGHTS</a>
</li>
<li class="pure-menu-item module module-mqtt">
@ -98,42 +110,38 @@
<a href="#" class="pure-menu-link" data="panel-ntp">NTP</a>
</li>
<li class="pure-menu-item module module-relay">
<a href="#" class="pure-menu-link" data="panel-relay">SWITCHES</a>
<li class="pure-menu-item module module-rfb">
<a href="#" class="pure-menu-link" data="panel-rfb">RF</a>
</li>
<li class="pure-menu-item module module-sch">
<a href="#" class="pure-menu-link" data="panel-schedule">SCHEDULE</a>
</li>
<li class="pure-menu-item module module-color">
<a href="#" class="pure-menu-link" data="panel-color">LIGHTS</a>
</li>
<li class="pure-menu-item module module-sensors">
<a href="#" class="pure-menu-link" data="panel-sensors">SENSORS</a>
</li>
<li class="pure-menu-item module module-dcz">
<a href="#" class="pure-menu-link" data="panel-domoticz">DOMOTICZ</a>
</li>
<li class="pure-menu-item module module-idb">
<a href="#" class="pure-menu-link" data="panel-idb">INFLUXDB</a>
<li class="pure-menu-item module module-relay">
<a href="#" class="pure-menu-link" data="panel-relay">SWITCHES</a>
</li>
<li class="pure-menu-item module module-tspk">
<a href="#" class="pure-menu-link" data="panel-thingspeak">THINGSPEAK</a>
</li>
<li class="pure-menu-item module module-rfb">
<a href="#" class="pure-menu-link" data="panel-rfb">RFBRIDGE</a>
<li class="pure-menu-item">
<a href="#" class="pure-menu-link" data="panel-wifi">WIFI</a>
</li>
<li class="pure-menu-item">
<li class="pure-menu-item menu-item-divided">
<a href="#" class="pure-menu-link" data="panel-admin">ADMIN</a>
</li>
<li class="pure-menu-item module module-dbg">
<a href="#" class="pure-menu-link" data="panel-dbg">DEBUG</a>
</li>
</ul>
<div class="main-buttons">
@ -171,71 +179,94 @@
<div id="colors"></div>
<div id="cct"></div>
<div id="channels"></div>
<div id="magnitudes"></div>
<div class="pure-u-1 state">
<div class="pure-u-1 pure-u-lg-1-2 state">
<div class="pure-u-1-2">Manufacturer</div>
<div class="pure-u-11-24"><span class="right" name="manufacturer"></span></div>
<div class="pure-u-1-2">Device</div>
<div class="pure-u-11-24"><span class="right" name="device"></span></div>
<div class="pure-u-1-2">Chip ID</div>
<div class="pure-u-11-24"><span class="right" name="chipid"></span></div>
<div class="pure-u-1-2">Wifi MAC</div>
<div class="pure-u-11-24"><span class="right" name="mac"></span></div>
<div class="pure-u-1-2">SDK version</div>
<div class="pure-u-11-24"><span class="right" name="sdk"></span></div>
<div class="pure-u-1-2">Core version</div>
<div class="pure-u-11-24"><span class="right" name="core"></span></div>
<div class="pure-u-1-2">Firmware name</div>
<div class="pure-u-11-24"><span class="right" name="app_name"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Manufacturer</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="manufacturer"></span></div>
<div class="pure-u-1-2">Firmware version</div>
<div class="pure-u-11-24"><span class="right" name="app_version"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Device</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="device"></span></div>
<!--
<div class="pure-u-1-2">Firmware revision</div>
<div class="pure-u-11-24"><span class="right" name="app_revision"></span></div>
-->
<div class="pure-u-1-2 pure-u-lg-1-4">Chip ID</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="chipid"></span></div>
<div class="pure-u-1-2">Firmware build date</div>
<div class="pure-u-11-24"><span class="right" name="app_build"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">MAC</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="mac"></span></div>
<div class="pure-u-1-2">Firmware size</div>
<div class="pure-u-11-24"><span class="right" name="sketch_size" post=" bytes"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Network</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="network"></span></div>
<div class="pure-u-1-2">Free space</div>
<div class="pure-u-11-24"><span class="right" name="free_size" post=" bytes"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">BSSID</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="bssid"></span></div>
</div>
<div class="pure-u-1-2 pure-u-lg-1-4">Channel</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="channel"></span></div>
<div class="pure-u-1 pure-u-lg-11-24 state">
<div class="pure-u-1-2 pure-u-lg-1-4">RSSI</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="rssi"></span> (<span name="distance" post="m"></span>)</div>
<div class="pure-u-1-2">Network</div>
<div class="pure-u-11-24"><span class="right" name="network"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">IP</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="deviceip"></span></div>
<div class="pure-u-1-2">BSSID</div>
<div class="pure-u-11-24"><span class="right" name="bssid"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Firmware name</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="app_name"></span></div>
<div class="pure-u-1-2">Channel</div>
<div class="pure-u-11-24"><span class="right" name="channel"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Firmware version</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="app_version"></span></div>
<div class="pure-u-1-2">RSSI</div>
<div class="pure-u-11-24"><span class="right" name="rssi"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Firmware build</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="app_build"></span></div>
<div class="pure-u-1-2">IP</div>
<div class="pure-u-11-24"><a href=""><span class="right" name="deviceip"></span></a> (<a href=""><span class="right">telnet</span></a>)</div>
<div class="pure-u-1-2 pure-u-lg-1-4">Current time</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="now"></span></div>
<div class="pure-u-1-2">Free heap</div>
<div class="pure-u-11-24"><span class="right" name="heap" post=" bytes"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Uptime</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="uptime"></span></div>
<div class="pure-u-1-2">Load average</div>
<div class="pure-u-11-24"><span class="right" name="loadaverage"></span><span>%</span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Free heap</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="heap" post=" bytes"></span></div>
<div class="pure-u-1-2">VCC</div>
<div class="pure-u-11-24"><span class="right" name="vcc">? </span><span>mV</span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Firmware size</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="sketch_size" post=" bytes"></span></div>
<div class="pure-u-1-2 module module-mqtt">MQTT Status</div>
<div class="pure-u-11-24 module module-mqtt"><span class="right" name="mqttStatus"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Free space</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="free_size" post=" bytes"></span></div>
<div class="pure-u-1-2 module module-ntp">NTP Status</div>
<div class="pure-u-11-24 module module-ntp"><span class="right" name="ntpStatus"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">MQTT Status</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="mqttStatus">NOT AVAILABLE</span></div>
<div class="pure-u-1-2 module module-ntp">Current time</div>
<div class="pure-u-11-24 module module-ntp"><span class="right" name="now"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">NTP Status</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="ntpStatus">NOT AVAILABLE</span></div>
<div class="pure-u-1-2">Uptime</div>
<div class="pure-u-11-24"><span class="right" name="uptime"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Last update</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="ago">?</span><span> seconds ago</span></div>
<div class="pure-u-1-2">Last update</div>
<div class="pure-u-11-24"><span class="right" name="ago">?</span><span> seconds ago</span></div>
</div>
@ -260,11 +291,13 @@
<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="14" type="text" action="reboot" tabindex="1" />
<input name="hostname" class="pure-u-1 pure-u-lg-1-4" maxlength="32" 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">
This name will identify this device in your network (http://&lt;hostname&gt;.local). For this setting to take effect you should restart the wifi interface by clicking the "Reconnect" button.
This name will identify this device in your network (http://&lt;hostname&gt;.local).<br />
Hostname may contain only the ASCII letters 'a' through 'z' (in a case-insensitive manner), the digits '0' through '9', and the hyphen ('-'). They can neither start or end with an hyphen.<br />
For this setting to take effect you should restart the wifi interface by clicking the "Reconnect" button.
</div>
</div>
@ -286,10 +319,11 @@
<label class="pure-u-1 pure-u-lg-1-4">LED mode</label>
<select name="ledMode0" class="pure-u-1 pure-u-lg-1-4" tabindex="7">
<option value="1">WiFi status</option>
<option value="8">Relay status</option>
<option value="0">MQTT managed</option>
<option value="4">Find me</option>
<option value="8">Status</option>
<option value="5">Mixed</option>
<option value="9">Relay &amp; WiFi</option>
<option value="5">Find me &amp; WiFi</option>
<option value="6">Always ON</option>
<option value="7">Always OFF</option>
</select>
@ -298,10 +332,11 @@
<div class="pure-u-1 pure-u-lg-3-4 hint">
This setting defines the behaviour of the main LED in the board.<br />
When in "WiFi status" it will blink at 1Hz when trying to connect. If successfully connected it will briefly blink every 5 seconds if in STA mode or every second if in AP mode.<br />
When in "Relay status" mode the LED will be ON whenever any relay is ON, and OFF otherwise. This is global status notification.<br />
When in "MQTT managed" mode you will be able to set the LED state sending a message to "&lt;base_topic&gt;/led/0/set" with a payload of 0, 1 or 2 (to toggle it).<br />
When in "Find me" mode the LED will be ON when all relays are OFF. This is meant to locate switches at night.<br />
When in "Status" mode the LED will be ON whenever any relay is ON, and OFF otherwise. This is global status notification.<br />
When in "Mixed" mode it will follow the WiFi status but will stay mostly on when relays are OFF, and mostly OFF when any of them is ON.<br />
When in "Relay &amp; WiFi" mode it will follow the WiFi status but will stay mostly off when relays are OFF, and mostly ON when any of them is ON.<br />
When in "Find me &amp; WiFi" mode is the opposite of the "Relay &amp; WiFi", it will follow the WiFi status but will stay mostly on when relays are OFF, and mostly OFF when any of them is ON.<br />
"Always ON" and "Always OFF" modes are self-explanatory.
</div>
</div>
@ -311,22 +346,6 @@
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="alexaEnabled" tabindex="13" /></div>
</div>
<div class="pure-g module module-ha">
<label class="pure-u-1 pure-u-lg-1-4">Home Assistant</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="haEnabled" tabindex="14" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
Home Assistant auto-discovery feature. Enable and save to add the device to your HA console.
When using a colour light you might want to disable CSS style so Home Assistant can parse the color.
</div>
</div>
<div class="pure-g module module-ha">
<label class="pure-u-1 pure-u-lg-1-4">Home Assistant Prefix</label>
<input class="pure-u-1 pure-u-lg-1-4" name="haPrefix" type="text" tabindex="15" />
</div>
</fieldset>
</div>
</div>
@ -375,11 +394,11 @@
<fieldset>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Use colorpicker</label>
<label class="pure-u-1 pure-u-lg-1-4">Use color</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useColor" action="reload" tabindex="8" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Use color picker for the first 3 channels as RGB.<br />Will only work if the device has at least 3 dimmable channels.<br />Reload the page to update the web interface.</div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Use the first three channels as RGB channels. This will also enable the color picker in the web UI. Will only work if the device has at least 3 dimmable channels.<br />Reload the page to update the web interface.</div>
</div>
<div class="pure-g">
@ -395,12 +414,20 @@
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useWhite" action="reload" tabindex="9" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Use forth dimmable channel as white when first 3 have the same RGB value.<br />Will only work if the device has at least 4 dimmable channels.<br />Reload the page to update the web interface.</div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Use forth dimmable channel as (cold) white light calculated out of the RGB values.<br />Will only work if the device has at least 4 dimmable channels.<br />Enabling this will render useless the "Channel 4" slider in the status page.<br />Reload the page to update the web interface.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Use white color temperature</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useCCT" action="reload" tabindex="10" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Use fifth dimmable channel as warm white light and the forth dimmable channel as cold white.<br />Will only work if the device has at least 5 dimmable channels and "white channel" above is also ON.<br />Enabling this will render useless the "Channel 5" slider in the status page.<br />Reload the page to update the web interface.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Use gamma correction</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useGamma" tabindex="10" /></div>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useGamma" tabindex="11" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Use gamma correction for RGB channels.<br />Will only work if "use colorpicker" above is also ON.</div>
@ -408,7 +435,7 @@
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Use CSS style</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useCSS" tabindex="11" /></div>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useCSS" tabindex="12" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Use CSS style to report colors to MQTT and REST API. <br />Red will be reported as "#FF0000" if ON, otherwise "255,0,0"</div>
@ -416,15 +443,23 @@
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Color transitions</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useTransitions" tabindex="12" /></div>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useTransitions" tabindex="13" /></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">If enabled color changes will be smoothed.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Transition time</label>
<div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-1" type="number" name="lightTime" min="10" max="5000" tabindex="14" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Time in millisecons to transition from one color to another.</div>
</div>
<div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>MQTT group</label></div>
<div class="pure-u-1 pure-u-lg-3-4"><input name="mqttGroupColor" class="pure-u-1" tabindex="13" action="reconnect" /></div>
<div class="pure-u-1 pure-u-lg-3-4"><input name="mqttGroupColor" class="pure-u-1" tabindex="15" action="reconnect" /></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Sync color between different lights.</div>
</div>
@ -444,6 +479,13 @@
<fieldset>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Settings</label>
<div class="pure-u-1-3 pure-u-lg-1-4"><button class="pure-button button-settings-backup pure-u-23-24">Backup</button></div>
<div class="pure-u-1-3 pure-u-lg-1-4"><button class="pure-button button-settings-restore pure-u-23-24">Restore</button></div>
<div class="pure-u-1-3 pure-u-lg-1-4"><button class="pure-button button-settings-factory pure-u-1">Factory Reset</button></div>
</div>
<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" />
@ -469,6 +511,11 @@
</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Enable WS Auth</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="wsAuth" /></div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Enable HTTP API</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiEnabled" /></div>
@ -518,19 +565,13 @@
<div class="pure-u-1 pure-u-lg-3-4 hint">This name address of the NoFUSS server for automatic remote updates (see https://bitbucket.org/xoseperez/nofuss).</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Settings</label>
<div class="pure-u-1-2 pure-u-lg-1-8"><button class="pure-button button-settings-backup pure-u-23-24">Backup</button></div>
<div class="pure-u-1-2 pure-u-lg-1-8"><button class="pure-button button-settings-restore pure-u-1">Restore</button></div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Upgrade</label>
<input class="pure-u-1-2 pure-u-lg-1-2" name="filename" type="text" readonly />
<div class=" pure-u-1-4 pure-u-lg-1-8"><button class="pure-button button-upgrade-browse pure-u-23-24">Browse</button></div>
<div class=" pure-u-1-4 pure-u-lg-1-8"><button class="pure-button button-upgrade pure-u-23-24">Upgrade</button></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-0 pure-u-lg-3-4 hint">The device has <span name="free_size"></span> bytes available for OTA updates. If your image is larger than this consider doing a <a href="https://github.com/xoseperez/espurna/wiki/TwoStepUpdates" target="_blank"><strong>two-step update</strong></a>.</div>
<div class="pure-u-1 pure-u-lg-3-4 hint">The device has <span name="free_size"></span> bytes available for OTA updates. If your image is larger than this consider doing a <a href="https://github.com/xoseperez/espurna/wiki/TwoStepUpdates" target="_blank"><strong>two-step update</strong></a>.</div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4"><progress id="upgrade-progress"></progress></div>
<input name="upgrade" type="file" tabindex="16" />
@ -570,8 +611,7 @@
</div>
<div class="pure-g">
<div class="pure-u-0 pure-u-lg-1-4"></div>
<span class="pure-u-1 pure-u-lg-3-4 terminal" id="scanResult" name="scanResult"></span>
<span class="pure-u-1 terminal" id="scanResult" name="scanResult"></span>
</div>
<legend>Networks</legend>
@ -597,7 +637,8 @@
<div id="schedules"></div>
<button type="button" class="pure-button button-add-schedule">Add schedule</button>
<button type="button" class="pure-button button-add-switch-schedule module module-relay">Add switch schedule</button>
<button type="button" class="pure-button button-add-light-schedule module module-color">Add channel schedule</button>
</fieldset>
@ -752,6 +793,14 @@
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="ntpDST" /></div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">DST Region</label>
<select class="pure-u-1 pure-u-lg-1-4" name="ntpRegion">
<option value="0">Europe</option>
<option value="1">USA</option>
</select>
</div>
</fieldset>
</div>
@ -802,6 +851,60 @@
</div>
<div class="panel" id="panel-ha">
<div class="header">
<h1>HOME ASSISTANT</h1>
<h2>
Add this device to your Home Assistant.
</h2>
</div>
<div class="page">
<fieldset>
<legend>Discover</legend>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Discover</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="haEnabled" tabindex="14" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
Home Assistant auto-discovery feature. Enable and save to add the device to your HA console.
When using a colour light you might want to disable CSS style so Home Assistant can parse the color.
</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Prefix</label>
<input class="pure-u-1 pure-u-lg-1-4" name="haPrefix" type="text" tabindex="15" />
</div>
<legend>Configuration</legend>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Configuration</label>
<div class="pure-u-1-4 pure-u-lg-3-4"><button class="pure-button button-ha-config pure-u-1-3">Show</button></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
These are the settings you should copy to your Home Assistant "configuration.yaml" file.
If any of the sections below (switch, light, sensor) already exists, do not duplicate it,
simply copy the contents of the section below the ones already present.
</div>
</div>
<div class="pure-g">
<span class="pure-u-1 terminal" id="haConfig" name="haConfig"></span>
</div>
</fieldset>
</div>
</div>
<div class="panel" id="panel-thingspeak">
<div class="header">
@ -890,6 +993,38 @@
</div>
<div class="panel" id="panel-dbg">
<div class="header">
<h1>DEBUG LOG</h1>
<h2>
Shows debug messages from the device
</h2>
</div>
<div class="page">
<fieldset>
<div class="pure-g module module-cmd">
<div class="pure-u-1 hint">
Write a command and click send to execute it on the device. The output will be shown in the debug text area below.
</div>
<input name="dbgcmd" class="pure-u-3-4" type="text" tabindex="2" />
<div class=" pure-u-1-4 pure-u-lg-1-4"><button class="pure-button button-dbgcmd pure-u-23-24">Send</button></div>
</div>
<div class="pure-g">
<textarea class="pure-u-1 terminal" id="weblog" name="weblog" wrap="off" readonly></textarea>
<div class=" pure-u-1-4 pure-u-lg-1-4"><button class="pure-button button-dbg-clear pure-u-23-24">Clear</button></div>
</div>
</fieldset>
</div>
</div>
<div class="panel" id="panel-sensors">
<div class="header">
@ -932,6 +1067,22 @@
</div>
</div>
<div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-lg-1-4">Power units</label>
<select name="pwrUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
<option value="0">Watts (W)</option>
<option value="1">Kilowatts (kW)</option>
</select>
</div>
<div class="pure-g module module-hlw module-cse module-emon">
<label class="pure-u-1 pure-u-lg-1-4">Energy units</label>
<select name="energyUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
<option value="0">Joules (J)</option>
<option value="1">Kilowatts·hour (kWh)</option>
</select>
</div>
<div class="pure-g module module-temperature">
<label class="pure-u-1 pure-u-lg-1-4">Temperature units</label>
<select name="tmpUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
@ -950,7 +1101,17 @@
</div>
</div>
<legend class="module module-hlw module-emon">Energy monitor</legend>
<div class="pure-g module module-humidity">
<label class="pure-u-1 pure-u-lg-1-4">Humidity correction</label>
<input name="humCorrection" class="pure-u-1 pure-u-lg-1-4" type="number" action="reboot" min="-100" step="0.1" max="100" tabindex="18" />
<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">
Humidity correction value is added to the measured value which may be inaccurate due to many factors. The value can be negative.
</div>
</div>
<legend class="module module-hlw module-cse module-emon">Energy monitor</legend>
<div class="pure-g module module-emon">
<label class="pure-u-1 pure-u-lg-1-4">Voltage</label>
@ -959,28 +1120,28 @@
<div class="pure-u-1 pure-u-lg-3-4 hint">Mains voltage in your system (in V).</div>
</div>
<div class="pure-g module module-hlw">
<div class="pure-g module module-hlw module-cse">
<label class="pure-u-1 pure-u-lg-1-4">Expected Current</label>
<input class="pure-u-1 pure-u-lg-3-4 pwrExpected" name="pwrExpectedC" type="text" tabindex="52" placeholder="0" />
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">In Amperes (A). If you are using a pure resistive load like a bulb, this will be the ratio between the two previous values, i.e. power / voltage. You can also use a current clamp around one of the power wires to get this value.</div>
</div>
<div class="pure-g module module-hlw">
<div class="pure-g module module-hlw module-cse">
<label class="pure-u-1 pure-u-lg-1-4">Expected Voltage</label>
<input class="pure-u-1 pure-u-lg-3-4 pwrExpected" name="pwrExpectedV" type="text" tabindex="53" placeholder="0" />
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">In Volts (V). Enter your the nominal AC voltage for your household or facility, or use multimeter to get this value.</div>
</div>
<div class="pure-g module module-hlw module-emon">
<div class="pure-g module module-hlw module-cse module-emon">
<label class="pure-u-1 pure-u-lg-1-4">Expected Power</label>
<input class="pure-u-1 pure-u-lg-3-4 pwrExpected" name="pwrExpectedP" type="text" tabindex="54" placeholder="0" />
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">In Watts (W). Calibrate your sensor connecting a pure resistive load (like a bulb) and enter here the its nominal power or use a multimeter.</div>
</div>
<div class="pure-g module module-hlw module-emon">
<div class="pure-g module module-hlw module-cse module-emon">
<label class="pure-u-1 pure-u-lg-1-4">Reset calibration</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="pwrResetCalibration" tabindex="55" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
@ -988,6 +1149,14 @@
<div class="pure-u-1 pure-u-lg-3-4 hint">Move this switch to ON and press "Save" to revert to factory calibration values.</div>
</div>
<div class="pure-g module module-hlw module-cse module-emon">
<label class="pure-u-1 pure-u-lg-1-4">Reset energy</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="pwrResetE" tabindex="56" /></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">Move this switch to ON and press "Save" to set energy count to 0.</div>
</div>
</fieldset>
</div>
@ -996,11 +1165,12 @@
<div class="panel" id="panel-rfb">
<div class="header">
<h1>RFBRIDGE</h1>
<h1>RADIO FREQUENCY</h1>
<h2>
Sonoff 433 RF Bridge Configuration<br /><br />
To learn a new code click <strong>LEARN</strong>, the Sonoff RFBridge will beep, then press a button on the remote, the RFBridge will then double beep and the new code should show up. If the device double beeps but the code does not update it has not been properly learnt. Keep trying.<br /><br />
Modify or create new codes manually (18 characters) and then click <strong>SAVE</strong> to store them in the device memory. If your controlled device uses the same code to switch ON and OFF, learn the code with the ON button and copy paste it to the OFF input box, then click SAVE on the last one to store the value.<br /><br />
Sonoff 433 RF Bridge &amp; RF Link Configuration<br /><br />
This page allows you to configure the RF codes for the Sonoff RFBridge 433 and also for a basic RF receiver.<br /><br />
To learn a new code click <strong>LEARN</strong> (the Sonoff RFBridge will beep) then press a button on the remote, the new code should show up (and the RFBridge will double beep). If the device double beeps but the code does not update it has not been properly learnt. Keep trying.<br /><br />
Modify or create new codes manually and then click <strong>SAVE</strong> to store them in the device memory. If your controlled device uses the same code to switch ON and OFF, learn the code with the ON button and copy paste it to the OFF input box, then click SAVE on the last one to store the value.<br /><br />
Delete any code clicking the <strong>FORGET</strong> button.
<span class="module module-rfbraw"><br /><br />You can also specify 116-chars long RAW codes. Raw codes require a <a target="_blank" href="https://github.com/rhx/RF-Bridge-EFM8BB1">specific firmware for for the EFM8BB1</a>.</span>
</h2>
@ -1087,40 +1257,55 @@
<label class="pure-u-1 pure-u-lg-1-4">When time is</label>
<div class="pure-u-1-4 pure-u-lg-1-5">
<input class="pure-u-2-3" name="schHour" type="number" min="0" step="1" max="23" />
<input class="pure-u-2-3" name="schHour" type="number" min="0" step="1" max="23" value="0" />
<div class="pure-u-1-4 hint center">&nbsp;h</div>
</div>
<div class="pure-u-1-4 pure-u-lg-1-5">
<input class="pure-u-2-3" name="schMinute" type="number" min="0" step="1" max="59" />
<input class="pure-u-2-3" name="schMinute" type="number" min="0" step="1" max="59" value="0" />
<div class="pure-u-1-4 hint center">&nbsp;m</div>
</div>
<div class="pure-u-0 pure-u-lg-1-3"></div>
<label class="pure-u-1 pure-u-lg-1-4">Use UTC time</label>
<div class="pure-u-1 pure-u-lg-3-4"><input type="checkbox" name="schUTC" /></div>
<label class="pure-u-1 pure-u-lg-1-4">And weekday is one of</label>
<div class="pure-u-2-5 pure-u-lg-1-5">
<input class="pure-u-23-24 pure-u-lg-23-24" name="schWDs" type="text" maxlength="15" tabindex="0" value="1,2,3,4,5,6,7" />
</div>
<div class="pure-u-3-5 pure-u-lg-1-2 hint center">&nbsp;1 for Monday, 2 for Tuesday...</div>
<label class="pure-u-1 pure-u-lg-1-4">Action</label>
<div class="pure-u-1 pure-u-lg-1-5">
<select class="pure-u-1 pure-u-lg-23-24" name="schAction">
<option value="0">Turn OFF</option>
<option value="1">Turn ON</option>
<option value="2">Toggle</option>
</select>
<div id="schActionDiv" class="pure-u-1">
</div>
<select class="pure-u-1 pure-u-lg-1-5 isrelay" name="schSwitch"></select>
<div class="pure-u-0 pure-u-lg-1-5"></div>
<label class="pure-u-1 pure-u-lg-1-4">Enabled</label>
<div class="pure-u-1 pure-u-lg-3-4"><input type="checkbox" name="schEnabled" /></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<button class="pure-button button-del-schedule" type="button">Delete schedule</button>
</div>
</div>
<div id="switchActionTemplate" class="template">
<label class="pure-u-1 pure-u-lg-1-4">Action</label>
<div class="pure-u-1 pure-u-lg-1-5">
<select class="pure-u-1 pure-u-lg-23-24" name="schAction">
<option value="0">Turn OFF</option>
<option value="1">Turn ON</option>
<option value="2">Toggle</option>
</select>
</div>
<select class="pure-u-1 pure-u-lg-1-5 isrelay" name="schSwitch"></select>
<input type="hidden" name="schType" value="1">
</div>
<div id="lightActionTemplate" class="template">
<label class="pure-u-1 pure-u-lg-1-4">Brightness</label>
<div class="pure-u-1 pure-u-lg-1-5">
<input class="pure-u-2-3" name="schAction" type="number" min="0" step="1" max="255" value="0" />
</div>
<select class="pure-u-1 pure-u-lg-1-5 islight" name="schSwitch"></select>
<input type="hidden" name="schType" value="2">
</div>
<div id="relayTemplate" class="template">
@ -1164,6 +1349,14 @@
<option value="1">Inverse</option>
</select>
</div>
<div class="pure-g module module-mqtt">
<div class="pure-u-1 pure-u-lg-1-4"><label>On MQTT disconnect</label></div>
<select class="pure-u-1 pure-u-lg-3-4" name="relayOnDisc">
<option value="0">Don't change</option>
<option value="1">Turn the switch OFF</option>
<option value="2">Turn the switch ON</option>
</select>
</div>
</div>
<div id="dczRelayTemplate" class="template">
@ -1224,6 +1417,14 @@
</div>
</div>
<div id="miredsTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Mireds (Cold &harr; Warm)</label>
<input type="range" min="153" max="500" class="slider pure-u-lg-1-4" id="mireds">
<span class="slider mireds pure-u-lg-1-4"></span>
</div>
</div>
<div id="magnitudeTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4"></label>


BIN
code/html/vendor/images/border-off.png View File

Before After
Width: 4  |  Height: 27  |  Size: 383 B Width: 4  |  Height: 27  |  Size: 308 B

BIN
code/html/vendor/images/border-on.png View File

Before After
Width: 4  |  Height: 27  |  Size: 363 B Width: 4  |  Height: 27  |  Size: 302 B

BIN
code/html/vendor/images/handle-center.png View File

Before After
Width: 4  |  Height: 27  |  Size: 264 B Width: 4  |  Height: 27  |  Size: 190 B

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save