Browse Source

Merge branch 'dev'

rules-rpn
Xose Pérez 5 years ago
parent
commit
2102806c18
93 changed files with 25953 additions and 20946 deletions
  1. +48
    -0
      .github/ISSUE_TEMPLATE/bug_report.md
  2. +26
    -0
      .github/ISSUE_TEMPLATE/feature_request.md
  3. +42
    -0
      .github/ISSUE_TEMPLATE/questions---troubleshooting.md
  4. +1
    -1
      .travis.yml
  5. +115
    -0
      CHANGELOG.md
  6. +16
    -0
      CONTRIBUTING.md
  7. +16
    -9
      README.md
  8. +8
    -0
      SUPPORT.md
  9. +25
    -7
      code/build.sh
  10. +7
    -6
      code/debug.sh
  11. +45
    -3
      code/espurna/alexa.ino
  12. +4
    -5
      code/espurna/api.ino
  13. +6
    -6
      code/espurna/broker.ino
  14. +36
    -12
      code/espurna/button.ino
  15. +1
    -0
      code/espurna/config/all.h
  16. +21
    -1
      code/espurna/config/arduino.h
  17. +19
    -1
      code/espurna/config/dependencies.h
  18. +9
    -0
      code/espurna/config/deprecated.h
  19. +98
    -9
      code/espurna/config/general.h
  20. +539
    -9
      code/espurna/config/hardware.h
  21. +31
    -9
      code/espurna/config/progmem.h
  22. +21
    -4
      code/espurna/config/prototypes.h
  23. +215
    -41
      code/espurna/config/sensors.h
  24. +62
    -40
      code/espurna/config/types.h
  25. +1
    -1
      code/espurna/config/version.h
  26. +157
    -0
      code/espurna/crash.ino
  27. BIN
      code/espurna/data/index.all.html.gz
  28. BIN
      code/espurna/data/index.light.html.gz
  29. BIN
      code/espurna/data/index.rfbridge.html.gz
  30. BIN
      code/espurna/data/index.rfm69.html.gz
  31. BIN
      code/espurna/data/index.sensor.html.gz
  32. BIN
      code/espurna/data/index.small.html.gz
  33. +44
    -173
      code/espurna/debug.ino
  34. +119
    -17
      code/espurna/domoticz.ino
  35. +53
    -10
      code/espurna/eeprom.ino
  36. +1
    -1
      code/espurna/encoder.ino
  37. +8
    -6
      code/espurna/espurna.ino
  38. +73
    -22
      code/espurna/homeassistant.ino
  39. +18
    -0
      code/espurna/i2c.ino
  40. +15
    -0
      code/espurna/influxdb.ino
  41. +11
    -24
      code/espurna/ir.ino
  42. +18
    -0
      code/espurna/led.ino
  43. +141
    -72
      code/espurna/light.ino
  44. +47
    -1
      code/espurna/migrate.ino
  45. +33
    -22
      code/espurna/mqtt.ino
  46. +7
    -3
      code/espurna/nofuss.ino
  47. +6
    -3
      code/espurna/ntp.ino
  48. +37
    -4
      code/espurna/ota.ino
  49. +155
    -115
      code/espurna/relay.ino
  50. +0
    -192
      code/espurna/rf.ino
  51. +260
    -95
      code/espurna/rfbridge.ino
  52. +1
    -2
      code/espurna/rfm69.ino
  53. +33
    -18
      code/espurna/scheduler.ino
  54. +236
    -41
      code/espurna/sensor.ino
  55. +253
    -0
      code/espurna/sensors/BMP180Sensor.h
  56. +212
    -0
      code/espurna/sensors/EZOPHSensor.h
  57. +1
    -1
      code/espurna/sensors/GUVAS12SDSensor.h
  58. +21
    -18
      code/espurna/sensors/HLW8012Sensor.h
  59. +150
    -0
      code/espurna/sensors/MAX6675.h
  60. +13
    -7
      code/espurna/sensors/PMSX003Sensor.h
  61. +188
    -24
      code/espurna/sensors/PZEM004TSensor.h
  62. +231
    -0
      code/espurna/sensors/PulseMeterSensor.h
  63. +96
    -0
      code/espurna/sensors/VEML6075Sensor.h
  64. +119
    -0
      code/espurna/sensors/VL53L1XSensor.h
  65. +9
    -297
      code/espurna/settings.ino
  66. +3149
    -3115
      code/espurna/static/index.all.html.gz.h
  67. +3003
    -2973
      code/espurna/static/index.light.html.gz.h
  68. +2608
    -2577
      code/espurna/static/index.rfbridge.html.gz.h
  69. +4063
    -4033
      code/espurna/static/index.rfm69.html.gz.h
  70. +2669
    -2634
      code/espurna/static/index.sensor.html.gz.h
  71. +2564
    -2535
      code/espurna/static/index.small.html.gz.h
  72. +56
    -14
      code/espurna/system.ino
  73. +51
    -35
      code/espurna/telnet.ino
  74. +283
    -0
      code/espurna/terminal.ino
  75. +37
    -16
      code/espurna/thinkspeak.ino
  76. +143
    -53
      code/espurna/utils.ino
  77. +18
    -0
      code/espurna/web.ino
  78. +14
    -12
      code/espurna/wifi.ino
  79. +139
    -65
      code/espurna/ws.ino
  80. +5
    -6
      code/gulpfile.js
  81. +77
    -41
      code/html/custom.js
  82. +106
    -16
      code/html/index.html
  83. +27
    -25
      code/memanalyzer.py
  84. +59
    -48
      code/ota.py
  85. +2205
    -1381
      code/package-lock.json
  86. +2
    -5
      code/package.json
  87. +470
    -19
      code/platformio.ini
  88. BIN
      images/devices/lombex-lux-nova-flash-2.jpg
  89. BIN
      images/devices/lombex-lux-nova-flash.jpg
  90. BIN
      images/devices/lombex-lux-nova.jpg
  91. BIN
      images/devices/schuko-wifi-plug-v2.jpg
  92. BIN
      images/devices/tonbux-xs-ssa01.jpg
  93. +27
    -11
      pre-commit

+ 48
- 0
.github/ISSUE_TEMPLATE/bug_report.md View File

@ -0,0 +1,48 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
*Before creating a new issue please check that you have:*
* *searched the existing [issues](https://github.com/xoseperez/espurna/issues) (both open and closed)*
* *searched the [wiki](https://github.com/xoseperez/espurna/wiki)*
* *asked for help in the [chat](https://gitter.im/tinkerman-cat/espurna)*
* *done the previous things again :)*
*Fulfilling this template will help developers and contributors to address the issue. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.*
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the issue (for instance, the screenshots) then you can delete them.*
**Bug description**
*A clear and concise description of what the bug is.*
**Steps to reproduce**
*Steps to reproduce the behavior.*
**Expected behavior**
*A clear and concise description of what you expected to happen.*
**Screenshots**
*If applicable, add screenshots to help explain your problem.*
**Device information**
*Copy-paste here the information as it is outputted by the device. You can get this information by typing `info` via serial, terminal or in the debug tab in the web UI. The relevant information is that surrounded by the scissors-cut lines (`---8<-------`).*
*If you cannot get this info from the device, please answer this questions:*
* *Arduino Core version*
* *ESPurna version*
* *Flash mode*
* *Device brand, model and version*
**Tools used**
* *Desktop operating system*
* *Browser & version*
* *IDE & version*
* *Compiler & version (if not embedded in IDE)*
**Additional context**
*Add any other context about the problem here.*

+ 26
- 0
.github/ISSUE_TEMPLATE/feature_request.md View File

@ -0,0 +1,26 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
*Before creating a new feature request please check that you have searched the existing [issues](https://github.com/xoseperez/espurna/issues) (both open and closed)*
*Fulfilling this template will help developers and contributors evaluating the feature. If the information provided is not enough the issue will likely be closed.*
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the request then you can delete them.*
**Is your feature request related to a problem? Please describe.**
*A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]*
**Describe the solution you'd like**
*A clear and concise description of what you want to happen.*
**Describe alternatives you've considered**
*A clear and concise description of any alternative solutions or features you've considered.*
**Additional context**
*Add any other context or screenshots about the feature request here.*

+ 42
- 0
.github/ISSUE_TEMPLATE/questions---troubleshooting.md View File

@ -0,0 +1,42 @@
---
name: Questions & troubleshooting
about: Anything not a bug or feature request
title: ''
labels: question
assignees: ''
---
*Before creating a new issue please check that you have:*
* *searched the existing [issues](https://github.com/xoseperez/espurna/issues) (both open and closed)*
* *searched the [wiki](https://github.com/xoseperez/espurna/wiki)*
* *asked for help in the [chat](https://gitter.im/tinkerman-cat/espurna)*
* *done the previous things again :)*
*Fulfilling this template will help developers and contributors help you. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.*
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the issue (for instance, the screenshots) then you can delete them.*
**Question**
*A clear and concise description of what the problem/doubt is.*
**Screenshots**
*If applicable, add screenshots to help explain your problem.*
**Device information**
*Copy-paste here the information as it is outputted by the device. You can get this information by typing `info` via serial, terminal or in the debug tab in the web UI. The relevant information is that surrounded by the scissors-cut lines (`---8<-------`).*
*If you cannot get this info from the device, please answer this questions:*
* *Arduino Core version*
* *ESPurna version*
* *Flash mode*
* *Device brand, model and version*
**Tools used**
* *Desktop operating system*
* *Browser & version*
* *IDE & version*
* *Compiler & version (if not embedded in IDE)*
**Additional context**
*Add any other context about the problem here.*

+ 1
- 1
.travis.yml View File

@ -11,7 +11,7 @@ cache:
install:
- pip install -U platformio
- npm install -g npm@latest
- cd code ; npm ci ; cd ..
- cd code && npm ci && cd ..
env:
global:
- BUILDER_TOTAL_THREADS=4


+ 115
- 0
CHANGELOG.md View File

@ -3,6 +3,121 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.13.4] 2019-02-21
### Fixed
- Travis fixes
- IR results on raw mode (thanks to @vtochq)
- Missing configuration in HTTP API (#1288)
- NTP sync changes (#1342)
- Proper buffer size to fit two digit rfbOFF key (#1348)
- Use correct arguments for stat on macOS (#1355, thanks to @jackwilson)
- Enable `reload` command when no web support (#1383)
- Wrong GPIO value for dummy relay (#1386)
- Wait until mqtt client has finished trying to connect
- Disable EEPROM Rotate before NoFUSS update (#1398, thanks to @arihantdaga)
- Only check domoticz state in broker callback (#1562)
- Fix upload_port and upload_args
- Fix heartbeat dropdown size
- Setup settings before using them in system module (#1542)
- Fix HEARTBEAT_REPORT_DESCRIPTION typo (#1539)
- Fix wsDebugSend prototype
- Fix pulse for dummy relays (#1496, thanks to @Niek)
- Fix RFBridge websocket data
- Only process Domoticz RGB MQTT Messages for the current idx (#1489, thanks to @soif)
- Fix pulse for dummy relays
- Fix compile error when both RF_SUPPORT and API_SUPPORT are enabled (#1479, thanks to @Niek)
- Fix compile error when TERMINAL_SUPPORT is disabled (#1426)
- Fix compile error when RF_SUPPORT is enabled (#1475)
- Fix CodingStyle link (#1473)
- Fix: Add Debug flag for compilation of wifiDebug() function (#1454)
- Fix bug in RFM69 that counted packets twice
- Escape hyphens in img.shields.io urls
- Fix travis builds based on latest core
- Increase buffer size to fit B0 code (#1423)
- Fix function call typo in RF code (#1421)
- Fix RF code conversion to long (#1410)
### Added
- Support for MAXCIO W-DE003 device (thanks to @kerk1v)
- Support for Tonbux XS-SSA01 device (thanks to @StevenWolfe)
- Support for Blitzwolf BW-SHP2 v2.3 (#1351)
- Support for Tecking SP22 v1.4+
- Support for Lombez Lux Nova 2 smart bulbs (thanks to @kcghost)
- Support for Orvibo B25 (#1402, thanks to @plutec)
- Support for GBLife RGBW Socket (#1305)
- Support for Generic Relay ESP01 V4.0 in inverse relay version (#1504, #1554)
- Support for Gosund WS1 aka KS-602S (#1551, thanks to @nsvrana)
- Support for Oukitel P1 smart switch (#1553, thanks to @quinnsam)
- Support for Lyasi light bulb (#1533, thanks to Eichhoernchen)
- Support for RGB(WW) controlled using Domoticz MQTT messages (#1459, thanks to @sq5gvm)
- Support for newer AL-LC02 boards with different pinout (#1469, thanks to @sq5gvm)
- Support for SmartLife Mini Smart Socket RGB (thanks to @kuppe234, #1411)
- Support for Gosund SP1 v2.3 (#1448)
- Support for OBI Wifi Schuko Plug V2 (#1408, thanks to @arthurf1969)
- Support for pulse meter power sensor for new-generation smart-meters
- Support for VL53L1X ToF sensor (thanks to @ruimarinho)
- Support for VEML6075 UV sensor (thanks to @ruimarinho)
- Support for EZO pH Circuit sensor (thanks to @ruimarinho)
- Support for MAX6675 temperature sensor (#1375, thanks to @lucciano)
- Support for MagicHome ZJ WFMN A/B v1.1 (#1339)
- Support for multiple PZEM004T sensors (thanks to @0x3333)
- Support for Support PMS5003S (#1511, thanks to @Yonsm)
- Support for pulse meter power sensor for new-generation smart-meters (including debouncing and energy ratio support by @jackwilson)
- Support for BMP085 and BMP180 sensors (#1082)
- Add dim up and down actions to button handler (#1250)
- Compact WS data (#1387)
- Improved analog sensor (#1326, thanks to @cconde)
- Report SSID in heartbeat messages
- Option to send full data to thinkgspeak on every message (#1369)
- Added RSSI to InfluxDB heartbeat (#1400, tahnks to @BuildTheRobots)
- Option to report time even if no NTP sync (#1310)
- Support for mixed combination of real and dummy relays (#1305)
- Report target color values on MQTT and API
- Note on WiFi tab about hostname (#1555)
- Allow saving heartbeat settings from web (#1538)
- Build images for Sonoff Basic R2 with DHT and DALLAS support
- Add warning about TELNET_PASSWORD
- Domoticz: track last relay state (#1536)
- Adding description field to web UI, reporting it via MQTT (#1523)
- ESP-01 + 2ch 5v relay LC tech Exclusive relay on (#1519, thanks to @clabnet)
- Add OTA support over MQTT (#1424, thanks to @Niek)
- Configure Heartbeat from WebUI & option HEARTBEAT_REPEAT_STATUS (#1474, thanks to martiera)
- Delay light comms (mqtt, ws, broker) to avoid jamming
- Added message type to broker
- Yield() after handling OTA request
- Disconnect websocket when auth fails
- Manage relay changes in third party modules via broker
- Added API entry points for RFBridge module (#1407)
- Domoticz over MQTT to Espurna RGB/RGBW/RGBWW
- Debug check position to make sure definition is not nullified to avoid putting checks in all places
- MQTT reconnect delay based on last disconnection
- Add terminal support for wifiDebug
- Created contribute.md and support.md files
- Created issue templates
- Runtime heartbeat configuration (#1406)
- APP_VERSION suffix (#1418)
- Allow {hostname} and {mac} placeholder for mqtt user and client_id fields (#1338)
- Split ws messages for relays and rf codes (#262)
- Added learn and forget terminal commands to RFBridge and RF modules (#1253)
- Change light transition time via MQTT or API (#1412)
### Changed
- Telnet password requirements (#1382)
- Separate tab for NoFUSS options (#1404)
- Updated to use gulp4 (#1403)
- Updated to EEPROM_Rotate 0.9.2
- Show proper switches names in web UI
- Removing loop delay if WIFI is not set to sleep, reducing it to 1ms otherwise (#1541)
- Change naming for BlitzWolf SHP2 and SHP6 (now SHPX) boards
- Print each HA config entry separately (#1535)
- Updated DebounceEvent to 2.0.5 (#1527, #1254)
- Python cleanup (@1526, thanks to Cabalist)
- Normalize naming for Arilux AL LC02 v14
- Increase version field size in OTA manager
- Merge RF and RFBridge code (#1435, thanks to @Niek)
- Update to fauxmoESP 3.1.0
- Move crash code to it's own module
## [1.13.3] 2018-10-08
### Fixed
- Honour build time settings for MQTT on fresh install (#719)


+ 16
- 0
CONTRIBUTING.md View File

@ -0,0 +1,16 @@
Do you want to do a pull request?
First things first: **THANK YOU!**. ESPurna started as a personal project and it will be great if it becomes a community project. There are so many things that can be improved, added and fixed (yeah, a lot a small bugs and not so small bugs there, I'm sure). And sometimes I just don't have the time to work on it as much as I'd like to.
Second. Let's try to keep it homogeneous and readable. I have my coding style. It's mostly standard but sometimes it can be opinionated. It you are willing to do a pull request, there are a few things I would ask you first:
## Pull request ##
* Do the pull request against the **`dev` branch**
* **Only touch relevant files** (beware if your editor has auto-formatting feature enabled)
* If you are adding a new functionality (new hardware, new library support) not related to an existing component move it to it's **own modules** (.ino file)
* If you are adding new library, include it in one of the **sample travis profiles**, so our integrated CI will try to compile it.
* Make sure you check [Coding Style](https://github.com/xoseperez/espurna/wiki/CodingStyle)
* PRs that don't compile (break Travis) or cause more coding errors (as reported by Codacy) will not be merged. Please fix the issue. Same goes for PRs that are raised against older commit in dev - you might need to rebase and resolve conflicts.
And thank you again!

+ 16
- 9
README.md View File

@ -3,11 +3,12 @@
ESPurna ("spark" in Catalan) is a custom firmware for ESP8285/ESP8266 based smart switches, lights and sensors.
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
[![version](https://img.shields.io/badge/version-1.13.3-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-master-orange.svg)](https://github.com/xoseperez/espurna/tree/master/)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=master)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://api.codacy.com/project/badge/Grade/c9496e25cf07434cba786b462cb15f49)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![version](https://img.shields.io/badge/version-1.13.4-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-dev-orange.svg)](https://github.com/xoseperez/espurna/tree/dev/)
[![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=dev)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://api.codacy.com/project/badge/Grade/c9496e25cf07434cba786b462cb15f49)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![downloads](https://img.shields.io/github/downloads/xoseperez/espurna/total.svg)](https://github.com/xoseperez/espurna/releases)
<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)
@ -15,6 +16,10 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
---
## Collaborators
Since last November, Max Prokhorov (@mcspr) is also working actively with ESPurna as a collaborator of the project.
## Contributors
**Without your help this project would not be possible**. I (@xoseperez) simply can't spend all the time I wish on ESPurna but luckly I recieve a lot of contributions, bug fixes, enhancement suggestions,... from people all around the world. I would like to thank each and every one of you. The [contributors](https://github.com/xoseperez/espurna/graphs/contributors) page shows the ones that have done a PR in the past, but I also get contributions in the issues, by email or via the [gitter ESPurna channel](https://gitter.im/tinkerman-cat/espurna), those I also want to thank.
@ -233,14 +238,16 @@ Here is the list of supported hardware. For more information please refer to the
|**Electrodragon WiFi IOT**|**OpenEnergyMonitor WiFi MQTT Relay / Thermostat**||
|![Itead S20](images/devices/itead-s20.jpg)|![Itead S20](images/devices/itead-s26.jpg)|![Neo Coolcam NAS WR01W](images/devices/neo-coolcam-wifi.jpg)|
|**Itead S20**|**Itead S26**|**Neo Coolcam NAS WR01W**|
|![Schuko Wifi Plug](images/devices/schuko-wifi-plug.jpg)|![KMC 70011](images/devices/kmc-70011.jpg)|![Xenon SM-PW702U](images/devices/xenon-sm-pw702u.jpg)|
|**Schuko Wifi Plug**|**KMC 70011**|**Xenon SM-PW702U**|
|![Schuko Wifi Plug](images/devices/schuko-wifi-plug.jpg)|![Schuko Wifi Plug V2](images/devices/schuko-wifi-plug-v2.jpg)|
|**Schuko Wifi Plug**|**Schuko Wifi Plug V2**|
|![KMC 70011](images/devices/kmc-70011.jpg)|![Xenon SM-PW702U](images/devices/xenon-sm-pw702u.jpg)||
**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)|![HomeCube 16A](images/devices/homecube-16a.jpg)|
|**WiOn 50055**|**LINGAN SWA1**|**HomeCube 16A**|
|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg)|![Bestek MRJ1011](images/devices/bestek-mrj1011.jpg)||
|**WorkChoice EcoPlug**|**Bestek MRJ1011**||
|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg)|![Bestek MRJ1011](images/devices/bestek-mrj1011.jpg)|![Tonbux XS-SSA01](images/devices/tonbux-xs-ssa01.jpg)|
|**WorkChoice EcoPlug**|**Bestek MRJ1011**|**Tonbux XS-SSA01**|
|![Tonbux PowerStrip02](images/devices/tonbux-powerstrip02.jpg)|![ForNorm Power Strip](images/devices/fornorm-power-strip.jpg)|![Zhilde ZLD-EU55-W](images/devices/zhilde-zld-eu55-w.jpg)|
|**Tonbux PowerStrip02**|**Fornorm Power Strip**|**Zhilde ZLD-EU55-W**|
|![Itead Sonoff Touch](images/devices/itead-sonoff-touch.jpg)|![Itead Sonoff T1](images/devices/itead-sonoff-t1.jpg)|![YJZK switch](images/devices/yjzk-2gang-switch.jpg)|
@ -279,7 +286,7 @@ Here is the list of supported hardware. For more information please refer to the
|**Tonbux Mosquito Killer**|**Itead Sonoff IFAN02**|||
**Other supported boards (beta):**
KMC 4 Outlet, Gosund WS1, Smart Dual Plug, MakerFocus Intelligent Module LM33 for Lamps
KMC 4 Outlet, Gosund WS1, MakerFocus Intelligent Module LM33 for Lamps
## License


+ 8
- 0
SUPPORT.md View File

@ -0,0 +1,8 @@
# ESPurna Support
If you're looking for support for ESPurna there are some options available:
* [Issues](https://github.com/xoseperez/espurna/issues?utf8=%E2%9C%93&q=is%3Aissue): this is the most dinamic channel at the moment, you might find an answer to your question by searching current or closed issues.
* [Wiki pages](https://github.com/xoseperez/espurna/wiki): might not be as up-to-date as we all would like (hey, you can also contribute in the documentation!).
* [Gitter channel](https://gitter.im/tinkerman-cat/espurna): you have better chances to get fast answers from me or other ESPurna users.
* [Issue a question](https://github.com/xoseperez/espurna/issues/new/choose): as a last resort, you can open new *question* issues on GitHub. Just remember: the more info you provide the more chances you'll have to get an accurate answer.

+ 25
- 7
code/build.sh View File

@ -9,17 +9,35 @@ is_git() {
return 0
}
stat_bytes() {
case "$(uname -s)" in
Darwin) stat -f %z "$1";;
*) stat -c %s "$1";;
esac
}
# Script settings
destination=../firmware
version=$(grep APP_VERSION espurna/config/version.h | awk '{print $3}' | sed 's/"//g')
version_file=espurna/config/version.h
version=$(grep -E '^#define APP_VERSION' $version_file | awk '{print $3}' | sed 's/"//g')
if is_git; then
if ${TRAVIS:-false}; then
git_revision=${TRAVIS_COMMIT::7}
git_tag=${TRAVIS_TAG}
elif is_git; then
git_revision=$(git rev-parse --short HEAD)
git_version=${version}-${git_revision}
git_tag=$(git tag --contains HEAD)
else
git_revision=
git_version=$version
git_revision=unknown
git_tag=
fi
if [[ -n $git_tag ]]; then
new_version=${version/-*}
sed -i -e "s@$version@$new_version@" $version_file
version=$new_version
trap "git checkout -- $version_file" EXIT
fi
par_build=false
@ -104,7 +122,7 @@ build_environments() {
for environment in $environments; do
echo -n "* espurna-$version-$environment.bin --- "
platformio run --silent --environment $environment || exit 1
stat -c %s .pioenvs/$environment/firmware.bin
stat_bytes .pioenvs/$environment/firmware.bin
[[ "${TRAVIS_BUILD_STAGE_NAME}" = "Test" ]] || \
mv .pioenvs/$environment/firmware.bin $destination/espurna-$version/espurna-$version-$environment.bin
done
@ -132,7 +150,7 @@ shift $((OPTIND-1))
# Welcome
echo "--------------------------------------------------------------"
echo "ESPURNA FIRMWARE BUILDER"
echo "Building for version ${git_version}"
echo "Building for version ${version}" ${git_revision:+($git_revision)}
# Environments to build
environments=$@


+ 7
- 6
code/debug.sh View File

@ -19,7 +19,7 @@ rm -rf $FILE
function help {
echo
echo "Syntax: $0 [-e <environment>] [-d <dumpfile>]"
echo "Syntax: $0 [-e <environment>] [-f <elf_file>] [-d <dumpfile>]"
echo
}
@ -29,6 +29,10 @@ while [[ $# -gt 1 ]]; do
key="$1"
case $key in
-f)
ELF="$2"
shift
;;
-e)
ENVIRONMENT="$2"
shift
@ -44,12 +48,9 @@ while [[ $# -gt 1 ]]; do
done
# check environment folder
if [ $ENVIRONMENT == "" ]; then
echo "No environment defined"
help
exit 1
if [ ! -f $ELF ]; then
ELF=.pioenvs/$ENVIRONMENT/firmware.elf
fi
ELF=.pioenvs/$ENVIRONMENT/firmware.elf
if [ ! -f $ELF ]; then
echo "Could not find ELF file for the selected environment: $ELF"
exit 2


+ 45
- 3
code/espurna/alexa.ino View File

@ -36,6 +36,39 @@ void _alexaConfigure() {
alexa.enable(wifiConnected() && alexaEnabled());
}
#if WEB_SUPPORT
bool _alexaBodyCallback(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
return alexa.process(request->client(), request->method() == HTTP_GET, request->url(), String((char *)data));
}
bool _alexaRequestCallback(AsyncWebServerRequest *request) {
String body = (request->hasParam("body", true)) ? request->getParam("body", true)->value() : String();
return alexa.process(request->client(), request->method() == HTTP_GET, request->url(), body);
}
#endif
#if BROKER_SUPPORT
void _alexaBrokerCallback(const unsigned char type, const char * topic, unsigned char id, const char * payload) {
// Only process status messages
if (BROKER_MSG_TYPE_STATUS != type) return;
unsigned char value = atoi(payload);
if (strcmp(MQTT_TOPIC_CHANNEL, topic) == 0) {
alexa.setState(id+1, value > 0, value);
}
if (strcmp(MQTT_TOPIC_RELAY, topic) == 0) {
#if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
if (id > 0) return;
#endif
alexa.setState(id, value, value > 0 ? 255 : 0);
}
}
#endif // BROKER_SUPPORT
// -----------------------------------------------------------------------------
bool alexaEnabled() {
@ -47,8 +80,9 @@ void alexaSetup() {
// Backwards compatibility
moveSetting("fauxmoEnabled", "alexaEnabled");
// Load & cache settings
_alexaConfigure();
// Basic fauxmoESP configuration
alexa.createServer(!WEB_SUPPORT);
alexa.setPort(80);
// Uses hostname as base name for all devices
// TODO: use custom switch name when available
@ -79,8 +113,13 @@ void alexaSetup() {
#endif
// Load & cache settings
_alexaConfigure();
// Websockets
#if WEB_SUPPORT
webBodyRegister(_alexaBodyCallback);
webRequestRegister(_alexaRequestCallback);
wsOnSendRegister(_alexaWebSocketOnSend);
wsOnReceiveRegister(_alexaWebSocketOnReceive);
#endif
@ -102,8 +141,11 @@ void alexaSetup() {
});
// Register main callbacks
espurnaRegisterLoop(alexaLoop);
#if BROKER_SUPPORT
brokerRegister(_alexaBrokerCallback);
#endif
espurnaRegisterReload(_alexaConfigure);
espurnaRegisterLoop(alexaLoop);
}


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

@ -19,7 +19,6 @@ typedef struct {
api_put_callback_f putFn = NULL;
} web_api_t;
std::vector<web_api_t> _apis;
bool _api_restful = API_RESTFUL;
// -----------------------------------------------------------------------------
@ -32,14 +31,13 @@ void _apiWebSocketOnSend(JsonObject& root) {
root["apiEnabled"] = getSetting("apiEnabled", API_ENABLED).toInt() == 1;
root["apiKey"] = getSetting("apiKey");
root["apiRealTime"] = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
root["apiRestFul"] = _api_restful;
root["apiRestFul"] = getSetting("apiRestFul", API_RESTFUL).toInt() == 1;
}
void _apiConfigure() {
_api_restful = getSetting("apiRestFul", API_RESTFUL).toInt() == 1;
// Nothing to do
}
// -----------------------------------------------------------------------------
// API
// -----------------------------------------------------------------------------
@ -165,7 +163,7 @@ bool _apiRequestCallback(AsyncWebServerRequest *request) {
// Check if its a PUT
if (api.putFn != NULL) {
if (!_api_restful || (request->method() == HTTP_PUT)) {
if ((getSetting("apiRestFul", API_RESTFUL).toInt() != 1) || (request->method() == HTTP_PUT)) {
if (request->hasParam("value", request->method() == HTTP_PUT)) {
AsyncWebParameter* p = request->getParam("value", request->method() == HTTP_PUT);
(api.putFn)((p->value()).c_str());
@ -221,6 +219,7 @@ void apiRegister(const char * key, api_get_callback_f getFn, api_put_callback_f
}
void apiSetup() {
_apiConfigure();
wsOnSendRegister(_apiWebSocketOnSend);
wsOnReceiveRegister(_apiWebSocketOnReceive);
webRequestRegister(_apiRequestCallback);


+ 6
- 6
code/espurna/broker.ino View File

@ -10,23 +10,23 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <vector>
std::vector<void (*)(const char *, unsigned char, const char *)> _broker_callbacks;
std::vector<void (*)(const unsigned char, const char *, unsigned char, const char *)> _broker_callbacks;
// -----------------------------------------------------------------------------
void brokerRegister(void (*callback)(const char *, unsigned char, const char *)) {
void brokerRegister(void (*callback)(const unsigned char, const char *, unsigned char, const char *)) {
_broker_callbacks.push_back(callback);
}
void brokerPublish(const char * topic, unsigned char id, const char * message) {
void brokerPublish(const unsigned char type, const char * topic, unsigned char id, const char * message) {
//DEBUG_MSG_P(PSTR("[BROKER] Message %s[%u] => %s\n"), topic, id, message);
for (unsigned char i=0; i<_broker_callbacks.size(); i++) {
(_broker_callbacks[i])(topic, id, message);
(_broker_callbacks[i])(type, topic, id, message);
}
}
void brokerPublish(const char * topic, const char * message) {
brokerPublish(topic, 0, message);
void brokerPublish(const unsigned char type, const char * topic, const char * message) {
brokerPublish(type, topic, 0, message);
}
#endif // BROKER_SUPPORT

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

@ -105,37 +105,61 @@ void buttonEvent(unsigned int id, unsigned char event) {
}
#endif
if (action == BUTTON_MODE_TOGGLE) {
if (BUTTON_MODE_TOGGLE == action) {
if (_buttons[id].relayID > 0) {
relayToggle(_buttons[id].relayID - 1);
}
}
if (action == BUTTON_MODE_ON) {
if (BUTTON_MODE_ON == action) {
if (_buttons[id].relayID > 0) {
relayStatus(_buttons[id].relayID - 1, true);
}
}
if (action == BUTTON_MODE_OFF) {
if (BUTTON_MODE_OFF == action) {
if (_buttons[id].relayID > 0) {
relayStatus(_buttons[id].relayID - 1, false);
}
}
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) {
if (BUTTON_MODE_AP == action) {
wifiStartAP();
}
if (BUTTON_MODE_RESET == action) {
deferredReset(100, CUSTOM_RESET_HARDWARE);
}
if (action == BUTTON_MODE_FACTORY) {
if (BUTTON_MODE_FACTORY == action) {
DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
resetSettings();
deferredReset(100, CUSTOM_RESET_FACTORY);
}
#if defined(JUSTWIFI_ENABLE_WPS)
if (BUTTON_MODE_WPS == action) {
wifiStartWPS();
}
#endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
if (BUTTON_MODE_SMART_CONFIG == action) {
wifiStartSmartConfig();
}
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (BUTTON_MODE_DIM_UP == action) {
lightBrightnessStep(1);
lightUpdate(true, true);
}
if (BUTTON_MODE_DIM_DOWN == action) {
lightBrightnessStep(-1);
lightUpdate(true, true);
}
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
}
void buttonSetup() {


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

@ -28,6 +28,7 @@
#include "arduino.h"
#include "hardware.h"
#include "defaults.h"
#include "deprecated.h"
#include "general.h"
#include "dependencies.h"
#include "debug.h"


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

@ -36,10 +36,12 @@
//#define ITEAD_SONOFF_T1_2CH
//#define ITEAD_SONOFF_T1_3CH
//#define ITEAD_SONOFF_S31
//#define ORVIBO_B25
//#define YJZK_SWITCH_2CH
//#define ELECTRODRAGON_WIFI_IOT
//#define WORKCHOICE_ECOPLUG
//#define AITHINKER_AI_LIGHT
//#define LYASI_LIGHT
//#define MAGICHOME_LED_CONTROLLER
//#define MAGICHOME_LED_CONTROLLER_20
//#define HUACANXING_H801
@ -66,12 +68,15 @@
//#define WEMOS_D1_TARPUNA_SHIELD
//#define GIZWITS_WITTY_CLOUD
//#define EUROMATE_WIFI_STECKER_SCHUKO
//#define EUROMATE_WIFI_STECKER_SCHUKO_V2
//#define TONBUX_POWERSTRIP02
//#define LINGAN_SWA1
//#define HEYGO_HY02
//#define MAXCIO_WUS002S
//#define OUKITEL_P1
//#define YIDIAN_XSSSA05
//#define TONBUX_XSSSA06
//#define TONBUX_XSSSA01
//#define GREEN_ESP8266RELAY
//#define IKE_ESPIKE
//#define ARNIEX_SWIFITCH
@ -87,7 +92,7 @@
//#define NEO_COOLCAM_NAS_WR01W
//#define ESTINK_WIFI_POWER_STRIP
//#define PILOTAK_ESP_DIN_V1
//#define BLITZWOLF_BWSHP2
//#define BLITZWOLF_BWSHPX
//#define BH_ONOFRE
//#define ITEAD_SONOFF_IFAN02
//#define GENERIC_AG_L4
@ -100,6 +105,17 @@
//#define PHYX_ESP12_RGB
//#define IWOOLE_LED_TABLE_LAMP
//#define EXS_WIFI_RELAY_V50
//#define TECKIN_SP22_V14
//#define LOMBEX_LUX_NOVA2_TUNABLE_WHITE
//#define LOMBEX_LUX_NOVA2_WHITE_COLOR
//#define MAGICHOME_ZJ_WFMN_A_11
//#define MAGICHOME_ZJ_WFMN_B_11
//#define GBLIFE_RGBW_SOCKET
//#define SMARTLIFE_MINI_SMART_SOCKET
//#define GOSUND_SP1_V23
//#define GOSUND_WS1
//#define ARILUX_AL_LC02_V14
//#define BLITZWOLF_BWSHPX_V23
//--------------------------------------------------------------------------------
// Features (values below are non-default values)
@ -146,6 +162,7 @@
//#define AM2320_SUPPORT 1
//#define ANALOG_SUPPORT 1
//#define BH1750_SUPPORT 1
//#define BMP180_SUPPORT 1
//#define BMX280_SUPPORT 1
//#define CSE7766_SUPPORT 1
//#define DALLAS_SUPPORT 1
@ -164,6 +181,7 @@
//#define MICS5525_SUPPORT 1
//#define NTC_SUPPORT 1
//#define PMSX003_SUPPORT 1
//#define PULSEMETER_SUPPORT 1
//#define PZEM004T_SUPPORT 1
//#define SDS011_SUPPORT 1
//#define SENSEAIR_SUPPORT 1
@ -172,3 +190,5 @@
//#define SONAR_SUPPORT 1
//#define TMP3X_SUPPORT 1
//#define V9261F_SUPPORT 1
//#define VL53L1X_SUPPORT 1
//#define EZOPH_SUPPORT 1

+ 19
- 1
code/espurna/config/dependencies.h View File

@ -33,9 +33,21 @@
#define DEBUG_SERIAL_SUPPORT 0
#endif
#if ALEXA_SUPPORT
#undef BROKER_SUPPORT
#define BROKER_SUPPORT 1 // If Alexa enabled enable BROKER
#endif
#if INFLUXDB_SUPPORT
#undef BROKER_SUPPORT
#define BROKER_SUPPORT 1 // If InfluxDB enabled enable BROKER
#endif
#if DOMOTICZ_SUPPORT
#undef MQTT_SUPPORT
#define MQTT_SUPPORT 1 // If Domoticz enabled enable MQTT
#undef BROKER_SUPPORT
#define BROKER_SUPPORT 1 // If Domoticz enabled enable BROKER
#endif
#if HOMEASSISTANT_SUPPORT
@ -45,8 +57,14 @@
#ifndef ASYNC_TCP_SSL_ENABLED
#if THINGSPEAK_USE_SSL && THINGSPEAK_USE_ASYNC
#undef THINGSPEAK_SUPPORT // Thingspeak in ASYNC mode requires ASYNC_TCP_SSL_ENABLED
#undef THINGSPEAK_SUPPORT
#define THINGSPEAK_SUPPORT 0 // Thingspeak in ASYNC mode requires ASYNC_TCP_SSL_ENABLED
#endif
#endif
#if THINKSPEAK_SUPPORT
#undef BROKER_SUPPORT
#define BROKER_SUPPORT 1 // If Thingspeak enabled enable BROKER
#endif
#if SCHEDULER_SUPPORT


+ 9
- 0
code/espurna/config/deprecated.h View File

@ -0,0 +1,9 @@
#pragma once
// 1.13.3 added TELNET_PASSWORD build-only flag
// 1.13.4 replaces it with TELNET_AUTHENTICATION runtime setting default
// TODO warning should be removed eventually
#ifdef TELNET_PASSWORD
#warning TELNET_PASSWORD is deprecated! Please replace it with TELNET_AUTHENTICATION
#define TELNET_AUTHENTICATION TELNET_PASSWORD
#endif

+ 98
- 9
code/espurna/config/general.h View File

@ -21,7 +21,7 @@
#endif
#ifndef LOOP_DELAY_TIME
#define LOOP_DELAY_TIME 10 // Delay for this millis in the main loop [0-250]
#define LOOP_DELAY_TIME 1 // Delay for this millis in the main loop [0-250] (see https://github.com/xoseperez/espurna/issues/1541)
#endif
//------------------------------------------------------------------------------
@ -110,8 +110,8 @@
#define TELNET_STA 0 // By default, disallow connections via STA interface
#endif
#ifndef TELNET_PASSWORD
#define TELNET_PASSWORD 1 // Request password to start telnet session by default
#ifndef TELNET_AUTHENTICATION
#define TELNET_AUTHENTICATION 1 // Request password to start telnet session by default
#endif
#define TELNET_PORT 23 // Port to listen to telnet clients
@ -169,6 +169,7 @@
#define HEARTBEAT_NONE 0 // Never send heartbeat
#define HEARTBEAT_ONCE 1 // Send it only once upon MQTT connection
#define HEARTBEAT_REPEAT 2 // Send it upon MQTT connection and every HEARTBEAT_INTERVAL
#define HEARTBEAT_REPEAT_STATUS 3 // Send it upon MQTT connection and every HEARTBEAT_INTERVAL only STATUS report
// Backwards compatibility check
#if defined(HEARTBEAT_ENABLED) && (HEARTBEAT_ENABLED == 0)
@ -180,27 +181,83 @@
#endif
#ifndef HEARTBEAT_INTERVAL
#define HEARTBEAT_INTERVAL 300000 // Interval between heartbeat messages (in ms)
#define HEARTBEAT_INTERVAL 300 // Interval between heartbeat messages (in sec)
#endif
#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value
// Topics that will be reported in heartbeat
// Values that will be reported in heartbeat
#ifndef HEARTBEAT_REPORT_STATUS
#define HEARTBEAT_REPORT_STATUS 1
#endif
#ifndef HEARTBEAT_REPORT_SSID
#define HEARTBEAT_REPORT_SSID 1
#endif
#ifndef HEARTBEAT_REPORT_IP
#define HEARTBEAT_REPORT_IP 1
#endif
#ifndef HEARTBEAT_REPORT_MAC
#define HEARTBEAT_REPORT_MAC 1
#endif
#ifndef HEARTBEAT_REPORT_RSSI
#define HEARTBEAT_REPORT_RSSI 1
#endif
#ifndef HEARTBEAT_REPORT_UPTIME
#define HEARTBEAT_REPORT_UPTIME 1
#endif
#ifndef HEARTBEAT_REPORT_DATETIME
#define HEARTBEAT_REPORT_DATETIME 1
#endif
#ifndef HEARTBEAT_REPORT_FREEHEAP
#define HEARTBEAT_REPORT_FREEHEAP 1
#endif
#ifndef HEARTBEAT_REPORT_VCC
#define HEARTBEAT_REPORT_VCC 1
#endif
#ifndef HEARTBEAT_REPORT_RELAY
#define HEARTBEAT_REPORT_RELAY 1
#endif
#ifndef HEARTBEAT_REPORT_LIGHT
#define HEARTBEAT_REPORT_LIGHT 1
#endif
#ifndef HEARTBEAT_REPORT_HOSTNAME
#define HEARTBEAT_REPORT_HOSTNAME 1
#endif
#ifndef HEARTBEAT_REPORT_DESCRIPTION
#define HEARTBEAT_REPORT_DESCRIPTION 1
#endif
#ifndef HEARTBEAT_REPORT_APP
#define HEARTBEAT_REPORT_APP 1
#endif
#ifndef HEARTBEAT_REPORT_VERSION
#define HEARTBEAT_REPORT_VERSION 1
#endif
#ifndef HEARTBEAT_REPORT_BOARD
#define HEARTBEAT_REPORT_BOARD 1
#endif
#ifndef HEARTBEAT_REPORT_LOADAVG
#define HEARTBEAT_REPORT_LOADAVG 1
#endif
#ifndef HEARTBEAT_REPORT_INTERVAL
#define HEARTBEAT_REPORT_INTERVAL 0
#endif
//------------------------------------------------------------------------------
// Load average
@ -210,10 +267,6 @@
#define LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms)
#endif
#ifndef LOADAVG_REPORT
#define LOADAVG_REPORT 1 // Should we report Load average over MQTT?
#endif
//------------------------------------------------------------------------------
// BUTTON
//------------------------------------------------------------------------------
@ -236,6 +289,9 @@
#ifndef BUTTON_LNGLNGCLICK_DELAY
#define BUTTON_LNGLNGCLICK_DELAY 10000 // Time in ms holding the button down to get a long-long click
#endif
#ifndef BUTTON_MQTT_SEND_ALL_EVENTS
#define BUTTON_MQTT_SEND_ALL_EVENTS 0 // 0 - to send only events the are bound to actions
// 1 - to send all button events to MQTT
#endif
@ -434,7 +490,9 @@
// or in the Internet. Since the WebUI is just one compressed file with HTML, CSS and JS
// there are no special requirements. Any static web server will do (NGinx, Apache, Lighttpd,...).
// The only requirement is that the resource must be available under this domain.
#ifndef WEB_REMOTE_DOMAIN
#define WEB_REMOTE_DOMAIN "http://tinkerman.cat"
#endif
// -----------------------------------------------------------------------------
// WEBSOCKETS
@ -531,6 +589,10 @@
#define OTA_PORT 8266 // OTA port
#endif
#ifndef OTA_MQTT_SUPPORT
#define OTA_MQTT_SUPPORT 0 // No support by default
#endif
#define OTA_GITHUB_FP "D7:9F:07:61:10:B3:92:93:E3:49:AC:89:84:5B:03:80:C1:9E:2F:8B"
// -----------------------------------------------------------------------------
@ -731,6 +793,7 @@
#define MQTT_TOPIC_LED "led"
#define MQTT_TOPIC_BUTTON "button"
#define MQTT_TOPIC_IP "ip"
#define MQTT_TOPIC_SSID "ssid"
#define MQTT_TOPIC_VERSION "version"
#define MQTT_TOPIC_UPTIME "uptime"
#define MQTT_TOPIC_DATETIME "datetime"
@ -743,6 +806,7 @@
#define MQTT_TOPIC_APP "app"
#define MQTT_TOPIC_INTERVAL "interval"
#define MQTT_TOPIC_HOSTNAME "host"
#define MQTT_TOPIC_DESCRIPTION "desc"
#define MQTT_TOPIC_TIME "time"
#define MQTT_TOPIC_RFOUT "rfout"
#define MQTT_TOPIC_RFIN "rfin"
@ -756,6 +820,7 @@
#define MQTT_TOPIC_SPEED "speed"
#define MQTT_TOPIC_IRIN "irin"
#define MQTT_TOPIC_IROUT "irout"
#define MQTT_TOPIC_OTA "ota"
// Light module
#define MQTT_TOPIC_CHANNEL "channel"
@ -766,6 +831,7 @@
#define MQTT_TOPIC_BRIGHTNESS "brightness"
#define MQTT_TOPIC_MIRED "mired"
#define MQTT_TOPIC_KELVIN "kelvin"
#define MQTT_TOPIC_TRANSITION "transition"
#define MQTT_STATUS_ONLINE "1" // Value for the device ON message
#define MQTT_STATUS_OFFLINE "0" // Value for the device OFF message (will)
@ -821,6 +887,10 @@
#define LIGHT_SAVE_ENABLED 1 // Light channel values saved by default after each change
#endif
#ifndef LIGHT_COMMS_DELAY
#define LIGHT_COMMS_DELAY 100 // Delay communication after light update (in ms)
#endif
#ifndef LIGHT_SAVE_DELAY
#define LIGHT_SAVE_DELAY 5 // Persist color after 5 seconds to avoid wearing out
#endif
@ -925,8 +995,13 @@
#define HOMEASSISTANT_SUPPORT MQTT_SUPPORT // Build with home assistant support (if MQTT, 1.64Kb)
#endif
#ifndef HOMEASSISTANT_ENABLED
#define HOMEASSISTANT_ENABLED 0 // Integration not enabled by default
#endif
#ifndef HOMEASSISTANT_PREFIX
#define HOMEASSISTANT_PREFIX "homeassistant" // Default MQTT prefix
#endif
#ifndef HOMEASSISTANT_PAYLOAD_ON
#define HOMEASSISTANT_PAYLOAD_ON "1" // Payload for ON and available messages
@ -993,6 +1068,11 @@
#define THINGSPEAK_APIKEY "" // Default API KEY
#endif
#ifndef THINGSPEAK_CLEAR_CACHE
#define THINGSPEAK_CLEAR_CACHE 1 // Clear cache after sending values
// Not clearing it will result in latest values for each field being sent every time
#endif
#define THINGSPEAK_USE_ASYNC 1 // Use AsyncClient instead of WiFiClientSecure
// THINGSPEAK OVER SSL
@ -1071,6 +1151,10 @@
#define NTP_DST_REGION 0 // 0 for Europe, 1 for USA (defined in NtpClientLib)
#endif
#ifndef NTP_WAIT_FOR_SYNC
#define NTP_WAIT_FOR_SYNC 1 // Do not report any datetime until NTP sync'ed
#endif
// -----------------------------------------------------------------------------
// ALEXA
// -----------------------------------------------------------------------------
@ -1335,8 +1419,13 @@
#define RF_PIN 14
#endif
#ifndef RF_DEBOUNCE
#define RF_DEBOUNCE 500
#endif
#ifndef RF_LEARN_TIMEOUT
#define RF_LEARN_TIMEOUT 60000
#endif
//--------------------------------------------------------------------------------
// Custom RFM69 to MQTT bridge


+ 539
- 9
code/espurna/config/hardware.h View File

@ -123,7 +123,7 @@
#define RELAY1_PIN 5
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
// Light RGBW
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
@ -930,6 +930,31 @@
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// ORVIBO
// -----------------------------------------------------------------------------
#elif defined(ORVIBO_B25)
// Info
#define MANUFACTURER "ORVIBO"
#define DEVICE "B25"
// Buttons
#define BUTTON1_PIN 14
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 5
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 12 // 4 blue led
#define LED1_PIN_INVERSE 1
#define LED2_PIN 4 // 12 red led
#define LED2_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// YJZK
// -----------------------------------------------------------------------------
@ -1126,6 +1151,28 @@
#define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT
#define MY92XX_MAPPING 0, 1, 2, 3
// -----------------------------------------------------------------------------
// Lyasi LED
// -----------------------------------------------------------------------------
#elif defined(LYASI_LIGHT)
// Info
#define MANUFACTURER "LYASI"
#define DEVICE "RGB-LED"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX
#define DUMMY_RELAY_COUNT 1
// Light
#define LIGHT_CHANNELS 4
#define MY92XX_MODEL MY92XX_MODEL_MY9291
#define MY92XX_CHIPS 1
#define MY92XX_DI_PIN 4
#define MY92XX_DCKI_PIN 5
#define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT
#define MY92XX_MAPPING 0, 1, 2, 3
// -----------------------------------------------------------------------------
// LED Controller
// -----------------------------------------------------------------------------
@ -1188,6 +1235,67 @@
#define IR_RX_PIN 4
#define IR_BUTTON_SET 1
#elif defined(MAGICHOME_ZJ_WFMN_A_11)
// Info
#define MANUFACTURER "MAGICHOME"
#define DEVICE "ZJ_WFMN_A_11"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
// LEDs
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
#define LED2_PIN 15
#define LED2_PIN_INVERSE 1
// Light
#define LIGHT_CHANNELS 4
#define LIGHT_CH1_PIN 12 // RED
#define LIGHT_CH2_PIN 5 // GREEN
#define LIGHT_CH3_PIN 13 // BLUE
#define LIGHT_CH4_PIN 14 // WHITE
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
// IR
#define IR_SUPPORT 1
#define IR_RX_PIN 4
#define IR_BUTTON_SET 1
#elif defined(MAGICHOME_ZJ_WFMN_B_11)
// Info
#define MANUFACTURER "MAGICHOME"
#define DEVICE "ZJ_WFMN_B_11"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
// LEDs
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
#define LED2_PIN 15
#define LED2_PIN_INVERSE 1
// Light
#define LIGHT_CHANNELS 4
#define LIGHT_CH1_PIN 14 // RED
#define LIGHT_CH2_PIN 5 // GREEN
#define LIGHT_CH3_PIN 12 // BLUE
#define LIGHT_CH4_PIN 13 // WHITE
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
// RF
#define RF_SUPPORT 1
#define RF_PIN 4
// -----------------------------------------------------------------------------
// HUACANXING H801 & H802
// -----------------------------------------------------------------------------
@ -1551,6 +1659,26 @@
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
#elif defined(ARILUX_AL_LC02_V14)
// Info
#define MANUFACTURER "ARILUX"
#define DEVICE "AL_LC02_V14"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
// Light
#define LIGHT_CHANNELS 4
#define LIGHT_CH1_PIN 14 // RED
#define LIGHT_CH2_PIN 5 // GREEN
#define LIGHT_CH3_PIN 12 // BLUE
#define LIGHT_CH4_PIN 13 // WHITE1
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
#elif defined(ARILUX_AL_LC06)
// Info
@ -1731,7 +1859,7 @@
#define HLW8012_VOLTAGE_R_UP ( 2 * 1000000 ) // Upstream voltage resistor
// -----------------------------------------------------------------------------
// Euromate (?) Wifi Stecker Shuko
// Euromate (?) Wifi Stecker Schuko
// https://www.obi.de/hausfunksteuerung/wifi-stecker-schuko/p/2291706
// Thanks to @Geitde
// -----------------------------------------------------------------------------
@ -1761,6 +1889,36 @@
#define LED1_PIN 4
#define LED1_PIN_INVERSE 0
// -----------------------------------------------------------------------------
// Euromate (?) Wifi Stecker Schuko Version 2
// This configuration is for the second generation of devices sold by OBI.
// https://www.obi.de/hausfunksteuerung/wifi-stecker-schuko-weiss/p/4077806
// -----------------------------------------------------------------------------
#elif defined(EUROMATE_WIFI_STECKER_SCHUKO_V2)
// Info
#define MANUFACTURER "EUROMATE"
#define DEVICE "WIFI_STECKER_SCHUKO_V2"
// Buttons
#define BUTTON1_PIN 5
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 4
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// Green
#define LED1_PIN 12
#define LED1_MODE LED_MODE_WIFI
#define LED1_PIN_INVERSE 0
// Red
#define LED2_PIN 13
#define LED2_MODE LED_MODE_RELAY
#define LED2_PIN_INVERSE 0
// -----------------------------------------------------------------------------
// Generic 8CH
// -----------------------------------------------------------------------------
@ -1920,6 +2078,61 @@
#define HLW8012_CURRENT_R 0.002 // Current resistor
#define HLW8012_VOLTAGE_R_UP ( 2 * 1000000 ) // Upstream voltage resistor
// -----------------------------------------------------------------------------
// Maxcio W-DE004
// -----------------------------------------------------------------------------
#elif defined(MAXCIO_WDE004)
// Info
#define MANUFACTURER "MAXCIO"
#define DEVICE "WDE004"
// Buttons
#define BUTTON1_PIN 1
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// Oukitel - P1
// -----------------------------------------------------------------------------
#elif defined(OUKITEL_P1)
// -----------------------------------------------------------------------------
// Oukitel P1 Smart Plug
// https://www.amazon.com/Docooler-OUKITEL-Control-Wireless-Adaptor/dp/B07J3BYFJX/ref=sr_1_fkmrnull_2?keywords=oukitel+p1+smart+switch&qid=1550424399&s=gateway&sr=8-2-fkmrnull
// -----------------------------------------------------------------------------
// Info
#define MANUFACTURER "Oukitel"
#define DEVICE "P1"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
// Right
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// Left
#define RELAY2_PIN 15
#define RELAY2_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 0 // blue
#define LED1_PIN_INVERSE 1
#define LED1_MODE LED_MODE_WIFI
// -----------------------------------------------------------------------------
// YiDian XS-SSA05
// -----------------------------------------------------------------------------
@ -1962,6 +2175,29 @@
#define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// TONBUX XS-SSA01
// -----------------------------------------------------------------------------
#elif defined(TONBUX_XSSSA01)
// Info
#define MANUFACTURER "TONBUX"
#define DEVICE "XSSSA01"
// Buttons
#define BUTTON1_PIN 4
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 13
#define LED1_PIN_INVERSE 0
// -----------------------------------------------------------------------------
// TONBUX XS-SSA06
// -----------------------------------------------------------------------------
@ -2095,7 +2331,9 @@
// Relays
#define RELAY1_PIN 0
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#ifndef RELAY1_TYPE
#define RELAY1_TYPE RELAY_TYPE_NORMAL // See #1504 and #1554
#endif
// LEDs
#define LED1_PIN 2
@ -2485,19 +2723,20 @@
#define RELAY2_TYPE RELAY_TYPE_NORMAL
// -----------------------------------------------------------------------------
// Several boards under different names uing a power chip labelled BL0937 or HJL-01
// BlitzWolf SHP2 and SHP6
// Also several boards under different names uing a power chip labelled BL0937 or HJL-01
// * Blitzwolf (https://www.amazon.es/Inteligente-Temporización-Dispositivos-Cualquier-BlitzWolf/dp/B07BMQP142)
// * HomeCube (https://www.amazon.de/Steckdose-Homecube-intelligente-Verbrauchsanzeige-funktioniert/dp/B076Q2LKHG)
// * Coosa (https://www.amazon.com/COOSA-Monitoring-Function-Campatible-Assiatant/dp/B0788W9TDR)
// * Goosund (http://www.gosund.com/?m=content&c=index&a=show&catid=6&id=5)
// * Gosund (http://www.gosund.com/?m=content&c=index&a=show&catid=6&id=5)
// * Ablue (https://www.amazon.de/Intelligente-Steckdose-Ablue-Funktioniert-Assistant/dp/B076DRFRZC)
// -----------------------------------------------------------------------------
#elif defined(BLITZWOLF_BWSHP2)
#elif defined(BLITZWOLF_BWSHPX)
// Info
#define MANUFACTURER "BLITZWOLF"
#define DEVICE "BWSHP2"
#define DEVICE "BWSHPX"
// Buttons
#define BUTTON1_PIN 13
@ -2530,10 +2769,156 @@
#define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// Same as the above but new board version marked V2.3
// BlitzWolf SHP2 V2.3
// Gosund SP1 V2.3
// -----------------------------------------------------------------------------
#elif defined(BLITZWOLF_BWSHPX_V23)
// Info
#define MANUFACTURER "BLITZWOLF"
#define DEVICE "BWSHPX_V23"
// Buttons
#define BUTTON1_PIN 3
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 1
#define LED1_PIN_INVERSE 1
#define LED2_PIN 13
#define LED2_PIN_INVERSE 1
#define LED2_MODE LED_MODE_FINDME
#define LED2_RELAY 1
// HJL01 / BL0937
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 1
#endif
#define HLW8012_SEL_PIN 12
#define HLW8012_CF1_PIN 5
#define HLW8012_CF_PIN 4
#define HLW8012_SEL_CURRENT LOW
#define HLW8012_CURRENT_RATIO 25740
#define HLW8012_VOLTAGE_RATIO 313400
#define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// Teckin SP22 v1.4 - v1.6
// -----------------------------------------------------------------------------
#elif defined(TECKIN_SP22_V14)
// Info
#define MANUFACTURER "TECKIN"
#define DEVICE "SP22_V14"
// Buttons
#define BUTTON1_PIN 1
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 3
#define LED1_PIN_INVERSE 1
#define LED2_PIN 13
#define LED2_PIN_INVERSE 1
#define LED2_MODE LED_MODE_FINDME
#define LED2_RELAY 1
// HJL01 / BL0937
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 1
#endif
#define HLW8012_SEL_PIN 12
#define HLW8012_CF1_PIN 5
#define HLW8012_CF_PIN 4
#define HLW8012_SEL_CURRENT LOW
#define HLW8012_CURRENT_RATIO 20730
#define HLW8012_VOLTAGE_RATIO 264935
#define HLW8012_POWER_RATIO 2533110
#define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// Several boards under different names uing a power chip labelled BL0937 or HJL-01
// -----------------------------------------------------------------------------
#elif defined(GOSUND_WS1)
// Info
#define MANUFACTURER "GOSUND"
#define DEVICE "WS1"
// Buttons
#define BUTTON1_PIN 0
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 1
#define LED1_PIN_INVERSE 1
// This one is the same as the BLITZWOLF_BWSHPX_V23
#elif defined(GOSUND_SP1_V23)
// Info
#define MANUFACTURER "GOSUND"
#define DEVICE "SP1_v23"
// Buttons
#define BUTTON1_PIN 3
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 1
#define LED1_PIN_INVERSE 1
#define LED2_PIN 13
#define LED2_PIN_INVERSE 0
#define LED2_MODE LED_MODE_FINDME
#define LED2_RELAY 1
// HJL01 / BL0937
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 1
#endif
#define HLW8012_SEL_PIN 12
#define HLW8012_CF1_PIN 5
#define HLW8012_CF_PIN 4
#define HLW8012_SEL_CURRENT LOW
#define HLW8012_CURRENT_RATIO 25740
#define HLW8012_VOLTAGE_RATIO 313400
#define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING
// ----------------------------------------------------------------------------------------
// Homecube 16A is similar but some pins differ and it also has RGB LEDs
// https://www.amazon.de/gp/product/B07D7RVF56/ref=oh_aui_detailpage_o00_s01?ie=UTF8&psc=1
// ----------------------------------------------------------------------------------------
#elif defined(HOMECUBE_16A)
// Info
@ -2828,6 +3213,53 @@
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
// -----------------------------------------------------------------------------
// Lombex Lux Nova 2 Tunable White
// https://www.amazon.com/Lombex-Compatible-Equivalent-Dimmable-2700K-6500K/dp/B07B8K72PR
// -----------------------------------------------------------------------------
#elif defined(LOMBEX_LUX_NOVA2_TUNABLE_WHITE)
// Info
#define MANUFACTURER "LOMBEX"
#define DEVICE "LUX_NOVA2_TUNABLE_WHITE"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX
#define DUMMY_RELAY_COUNT 1
// Light
#define LIGHT_CHANNELS 5
#define MY92XX_MODEL MY92XX_MODEL_MY9291
#define MY92XX_CHIPS 1
#define MY92XX_DI_PIN 4
#define MY92XX_DCKI_PIN 5
#define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT
// No RGB on this bulb. Warm white on channel 0, cool white on channel 3
#define MY92XX_MAPPING 255, 255, 255, 3, 0
// -----------------------------------------------------------------------------
// Lombex Lux Nova 2 White and Color
// https://www.amazon.com/Lombex-Compatible-Equivalent-Dimmable-2700K-6500K/dp/B07B8K72PR
// -----------------------------------------------------------------------------
#elif defined(LOMBEX_LUX_NOVA2_WHITE_COLOR)
// Info
#define MANUFACTURER "LOMBEX"
#define DEVICE "LUX_NOVA2_WHITE_COLOR"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX
#define DUMMY_RELAY_COUNT 1
// Light
#define LIGHT_CHANNELS 4
#define MY92XX_MODEL MY92XX_MODEL_MY9291
#define MY92XX_CHIPS 1
#define MY92XX_DI_PIN 4
#define MY92XX_DCKI_PIN 5
#define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT
// RGB on channels 0/1/2, either cool or warm white on channel 3
// The bulb *should* have cool leds, but could also have warm leds as a common defect
#define MY92XX_MAPPING 0, 1, 2, 3
// -----------------------------------------------------------------------------
// Bestek Smart Plug with 2 USB ports
// https://www.bestekcorp.com/bestek-smart-plug-works-with-amazon-alexa-google-assistant-and-ifttt-with-2-usb
@ -2852,6 +3284,91 @@
#define LED1_PIN 4
#define LED1_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// GBLIFE RGBW SOCKET
// -----------------------------------------------------------------------------
#elif defined(GBLIFE_RGBW_SOCKET)
// Info
#define MANUFACTURER "GBLIFE"
#define DEVICE "RGBW_SOCKET"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 15
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// Light RGBW
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
#define LIGHT_CHANNELS 4
#define LIGHT_CH1_PIN 5 // RED
#define LIGHT_CH2_PIN 14 // GREEN
#define LIGHT_CH3_PIN 12 // BLUE
#define LIGHT_CH4_PIN 4 // WHITE
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
// ----------------------------------------------------------------------------------------
// Smart life Mini Smart Socket is similar Homecube 16A but some GPIOs differ
// https://www.ebay.de/itm/Smart-Steckdose-WIFI-WLAN-Amazon-Alexa-Fernbedienung-Home-Socket-Zeitschaltuh-DE/123352026749?hash=item1cb85a8e7d:g:IasAAOSwk6dbj390
// ----------------------------------------------------------------------------------------
#elif defined(SMARTLIFE_MINI_SMART_SOCKET)
// Info
#define MANUFACTURER "SMARTLIFE"
#define DEVICE "MINI_SMART_SOCKET"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 15
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
//Red LED: 0
//Green LED: 4
//Blue LED: 2
// Light
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
#define LIGHT_CHANNELS 3
#define LIGHT_CH1_PIN 0 // RED
#define LIGHT_CH2_PIN 4 // GREEN
#define LIGHT_CH3_PIN 2 // BLUE
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
// HJL01 / BL0937
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 1
#endif
#define HLW8012_SEL_PIN 12
#define HLW8012_CF1_PIN 14
#define HLW8012_CF_PIN 5
#define HLW8012_SEL_CURRENT LOW
#define HLW8012_CURRENT_RATIO 25740
#define HLW8012_VOLTAGE_RATIO 313400
#define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// TEST boards (do not use!!)
// -----------------------------------------------------------------------------
@ -2888,6 +3405,7 @@
// If we dont got it, you don't want it!
#define AM2320_SUPPORT 1
#define BH1750_SUPPORT 1
#define BMP180_SUPPORT 1
#define BMX280_SUPPORT 1
#define SHT3X_I2C_SUPPORT 1
#define EMON_ADC121_SUPPORT 1
@ -2895,8 +3413,9 @@
#define SHT3X_I2C_SUPPORT 1
#define SI7021_SUPPORT 1
#define PMSX003_SUPPORT 1
#define SENSEAIR_SUPPORT1
#define SENSEAIR_SUPPORT 1
#define VL53L1X_SUPPORT 1
#define MAX6675_SUPPORT 1
// A bit of lights - pin 5
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
@ -2932,6 +3451,14 @@
#define MICS2710_SUPPORT 1
#define MICS5525_SUPPORT 1
// MAX6675 14 11 10
#ifndef MAX6675_SUPPORT
#define MAX6675_SUPPORT 1
#endif
#define MAX6675_CS_PIN 14
#define MAX6675_SO_PIN 11
#define MAX6675_SCK_PIN 10
#elif defined(TRAVIS02)
// Relay provider dual
@ -3032,10 +3559,13 @@
#define EMON_ANALOG_SUPPORT 1
#endif
#define PULSEMETER_SUPPORT 1
// Test non-default modules
#define LLMNR_SUPPORT 1
#define NETBIOS_SUPPORT 1
#define SSDP_SUPPORT 1
#define RF_SUPPORT 1
#endif


+ 31
- 9
code/espurna/config/progmem.h View File

@ -97,7 +97,7 @@ PROGMEM const char espurna_modules[] =
#if RFM69_SUPPORT
"RFM69 "
#endif
#if RF_SUPPORT
#if RF_SUPPORT || defined(ITEAD_SONOFF_RFBRIDGE)
"RF "
#endif
#if SCHEDULER_SUPPORT
@ -145,6 +145,9 @@ PROGMEM const char espurna_sensors[] =
#if BH1750_SUPPORT
"BH1750 "
#endif
#if BMP180_SUPPORT
"BMP180 "
#endif
#if BMX280_SUPPORT
"BMX280 "
#endif
@ -199,6 +202,9 @@ PROGMEM const char espurna_sensors[] =
#if PMSX003_SUPPORT
"PMSX003 "
#endif
#if PULSEMETER_SUPPORT
"PULSEMETER "
#endif
#if PZEM004T_SUPPORT
"PZEM004T "
#endif
@ -223,6 +229,15 @@ PROGMEM const char espurna_sensors[] =
#if V9261F_SUPPORT
"V9261F "
#endif
#if VEML6075_SUPPORT
"VEML6075 "
#endif
#if VL53L1X_SUPPORT
"VL53L1X "
#endif
#if EZOPH_SUPPORT
"EZOPH "
#endif
"";
@ -232,10 +247,12 @@ PROGMEM const unsigned char magnitude_decimals[] = {
3, 0, 0, 0, 0, 0, 0, 0, // Power decimals
0, 0, 0, // analog, digital, event
0, 0, 0, // PM
0, 0, 3, 3, 0,
0, 0,
0, 0, 3, // UVA, UVB, UVI
3, 0,
4, 4, // Geiger Counter decimals
0,
0, 0, 0 // NO2, CO, Ohms
0, 0, 0, 3 // NO2, CO, Ohms, pH
};
PROGMEM const char magnitude_unknown_topic[] = "unknown";
@ -258,7 +275,9 @@ 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_uva_topic[] = "uva";
PROGMEM const char magnitude_uvb_topic[] = "uvb";
PROGMEM const char magnitude_uvi_topic[] = "uvi";
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]
@ -267,6 +286,7 @@ PROGMEM const char magnitude_count_topic[] = "count";
PROGMEM const char magnitude_no2_topic[] = "no2";
PROGMEM const char magnitude_co_topic[] = "co";
PROGMEM const char magnitude_resistance_topic[] = "resistance";
PROGMEM const char magnitude_ph_topic[] = "ph";
PROGMEM const char* const magnitude_topics[] = {
magnitude_unknown_topic, magnitude_temperature_topic, magnitude_humidity_topic,
@ -275,11 +295,12 @@ PROGMEM const char* const magnitude_topics[] = {
magnitude_power_factor_topic, magnitude_energy_topic, magnitude_energy_delta_topic,
magnitude_analog_topic, magnitude_digital_topic, magnitude_event_topic,
magnitude_pm1dot0_topic, magnitude_pm2dot5_topic, magnitude_pm10_topic,
magnitude_co2_topic, magnitude_lux_topic, magnitude_uv_topic,
magnitude_co2_topic, magnitude_lux_topic,
magnitude_uva_topic, magnitude_uvb_topic, magnitude_uvi_topic,
magnitude_distance_topic, magnitude_hcho_topic,
magnitude_geiger_cpm_topic, magnitude_geiger_sv_topic,
magnitude_count_topic,
magnitude_no2_topic, magnitude_co_topic, magnitude_resistance_topic
magnitude_no2_topic, magnitude_co_topic, magnitude_resistance_topic, magnitude_ph_topic
};
PROGMEM const char magnitude_empty[] = "";
@ -296,7 +317,6 @@ 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)
@ -311,12 +331,14 @@ PROGMEM const char* const magnitude_units[] = {
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_ppm, magnitude_lux,
magnitude_empty, magnitude_empty, magnitude_empty,
magnitude_distance, magnitude_mgm3,
magnitude_geiger_cpm, magnitude_geiger_sv, // Geiger counter units
magnitude_empty, //
magnitude_ppm, magnitude_ppm, // NO2 & CO2
magnitude_resistance
magnitude_resistance,
magnitude_empty // pH
};
#endif

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

@ -24,7 +24,7 @@ extern "C" {
// Broker
// -----------------------------------------------------------------------------
#if BROKER_SUPPORT
void brokerRegister(void (*)(const char *, unsigned char, const char *));
void brokerRegister(void (*)(const unsigned char, const char *, unsigned char, const char *));
#endif
// -----------------------------------------------------------------------------
@ -69,6 +69,8 @@ extern "C" {
#include <EEPROM_Rotate.h>
EEPROM_Rotate EEPROMr;
void eepromSectorsDebug();
// -----------------------------------------------------------------------------
// GPIO
// -----------------------------------------------------------------------------
@ -140,9 +142,15 @@ template<typename T> String getSetting(const String& key, T defaultValue);
template<typename T> String getSetting(const String& key, unsigned int index, T defaultValue);
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();
// -----------------------------------------------------------------------------
// Terminal
// -----------------------------------------------------------------------------
#if TERMINAL_SUPPORT
void terminalRegisterCommand(const String& name, void (*call)(Embedis*));
void terminalInject(void *data, size_t len);
Stream & terminalSerial();
#endif
// -----------------------------------------------------------------------------
// Utils
@ -165,7 +173,9 @@ void nice_delay(unsigned long ms);
#define AsyncWebSocket void
#define AwsEventType void *
#endif
typedef std::function<bool(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)> web_body_callback_f;
typedef std::function<bool(AsyncWebServerRequest *request)> web_request_callback_f;
void webBodyRegister(web_body_callback_f callback);
void webRequestRegister(web_request_callback_f callback);
// -----------------------------------------------------------------------------
@ -174,6 +184,8 @@ void webRequestRegister(web_request_callback_f callback);
#if WEB_SUPPORT
typedef std::function<void(JsonObject&)> ws_on_send_callback_f;
void wsOnSendRegister(ws_on_send_callback_f callback);
void wsSend(uint32_t, JsonObject& root);
void wsSend(JsonObject& root);
void wsSend(ws_on_send_callback_f sender);
typedef std::function<void(uint32_t, const char *, JsonObject&)> ws_on_action_callback_f;
@ -181,6 +193,10 @@ void webRequestRegister(web_request_callback_f callback);
typedef std::function<bool(const char *, JsonVariant&)> ws_on_receive_callback_f;
void wsOnReceiveRegister(ws_on_receive_callback_f callback);
bool wsConnected();
bool wsConnected(uint32_t);
bool wsDebugSend(const char*, const char*);
#else
#define ws_on_send_callback_f void *
#define ws_on_action_callback_f void *
@ -193,3 +209,4 @@ void webRequestRegister(web_request_callback_f callback);
#include "JustWifi.h"
typedef std::function<void(justwifi_messages_t code, char * parameter)> wifi_callback_f;
void wifiRegister(wifi_callback_f callback);
bool wifiConnected();

+ 215
- 41
code/espurna/config/sensors.h View File

@ -134,6 +134,21 @@
#define BH1750_MODE BH1750_CONTINUOUS_HIGH_RES_MODE
//------------------------------------------------------------------------------
// BMP085/BMP180
// Enable support by passing BMP180_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef BMP180_SUPPORT
#define BMP180_SUPPORT 0
#endif
#ifndef BMP180_ADDRESS
#define BMP180_ADDRESS 0x00 // 0x00 means auto
#endif
#define BMP180_MODE 3 // 0 for ultra-low power, 1 for standard, 2 for high resolution and 3 for ultrahigh resolution
//------------------------------------------------------------------------------
// BME280/BMP280
// Enable support by passing BMX280_SUPPORT=1 build flag
@ -262,12 +277,28 @@
#define EMON_MAX_SAMPLES 1000 // Max number of samples to get
#define EMON_MAX_TIME 250 // Max time in ms to sample
#define EMON_FILTER_SPEED 512 // Mobile average filter speed
#define EMON_MAINS_VOLTAGE 230 // Mains voltage
#define EMON_REFERENCE_VOLTAGE 3.3 // Reference voltage of the ADC
#define EMON_CURRENT_RATIO 30 // Current ratio in the clamp (30V/1A)
#ifndef EMON_MAINS_VOLTAGE
#define EMON_MAINS_VOLTAGE 230 // Mains voltage
#endif
#ifndef EMON_CURRENT_RATIO
#define EMON_CURRENT_RATIO 30 // Current ratio in the clamp (30A/1V)
#endif
#ifndef EMON_REPORT_CURRENT
#define EMON_REPORT_CURRENT 0 // Report current
#endif
#ifndef EMON_REPORT_POWER
#define EMON_REPORT_POWER 1 // Report power
#endif
#ifndef EMON_REPORT_ENERGY
#define EMON_REPORT_ENERGY 1 // Report energy
#endif
//------------------------------------------------------------------------------
// Energy Monitor based on ADC121
@ -426,6 +457,11 @@
#define HLW8012_USE_INTERRUPTS 1 // Use interrupts to trap HLW8012 signals
#endif
#ifndef HLW8012_WAIT_FOR_WIFI
#define HLW8012_WAIT_FOR_WIFI 0 // Weather to enable interrupts only after
// wifi connection has been stablished
#endif
#ifndef HLW8012_INTERRUPT_ON
#define HLW8012_INTERRUPT_ON CHANGE // When to trigger the interrupt
// Use CHANGE for HLW8012
@ -525,40 +561,6 @@
#define NTC_BETA 3977 // Beta coeficient
#endif
//------------------------------------------------------------------------------
// SDS011 particulates sensor
// Enable support by passing SDS011_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef SDS011_SUPPORT
#define SDS011_SUPPORT 0
#endif
#ifndef SDS011_RX_PIN
#define SDS011_RX_PIN 14
#endif
#ifndef SDS011_TX_PIN
#define SDS011_TX_PIN 12
#endif
//------------------------------------------------------------------------------
// SenseAir CO2 sensor
// Enable support by passing SENSEAIR_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#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
@ -580,20 +582,46 @@
#endif
#ifndef PMS_USE_SOFT
#define PMS_USE_SOFT 0 // If PMS_USE_SOFT == 1, DEBUG_SERIAL_SUPPORT must be 0
#define PMS_USE_SOFT 0 // If PMS_USE_SOFT == 1, DEBUG_SERIAL_SUPPORT must be 0
#endif
#ifndef PMS_RX_PIN
#define PMS_RX_PIN 13 // Software serial RX GPIO (if PMS_USE_SOFT == 1)
#define PMS_RX_PIN 13 // Software serial RX GPIO (if PMS_USE_SOFT == 1)
#endif
#ifndef PMS_TX_PIN
#define PMS_TX_PIN 15 // Software serial TX GPIO (if PMS_USE_SOFT == 1)
#define PMS_TX_PIN 15 // Software serial TX GPIO (if PMS_USE_SOFT == 1)
#endif
#ifndef PMS_HW_PORT
#define PMS_HW_PORT Serial // Hardware serial port (if PMS_USE_SOFT == 0)
#define PMS_HW_PORT Serial // Hardware serial port (if PMS_USE_SOFT == 0)
#endif
//------------------------------------------------------------------------------
// Pulse Meter Energy monitor
// Enable support by passing PULSEMETER_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef PULSEMETER_SUPPORT
#define PULSEMETER_SUPPORT 0
#endif
#ifndef PULSEMETER_PIN
#define PULSEMETER_PIN 5
#endif
#ifndef PULSEMETER_ENERGY_RATIO
#define PULSEMETER_ENERGY_RATIO 4000 // In pulses/kWh
#endif
#ifndef PULSEMETER_INTERRUPT_ON
#define PULSEMETER_INTERRUPT_ON FALLING
#endif
#ifndef PULSEMETER_DEBOUNCE
#define PULSEMETER_DEBOUNCE 50 // Do not register pulses within less than 50 millis
#endif
//------------------------------------------------------------------------------
// PZEM004T based power monitor
// Enable support by passing PZEM004T_SUPPORT=1 build flag
@ -619,6 +647,52 @@
#define PZEM004T_HW_PORT Serial // Hardware serial port (if PZEM004T_USE_SOFT == 0)
#endif
#ifndef PZEM004T_ADDRESSES
#define PZEM004T_ADDRESSES "192.168.1.1" // Device(s) address(es), separated by space, "192.168.1.1 192.168.1.2 192.168.1.3"
#endif
#ifndef PZEM004T_READ_INTERVAL
#define PZEM004T_READ_INTERVAL 1500 // Read interval between same device
#endif
#ifndef PZEM004T_MAX_DEVICES
#define PZEM004T_MAX_DEVICES 3
#endif
//------------------------------------------------------------------------------
// SDS011 particulates sensor
// Enable support by passing SDS011_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef SDS011_SUPPORT
#define SDS011_SUPPORT 0
#endif
#ifndef SDS011_RX_PIN
#define SDS011_RX_PIN 14
#endif
#ifndef SDS011_TX_PIN
#define SDS011_TX_PIN 12
#endif
//------------------------------------------------------------------------------
// SenseAir CO2 sensor
// Enable support by passing SENSEAIR_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#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
//------------------------------------------------------------------------------
// SHT3X I2C (Wemos) temperature & humidity sensor
// Enable support by passing SHT3X_I2C_SUPPORT=1 build flag
@ -709,6 +783,77 @@
#define V9261F_POWER_FACTOR 153699.0
#define V9261F_RPOWER_FACTOR V9261F_CURRENT_FACTOR
//------------------------------------------------------------------------------
// VEML6075 based power sensor
// Enable support by passing VEML6075_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef VEML6075_SUPPORT
#define VEML6075_SUPPORT 0
#endif
#ifndef VEML6075_INTEGRATION_TIME
#define VEML6075_INTEGRATION_TIME VEML6075::IT_100MS // The time, in milliseconds, allocated for a single
#endif // measurement. A longer timing budget allows for more
// accurate results at the cost of power.
#ifndef VEML6075_DYNAMIC_MODE
#define VEML6075_DYNAMIC_MODE VEML6075::DYNAMIC_NORMAL // The dynamic mode can either be normal or high. In high
#endif // dynamic mode, the resolution increases by about two
// times.
//------------------------------------------------------------------------------
// VL53L1X
// Enable support by passing VL53L1X_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef VL53L1X_SUPPORT
#define VL53L1X_SUPPORT 0
#endif
#ifndef VL53L1X_I2C_ADDRESS
#define VL53L1X_I2C_ADDRESS 0x00 // 0x00 means auto
#endif
#ifndef VL53L1X_DISTANCE_MODE
#define VL53L1X_DISTANCE_MODE VL53L1X::Long // The distance mode of the sensor. Can be one of
#endif // `VL53L1X::Short`, `VL53L1X::Medium`, or `VL53L1X::Long.
// Shorter distance modes are less affected by ambient light
// but have lower maximum ranges, especially in the dark.
#ifndef VL53L1X_MEASUREMENT_TIMING_BUDGET
#define VL53L1X_MEASUREMENT_TIMING_BUDGET 140000 // The time, in microseconds, allocated for a single
// measurement. A longer timing budget allows for more
// accurate at the cost of power. The minimum budget is
// 20 ms (20000 us) in short distance mode and 33 ms for
// medium and long distance modes.
#endif
#ifndef VL53L1X_INTER_MEASUREMENT_PERIOD
#define VL53L1X_INTER_MEASUREMENT_PERIOD 50 // Period, in milliseconds, determining how
#endif // often the sensor takes a measurement.
//------------------------------------------------------------------------------
// EZOPH pH meter
// Enable support by passing EZOPH_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef EZOPH_SUPPORT
#define EZOPH_SUPPORT 0
#endif
#ifndef EZOPH_RX_PIN
#define EZOPH_RX_PIN 13 // Software serial RX GPIO
#endif
#ifndef EZOPH_TX_PIN
#define EZOPH_TX_PIN 15 // Software serial TX GPIO
#endif
#ifndef EZOPH_SYNC_INTERVAL
#define EZOPH_SYNC_INTERVAL 1000 // Amount of time (in ms) sync new readings.
#endif
// =============================================================================
// Sensor helpers configuration - can't move to dependencies.h
// =============================================================================
@ -718,6 +863,7 @@
AM2320_SUPPORT || \
ANALOG_SUPPORT || \
BH1750_SUPPORT || \
BMP180_SUPPORT || \
BMX280_SUPPORT || \
CSE7766_SUPPORT || \
DALLAS_SUPPORT || \
@ -739,11 +885,15 @@
SENSEAIR_SUPPORT || \
PMSX003_SUPPORT || \
PZEM004T_SUPPORT || \
PULSEMETER_SUPPORT || \
SHT3X_I2C_SUPPORT || \
SI7021_SUPPORT || \
SONAR_SUPPORT || \
TMP3X_SUPPORT || \
V9261F_SUPPORT \
V9261F_SUPPORT || \
VEML6075_SUPPORT || \
VL53L1X_SUPPORT || \
EZOPH_SUPPORT \
)
#endif
@ -797,6 +947,10 @@
#include "../sensors/BH1750Sensor.h"
#endif
#if BMP180_SUPPORT
#include "../sensors/BMP180Sensor.h"
#endif
#if BMX280_SUPPORT
#include "../sensors/BMX280Sensor.h"
#endif
@ -837,6 +991,10 @@
#include "../sensors/EventSensor.h"
#endif
#if EZOPH_SUPPORT
#include "../sensors/EZOPHSensor.h"
#endif
#if GEIGER_SUPPORT
#include "../sensors/GeigerSensor.h"
#endif
@ -849,6 +1007,10 @@
#include "../sensors/HLW8012Sensor.h"
#endif
#if MAX6675_SUPPORT
#include "../sensors/MAX6675.h"
#endif
#if MHZ19_SUPPORT
#include "../sensors/MHZ19Sensor.h"
#endif
@ -877,6 +1039,10 @@
#include "../sensors/PMSX003Sensor.h"
#endif
#if PULSEMETER_SUPPORT
#include "../sensors/PulseMeterSensor.h"
#endif
#if PZEM004T_SUPPORT
#include "../sensors/PZEM004TSensor.h"
#endif
@ -901,4 +1067,12 @@
#include "../sensors/V9261FSensor.h"
#endif
#if VEML6075_SUPPORT
#include "../sensors/VEML6075Sensor.h"
#endif
#if VL53L1X_SUPPORT
#include "../sensors/VL53L1XSensor.h"
#endif
#endif // SENSOR_SUPPORT

+ 62
- 40
code/espurna/config/types.h View File

@ -3,6 +3,15 @@
// Do not touch this definitions
//------------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// BROKER
// -----------------------------------------------------------------------------
#define BROKER_MSG_TYPE_SYSTEM 0
#define BROKER_MSG_TYPE_DATETIME 1
#define BROKER_MSG_TYPE_STATUS 2
#define BROKER_MSG_TYPE_SENSOR 3
// -----------------------------------------------------------------------------
// WIFI
// -----------------------------------------------------------------------------
@ -39,6 +48,9 @@
#define BUTTON_MODE_FACTORY 7
#define BUTTON_MODE_WPS 8
#define BUTTON_MODE_SMART_CONFIG 9
#define BUTTON_MODE_DIM_UP 10
#define BUTTON_MODE_DIM_DOWN 11
// Needed for ESP8285 boards under Windows using PlatformIO (?)
#ifndef BUTTON_PUSHBUTTON
@ -250,38 +262,45 @@
// 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_SONAR_ID 0x23
#define SENSOR_SENSEAIR_ID 0x24
#define SENSOR_GEIGER_ID 0x25
#define SENSOR_NTC_ID 0x26
#define SENSOR_SDS011_ID 0x27
#define SENSOR_MICS2710_ID 0x28
#define SENSOR_MICS5525_ID 0x29
#define SENSOR_DHTXX_ID 1
#define SENSOR_DALLAS_ID 2
#define SENSOR_EMON_ANALOG_ID 3
#define SENSOR_EMON_ADC121_ID 4
#define SENSOR_EMON_ADS1X15_ID 5
#define SENSOR_HLW8012_ID 6
#define SENSOR_V9261F_ID 7
#define SENSOR_ECH1560_ID 8
#define SENSOR_ANALOG_ID 9
#define SENSOR_DIGITAL_ID 10
#define SENSOR_EVENTS_ID 11
#define SENSOR_PMSX003_ID 12
#define SENSOR_BMX280_ID 13
#define SENSOR_MHZ19_ID 14
#define SENSOR_SI7021_ID 15
#define SENSOR_SHT3X_I2C_ID 16
#define SENSOR_BH1750_ID 17
#define SENSOR_PZEM004T_ID 18
#define SENSOR_AM2320_ID 19
#define SENSOR_GUVAS12SD_ID 20
#define SENSOR_CSE7766_ID 21
#define SENSOR_TMP3X_ID 22
#define SENSOR_SONAR_ID 23
#define SENSOR_SENSEAIR_ID 24
#define SENSOR_GEIGER_ID 25
#define SENSOR_NTC_ID 26
#define SENSOR_SDS011_ID 27
#define SENSOR_MICS2710_ID 28
#define SENSOR_MICS5525_ID 29
#define SENSOR_PULSEMETER_ID 30
#define SENSOR_VEML6075_ID 31
#define SENSOR_VL53L1X_ID 32
#define SENSOR_EZOPH_ID 33
#define SENSOR_BMP180_ID 34
#define SENSOR_MAX6675_ID 35
//--------------------------------------------------------------------------------
// Magnitudes
// These should remain over time, do not modify their values, only add new ones at the end
//--------------------------------------------------------------------------------
#define MAGNITUDE_NONE 0
@ -304,14 +323,17 @@
#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_COUNT 25
#define MAGNITUDE_NO2 26
#define MAGNITUDE_CO 27
#define MAGNITUDE_RESISTANCE 28
#define MAGNITUDE_MAX 29
#define MAGNITUDE_UVA 20
#define MAGNITUDE_UVB 21
#define MAGNITUDE_UVI 22
#define MAGNITUDE_DISTANCE 23
#define MAGNITUDE_HCHO 24
#define MAGNITUDE_GEIGER_CPM 25
#define MAGNITUDE_GEIGER_SIEVERT 26
#define MAGNITUDE_COUNT 27
#define MAGNITUDE_NO2 28
#define MAGNITUDE_CO 29
#define MAGNITUDE_RESISTANCE 30
#define MAGNITUDE_PH 31
#define MAGNITUDE_MAX 32

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

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

+ 157
- 0
code/espurna/crash.ino View File

@ -0,0 +1,157 @@
// -----------------------------------------------------------------------------
// Save crash info
// Taken from krzychb EspSaveCrash
// https://github.com/krzychb/EspSaveCrash
// -----------------------------------------------------------------------------
#if DEBUG_SUPPORT
#include <stdio.h>
#include <stdarg.h>
#include <EEPROM_Rotate.h>
extern "C" {
#include "user_interface.h"
}
#define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data
/**
* Structure of the single crash data set
*
* 1. Crash time
* 2. Restart reason
* 3. Exception cause
* 4. epc1
* 5. epc2
* 6. epc3
* 7. excvaddr
* 8. depc
* 9. adress of stack start
* 10. adress of stack end
* 11. stack trace bytes
* ...
*/
#define SAVE_CRASH_CRASH_TIME 0x00 // 4 bytes
#define SAVE_CRASH_RESTART_REASON 0x04 // 1 byte
#define SAVE_CRASH_EXCEPTION_CAUSE 0x05 // 1 byte
#define SAVE_CRASH_EPC1 0x06 // 4 bytes
#define SAVE_CRASH_EPC2 0x0A // 4 bytes
#define SAVE_CRASH_EPC3 0x0E // 4 bytes
#define SAVE_CRASH_EXCVADDR 0x12 // 4 bytes
#define SAVE_CRASH_DEPC 0x16 // 4 bytes
#define SAVE_CRASH_STACK_START 0x1A // 4 bytes
#define SAVE_CRASH_STACK_END 0x1E // 4 bytes
#define SAVE_CRASH_STACK_TRACE 0x22 // variable
/**
* Save crash information in EEPROM
* This function is called automatically if ESP8266 suffers an exception
* It should be kept quick / consise to be able to execute before hardware wdt may kick in
*/
extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack_start, uint32_t stack_end ) {
// Do not record crash data when resetting the board
if (checkNeedsReset()) {
return;
}
// This method assumes EEPROM has already been initialized
// which is the first thing ESPurna does
// write crash time to EEPROM
uint32_t crash_time = millis();
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
// write reset info to EEPROM
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
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
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
// starting address of Embedis data plus reserve
const uint16_t settings_start = SPI_FLASH_SEC_SIZE - settingsSize() - 0x10;
// write stack trace to EEPROM and avoid overwriting settings
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
for (uint32_t i = stack_start; i < stack_end; i++) {
if (current_address >= settings_start) break;
byte* byteValue = (byte*) i;
EEPROMr.write(current_address++, *byteValue);
}
EEPROMr.commit();
}
/**
* Clears crash info
*/
void crashClear() {
uint32_t crash_time = 0xFFFFFFFF;
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROMr.commit();
}
/**
* Print out crash information that has been previusly saved in EEPROM
*/
void crashDump() {
uint32_t 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] 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;
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;
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] sp=0x%08x end=0x%08x\n"), stack_start, stack_end);
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
int16_t stack_len = stack_end - stack_start;
uint32_t stack_trace;
DEBUG_MSG_P(PSTR("[DEBUG] >>>stack>>>\n[DEBUG] "));
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++) {
EEPROMr.get(current_address, stack_trace);
DEBUG_MSG_P(PSTR("%08x "), stack_trace);
current_address += 4;
}
DEBUG_MSG_P(PSTR("\n[DEBUG] "));
}
DEBUG_MSG_P(PSTR("<<<stack<<<\n"));
}
#endif // DEBUG_SUPPORT

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


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


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


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


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


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


+ 44
- 173
code/espurna/debug.ino View File

@ -8,14 +8,6 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#if DEBUG_SUPPORT
#include <stdio.h>
#include <stdarg.h>
#include <EEPROM_Rotate.h>
extern "C" {
#include "user_interface.h"
}
#if DEBUG_UDP_SUPPORT
#include <WiFiUdp.h>
WiFiUDP _udp_debug;
@ -24,22 +16,43 @@ char _udp_syslog_header[40] = {0};
#endif
#endif
void _debugSend(char * message) {
#if DEBUG_SERIAL_SUPPORT
void _debugSendSerial(const char* prefix, const char* data) {
if (prefix && (prefix[0] != '\0')) {
Serial.print(prefix);
}
Serial.print(data);
}
#endif
#if DEBUG_TELNET_SUPPORT
void _debugSendTelnet(const char* prefix, const char* data) {
if (prefix && (prefix[0] != '\0')) {
_telnetWrite(prefix);
}
_telnetWrite(data);
}
#endif
void _debugSend(const char * message) {
const size_t msg_len = strlen(message);
bool pause = false;
char timestamp[10] = {0};
#if DEBUG_ADD_TIMESTAMP
static bool add_timestamp = true;
char timestamp[10] = {0};
if (add_timestamp) snprintf_P(timestamp, sizeof(timestamp), PSTR("[%06lu] "), millis() % 1000000);
add_timestamp = (message[strlen(message)-1] == 10) || (message[strlen(message)-1] == 13);
if (add_timestamp) {
snprintf(timestamp, sizeof(timestamp), "[%06lu] ", millis() % 1000000);
}
add_timestamp = (message[msg_len - 1] == 10) || (message[msg_len - 1] == 13);
#endif
#if DEBUG_SERIAL_SUPPORT
#if DEBUG_ADD_TIMESTAMP
DEBUG_PORT.printf(timestamp);
#endif
DEBUG_PORT.printf(message);
_debugSendSerial(timestamp, message);
#endif
#if DEBUG_UDP_SUPPORT
@ -59,31 +72,13 @@ void _debugSend(char * message) {
#endif
#if DEBUG_TELNET_SUPPORT
#if DEBUG_ADD_TIMESTAMP
_telnetWrite(timestamp, strlen(timestamp));
#endif
_telnetWrite(message, strlen(message));
_debugSendTelnet(timestamp, 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;
}
wsDebugSend(timestamp, message);
pause = true;
#endif
if (pause) optimistic_yield(100);
@ -136,12 +131,16 @@ void debugWebSetup() {
});
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 TERMINAL_SUPPORT
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);
terminalInject((void*) buffer, strlen(buffer));
}
#endif
});
#if DEBUG_UDP_SUPPORT
@ -155,6 +154,8 @@ void debugWebSetup() {
#endif // DEBUG_WEB_SUPPORT
// -----------------------------------------------------------------------------
void debugSetup() {
#if DEBUG_SERIAL_SUPPORT
@ -166,134 +167,4 @@ void debugSetup() {
}
// -----------------------------------------------------------------------------
// Save crash info
// Taken from krzychb EspSaveCrash
// https://github.com/krzychb/EspSaveCrash
// -----------------------------------------------------------------------------
#define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data
/**
* Structure of the single crash data set
*
* 1. Crash time
* 2. Restart reason
* 3. Exception cause
* 4. epc1
* 5. epc2
* 6. epc3
* 7. excvaddr
* 8. depc
* 9. adress of stack start
* 10. adress of stack end
* 11. stack trace bytes
* ...
*/
#define SAVE_CRASH_CRASH_TIME 0x00 // 4 bytes
#define SAVE_CRASH_RESTART_REASON 0x04 // 1 byte
#define SAVE_CRASH_EXCEPTION_CAUSE 0x05 // 1 byte
#define SAVE_CRASH_EPC1 0x06 // 4 bytes
#define SAVE_CRASH_EPC2 0x0A // 4 bytes
#define SAVE_CRASH_EPC3 0x0E // 4 bytes
#define SAVE_CRASH_EXCVADDR 0x12 // 4 bytes
#define SAVE_CRASH_DEPC 0x16 // 4 bytes
#define SAVE_CRASH_STACK_START 0x1A // 4 bytes
#define SAVE_CRASH_STACK_END 0x1E // 4 bytes
#define SAVE_CRASH_STACK_TRACE 0x22 // variable
/**
* Save crash information in EEPROM
* This function is called automatically if ESP8266 suffers an exception
* It should be kept quick / consise to be able to execute before hardware wdt may kick in
*/
extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack_start, uint32_t stack_end ) {
// This method assumes EEPROM has already been initialized
// which is the first thing ESPurna does
// write crash time to EEPROM
uint32_t crash_time = millis();
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
// write reset info to EEPROM
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
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
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;
EEPROMr.write(current_address++, *byteValue);
}
EEPROMr.commit();
}
/**
* Clears crash info
*/
void debugClearCrashInfo() {
uint32_t crash_time = 0xFFFFFFFF;
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROMr.commit();
}
/**
* Print out crash information that has been previusly saved in EEPROM
*/
void debugDumpCrashInfo() {
uint32_t 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] 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;
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;
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;
uint32_t stack_trace;
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++) {
EEPROMr.get(current_address, stack_trace);
DEBUG_MSG_P(PSTR("%08x "), stack_trace);
current_address += 4;
}
DEBUG_MSG_P(PSTR("\n[DEBUG] "));
}
DEBUG_MSG_P(PSTR("<<<stack<<<\n"));
}
#endif // DEBUG_SUPPORT

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

@ -11,6 +11,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h>
bool _dcz_enabled = false;
std::vector<bool> _dcz_relay_state;
//------------------------------------------------------------------------------
// Private methods
@ -36,6 +37,15 @@ void _domoticzMqttSubscribe(bool value) {
}
bool _domoticzStatus(unsigned char id) {
return _dcz_relay_state[id];
}
void _domoticzStatus(unsigned char id, bool status) {
_dcz_relay_state[id] = status;
relayStatus(id, status);
}
void _domoticzMqtt(unsigned int type, const char * topic, const char * payload) {
if (!_dcz_enabled) return;
@ -67,11 +77,80 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
// IDX
unsigned int idx = root["idx"];
unsigned char relayID = _domoticzRelay(idx);
if (relayID >= 0) {
unsigned char value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx);
relayStatus(relayID, value == 1);
String stype = root["stype"];
if (
(stype.equals("RGB") || stype.equals("RGBW") || stype.equals("RGBWW"))
&& domoticzIdx(0) == idx
) {
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (lightHasColor()) {
// m field contains information about color mode (enum ColorMode from domoticz ColorSwitch.h):
unsigned int cmode = root["Color"]["m"];
unsigned int cval;
if (cmode == 3 || cmode == 4) { // ColorModeRGB or ColorModeCustom - see domoticz ColorSwitch.h
// RED
cval = root["Color"]["r"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received RED value %u for IDX %u\n"), cval, idx);
lightChannel(0, cval);
// GREEN
cval = root["Color"]["g"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received GREEN value %u for IDX %u\n"), cval, idx);
lightChannel(1, cval);
// BLUE
cval = root["Color"]["b"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received BLUE value %u for IDX %u\n"), cval, idx);
lightChannel(2, cval);
// WARM WHITE or MONOCHROME WHITE if supported
if (lightChannels() > 3) {
cval = root["Color"]["ww"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received WARM WHITE value %u for IDX %u\n"), cval, idx);
lightChannel(3, cval);
}
// COLD WHITE if supported
if (lightChannels() > 4) {
cval = root["Color"]["cw"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received COLD WHITE value %u for IDX %u\n"), cval, idx);
lightChannel(4, cval);
}
unsigned int brightness = root["Level"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received BRIGHTNESS value %u for IDX %u\n"), brightness, idx);
unsigned int br = (brightness / 100.0) * LIGHT_MAX_BRIGHTNESS;
DEBUG_MSG_P(PSTR("[DOMOTICZ] Calculated BRIGHTNESS value %u for IDX %u\n"), br, idx);
lightBrightness(br); // domoticz uses 100 as maximum value while we're using LIGHT_MAX_BRIGHTNESS
// update lights
lightUpdate(true, mqttForward());
}
unsigned char relayID = _domoticzRelay(idx);
if (relayID >= 0) {
unsigned char value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx);
_domoticzStatus(relayID, value > 0);
}
}
#else
DEBUG_MSG_P(PSTR("[DOMOTICZ] ESPurna compiled without LIGHT_PROVIDER"));
#endif
} else {
unsigned char relayID = _domoticzRelay(idx);
if (relayID >= 0) {
unsigned char value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx);
_domoticzStatus(relayID, value == 1);
}
}
}
@ -80,6 +159,22 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
};
#if BROKER_SUPPORT
void _domoticzBrokerCallback(const unsigned char type, const char * topic, unsigned char id, const char * payload) {
// Only process status messages
if (BROKER_MSG_TYPE_STATUS != type) return;
if (strcmp(MQTT_TOPIC_RELAY, topic) == 0) {
bool status = atoi(payload) == 1;
if (_domoticzStatus(id) == status) return;
_dcz_relay_state[id] = status;
domoticzSendRelay(id, status);
}
}
#endif // BROKER_SUPPORT
#if WEB_SUPPORT
bool _domoticzWebSocketOnReceive(const char * key, JsonVariant& value) {
@ -88,7 +183,7 @@ bool _domoticzWebSocketOnReceive(const char * key, JsonVariant& value) {
void _domoticzWebSocketOnSend(JsonObject& root) {
root["dczVisible"] = 1;
unsigned char visible = 0;
root["dczEnabled"] = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC);
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
@ -97,18 +192,15 @@ void _domoticzWebSocketOnSend(JsonObject& root) {
for (unsigned char i=0; i<relayCount(); i++) {
relays.add(domoticzIdx(i));
}
visible = (relayCount() > 0);
#if SENSOR_SUPPORT
JsonArray& list = root.createNestedArray("dczMagnitudes");
for (byte i=0; i<magnitudeCount(); i++) {
JsonObject& element = list.createNestedObject();
element["name"] = magnitudeName(i);
element["type"] = magnitudeType(i);
element["index"] = magnitudeIndex(i);
element["idx"] = getSetting("dczMagnitude", i, 0).toInt();
}
_sensorWebSocketMagnitudes(root, "dcz");
visible = visible || (magnitudeCount() > 0);
#endif
root["dczVisible"] = visible;
}
#endif // WEB_SUPPORT
@ -116,6 +208,12 @@ void _domoticzWebSocketOnSend(JsonObject& root) {
void _domoticzConfigure() {
bool enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
if (enabled != _dcz_enabled) _domoticzMqttSubscribe(enabled);
_dcz_relay_state.reserve(relayCount());
for (size_t n = 0; n < relayCount(); ++n) {
_dcz_relay_state[n] = relayStatus(n);
}
_dcz_enabled = enabled;
}
@ -137,16 +235,16 @@ template<typename T> void domoticzSend(const char * key, T nvalue) {
domoticzSend(key, nvalue, "");
}
void domoticzSendRelay(unsigned char relayID) {
void domoticzSendRelay(unsigned char relayID, bool status) {
if (!_dcz_enabled) return;
char buffer[15];
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%u"), relayID);
domoticzSend(buffer, relayStatus(relayID) ? "1" : "0");
domoticzSend(buffer, status ? "1" : "0");
}
void domoticzSendRelays() {
for (uint8_t relayID=0; relayID < relayCount(); relayID++) {
domoticzSendRelay(relayID);
domoticzSendRelay(relayID, relayStatus(relayID));
}
}
@ -165,6 +263,10 @@ void domoticzSetup() {
wsOnReceiveRegister(_domoticzWebSocketOnReceive);
#endif
#if BROKER_SUPPORT
brokerRegister(_domoticzBrokerCallback);
#endif
// Callbacks
mqttRegister(_domoticzMqtt);
espurnaRegisterReload(_domoticzConfigure);


+ 53
- 10
code/espurna/eeprom.ino View File

@ -8,6 +8,11 @@ EEPROM MODULE
// -----------------------------------------------------------------------------
bool _eeprom_commit = false;
uint32_t _eeprom_commit_count = 0;
bool _eeprom_last_commit_result = false;
void eepromRotate(bool value) {
// Enable/disable EEPROM rotation only if we are using more sectors than the
// reserved by the memory layout
@ -34,33 +39,62 @@ String eepromSectors() {
return response;
}
void eepromSectorsDebug() {
DEBUG_MSG_P(PSTR("[MAIN] EEPROM sectors: %s\n"), (char *) eepromSectors().c_str());
DEBUG_MSG_P(PSTR("[MAIN] EEPROM current: %lu\n"), eepromCurrent());
}
bool _eepromCommit() {
_eeprom_commit_count++;
_eeprom_last_commit_result = EEPROMr.commit();
return _eeprom_last_commit_result;
}
void eepromCommit() {
_eeprom_commit = true;
}
#if TERMINAL_SUPPORT
void _eepromInitCommands() {
settingsRegisterCommand(F("EEPROM"), [](Embedis* e) {
terminalRegisterCommand(F("EEPROM"), [](Embedis* e) {
infoMemory("EEPROM", SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE - settingsSize());
DEBUG_MSG_P(PSTR("+OK\n"));
eepromSectorsDebug();
if (_eeprom_commit_count > 0) {
DEBUG_MSG_P(PSTR("[MAIN] Commits done: %lu\n"), _eeprom_commit_count);
DEBUG_MSG_P(PSTR("[MAIN] Last result: %s\n"), _eeprom_last_commit_result ? "OK" : "ERROR");
}
terminalOK();
});
settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) {
EEPROMr.dump(settingsSerial());
DEBUG_MSG_P(PSTR("\n+OK\n"));
terminalRegisterCommand(F("EEPROM.COMMIT"), [](Embedis* e) {
const bool res = _eepromCommit();
if (res) {
terminalOK();
} else {
DEBUG_MSG_P(PSTR("-ERROR\n"));
}
});
terminalRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) {
EEPROMr.dump(terminalSerial());
terminalOK();
});
settingsRegisterCommand(F("FLASH.DUMP"), [](Embedis* e) {
terminalRegisterCommand(F("FLASH.DUMP"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
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"));
terminalError(F("Sector out of range"));
return;
}
EEPROMr.dump(settingsSerial(), sector);
DEBUG_MSG_P(PSTR("\n+OK\n"));
EEPROMr.dump(terminalSerial(), sector);
terminalOK();
});
}
@ -69,6 +103,13 @@ void _eepromInitCommands() {
// -----------------------------------------------------------------------------
void eepromLoop() {
if (_eeprom_commit) {
_eepromCommit();
_eeprom_commit = false;
}
}
void eepromSetup() {
#ifdef EEPROM_ROTATE_SECTORS
@ -92,4 +133,6 @@ void eepromSetup() {
_eepromInitCommands();
#endif
espurnaRegisterLoop(eepromLoop);
}

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

@ -105,7 +105,7 @@ void _encoderLoop() {
// action
if (encoder.button_pin == GPIO_NONE) {
// if there is no button, the encoder driver the CHANNEL1
// if there is no button, the encoder drives CHANNEL1
lightChannelStep(encoder.channel1, delta);
} else {


+ 8
- 6
code/espurna/espurna.ino View File

@ -64,11 +64,16 @@ void setup() {
// Init EEPROM
eepromSetup();
// Init persistance
settingsSetup();
// Init Serial, SPIFFS and system check
systemSetup();
// Init persistance and terminal features
settingsSetup();
// Init terminal features
#if TERMINAL_SUPPORT
terminalSetup();
#endif
// Hostname & board name initialization
if (getSetting("hostname").length() == 0) {
@ -148,7 +153,7 @@ void setup() {
#if I2C_SUPPORT
i2cSetup();
#endif
#ifdef ITEAD_SONOFF_RFBRIDGE
#if defined(ITEAD_SONOFF_RFBRIDGE) || RF_SUPPORT
rfbSetup();
#endif
#if ALEXA_SUPPORT
@ -166,9 +171,6 @@ void setup() {
#if RFM69_SUPPORT
rfm69Setup();
#endif
#if RF_SUPPORT
rfSetup();
#endif
#if IR_SUPPORT
irSetup();
#endif


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

@ -9,6 +9,7 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
#if HOMEASSISTANT_SUPPORT
#include <ArduinoJson.h>
#include <queue>
bool _haEnabled = false;
bool _haSendFlag = false;
@ -147,9 +148,7 @@ void _haSendSwitches() {
// -----------------------------------------------------------------------------
String _haGetConfig() {
String output;
void _haDumpConfig(std::function<void(String&)> printer, bool wrapJson = false) {
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light");
@ -163,8 +162,16 @@ String _haGetConfig() {
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
output += "\n" + type + ":\n";
String output;
output.reserve(config.measureLength() + 32);
if (wrapJson) {
output += "{\"haConfig\": \"";
}
output += "\n\n" + type + ":\n";
bool first = true;
for (auto kv : config) {
if (first) {
output += " - ";
@ -172,11 +179,21 @@ String _haGetConfig() {
} else {
output += " ";
}
output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
output += kv.key;
output += ": ";
output += kv.value.as<String>();
output += "\n";
}
output += " ";
if (wrapJson) {
output += "\"}";
}
jsonBuffer.clear();
printer(output);
}
#if SENSOR_SUPPORT
@ -187,8 +204,16 @@ String _haGetConfig() {
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
output += "\nsensor:\n";
String output;
output.reserve(config.measureLength() + 32);
if (wrapJson) {
output += "{\"haConfig\": \"";
}
output += "\n\nsensor:\n";
bool first = true;
for (auto kv : config) {
if (first) {
output += " - ";
@ -198,18 +223,25 @@ String _haGetConfig() {
}
String value = kv.value.as<String>();
value.replace("%", "'%'");
output += kv.key + String(": ") + value + String("\n");
output += kv.key;
output += ": ";
output += value;
output += "\n";
}
output += " ";
if (wrapJson) {
output += "\"}";
}
output += "\n";
jsonBuffer.clear();
printer(output);
}
#endif
return output;
}
void _haSend() {
@ -241,6 +273,8 @@ void _haConfigure() {
#if WEB_SUPPORT
std::queue<uint32_t> _ha_send_config;
bool _haWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "ha", 2) == 0);
}
@ -253,11 +287,7 @@ void _haWebSocketOnSend(JsonObject& root) {
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());
_ha_send_config.push(client_id);
}
}
@ -267,27 +297,30 @@ void _haWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& d
void _haInitCommands() {
settingsRegisterCommand(F("HA.CONFIG"), [](Embedis* e) {
DEBUG_MSG(_haGetConfig().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
terminalRegisterCommand(F("HA.CONFIG"), [](Embedis* e) {
_haDumpConfig([](String& data) {
DEBUG_MSG(data.c_str());
});
DEBUG_MSG("\n");
terminalOK();
});
settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) {
terminalRegisterCommand(F("HA.SEND"), [](Embedis* e) {
setSetting("haEnabled", "1");
_haConfigure();
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
#endif
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
terminalRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
setSetting("haEnabled", "0");
_haConfigure();
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
#endif
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
}
@ -296,6 +329,23 @@ void _haInitCommands() {
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
void _haLoop() {
if (_ha_send_config.empty()) return;
uint32_t client_id = _ha_send_config.front();
_ha_send_config.pop();
if (!wsConnected(client_id)) return;
// TODO check wsConnected after each "printer" call?
_haDumpConfig([client_id](String& output) {
wsSend(client_id, output.c_str());
yield();
}, true);
}
#endif
void haSetup() {
_haConfigure();
@ -304,6 +354,7 @@ void haSetup() {
wsOnSendRegister(_haWebSocketOnSend);
wsOnActionRegister(_haWebSocketOnAction);
wsOnReceiveRegister(_haWebSocketOnReceive);
espurnaRegisterLoop(_haLoop);
#endif
#if TERMINAL_SUPPORT


+ 18
- 0
code/espurna/i2c.ino View File

@ -351,6 +351,20 @@ void i2cScan() {
if (nDevices == 0) DEBUG_MSG_P(PSTR("[I2C] No devices found\n"));
}
void i2cCommands() {
terminalRegisterCommand(F("I2C.SCAN"), [](Embedis* e) {
i2cScan();
terminalOK();
});
terminalRegisterCommand(F("I2C.CLEAR"), [](Embedis* e) {
i2cClearBus();
terminalOK();
});
}
void i2cSetup() {
unsigned char sda = getSetting("i2cSDA", I2C_SDA_PIN).toInt();
@ -366,6 +380,10 @@ void i2cSetup() {
DEBUG_MSG_P(PSTR("[I2C] Using GPIO%u for SDA and GPIO%u for SCL\n"), sda, scl);
#if TERMINAL_SUPPORT
i2cCommands();
#endif
#if I2C_CLEAR_BUS
i2cClearBus();
#endif


+ 15
- 0
code/espurna/influxdb.ino View File

@ -38,6 +38,17 @@ void _idbConfigure() {
}
}
#if BROKER_SUPPORT
void _idbBrokerCallback(const unsigned char type, const char * topic, unsigned char id, const char * payload) {
// Only process status & senssor messages
if ((BROKER_MSG_TYPE_STATUS == type) || (BROKER_MSG_TYPE_SENSOR == type)) {
idbSend(topic, id, (char *) payload);
}
}
#endif // BROKER_SUPPORT
// -----------------------------------------------------------------------------
bool idbSend(const char * topic, const char * payload) {
@ -108,6 +119,10 @@ void idbSetup() {
wsOnReceiveRegister(_idbWebSocketOnReceive);
#endif
#if BROKER_SUPPORT
brokerRegister(_idbBrokerCallback);
#endif
// Main callbacks
espurnaRegisterReload(_idbConfigure);


+ 11
- 24
code/espurna/ir.ino View File

@ -43,8 +43,6 @@ Raw messages:
Payload: 1000,1000,1000,1000,1000
| IR codes |
* To support long codes (Air Conditioneer) increase MQTT packet size -DMQTT_MAX_PACKET_SIZE=1200
--------------------------------------------------------------------------------
*/
@ -91,7 +89,6 @@ void _irMqttCallback(unsigned int type, const char * topic, const char * payload
}
if (type == MQTT_MESSAGE_EVENT) {
String t = mqttMagnitude((char *) topic);
// Match topic
@ -110,7 +107,7 @@ void _irMqttCallback(unsigned int type, const char * topic, const char * payload
_ir_repeat_size = 1;
// count & validate repeat-string
for(int i = col+1; i < len; i++) {
for(unsigned int i = col+1; i < len; i++) {
if (i < len-1) {
if ( payload[i] == ',' && isDigit(payload[i+1]) && i>0 ) { //validate string
_ir_repeat_size++;
@ -129,7 +126,7 @@ void _irMqttCallback(unsigned int type, const char * topic, const char * payload
} // end of counting & validating repeat code
// count & validate main code string
for(int i = 0; i < len; i++) {
for(unsigned int i = 0; i < len; i++) {
if (i<len-1) {
if ( payload[i] == ',' && isDigit(payload[i+1]) && i>0 ) { //validate string
count++;
@ -149,7 +146,7 @@ void _irMqttCallback(unsigned int type, const char * topic, const char * payload
int j = 0; // for populating values of array from comma separated string
// populating main code array from part of MQTT string
for (int i = 0; i < len; i++) {
for (unsigned int i = 0; i < len; i++) {
if (payload[i] != ',') {
value = value + data[i];
}
@ -173,14 +170,6 @@ void _irMqttCallback(unsigned int type, const char * topic, const char * payload
DEBUG_MSG_P(PSTR("[IR] Raw IR output %d codes, repeat %d times on %d(k)Hz freq.\n"), count, _ir_repeat, _ir_freq);
/*
DEBUG_MSG_P(PSTR("[IR] main codes: "));
for(int i = 0; i < count; i++) {
DEBUG_MSG_P(PSTR("%d,"),_ir_raw[i]);
}
DEBUG_MSG_P(PSTR("\n"));
*/
#if defined(IR_RX_PIN)
_ir_receiver.disableIRIn();
#endif
@ -203,7 +192,7 @@ void _irMqttCallback(unsigned int type, const char * topic, const char * payload
len = data.length(); //redifining length to full lenght
// populating repeat code array from part of MQTT string
for (int i = col+1; i < len; i++) {
for (unsigned int i = col+1; i < len; i++) {
value = value + data[i];
if ((payload[i] == ',') || (i == len - 1)) {
_ir_raw[j]= value.toInt();
@ -211,7 +200,6 @@ void _irMqttCallback(unsigned int type, const char * topic, const char * payload
j++;
}
}
} else { // if repeat code not specified (col<=2) repeat with current main code
_ir_repeat_size = count;
}
@ -223,7 +211,7 @@ void _irMqttCallback(unsigned int type, const char * topic, const char * payload
if (col > 0) {
_ir_type = data.toInt();
_ir_code = data.substring(col+1).toInt();
_ir_code = strtoul(data.substring(col+1).c_str(), NULL, 10);
col = data.indexOf(":", col+1);
if (col > 0) {
@ -234,9 +222,7 @@ void _irMqttCallback(unsigned int type, const char * topic, const char * payload
} else {
_ir_repeat = IR_REPEAT;
}
}
}
if (_ir_repeat > 0) {
@ -364,12 +350,9 @@ void _irRXLoop() {
if (millis() - last_time < IR_DEBOUNCE) return;
last_time = millis();
// Check code
if (_ir_results.value < 1) return;
if (_ir_results.decode_type < 1) return;
if (_ir_results.bits < 1) return;
#if IR_USE_RAW
// Check code
if (_ir_results.rawlen < 1) return;
char * payload;
String value = "";
for (int i = 1; i < _ir_results.rawlen; i++) {
@ -378,6 +361,10 @@ void _irRXLoop() {
}
payload = const_cast<char*>(value.c_str());
#else
// Check code
if (_ir_results.value < 1) return;
if (_ir_results.decode_type < 1) return;
if (_ir_results.bits < 1) return;
char payload[32];
snprintf_P(payload, sizeof(payload), PSTR("%u:%lu:%u"), _ir_results.decode_type, (unsigned long) _ir_results.value, _ir_results.bits);
#endif


+ 18
- 0
code/espurna/led.ino View File

@ -73,6 +73,19 @@ void _ledWebSocketOnSend(JsonObject& root) {
#endif
#if BROKER_SUPPORT
void _ledBrokerCallback(const unsigned char type, const char * topic, unsigned char id, const char * payload) {
// Only process status messages
if (BROKER_MSG_TYPE_STATUS != type) return;
if (strcmp(MQTT_TOPIC_RELAY, topic) == 0) {
ledUpdate(true);
}
}
#endif // BROKER_SUPPORT
#if MQTT_SUPPORT
void _ledMQTTCallback(unsigned int type, const char * topic, const char * payload) {
@ -173,6 +186,11 @@ void ledSetup() {
wsOnReceiveRegister(_ledWebSocketOnReceive);
#endif
#if BROKER_SUPPORT
brokerRegister(_ledBrokerCallback);
#endif
DEBUG_MSG_P(PSTR("[LED] Number of leds: %d\n"), _leds.size());
// Main callbacks


+ 141
- 72
code/espurna/light.ino View File

@ -25,6 +25,7 @@ extern "C" {
// -----------------------------------------------------------------------------
Ticker _light_comms_ticker;
Ticker _light_save_ticker;
Ticker _light_transition_ticker;
@ -34,7 +35,7 @@ typedef struct {
bool state;
unsigned char inputValue; // value that has been inputted
unsigned char value; // normalized value including brightness
unsigned char shadow; // represented value
unsigned char target; // target value
double current; // transition value
} channel_t;
std::vector<channel_t> _light_channel;
@ -314,25 +315,29 @@ void _fromMireds(unsigned long mireds) {
// Output Values
// -----------------------------------------------------------------------------
void _toRGB(char * rgb, size_t len) {
void _toRGB(char * rgb, size_t len, bool target) {
unsigned long value = 0;
value += _light_channel[0].inputValue;
value += target ? _light_channel[0].target : _light_channel[0].inputValue;
value <<= 8;
value += _light_channel[1].inputValue;
value += target ? _light_channel[1].target : _light_channel[1].inputValue;
value <<= 8;
value += _light_channel[2].inputValue;
value += target ? _light_channel[2].target : _light_channel[2].inputValue;
snprintf_P(rgb, len, PSTR("#%06X"), value);
}
void _toHSV(char * hsv, size_t len) {
void _toRGB(char * rgb, size_t len) {
_toRGB(rgb, len, false);
}
void _toHSV(char * hsv, size_t len, bool target) {
double h, s, v;
double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS;
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;
double r = (double) ((target ? _light_channel[0].target : _light_channel[0].inputValue) * brightness) / 255.0;
double g = (double) ((target ? _light_channel[1].target : _light_channel[1].inputValue) * brightness) / 255.0;
double b = (double) ((target ? _light_channel[2].target : _light_channel[2].inputValue) * brightness) / 255.0;
double min = std::min(r, std::min(g, b));
double max = std::max(r, std::max(g, b));
@ -363,27 +368,41 @@ void _toHSV(char * hsv, size_t len) {
snprintf_P(hsv, len, PSTR("%d,%d,%d"), round(h), round(s), round(v));
}
void _toLong(char * color, size_t len) {
void _toHSV(char * hsv, size_t len) {
_toHSV(hsv, len, false);
}
void _toLong(char * color, size_t len, bool target) {
if (!_light_has_color) return;
snprintf_P(color, len, PSTR("%d,%d,%d"),
(int) _light_channel[0].inputValue,
(int) _light_channel[1].inputValue,
(int) _light_channel[2].inputValue
(int) (target ? _light_channel[0].target : _light_channel[0].inputValue),
(int) (target ? _light_channel[1].target : _light_channel[1].inputValue),
(int) (target ? _light_channel[2].target : _light_channel[2].inputValue)
);
}
void _toCSV(char * buffer, size_t len, bool applyBrightness) {
void _toLong(char * color, size_t len) {
_toLong(color, len, false);
}
void _toCSV(char * buffer, size_t len, bool applyBrightness, bool target) {
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].inputValue * b, num, 10);
itoa((target ? _light_channel[i].target : _light_channel[i].inputValue) * b, num, 10);
if (i>0) strncat(buffer, ",", len--);
strncat(buffer, num, len);
len = len - strlen(num);
}
}
void _toCSV(char * buffer, size_t len, bool applyBrightness) {
_toCSV(buffer, len, applyBrightness, false);
}
// -----------------------------------------------------------------------------
// PROVIDER
// -----------------------------------------------------------------------------
@ -399,37 +418,32 @@ unsigned int _toPWM(unsigned long value, bool gamma, bool reverse) {
// Returns a PWM value for the given channel ID
unsigned int _toPWM(unsigned char id) {
bool useGamma = _light_use_gamma && _light_has_color && (id < 3);
return _toPWM(_light_channel[id].shadow, useGamma, _light_channel[id].reverse);
return _toPWM(_light_channel[id].current, useGamma, _light_channel[id].reverse);
}
void _shadow() {
void _transition() {
// Update transition ticker
_light_steps_left--;
if (_light_steps_left == 0) _light_transition_ticker.detach();
// Transitions
unsigned char target;
for (unsigned int i=0; i < _light_channel.size(); i++) {
target = _light_state && _light_channel[i].state ? _light_channel[i].value : 0;
if (_light_steps_left == 0) {
_light_channel[i].current = target;
_light_channel[i].current = _light_channel[i].target;
} else {
double difference = (double) (target - _light_channel[i].current) / (_light_steps_left + 1);
double difference = (double) (_light_channel[i].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;
}
}
void _lightProviderUpdate() {
_shadow();
_transition();
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX
@ -491,6 +505,7 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl
mqttSubscribe(MQTT_TOPIC_KELVIN);
mqttSubscribe(MQTT_TOPIC_COLOR_RGB);
mqttSubscribe(MQTT_TOPIC_COLOR_HSV);
mqttSubscribe(MQTT_TOPIC_TRANSITION);
}
// Group color
@ -548,6 +563,12 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl
return;
}
// Transitions
if (t.equals(MQTT_TOPIC_TRANSITION)) {
lightTransitionTime(atol(payload));
return;
}
// Channel
if (t.startsWith(MQTT_TOPIC_CHANNEL)) {
unsigned int channelID = t.substring(strlen(MQTT_TOPIC_CHANNEL)+1).toInt();
@ -572,13 +593,13 @@ void lightMQTT() {
// Color
if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) {
_toRGB(buffer, sizeof(buffer));
_toRGB(buffer, sizeof(buffer), true);
} else {
_toLong(buffer, sizeof(buffer));
_toLong(buffer, sizeof(buffer), true);
}
mqttSend(MQTT_TOPIC_COLOR_RGB, buffer);
_toHSV(buffer, sizeof(buffer));
_toHSV(buffer, sizeof(buffer), true);
mqttSend(MQTT_TOPIC_COLOR_HSV, buffer);
// Mireds
@ -589,7 +610,7 @@ void lightMQTT() {
// Channels
for (unsigned int i=0; i < _light_channel.size(); i++) {
itoa(_light_channel[i].inputValue, buffer, 10);
itoa(_light_channel[i].target, buffer, 10);
mqttSend(MQTT_TOPIC_CHANNEL, i, buffer);
}
@ -620,7 +641,7 @@ void lightBroker() {
char buffer[10];
for (unsigned int i=0; i < _light_channel.size(); i++) {
itoa(_light_channel[i].inputValue, buffer, 10);
brokerPublish(MQTT_TOPIC_CHANNEL, i, buffer);
brokerPublish(BROKER_MSG_TYPE_STATUS, MQTT_TOPIC_CHANNEL, i, buffer);
}
}
@ -638,29 +659,45 @@ bool lightHasColor() {
return _light_has_color;
}
void lightUpdate(bool save, bool forward, bool group_forward) {
void _lightComms(unsigned char mask) {
_generateBrightness();
// Report color & brightness to MQTT broker
#if MQTT_SUPPORT
if (mask & 0x01) lightMQTT();
if (mask & 0x02) lightMQTTGroup();
#endif
// Configure color transition
_light_steps_left = _light_use_transitions ? _light_transition_time / LIGHT_TRANSITION_STEP : 1;
_light_transition_ticker.attach_ms(LIGHT_TRANSITION_STEP, _lightProviderUpdate);
// Report color to WS clients (using current brightness setting)
#if WEB_SUPPORT
wsSend(_lightWebSocketStatus);
#endif
// Report channels to local broker
#if BROKER_SUPPORT
lightBroker();
#endif
// Report color & brightness to MQTT broker
#if MQTT_SUPPORT
if (forward) lightMQTT();
if (group_forward) lightMQTTGroup();
#endif
}
// Report color to WS clients (using current brightness setting)
#if WEB_SUPPORT
wsSend(_lightWebSocketOnSend);
#endif
void lightUpdate(bool save, bool forward, bool group_forward) {
_generateBrightness();
// Update channels
for (unsigned int i=0; i < _light_channel.size(); i++) {
_light_channel[i].target = _light_state && _light_channel[i].state ? _light_channel[i].value : 0;
//DEBUG_MSG_P("[LIGHT] Channel #%u target value: %u\n", i, _light_channel[i].target);
}
// Configure color transition
_light_steps_left = _light_use_transitions ? _light_transition_time / LIGHT_TRANSITION_STEP : 1;
_light_transition_ticker.attach_ms(LIGHT_TRANSITION_STEP, _lightProviderUpdate);
// Delay every communication 100ms to avoid jamming
unsigned char mask = 0;
if (forward) mask += 1;
if (group_forward) mask += 2;
_light_comms_ticker.once_ms(LIGHT_COMMS_DELAY, _lightComms, mask);
#if LIGHT_SAVE_ENABLED
// Delay saving to EEPROM 5 seconds to avoid wearing it out unnecessarily
@ -755,6 +792,26 @@ void lightBrightnessStep(int steps) {
lightBrightness(_light_brightness + steps * LIGHT_STEP);
}
unsigned long lightTransitionTime() {
if (_light_use_transitions) {
return _light_transition_time;
} else {
return 0;
}
}
void lightTransitionTime(unsigned long m) {
if (0 == m) {
_light_use_transitions = false;
} else {
_light_use_transitions = true;
_light_transition_time = m;
}
setSetting("useTransitions", _light_use_transitions);
setSetting("lightTime", _light_transition_time);
saveSettings();
}
// -----------------------------------------------------------------------------
// SETUP
// -----------------------------------------------------------------------------
@ -767,23 +824,13 @@ bool _lightWebSocketOnReceive(const char * key, JsonVariant& value) {
return false;
}
void _lightWebSocketOnSend(JsonObject& root) {
root["colorVisible"] = 1;
root["mqttGroupColor"] = getSetting("mqttGroupColor");
root["useColor"] = _light_has_color;
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;
void _lightWebSocketStatus(JsonObject& root) {
if (_light_has_color) {
if (_light_use_cct) {
root["useCCT"] = _light_use_cct;
root["mireds"] = _light_mireds;
}
if (useRGB) {
if (getSetting("useRGB", LIGHT_USE_RGB).toInt() == 1) {
root["rgb"] = lightColor(true);
} else {
root["hsv"] = lightColor(false);
@ -793,7 +840,20 @@ void _lightWebSocketOnSend(JsonObject& root) {
for (unsigned char id=0; id < _light_channel.size(); id++) {
channels.add(lightChannel(id));
}
root["brightness"] = lightBrightness();
}
void _lightWebSocketOnSend(JsonObject& root) {
root["colorVisible"] = 1;
root["mqttGroupColor"] = getSetting("mqttGroupColor");
root["useColor"] = _light_has_color;
root["useWhite"] = _light_use_white;
root["useGamma"] = _light_use_gamma;
root["useTransitions"] = _light_use_transitions;
root["useCSS"] = getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1;
root["useRGB"] = getSetting("useRGB", LIGHT_USE_RGB).toInt() == 1;
root["lightTime"] = _light_transition_time;
_lightWebSocketStatus(root);
}
void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
@ -844,9 +904,9 @@ void _lightAPISetup() {
apiRegister(MQTT_TOPIC_COLOR_RGB,
[](char * buffer, size_t len) {
if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) {
_toRGB(buffer, len);
_toRGB(buffer, len, true);
} else {
_toLong(buffer, len);
_toLong(buffer, len, true);
}
},
[](const char * payload) {
@ -857,7 +917,7 @@ void _lightAPISetup() {
apiRegister(MQTT_TOPIC_COLOR_HSV,
[](char * buffer, size_t len) {
_toHSV(buffer, len);
_toHSV(buffer, len, true);
},
[](const char * payload) {
lightColor(payload, false);
@ -889,7 +949,7 @@ void _lightAPISetup() {
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));
snprintf_P(buffer, len, PSTR("%d"), _light_channel[id].target);
},
[id](const char * payload) {
lightChannel(id, atoi(payload));
@ -899,6 +959,15 @@ void _lightAPISetup() {
}
apiRegister(MQTT_TOPIC_TRANSITION,
[](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), lightTransitionTime());
},
[](const char * payload) {
lightTransitionTime(atol(payload));
}
);
apiRegister(MQTT_TOPIC_BRIGHTNESS,
[](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), _light_brightness);
@ -917,18 +986,18 @@ void _lightAPISetup() {
void _lightInitCommands() {
settingsRegisterCommand(F("BRIGHTNESS"), [](Embedis* e) {
terminalRegisterCommand(F("BRIGHTNESS"), [](Embedis* e) {
if (e->argc > 1) {
lightBrightness(String(e->argv[1]).toInt());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Brightness: %d\n"), lightBrightness());
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
settingsRegisterCommand(F("CHANNEL"), [](Embedis* e) {
terminalRegisterCommand(F("CHANNEL"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
}
int id = String(e->argv[1]).toInt();
if (e->argc > 2) {
@ -937,37 +1006,37 @@ void _lightInitCommands() {
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Channel #%d: %d\n"), id, lightChannel(id));
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
settingsRegisterCommand(F("COLOR"), [](Embedis* e) {
terminalRegisterCommand(F("COLOR"), [](Embedis* e) {
if (e->argc > 1) {
String color = String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
settingsRegisterCommand(F("KELVIN"), [](Embedis* e) {
terminalRegisterCommand(F("KELVIN"), [](Embedis* e) {
if (e->argc > 1) {
String color = String("K") + String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
settingsRegisterCommand(F("MIRED"), [](Embedis* e) {
terminalRegisterCommand(F("MIRED"), [](Embedis* e) {
if (e->argc > 1) {
String color = String("M") + String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
}


+ 47
- 1
code/espurna/migrate.ino View File

@ -257,6 +257,17 @@ void migrate() {
setSetting("myDCKIGPIO", 15);
setSetting("relays", 1);
#elif defined(LYASI_LIGHT)
setSetting("board", 20);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_MY92XX);
setSetting("myModel", MY92XX_MODEL_MY9291);
setSetting("myChips", 1);
setSetting("myDIGPIO", 4);
setSetting("myDCKIGPIO", 5);
setSetting("relays", 1);
#elif defined(MAGICHOME_LED_CONTROLLER)
setSetting("board", 21);
@ -1016,7 +1027,7 @@ void migrate() {
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(BLITZWOLF_BWSHP2)
#elif defined(BLITZWOLF_BWSHPX)
setSetting("board", 79);
setSetting("ledGPIO", 0, 2);
@ -1223,6 +1234,41 @@ void migrate() {
setSetting("ledGPIO", 1, 15);
setSetting("ledLogic", 1, 0);
#elif defined(TONBUX_XSSSA01)
setSetting("board", 92);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(EUROMATE_WIFI_STECKER_SCHUKO_V2)
setSetting("board", 93);
setSetting("ledGPIO", 0, 13); // Red LED
setSetting("ledLogic", 0, 1);
setSetting("ledGPIO", 1, 12); // Green LED
setSetting("ledLogic", 1, 1);
setSetting("btnGPIO", 0, 5);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(OUKITEL_P1)
setSetting("board", 94);
setSetting("ledGPIO", 0, 0); // Blue LED
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12); // Right outlet
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayGPIO", 1, 15); // Left outlet
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#else
// Allow users to define new settings without migration config


+ 33
- 22
code/espurna/mqtt.ino View File

@ -36,6 +36,8 @@ WiFiClientSecure _mqtt_client_secure;
bool _mqtt_enabled = MQTT_ENABLED;
bool _mqtt_use_json = false;
unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
unsigned long _mqtt_last_connection = 0;
bool _mqtt_connecting = false;
unsigned char _mqtt_qos = MQTT_QOS;
bool _mqtt_retain = MQTT_RETAIN;
unsigned long _mqtt_keepalive = MQTT_KEEPALIVE;
@ -48,9 +50,6 @@ char *_mqtt_user = 0;
char *_mqtt_pass = 0;
char *_mqtt_will;
char *_mqtt_clientid;
#if MQTT_SKIP_RETAINED
unsigned long _mqtt_connected_at = 0;
#endif
std::vector<mqtt_callback_f> _mqtt_callbacks;
@ -71,13 +70,11 @@ void _mqttConnect() {
// Do not connect if disabled
if (!_mqtt_enabled) return;
// Do not connect if already connected
if (_mqtt.connected()) return;
// Do not connect if already connected or still trying to connect
if (_mqtt.connected() || _mqtt_connecting) return;
// Check reconnect interval
static unsigned long last = 0;
if (millis() - last < _mqtt_reconnect_delay) return;
last = millis();
if (millis() - _mqtt_last_connection < _mqtt_reconnect_delay) return;
// Increase the reconnect delay
_mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP;
@ -98,14 +95,19 @@ void _mqttConnect() {
if (_mqtt_will) free(_mqtt_will);
if (_mqtt_clientid) free(_mqtt_clientid);
_mqtt_user = strdup(getSetting("mqttUser", MQTT_USER).c_str());
String user = getSetting("mqttUser", MQTT_USER);
_mqttPlaceholders(&user);
_mqtt_user = strdup(user.c_str());
_mqtt_pass = strdup(getSetting("mqttPassword", MQTT_PASS).c_str());
_mqtt_will = strdup(mqttTopic(MQTT_TOPIC_STATUS, false).c_str());
_mqtt_clientid = strdup(getSetting("mqttClientID", getIdentifier()).c_str());
String clientid = getSetting("mqttClientID", getIdentifier());
_mqttPlaceholders(&clientid);
_mqtt_clientid = strdup(clientid.c_str());
DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d\n"), host, port);
#if MQTT_USE_ASYNC
_mqtt_connecting = true;
_mqtt.setServer(host, port);
_mqtt.setClientId(_mqtt_clientid);
@ -211,6 +213,17 @@ void _mqttConnect() {
}
void _mqttPlaceholders(String *text) {
text->replace("{hostname}", getSetting("hostname"));
text->replace("{magnitude}", "#");
String mac = WiFi.macAddress();
mac.replace(":", "");
text->replace("{mac}", mac);
}
void _mqttConfigure() {
// Get base topic
@ -218,12 +231,8 @@ void _mqttConfigure() {
if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1);
// Placeholders
_mqtt_topic.replace("{hostname}", getSetting("hostname"));
_mqtt_topic.replace("{magnitude}", "#");
_mqttPlaceholders(&_mqtt_topic);
if (_mqtt_topic.indexOf("#") == -1) _mqtt_topic = _mqtt_topic + "/#";
String mac = WiFi.macAddress();
mac.replace(":", "");
_mqtt_topic.replace("{mac}", mac);
// Getters and setters
_mqtt_setter = getSetting("mqttSetter", MQTT_SETTER);
@ -291,7 +300,7 @@ unsigned long _mqttNextMessageId() {
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);
saveSettings();
eepromCommit();
}
id++;
@ -340,10 +349,10 @@ void _mqttWebSocketOnSend(JsonObject& root) {
void _mqttInitCommands() {
settingsRegisterCommand(F("MQTT.RESET"), [](Embedis* e) {
terminalRegisterCommand(F("MQTT.RESET"), [](Embedis* e) {
_mqttConfigure();
mqttDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
}
@ -387,9 +396,7 @@ void _mqttOnConnect() {
DEBUG_MSG_P(PSTR("[MQTT] Connected!\n"));
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
#if MQTT_SKIP_RETAINED
_mqtt_connected_at = millis();
#endif
_mqtt_last_connection = millis();
// Clean subscriptions
mqttUnsubscribeRaw("#");
@ -403,6 +410,10 @@ void _mqttOnConnect() {
void _mqttOnDisconnect() {
// Reset reconnection delay
_mqtt_last_connection = millis();
_mqtt_connecting = false;
DEBUG_MSG_P(PSTR("[MQTT] Disconnected!\n"));
// Send disconnect event to subscribers
@ -420,7 +431,7 @@ void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
strlcpy(message, (char *) payload, len + 1);
#if MQTT_SKIP_RETAINED
if (millis() - _mqtt_connected_at < MQTT_SKIP_TIME) {
if (millis() - _mqtt_last_connection < MQTT_SKIP_TIME) {
DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s - SKIPPED\n"), topic, message);
return;
}


+ 7
- 3
code/espurna/nofuss.ino View File

@ -74,8 +74,8 @@ void _nofussConfigure() {
void _nofussInitCommands() {
settingsRegisterCommand(F("NOFUSS"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
terminalRegisterCommand(F("NOFUSS"), [](Embedis* e) {
terminalOK();
nofussRun();
});
@ -120,6 +120,9 @@ void nofussSetup() {
#if WEB_SUPPORT
wsSend_P(PSTR("{\"message\": 1}"));
#endif
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
eepromRotate(false);
}
if (code == NOFUSS_FILESYSTEM_UPDATE_ERROR) {
@ -147,7 +150,8 @@ void nofussSetup() {
}
if (code == NOFUSS_END) {
DEBUG_MSG_P(PSTR("[NoFUSS] End\n"));
DEBUG_MSG_P(PSTR("[NoFUSS] End\n"));
eepromRotate(true);
}
});


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

@ -34,7 +34,6 @@ void _ntpWebSocketOnSend(JsonObject& root) {
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
@ -108,7 +107,7 @@ void _ntpLoop() {
static unsigned char last_minute = 60;
if (ntpSynced() && (minute() != last_minute)) {
last_minute = minute();
brokerPublish(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
brokerPublish(BROKER_MSG_TYPE_DATETIME, MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
}
#endif
@ -128,7 +127,11 @@ void _ntpBackwards() {
// -----------------------------------------------------------------------------
bool ntpSynced() {
return (year() > 2017);
#if NTP_WAIT_FOR_SYNC
return (NTP.getLastNTPSync() > 0);
#else
return true;
#endif
}
String ntpDateTime(time_t t) {


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

@ -28,7 +28,7 @@ void _otaLoop() {
// Terminal OTA
// -----------------------------------------------------------------------------
#if TERMINAL_SUPPORT
#if TERMINAL_SUPPORT || OTA_MQTT_SUPPORT
#include <ESPAsyncTCP.h>
AsyncClient * _ota_client;
@ -118,6 +118,7 @@ void _otaFrom(const char * host, unsigned int port, const char * url) {
_ota_size += len;
DEBUG_MSG_P(PSTR("[OTA] Progress: %u bytes\r"), _ota_size);
delay(0);
}, NULL);
@ -158,6 +159,10 @@ void _otaFrom(const char * host, unsigned int port, const char * url) {
}
void _otaFrom(String url) {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n"));
return;
}
// Port from protocol
unsigned int port = 80;
@ -181,13 +186,18 @@ void _otaFrom(String url) {
}
#endif // TERMINAL_SUPPORT || OTA_MQTT_SUPPORT
#if TERMINAL_SUPPORT
void _otaInitCommands() {
settingsRegisterCommand(F("OTA"), [](Embedis* e) {
terminalRegisterCommand(F("OTA"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
} else {
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
String url = String(e->argv[1]);
_otaFrom(url);
}
@ -197,6 +207,25 @@ void _otaInitCommands() {
#endif // TERMINAL_SUPPORT
#if OTA_MQTT_SUPPORT
void _otaMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribe(MQTT_TOPIC_OTA);
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttMagnitude((char *) topic);
if (t.equals(MQTT_TOPIC_OTA)) {
DEBUG_MSG_P(PSTR("[OTA] Initiating from URL: %s\n"), payload);
_otaFrom(payload);
}
}
}
#endif // OTA_MQTT_SUPPORT
// -----------------------------------------------------------------------------
void otaSetup() {
@ -207,6 +236,10 @@ void otaSetup() {
_otaInitCommands();
#endif
#if OTA_MQTT_SUPPORT
mqttRegister(_otaMQTTCallback);
#endif
// Main callbacks
espurnaRegisterLoop(_otaLoop);
espurnaRegisterReload(_otaConfigure);


+ 155
- 115
code/espurna/relay.ino View File

@ -83,37 +83,54 @@ void _relayProviderStatus(unsigned char id, bool status) {
Serial.write(id + 1);
Serial.write(status);
Serial.write(0xA1 + status + id);
// The serial init are not full recognized by relais board.
// References: https://github.com/xoseperez/espurna/issues/1519 , https://github.com/xoseperez/espurna/issues/1130
delay(100);
Serial.flush();
#endif
#if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
// If the number of relays matches the number of light channels
// assume each relay controls one channel.
// If the number of relays is the number of channels plus 1
// assume the first one controls all the channels and
// the rest one channel each.
// Otherwise every relay controls all channels.
// TODO: this won't work with a mixed of dummy and real relays
// but this option is not allowed atm (YANGNI)
if (_relays.size() == lightChannels()) {
lightState(id, status);
lightState(true);
} else if (_relays.size() == (lightChannels() + 1u)) {
if (id == 0) {
lightState(status);
// Real relays
uint8_t physical = _relays.size() - DUMMY_RELAY_COUNT;
// Support for a mixed of dummy and real relays
// Reference: https://github.com/xoseperez/espurna/issues/1305
if (id >= physical) {
// If the number of dummy relays matches the number of light channels
// assume each relay controls one channel.
// If the number of dummy relays is the number of channels plus 1
// assume the first one controls all the channels and
// the rest one channel each.
// Otherwise every dummy relay controls all channels.
if (DUMMY_RELAY_COUNT == lightChannels()) {
lightState(id-physical, status);
lightState(true);
} else if (DUMMY_RELAY_COUNT == (lightChannels() + 1u)) {
if (id == physical) {
lightState(status);
} else {
lightState(id-1-physical, status);
}
} else {
lightState(id-1, status);
lightState(status);
}
} else {
lightState(status);
}
lightUpdate(true, true);
lightUpdate(true, true);
return;
}
#endif
#if RELAY_PROVIDER == RELAY_PROVIDER_RELAY
#if (RELAY_PROVIDER == RELAY_PROVIDER_RELAY) || (RELAY_PROVIDER == RELAY_PROVIDER_LIGHT)
// If this is a light, all dummy relays have already been processed above
// we reach here if the user has toggled a physical relay
if (_relays[id].type == RELAY_TYPE_NORMAL) {
digitalWrite(_relays[id].pin, status);
} else if (_relays[id].type == RELAY_TYPE_INVERSE) {
@ -131,6 +148,7 @@ void _relayProviderStatus(unsigned char id, bool status) {
digitalWrite(_relays[id].pin, !pulse);
if (GPIO_NONE != _relays[id].reset_pin) digitalWrite(_relays[id].reset_pin, !pulse);
}
#endif
}
@ -151,7 +169,7 @@ void _relayProcess(bool mode) {
// Only process the relays we have to change
if (target == _relays[id].current_status) continue;
// Only process the relays we have change to the requested mode
// Only process the relays we have to change to the requested mode
if (target != mode) continue;
// Only process if the change_time has arrived
@ -164,7 +182,7 @@ void _relayProcess(bool mode) {
// Send to Broker
#if BROKER_SUPPORT
brokerPublish(MQTT_TOPIC_RELAY, id, target ? "1" : "0");
brokerPublish(BROKER_MSG_TYPE_STATUS, MQTT_TOPIC_RELAY, id, target ? "1" : "0");
#endif
// Send MQTT
@ -188,24 +206,6 @@ void _relayProcess(bool mode) {
}
#if DOMOTICZ_SUPPORT
domoticzSendRelay(id);
#endif
#if INFLUXDB_SUPPORT
relayInfluxDB(id);
#endif
#if THINGSPEAK_SUPPORT
tspkEnqueueRelay(id, target);
tspkFlush();
#endif
// Flag relay-based LEDs to update status
#if LED_SUPPORT
ledUpdate(true);
#endif
_relays[id].report = false;
_relays[id].group_report = false;
@ -418,7 +418,7 @@ void relaySave(bool do_commit) {
// We are actually enqueuing the commit so it will be
// executed on the main loop, in case this is called from a callback
saveSettings();
eepromCommit();
}
@ -548,7 +548,7 @@ void _relayBoot() {
// Save if there is any relay in the RELAY_BOOT_TOGGLE mode
if (trigger_save) {
EEPROMr.write(EEPROM_RELAY_STATUS, mask);
saveSettings();
eepromCommit();
}
_relayRecursive = false;
@ -557,6 +557,11 @@ void _relayBoot() {
void _relayConfigure() {
for (unsigned int i=0; i<_relays.size(); i++) {
_relays[i].pulse = getSetting("relayPulse", i, RELAY_PULSE_MODE).toInt();
_relays[i].pulse_ms = 1000 * getSetting("relayTime", i, RELAY_PULSE_MODE).toFloat();
if (GPIO_NONE == _relays[i].pin) continue;
pinMode(_relays[i].pin, OUTPUT);
if (GPIO_NONE != _relays[i].reset_pin) {
pinMode(_relays[i].reset_pin, OUTPUT);
@ -565,8 +570,6 @@ void _relayConfigure() {
//set to high to block short opening of relay
digitalWrite(_relays[i].pin, HIGH);
}
_relays[i].pulse = getSetting("relayPulse", i, RELAY_PULSE_MODE).toInt();
_relays[i].pulse_ms = 1000 * getSetting("relayTime", i, RELAY_PULSE_MODE).toFloat();
}
}
@ -583,34 +586,91 @@ bool _relayWebSocketOnReceive(const char * key, JsonVariant& value) {
void _relayWebSocketUpdate(JsonObject& root) {
JsonArray& relay = root.createNestedArray("relayStatus");
for (unsigned char i=0; i<relayCount(); i++) {
relay.add(_relays[i].target_status);
relay.add<uint8_t>(_relays[i].target_status);
}
}
void _relayWebSocketOnStart(JsonObject& root) {
String _relayFriendlyName(unsigned char i) {
String res = String("GPIO") + String(_relays[i].pin);
if (GPIO_NONE == _relays[i].pin) {
#if (RELAY_PROVIDER == RELAY_PROVIDER_LIGHT)
uint8_t physical = _relays.size() - DUMMY_RELAY_COUNT;
if (i >= physical) {
if (DUMMY_RELAY_COUNT == lightChannels()) {
res = String("CH") + String(i-physical);
} else if (DUMMY_RELAY_COUNT == (lightChannels() + 1u)) {
if (physical == i) {
res = String("Light");
} else {
res = String("CH") + String(i-1-physical);
}
} else {
res = String("Light");
}
} else {
res = String("?");
}
#else
res = String("SW") + String(i);
#endif
}
if (relayCount() == 0) return;
return res;
}
// Statuses
_relayWebSocketUpdate(root);
void _relayWebSocketSendRelays() {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
JsonObject& relays = root.createNestedObject("relayConfig");
relays["size"] = relayCount();
relays["start"] = 0;
JsonArray& gpio = relays.createNestedArray("gpio");
JsonArray& type = relays.createNestedArray("type");
JsonArray& reset = relays.createNestedArray("reset");
JsonArray& boot = relays.createNestedArray("boot");
JsonArray& pulse = relays.createNestedArray("pulse");
JsonArray& pulse_time = relays.createNestedArray("pulse_time");
#if MQTT_SUPPORT
JsonArray& group = relays.createNestedArray("group");
JsonArray& group_inverse = relays.createNestedArray("group_inv");
JsonArray& on_disconnect = relays.createNestedArray("on_disc");
#endif
// Configuration
JsonArray& config = root.createNestedArray("relayConfig");
for (unsigned char i=0; i<relayCount(); i++) {
JsonObject& line = config.createNestedObject();
line["gpio"] = _relays[i].pin;
line["type"] = _relays[i].type;
line["reset"] = _relays[i].reset_pin;
line["boot"] = getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt();
line["pulse"] = _relays[i].pulse;
line["pulse_ms"] = _relays[i].pulse_ms / 1000.0;
gpio.add(_relayFriendlyName(i));
type.add(_relays[i].type);
reset.add(_relays[i].reset_pin);
boot.add(getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt());
pulse.add(_relays[i].pulse);
pulse_time.add(_relays[i].pulse_ms / 1000.0);
#if MQTT_SUPPORT
line["group"] = getSetting("mqttGroup", i, "");
line["group_inv"] = getSetting("mqttGroupInv", i, 0).toInt();
line["on_disc"] = getSetting("relayOnDisc", i, 0).toInt();
group.add(getSetting("mqttGroup", i, ""));
group_inverse.add(getSetting("mqttGroupInv", i, 0).toInt() == 1);
on_disconnect.add(getSetting("relayOnDisc", i, 0).toInt());
#endif
}
wsSend(root);
}
void _relayWebSocketOnStart(JsonObject& root) {
if (relayCount() == 0) return;
// Per-relay configuration
_relayWebSocketSendRelays();
// Statuses
_relayWebSocketUpdate(root);
// Options
if (relayCount() > 1) {
root["multirelayVisible"] = 1;
root["relaySync"] = getSetting("relaySync", RELAY_SYNC);
@ -934,20 +994,6 @@ void relaySetupMQTT() {
#endif
//------------------------------------------------------------------------------
// InfluxDB
//------------------------------------------------------------------------------
#if INFLUXDB_SUPPORT
void relayInfluxDB(unsigned char id) {
if (id >= _relays.size()) return;
idbSend(MQTT_TOPIC_RELAY, id, relayStatus(id) ? "1" : "0");
}
#endif
//------------------------------------------------------------------------------
// Settings
//------------------------------------------------------------------------------
@ -956,9 +1002,9 @@ void relayInfluxDB(unsigned char id) {
void _relayInitCommands() {
settingsRegisterCommand(F("RELAY"), [](Embedis* e) {
terminalRegisterCommand(F("RELAY"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
terminalError(F("Wrong arguments"));
return;
}
int id = String(e->argv[1]).toInt();
@ -981,7 +1027,7 @@ void _relayInitCommands() {
DEBUG_MSG_P(PSTR("Pulse time: %d\n"), _relays[id].pulse_ms);
}
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
}
@ -999,44 +1045,38 @@ void _relayLoop() {
void relaySetup() {
// Dummy relays for AI Light, Magic Home LED Controller, H801,
// Sonoff Dual and Sonoff RF Bridge
#if DUMMY_RELAY_COUNT > 0
// No delay_on or off for these devices to easily allow having more than
// 8 channels. This behaviour will be recovered with v2.
for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL, 0, 0, 0});
}
#else
#if RELAY1_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY1_PIN, RELAY1_TYPE, RELAY1_RESET_PIN, RELAY1_DELAY_ON, RELAY1_DELAY_OFF });
#endif
#if RELAY2_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY2_PIN, RELAY2_TYPE, RELAY2_RESET_PIN, RELAY2_DELAY_ON, RELAY2_DELAY_OFF });
#endif
#if RELAY3_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY3_PIN, RELAY3_TYPE, RELAY3_RESET_PIN, RELAY3_DELAY_ON, RELAY3_DELAY_OFF });
#endif
#if RELAY4_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY4_PIN, RELAY4_TYPE, RELAY4_RESET_PIN, RELAY4_DELAY_ON, RELAY4_DELAY_OFF });
#endif
#if RELAY5_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY5_PIN, RELAY5_TYPE, RELAY5_RESET_PIN, RELAY5_DELAY_ON, RELAY5_DELAY_OFF });
#endif
#if RELAY6_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY6_PIN, RELAY6_TYPE, RELAY6_RESET_PIN, RELAY6_DELAY_ON, RELAY6_DELAY_OFF });
#endif
#if RELAY7_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY7_PIN, RELAY7_TYPE, RELAY7_RESET_PIN, RELAY7_DELAY_ON, RELAY7_DELAY_OFF });
#endif
#if RELAY8_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY8_PIN, RELAY8_TYPE, RELAY8_RESET_PIN, RELAY8_DELAY_ON, RELAY8_DELAY_OFF });
#endif
// Ad-hoc relays
#if RELAY1_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY1_PIN, RELAY1_TYPE, RELAY1_RESET_PIN, RELAY1_DELAY_ON, RELAY1_DELAY_OFF });
#endif
#if RELAY2_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY2_PIN, RELAY2_TYPE, RELAY2_RESET_PIN, RELAY2_DELAY_ON, RELAY2_DELAY_OFF });
#endif
#if RELAY3_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY3_PIN, RELAY3_TYPE, RELAY3_RESET_PIN, RELAY3_DELAY_ON, RELAY3_DELAY_OFF });
#endif
#if RELAY4_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY4_PIN, RELAY4_TYPE, RELAY4_RESET_PIN, RELAY4_DELAY_ON, RELAY4_DELAY_OFF });
#endif
#if RELAY5_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY5_PIN, RELAY5_TYPE, RELAY5_RESET_PIN, RELAY5_DELAY_ON, RELAY5_DELAY_OFF });
#endif
#if RELAY6_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY6_PIN, RELAY6_TYPE, RELAY6_RESET_PIN, RELAY6_DELAY_ON, RELAY6_DELAY_OFF });
#endif
#if RELAY7_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY7_PIN, RELAY7_TYPE, RELAY7_RESET_PIN, RELAY7_DELAY_ON, RELAY7_DELAY_OFF });
#endif
#if RELAY8_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY8_PIN, RELAY8_TYPE, RELAY8_RESET_PIN, RELAY8_DELAY_ON, RELAY8_DELAY_OFF });
#endif
// Dummy relays for AI Light, Magic Home LED Controller, H801, Sonoff Dual and Sonoff RF Bridge
// No delay_on or off for these devices to easily allow having more than
// 8 channels. This behaviour will be recovered with v2.
for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
_relays.push_back((relay_t) {GPIO_NONE, RELAY_TYPE_NORMAL, 0, 0, 0});
}
_relayBackwards();
_relayConfigure();


+ 0
- 192
code/espurna/rf.ino View File

@ -1,192 +0,0 @@
/*
RF MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if RF_SUPPORT
#include <RCSwitch.h>
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
// -----------------------------------------------------------------------------
unsigned long _rfRetrieve(unsigned char id, bool status) {
String code = getSetting(status ? "rfbON" : "rfbOFF", id, "0");
return strtoul(code.c_str(), 0, 16);
}
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);
}
void _rfLearn(unsigned char id, bool status) {
_rf_learn_start = millis();
_rf_learn_id = id;
_rf_learn_status = status;
_rf_learn_active = true;
}
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
}
bool _rfMatch(unsigned long code, unsigned char& relayID, unsigned char& value) {
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 _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 _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 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);
// 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
} else {
unsigned char id;
unsigned char value;
if (_rfMatch(rf_code, id, value)) {
if (2 == value) {
relayToggle(id);
} else {
relayStatus(id, 1 == value);
}
}
}
}
}
_rfModem->resetAvailable();
}
if (_rf_learn_active && (millis() - _rf_learn_start > RF_LEARN_TIMEOUT)) {
_rf_learn_active = false;
}
}
void rfSetup() {
_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);
wsOnActionRegister(_rfWebSocketOnAction);
#endif
// Register loop
espurnaRegisterLoop(rfLoop);
}
#endif

+ 260
- 95
code/espurna/rfbridge.ino View File

@ -1,17 +1,17 @@
/*
ITEAD RF BRIDGE MODULE
RF MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#ifdef ITEAD_SONOFF_RFBRIDGE
#if defined(ITEAD_SONOFF_RFBRIDGE) || RF_SUPPORT
#include <queue>
#include <Ticker.h>
#if RFB_DIRECT
#if RFB_DIRECT || RF_SUPPORT
#include <RCSwitch.h>
#endif
@ -19,6 +19,8 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// DEFINITIONS
// -----------------------------------------------------------------------------
// EFM8 Protocol
#define RF_MESSAGE_SIZE 9
#define RF_MAX_MESSAGE_SIZE (112+4)
#define RF_CODE_START 0xAA
@ -37,6 +39,10 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
#define RF_CODE_RFOUT_BUCKET 0xB0
#define RF_CODE_STOP 0x55
// Settings
#define RF_MAX_KEY_LENGTH (9)
// -----------------------------------------------------------------------------
// GLOBALS TO THE MODULE
// -----------------------------------------------------------------------------
@ -47,6 +53,7 @@ unsigned char _learnId = 0;
bool _learnStatus = true;
bool _rfbin = false;
#if not RF_SUPPORT
typedef struct {
byte code[RF_MESSAGE_SIZE];
byte times;
@ -54,31 +61,21 @@ typedef struct {
static std::queue<rfb_message_t> _rfb_message_queue;
Ticker _rfb_ticker;
bool _rfb_ticker_active = false;
#endif
#if RFB_DIRECT
#if RFB_DIRECT || RF_SUPPORT
RCSwitch * _rfModem;
bool _learning = false;
#endif
#if WEB_SUPPORT
Ticker _rfb_sendcodes;
#endif
// -----------------------------------------------------------------------------
// PRIVATES
// -----------------------------------------------------------------------------
/*
From an hexa char array ("A220EE...") to a byte array (half the size)
*/
static int _rfbToArray(const char * in, byte * out, int length = RF_MESSAGE_SIZE * 2) {
int n = strlen(in);
if (n > RF_MAX_MESSAGE_SIZE*2 || (length > 0 && n != length)) return 0;
char tmp[3] = {0,0,0};
n /= 2;
for (unsigned char p = 0; p<n; p++) {
memcpy(tmp, &in[p*2], 2);
out[p] = strtol(tmp, NULL, 16);
}
return n;
}
/*
From a byte array to an hexa char array ("A220EE...", double the size)
*/
@ -89,6 +86,34 @@ static bool _rfbToChar(byte * in, char * out, int n = RF_MESSAGE_SIZE) {
return true;
}
#if WEB_SUPPORT
void _rfbWebSocketSendCodeArray(unsigned char start, unsigned char size) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
JsonObject& rfb = root.createNestedObject("rfb");
rfb["size"] = size;
rfb["start"] = start;
JsonArray& on = rfb.createNestedArray("on");
JsonArray& off = rfb.createNestedArray("off");
for (byte id=start; id<start+size; id++) {
on.add(rfbRetrieve(id, true));
off.add(rfbRetrieve(id, false));
}
wsSend(rfb);
}
void _rfbWebSocketSendCode(unsigned char id) {
_rfbWebSocketSendCodeArray(id, 1);
}
void _rfbWebSocketSendCodes() {
_rfbWebSocketSendCodeArray(0, relayCount());
}
void _rfbWebSocketOnSend(JsonObject& root) {
root["rfbVisible"] = 1;
@ -96,15 +121,7 @@ void _rfbWebSocketOnSend(JsonObject& root) {
#if RF_RAW_SUPPORT
root["rfbrawVisible"] = 1;
#endif
JsonArray& rfb = root.createNestedArray("rfb");
for (byte id=0; id<relayCount(); id++) {
for (byte status=0; status<2; status++) {
JsonObject& node = rfb.createNestedObject();
node["id"] = id;
node["status"] = status;
node["data"] = rfbRetrieve(id, status == 1);
}
}
_rfb_sendcodes.once_ms(1000, _rfbWebSocketSendCodes);
}
void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
@ -113,9 +130,11 @@ void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject&
if (strcmp(action, "rfbsend") == 0) rfbStore(data["id"], data["status"], data["data"].as<const char*>());
}
#endif // WEB_SUPPORT
void _rfbAck() {
#if not RFB_DIRECT
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n"));
#if (not RFB_DIRECT) && (not RF_SUPPORT)
DEBUG_MSG_P(PSTR("[RF] Sending ACK\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_ACK);
@ -126,11 +145,11 @@ void _rfbAck() {
}
void _rfbLearn() {
#if RFB_DIRECT
DEBUG_MSG_P(PSTR("[RFBRIDGE] Entering LEARN mode\n"));
#if RFB_DIRECT || RF_SUPPORT
DEBUG_MSG_P(PSTR("[RF] Entering LEARN mode\n"));
_learning = true;
#else
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n"));
DEBUG_MSG_P(PSTR("[RF] Sending LEARN\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_LEARN);
@ -147,6 +166,23 @@ void _rfbLearn() {
}
/*
From an hexa char array ("A220EE...") to a byte array (half the size)
*/
static int _rfbToArray(const char * in, byte * out, int length = RF_MESSAGE_SIZE * 2) {
int n = strlen(in);
if (n > RF_MAX_MESSAGE_SIZE*2 || (length > 0 && n != length)) return 0;
char tmp[3] = {0,0,0};
n /= 2;
for (unsigned char p = 0; p<n; p++) {
memcpy(tmp, &in[p*2], 2);
out[p] = strtol(tmp, NULL, 16);
}
return n;
}
#if not RF_SUPPORT
void _rfbSendRaw(const byte *message, const unsigned char n = RF_MESSAGE_SIZE) {
for (unsigned char j=0; j<n; j++) {
Serial.write(message[j]);
@ -215,7 +251,7 @@ void _rfbSend(byte * code, unsigned char times) {
char buffer[RF_MESSAGE_SIZE];
_rfbToChar(code, buffer);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Enqueuing MESSAGE '%s' %d time(s)\n"), buffer, times);
DEBUG_MSG_P(PSTR("[RF] Enqueuing MESSAGE '%s' %d time(s)\n"), buffer, times);
rfb_message_t message;
memcpy(message.code, code, RF_MESSAGE_SIZE);
@ -230,12 +266,14 @@ void _rfbSend(byte * code, unsigned char times) {
}
#endif // not RF_SUPPORT
#if RF_RAW_SUPPORT
void _rfbSendRawOnce(byte *code, unsigned char length) {
char buffer[length*2];
_rfbToChar(code, buffer, length);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending RAW MESSAGE '%s'\n"), buffer);
DEBUG_MSG_P(PSTR("[RF] Sending RAW MESSAGE '%s'\n"), buffer);
_rfbSendRaw(code, length);
}
@ -248,13 +286,13 @@ bool _rfbMatch(char* code, unsigned char& relayID, unsigned char& value, char* b
bool found = false;
String compareto = String(&code[12]);
compareto.toUpperCase();
DEBUG_MSG_P(PSTR("[RFBRIDGE] Trying to match code %s\n"), compareto.c_str());
DEBUG_MSG_P(PSTR("[RF] Trying to match code %s\n"), compareto.c_str());
for (unsigned char i=0; i<relayCount(); i++) {
String code_on = rfbRetrieve(i, true);
if (code_on.length() && code_on.endsWith(compareto)) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Match ON code for relay %d\n"), i);
DEBUG_MSG_P(PSTR("[RF] Match ON code for relay %d\n"), i);
value = 1;
found = true;
if (buffer) strcpy(buffer, code_on.c_str());
@ -262,7 +300,7 @@ bool _rfbMatch(char* code, unsigned char& relayID, unsigned char& value, char* b
String code_off = rfbRetrieve(i, false);
if (code_off.length() && code_off.endsWith(compareto)) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Match OFF code for relay %d\n"), i);
DEBUG_MSG_P(PSTR("[RF] Match OFF code for relay %d\n"), i);
if (found) value = 2;
found = true;
if (buffer) strcpy(buffer, code_off.c_str());
@ -287,11 +325,11 @@ void _rfbDecode() {
byte action = _uartbuf[0];
char buffer[RF_MESSAGE_SIZE * 2 + 1] = {0};
DEBUG_MSG_P(PSTR("[RFBRIDGE] Action 0x%02X\n"), action);
DEBUG_MSG_P(PSTR("[RF] Action 0x%02X\n"), action);
if (action == RF_CODE_LEARN_KO) {
_rfbAck();
DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn timeout\n"));
DEBUG_MSG_P(PSTR("[RF] Learn timeout\n"));
#if WEB_SUPPORT
wsSend_P(PSTR("{\"action\": \"rfbTimeout\"}"));
#endif
@ -302,20 +340,18 @@ void _rfbDecode() {
_rfbAck();
_rfbToChar(&_uartbuf[1], buffer);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Received message '%s'\n"), buffer);
DEBUG_MSG_P(PSTR("[RF] Received message '%s'\n"), buffer);
}
if (action == RF_CODE_LEARN_OK) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn success\n"));
DEBUG_MSG_P(PSTR("[RF] Learn success\n"));
rfbStore(_learnId, _learnStatus, buffer);
// Websocket update
#if WEB_SUPPORT
char wsb[100];
snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%s\"}]}"), _learnId, _learnStatus ? 1 : 0, buffer);
wsSend(wsb);
_rfbWebSocketSendCode(_learnId);
#endif
}
@ -331,7 +367,7 @@ void _rfbDecode() {
bool matched = _rfbMatch(buffer, id, status, buffer);
if (matched) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Matched message '%s'\n"), buffer);
DEBUG_MSG_P(PSTR("[RF] Matched message '%s'\n"), buffer);
_rfbin = true;
if (status == 2) {
relayToggle(id);
@ -349,18 +385,18 @@ void _rfbDecode() {
}
void _rfbReceive() {
#if RFB_DIRECT
#if RFB_DIRECT || RF_SUPPORT
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"));
DEBUG_MSG_P(PSTR("[RF] 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"));
DEBUG_MSG_P(PSTR("[RF] Learn timeout triggered\n"));
memset(_uartbuf, 0, sizeof(_uartbuf));
_uartbuf[0] = RF_CODE_LEARN_KO;
_rfbDecode();
@ -374,7 +410,7 @@ void _rfbReceive() {
last = millis();
unsigned long rf_code = _rfModem->getReceivedValue();
if ( rf_code > 0) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Received code: %08X\n"), rf_code);
DEBUG_MSG_P(PSTR("[RF] Received code: %08X\n"), rf_code);
unsigned int timing = _rfModem->getReceivedDelay();
memset(_uartbuf, 0, sizeof(_uartbuf));
unsigned char *msgbuf = _uartbuf + 1;
@ -401,7 +437,7 @@ void _rfbReceive() {
yield();
byte c = Serial.read();
//DEBUG_MSG_P(PSTR("[RFBRIDGE] Received 0x%02X\n"), c);
//DEBUG_MSG_P(PSTR("[RF] Received 0x%02X\n"), c);
if (receiving) {
if (c == RF_CODE_STOP && (_uartpos == 1 || _uartpos == RF_MESSAGE_SIZE + 1)) {
@ -430,14 +466,59 @@ bool _rfbSameOnOff(unsigned char id) {
return _rfbCompare(rfbRetrieve(id, true).c_str(), rfbRetrieve(id, false).c_str());
}
void _rfbParseCode(char * code) {
// The payload may be a code in HEX format ([0-9A-Z]{18}) or
// the code comma the number of times to transmit it.
char * tok = strtok(code, ",");
// Check if a switch is linked to that message
unsigned char id;
unsigned char status = 0;
if (_rfbMatch(tok, id, status)) {
if (status == 2) {
relayToggle(id);
} else {
relayStatus(id, status == 1);
}
return;
}
#if RF_RAW_SUPPORT
byte message[RF_MAX_MESSAGE_SIZE];
int len = _rfbToArray(tok, message, 0);
if ((len > 0) && (isRFRaw || len != RF_MESSAGE_SIZE)) {
_rfbSendRawOnce(message, len);
} else {
tok = strtok(NULL, ",");
byte times = (tok != NULL) ? atoi(tok) : 1;
_rfbSend(message, times);
}
#else // RF_RAW_SUPPORT
byte message[RF_MESSAGE_SIZE];
if (_rfbToArray(tok, message)) {
tok = strtok(NULL, ",");
byte times = (tok != NULL) ? atoi(tok) : 1;
_rfbSend(message, times);
}
#endif // RF_RAW_SUPPORT
}
#if MQTT_SUPPORT
void _rfbMqttCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
char buffer[strlen(MQTT_TOPIC_RFLEARN) + 3];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RFLEARN);
mqttSubscribe(buffer);
#if not RF_SUPPORT
mqttSubscribe(MQTT_TOPIC_RFOUT);
#endif
#if RF_RAW_SUPPORT
mqttSubscribe(MQTT_TOPIC_RFRAW);
#endif
@ -453,7 +534,7 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
_learnId = t.substring(strlen(MQTT_TOPIC_RFLEARN)+1).toInt();
if (_learnId >= relayCount()) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Wrong learnID (%d)\n"), _learnId);
DEBUG_MSG_P(PSTR("[RF] Wrong learnID (%d)\n"), _learnId);
return;
}
_learnStatus = (char)payload[0] != '0';
@ -462,78 +543,148 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
}
bool isRFOut = t.equals(MQTT_TOPIC_RFOUT);
#if not RF_SUPPORT
bool isRFOut = t.equals(MQTT_TOPIC_RFOUT);
#endif
#if RF_RAW_SUPPORT
bool isRFRaw = !isRFOut && t.equals(MQTT_TOPIC_RFRAW);
#else
#elif not RF_SUPPORT
bool isRFRaw = false;
#endif
if (isRFOut || isRFRaw) {
#if not RF_SUPPORT
if (isRFOut || isRFRaw) {
_rfbParseCode((char *) payload);
}
#endif // not RF_SUPPORT
// The payload may be a code in HEX format ([0-9A-Z]{18}) or
// the code comma the number of times to transmit it.
char * tok = strtok((char *) payload, ",");
}
// Check if a switch is linked to that message
unsigned char id;
unsigned char status = 0;
if (_rfbMatch(tok, id, status)) {
if (status == 2) {
relayToggle(id);
} else {
relayStatus(id, status == 1);
}
}
#endif // MQTT_SUPPORT
#if API_SUPPORT
void _rfbAPISetup() {
#if not RF_SUPPORT
apiRegister(MQTT_TOPIC_RFOUT,
[](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("OK"));
},
[](const char * payload) {
_rfbParseCode((char *) payload);
}
);
#endif // RF_SUPPORT
apiRegister(MQTT_TOPIC_RFLEARN,
[](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("OK"));
},
[](const char * payload) {
// The payload must be the relayID plus the mode (0 or 1)
char * tok = strtok((char *) payload, ",");
if (NULL == tok) return;
if (!isNumber(tok)) return;
_learnId = atoi(tok);
if (_learnId >= relayCount()) {
DEBUG_MSG_P(PSTR("[RF] Wrong learnID (%d)\n"), _learnId);
return;
}
tok = strtok(NULL, ",");
if (NULL == tok) return;
_learnStatus = (char) tok[0] != '0';
_rfbLearn();
}
);
#if RF_RAW_SUPPORT
#if RF_RAW_SUPPORT
apiRegister(MQTT_TOPIC_RFRAW,
[](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("OK"));
},
[](const char * payload) {
_rfbParseCode(payload);
}
);
#endif // RF_RAW_SUPPORT
byte message[RF_MAX_MESSAGE_SIZE];
int len = _rfbToArray(tok, message, 0);
if ((len > 0) && (isRFRaw || len != RF_MESSAGE_SIZE)) {
_rfbSendRawOnce(message, len);
} else {
tok = strtok(NULL, ",");
byte times = (tok != NULL) ? atoi(tok) : 1;
_rfbSend(message, times);
}
}
#else // RF_RAW_SUPPORT
#endif // API_SUPPORT
byte message[RF_MESSAGE_SIZE];
if (_rfbToArray(tok, message)) {
tok = strtok(NULL, ",");
byte times = (tok != NULL) ? atoi(tok) : 1;
_rfbSend(message, times);
}
#if TERMINAL_SUPPORT
void _rfbInitCommands() {
#endif // RF_RAW_SUPPORT
terminalRegisterCommand(F("LEARN"), [](Embedis* e) {
if (e->argc < 3) {
terminalError(F("Wrong arguments"));
return;
}
int id = String(e->argv[1]).toInt();
if (id >= relayCount()) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong relayID (%d)\n"), id);
return;
}
}
int status = String(e->argv[2]).toInt();
rfbLearn(id, status == 1);
terminalOK();
});
terminalRegisterCommand(F("FORGET"), [](Embedis* e) {
if (e->argc < 3) {
terminalError(F("Wrong arguments"));
return;
}
int id = String(e->argv[1]).toInt();
if (id >= relayCount()) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong relayID (%d)\n"), id);
return;
}
int status = String(e->argv[2]).toInt();
rfbForget(id, status == 1);
terminalOK();
});
}
#endif
#endif // TERMINAL_SUPPORT
// -----------------------------------------------------------------------------
// PUBLIC
// -----------------------------------------------------------------------------
void rfbStore(unsigned char id, bool status, const char * code) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Storing %d-%s => '%s'\n"), id, status ? "ON" : "OFF", code);
char key[8] = {0};
DEBUG_MSG_P(PSTR("[RF] Storing %d-%s => '%s'\n"), id, status ? "ON" : "OFF", code);
char key[RF_MAX_KEY_LENGTH] = {0};
snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
setSetting(key, code);
}
String rfbRetrieve(unsigned char id, bool status) {
char key[8] = {0};
char key[RF_MAX_KEY_LENGTH] = {0};
snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
return getSetting(key);
}
#if not RF_SUPPORT
void rfbStatus(unsigned char id, bool status) {
String value = rfbRetrieve(id, status);
@ -577,6 +728,7 @@ void rfbStatus(unsigned char id, bool status) {
_rfbin = false;
}
#endif // not RF_SUPPORT
void rfbLearn(unsigned char id, bool status) {
_learnId = id;
@ -586,7 +738,7 @@ void rfbLearn(unsigned char id, bool status) {
void rfbForget(unsigned char id, bool status) {
char key[8] = {0};
char key[RF_MAX_KEY_LENGTH] = {0};
snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
delSetting(key);
@ -609,18 +761,31 @@ void rfbSetup() {
mqttRegister(_rfbMqttCallback);
#endif
#if API_SUPPORT
_rfbAPISetup();
#endif
#if WEB_SUPPORT
wsOnSendRegister(_rfbWebSocketOnSend);
wsOnActionRegister(_rfbWebSocketOnAction);
#endif
#if RFB_DIRECT
#if TERMINAL_SUPPORT
_rfbInitCommands();
#endif
#if RFB_DIRECT || RF_SUPPORT
_rfModem = new RCSwitch();
#if RF_SUPPORT
_rfModem->enableReceive(RF_PIN);
DEBUG_MSG_P(PSTR("[RF] RF receiver on GPIO %u\n"), RF_PIN);
#else
_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);
DEBUG_MSG_P(PSTR("[RF] RF receiver on GPIO %u\n"), RFB_RX_PIN);
DEBUG_MSG_P(PSTR("[RF] RF transmitter on GPIO %u\n"), RFB_TX_PIN);
#endif
#endif
// Register loop


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

@ -118,9 +118,8 @@ void _rfm69Process(packet_t * data) {
// Is node beyond RFM69_MAX_NODES?
if (data->senderID >= RFM69_MAX_NODES) return;
// Count seen nodes and packets
// Count seen nodes
if (_rfm69_node_info[data->senderID].count == 0) ++_rfm69_node_count;
++_rfm69_packet_count;
// Detect duplicates and missing packets
// packetID==0 means device is not sending packetID info


+ 33
- 18
code/espurna/scheduler.ino View File

@ -21,26 +21,41 @@ bool _schWebSocketOnReceive(const char * key, JsonVariant& value) {
void _schWebSocketOnSend(JsonObject &root){
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, "");
}
if (!relayCount()) return;
root["schVisible"] = 1;
root["maxSchedules"] = SCHEDULER_MAX_SCHEDULES;
JsonObject &schedules = root.createNestedObject("schedules");
uint8_t size = 0;
JsonArray& enabled = schedules.createNestedArray("schEnabled");
JsonArray& switch_ = schedules.createNestedArray("schSwitch");
JsonArray& action = schedules.createNestedArray("schAction");
JsonArray& type = schedules.createNestedArray("schType");
JsonArray& hour = schedules.createNestedArray("schHour");
JsonArray& minute = schedules.createNestedArray("schMinute");
JsonArray& utc = schedules.createNestedArray("schUTC");
JsonArray& weekdays = schedules.createNestedArray("schWDs");
for (byte i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
if (!hasSetting("schSwitch", i)) break;
++size;
enabled.add<uint8_t>(getSetting("schEnabled", i, 1).toInt() == 1);
utc.add<uint8_t>(getSetting("schUTC", i, 0).toInt() == 1);
switch_.add(getSetting("schSwitch", i, 0).toInt());
action.add(getSetting("schAction", i, 0).toInt());
type.add(getSetting("schType", i, 0).toInt());
hour.add(getSetting("schHour", i, 0).toInt());
minute.add(getSetting("schMinute", i, 0).toInt());
weekdays.add(getSetting("schWDs", i, ""));
}
schedules["size"] = size;
schedules["start"] = 0;
}
#endif // WEB_SUPPORT


+ 236
- 41
code/espurna/sensor.ino View File

@ -42,6 +42,10 @@ unsigned char _sensor_temperature_units = SENSOR_TEMPERATURE_UNITS;
double _sensor_temperature_correction = SENSOR_TEMPERATURE_CORRECTION;
double _sensor_humidity_correction = SENSOR_HUMIDITY_CORRECTION;
#if PZEM004T_SUPPORT
PZEM004TSensor *pzem004t_sensor;
#endif
String _sensor_energy_reset_ts = String();
// -----------------------------------------------------------------------------
@ -98,6 +102,32 @@ double _magnitudeProcess(unsigned char type, double value) {
#if WEB_SUPPORT
template<typename T>
void _sensorWebSocketMagnitudes(JsonObject& root, T prefix) {
// ws produces flat list <prefix>Magnitudes
String ws_name = String(prefix);
ws_name.concat("Magnitudes");
// config uses <prefix>Magnitude<index> (cut 's')
String conf_name = ws_name.substring(0, ws_name.length() - 1);
JsonObject& list = root.createNestedObject(ws_name);
list["size"] = magnitudeCount();
JsonArray& name = list.createNestedArray("name");
JsonArray& type = list.createNestedArray("type");
JsonArray& index = list.createNestedArray("index");
JsonArray& idx = list.createNestedArray("idx");
for (unsigned char i=0; i<magnitudeCount(); ++i) {
name.add(magnitudeName(i));
type.add(magnitudeType(i));
index.add(magnitudeIndex(i));
idx.add(getSetting(conf_name, i, 0).toInt());
}
}
bool _sensorWebSocketOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, "pwr", 3) == 0) return true;
if (strncmp(key, "sns", 3) == 0) return true;
@ -114,27 +144,36 @@ void _sensorWebSocketSendData(JsonObject& root) {
bool hasHumidity = false;
bool hasMICS = false;
JsonArray& list = root.createNestedArray("magnitudes");
for (unsigned char i=0; i<_magnitudes.size(); i++) {
JsonObject& magnitudes = root.createNestedObject("magnitudes");
uint8_t size = 0;
JsonArray& index = magnitudes.createNestedArray("index");
JsonArray& type = magnitudes.createNestedArray("type");
JsonArray& value = magnitudes.createNestedArray("value");
JsonArray& units = magnitudes.createNestedArray("units");
JsonArray& error = magnitudes.createNestedArray("error");
JsonArray& description = magnitudes.createNestedArray("description");
for (unsigned char i=0; i<magnitudeCount(); i++) {
sensor_magnitude_t magnitude = _magnitudes[i];
if (magnitude.type == MAGNITUDE_EVENT) continue;
++size;
unsigned char decimals = _magnitudeDecimals(magnitude.type);
dtostrf(magnitude.current, 1-sizeof(buffer), decimals, buffer);
JsonObject& element = list.createNestedObject();
element["index"] = int(magnitude.global);
element["type"] = int(magnitude.type);
element["value"] = String(buffer);
element["units"] = magnitudeUnits(magnitude.type);
element["error"] = magnitude.sensor->error();
index.add<uint8_t>(magnitude.global);
type.add<uint8_t>(magnitude.type);
value.add(buffer);
units.add(magnitudeUnits(magnitude.type));
error.add(magnitude.sensor->error());
if (magnitude.type == MAGNITUDE_ENERGY) {
if (_sensor_energy_reset_ts.length() == 0) _sensorResetTS();
element["description"] = magnitude.sensor->slot(magnitude.local) + String(" (since ") + _sensor_energy_reset_ts + String(")");
description.add(magnitude.sensor->slot(magnitude.local) + String(" (since ") + _sensor_energy_reset_ts + String(")"));
} else {
element["description"] = magnitude.sensor->slot(magnitude.local);
description.add(magnitude.sensor->slot(magnitude.local));
}
if (magnitude.type == MAGNITUDE_TEMPERATURE) hasTemperature = true;
@ -144,6 +183,8 @@ void _sensorWebSocketSendData(JsonObject& root) {
#endif
}
magnitudes["size"] = size;
if (hasTemperature) root["temperatureVisible"] = 1;
if (hasHumidity) root["humidityVisible"] = 1;
if (hasMICS) root["micsVisible"] = 1;
@ -197,9 +238,16 @@ void _sensorWebSocketStart(JsonObject& root) {
}
#endif
#if PULSEMETER_SUPPORT
if (sensor->getID() == SENSOR_PULSEMETER_ID) {
root["pmVisible"] = 1;
root["pwrRatioE"] = ((PulseMeterSensor *) sensor)->getEnergyRatio();
}
#endif
}
if (_magnitudes.size() > 0) {
if (magnitudeCount()) {
root["snsVisible"] = 1;
//root["apiRealTime"] = _sensor_realtime;
root["pwrUnits"] = _sensor_power_units;
@ -260,7 +308,7 @@ void _sensorAPISetup() {
#if TERMINAL_SUPPORT
void _sensorInitCommands() {
settingsRegisterCommand(F("MAGNITUDES"), [](Embedis* e) {
terminalRegisterCommand(F("MAGNITUDES"), [](Embedis* e) {
for (unsigned char i=0; i<_magnitudes.size(); i++) {
sensor_magnitude_t magnitude = _magnitudes[i];
DEBUG_MSG_P(PSTR("[SENSOR] * %2d: %s @ %s (%s/%d)\n"),
@ -271,8 +319,65 @@ void _sensorInitCommands() {
magnitude.global
);
}
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
#if PZEM004T_SUPPORT
terminalRegisterCommand(F("PZ.ADDRESS"), [](Embedis* e) {
if (e->argc == 1) {
DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
unsigned char dev_count = pzem004t_sensor->getAddressesCount();
for(unsigned char dev = 0; dev < dev_count; dev++) {
DEBUG_MSG_P(PSTR("Device %d/%s\n"), dev, pzem004t_sensor->getAddress(dev).c_str());
}
terminalOK();
} else if(e->argc == 2) {
IPAddress addr;
if (addr.fromString(String(e->argv[1]))) {
if(pzem004t_sensor->setDeviceAddress(&addr)) {
terminalOK();
}
} else {
terminalError(F("Invalid address argument"));
}
} else {
terminalError(F("Wrong arguments"));
}
});
terminalRegisterCommand(F("PZ.RESET"), [](Embedis* e) {
if(e->argc > 2) {
terminalError(F("Wrong arguments"));
} else {
unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0;
unsigned char limit = e->argc == 2 ? init +1 : pzem004t_sensor->getAddressesCount();
DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
for(unsigned char dev = init; dev < limit; dev++) {
float offset = pzem004t_sensor->resetEnergy(dev);
setSetting("pzEneTotal", dev, offset);
DEBUG_MSG_P(PSTR("Device %d/%s - Offset: %s\n"), dev, pzem004t_sensor->getAddress(dev).c_str(), String(offset).c_str());
}
terminalOK();
}
});
terminalRegisterCommand(F("PZ.VALUE"), [](Embedis* e) {
if(e->argc > 2) {
terminalError(F("Wrong arguments"));
} else {
unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0;
unsigned char limit = e->argc == 2 ? init +1 : pzem004t_sensor->getAddressesCount();
DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
for(unsigned char dev = init; dev < limit; dev++) {
DEBUG_MSG_P(PSTR("Device %d/%s - Current: %s Voltage: %s Power: %s Energy: %s\n"), //
dev,
pzem004t_sensor->getAddress(dev).c_str(),
String(pzem004t_sensor->value(dev * PZ_MAGNITUDE_CURRENT_INDEX)).c_str(),
String(pzem004t_sensor->value(dev * PZ_MAGNITUDE_VOLTAGE_INDEX)).c_str(),
String(pzem004t_sensor->value(dev * PZ_MAGNITUDE_POWER_ACTIVE_INDEX)).c_str(),
String(pzem004t_sensor->value(dev * PZ_MAGNITUDE_ENERGY_INDEX)).c_str());
}
terminalOK();
}
});
#endif
}
#endif
@ -364,6 +469,14 @@ void _sensorLoad() {
}
#endif
#if BMP180_SUPPORT
{
BMP180Sensor * sensor = new BMP180Sensor();
sensor->setAddress(BMP180_ADDRESS);
_sensors.push_back(sensor);
}
#endif
#if BMX280_SUPPORT
{
BMX280Sensor * sensor = new BMX280Sensor();
@ -563,24 +676,6 @@ void _sensorLoad() {
}
#endif
#if SENSEAIR_SUPPORT
{
SenseAirSensor * sensor = new SenseAirSensor();
sensor->setRX(SENSEAIR_RX_PIN);
sensor->setTX(SENSEAIR_TX_PIN);
_sensors.push_back(sensor);
}
#endif
#if SDS011_SUPPORT
{
SDS011Sensor * sensor = new SDS011Sensor();
sensor->setRX(SDS011_RX_PIN);
sensor->setTX(SDS011_TX_PIN);
_sensors.push_back(sensor);
}
#endif
#if PMSX003_SUPPORT
{
PMSX003Sensor * sensor = new PMSX003Sensor();
@ -595,15 +690,50 @@ void _sensorLoad() {
}
#endif
#if PULSEMETER_SUPPORT
{
PulseMeterSensor * sensor = new PulseMeterSensor();
sensor->setGPIO(PULSEMETER_PIN);
sensor->setEnergyRatio(PULSEMETER_ENERGY_RATIO);
sensor->setDebounceTime(PULSEMETER_DEBOUNCE);
_sensors.push_back(sensor);
}
#endif
#if PZEM004T_SUPPORT
{
PZEM004TSensor * sensor = new PZEM004TSensor();
PZEM004TSensor * sensor = pzem004t_sensor = new PZEM004TSensor();
#if PZEM004T_USE_SOFT
sensor->setRX(PZEM004T_RX_PIN);
sensor->setTX(PZEM004T_TX_PIN);
#else
sensor->setSerial(& PZEM004T_HW_PORT);
#endif
sensor->setAddresses(PZEM004T_ADDRESSES);
// Read saved energy offset
unsigned char dev_count = sensor->getAddressesCount();
for(unsigned char dev = 0; dev < dev_count; dev++) {
float value = getSetting("pzEneTotal", dev, 0).toFloat();
if (value > 0) sensor->resetEnergy(dev, value);
}
_sensors.push_back(sensor);
}
#endif
#if SENSEAIR_SUPPORT
{
SenseAirSensor * sensor = new SenseAirSensor();
sensor->setRX(SENSEAIR_RX_PIN);
sensor->setTX(SENSEAIR_TX_PIN);
_sensors.push_back(sensor);
}
#endif
#if SDS011_SUPPORT
{
SDS011Sensor * sensor = new SDS011Sensor();
sensor->setRX(SDS011_RX_PIN);
sensor->setTX(SDS011_TX_PIN);
_sensors.push_back(sensor);
}
#endif
@ -641,6 +771,43 @@ void _sensorLoad() {
}
#endif
#if MAX6675_SUPPORT
{
MAX6675Sensor * sensor = new MAX6675Sensor();
sensor->setCS(MAX6675_CS_PIN);
sensor->setSO(MAX6675_SO_PIN);
sensor->setSCK(MAX6675_SCK_PIN);
_sensors.push_back(sensor);
}
#endif
#if VEML6075_SUPPORT
{
VEML6075Sensor * sensor = new VEML6075Sensor();
sensor->setIntegrationTime(VEML6075_INTEGRATION_TIME);
sensor->setDynamicMode(VEML6075_DYNAMIC_MODE);
_sensors.push_back(sensor);
}
#endif
#if VL53L1X_SUPPORT
{
VL53L1XSensor * sensor = new VL53L1XSensor();
sensor->setInterMeasurementPeriod(VL53L1X_INTER_MEASUREMENT_PERIOD);
sensor->setDistanceMode(VL53L1X_DISTANCE_MODE);
sensor->setMeasurementTimingBudget(VL53L1X_MEASUREMENT_TIMING_BUDGET);
_sensors.push_back(sensor);
}
#endif
#if EZOPH_SUPPORT
{
EZOPHSensor * sensor = new EZOPHSensor();
sensor->setRX(EZOPH_RX_PIN);
sensor->setTX(EZOPH_TX_PIN);
_sensors.push_back(sensor);
}
#endif
}
void _sensorCallback(unsigned char i, unsigned char type, double value) {
@ -799,6 +966,13 @@ void _sensorInit() {
#endif // CSE7766_SUPPORT
#if PULSEMETER_SUPPORT
if (_sensors[i]->getID() == SENSOR_PULSEMETER_ID) {
PulseMeterSensor * sensor = (PulseMeterSensor *) _sensors[i];
sensor->setEnergyRatio(getSetting("pwrRatioE", PULSEMETER_ENERGY_RATIO).toInt());
}
#endif // PULSEMETER_SUPPORT
}
}
@ -974,6 +1148,35 @@ void _sensorConfigure() {
#endif // CSE7766_SUPPORT
#if PULSEMETER_SUPPORT
if (_sensors[i]->getID() == SENSOR_PULSEMETER_ID) {
PulseMeterSensor * sensor = (PulseMeterSensor *) _sensors[i];
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
delSetting("eneTotal");
_sensorResetTS();
}
sensor->setEnergyRatio(getSetting("pwrRatioE", PULSEMETER_ENERGY_RATIO).toInt());
}
#endif // PULSEMETER_SUPPORT
#if PZEM004T_SUPPORT
if (_sensors[i]->getID() == SENSOR_PZEM004T_ID) {
PZEM004TSensor * sensor = (PZEM004TSensor *) _sensors[i];
if (getSetting("pwrResetE", 0).toInt() == 1) {
unsigned char dev_count = sensor->getAddressesCount();
for(unsigned char dev = 0; dev < dev_count; dev++) {
sensor->resetEnergy(dev, 0);
delSetting("pzEneTotal", dev);
}
_sensorResetTS();
}
}
#endif // PZEM004T_SUPPORT
}
// Update filter sizes
@ -1006,7 +1209,7 @@ void _sensorReport(unsigned char index, double value) {
dtostrf(value, 1-sizeof(buffer), decimals, buffer);
#if BROKER_SUPPORT
brokerPublish(magnitudeTopic(magnitude.type).c_str(), magnitude.local, buffer);
brokerPublish(BROKER_MSG_TYPE_SENSOR ,magnitudeTopic(magnitude.type).c_str(), magnitude.local, buffer);
#endif
#if MQTT_SUPPORT
@ -1025,14 +1228,6 @@ void _sensorReport(unsigned char index, double value) {
#endif // MQTT_SUPPORT
#if INFLUXDB_SUPPORT
if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) {
idbSend(magnitudeTopic(magnitude.type).c_str(), magnitude.global, buffer);
} else {
idbSend(magnitudeTopic(magnitude.type).c_str(), buffer);
}
#endif // INFLUXDB_SUPPORT
#if THINGSPEAK_SUPPORT
tspkEnqueueMeasurement(index, buffer);
#endif


+ 253
- 0
code/espurna/sensors/BMP180Sensor.h View File

@ -0,0 +1,253 @@
// -----------------------------------------------------------------------------
// BMP085/BMP180 Sensor over I2C
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && BMP180_SUPPORT
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
#define BMP180_CHIP_ID 0x55
#define BMP180_REGISTER_CHIPID 0xD0
#define BMP180_REGISTER_CAL_AC1 0xAA
#define BMP180_REGISTER_CAL_AC2 0xAC
#define BMP180_REGISTER_CAL_AC3 0xAE
#define BMP180_REGISTER_CAL_AC4 0xB0
#define BMP180_REGISTER_CAL_AC5 0xB2
#define BMP180_REGISTER_CAL_AC6 0xB4
#define BMP180_REGISTER_CAL_B1 0xB6
#define BMP180_REGISTER_CAL_B2 0xB8
#define BMP180_REGISTER_CAL_MB 0xBA
#define BMP180_REGISTER_CAL_MC 0xBC
#define BMP180_REGISTER_CAL_MD 0xBE
#define BMP180_REGISTER_VERSION 0xD1
#define BMP180_REGISTER_SOFTRESET 0xE0
#define BMP180_REGISTER_CONTROL 0xF4
#define BMP180_REGISTER_TEMPDATA 0xF6
#define BMP180_REGISTER_PRESSUREDATA 0xF6
#define BMP180_REGISTER_READTEMPCMD 0x2E
#define BMP180_REGISTER_READPRESSURECMD 0x34
class BMP180Sensor : public I2CSensor {
public:
static unsigned char addresses[1];
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
BMP180Sensor(): I2CSensor() {
_sensor_id = SENSOR_BMP180_ID;
_count = 2;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_init();
_dirty = !_ready;
}
// Descriptive name of the sensor
String description() {
char buffer[20];
snprintf(buffer, sizeof(buffer), "BMP180 @ I2C (0x%02X)", _address);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_PRESSURE;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
virtual void pre() {
if (_run_init) {
i2cClearBus();
_init();
}
if (_chip == 0) {
_error = SENSOR_ERROR_UNKNOWN_ID;
return;
}
_error = SENSOR_ERROR_OK;
_error = _read();
if (_error != SENSOR_ERROR_OK) {
_run_init = true;
}
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _temperature;
if (index == 1) return _pressure / 100;
return 0;
}
protected:
void _init() {
// Make sure sensor had enough time to turn on. BMP180 requires 2ms to start up
nice_delay(10);
// I2C auto-discover
_address = _begin_i2c(_address, sizeof(BMP180Sensor::addresses), BMP180Sensor::addresses);
if (_address == 0) return;
// Check sensor correctly initialized
_chip = i2c_read_uint8(_address, BMP180_REGISTER_CHIPID);
if (_chip != BMP180_CHIP_ID) {
_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;
}
_readCoefficients();
_run_init = false;
_ready = true;
}
void _readCoefficients() {
_bmp180_calib.ac1 = i2c_read_int16(_address, BMP180_REGISTER_CAL_AC1);
_bmp180_calib.ac2 = i2c_read_int16(_address, BMP180_REGISTER_CAL_AC2);
_bmp180_calib.ac3 = i2c_read_int16(_address, BMP180_REGISTER_CAL_AC3);
_bmp180_calib.ac4 = i2c_read_uint16(_address, BMP180_REGISTER_CAL_AC4);
_bmp180_calib.ac5 = i2c_read_uint16(_address, BMP180_REGISTER_CAL_AC5);
_bmp180_calib.ac6 = i2c_read_uint16(_address, BMP180_REGISTER_CAL_AC6);
_bmp180_calib.b1 = i2c_read_int16(_address, BMP180_REGISTER_CAL_B1);
_bmp180_calib.b2 = i2c_read_int16(_address, BMP180_REGISTER_CAL_B2);
_bmp180_calib.mb = i2c_read_int16(_address, BMP180_REGISTER_CAL_MB);
_bmp180_calib.mc = i2c_read_int16(_address, BMP180_REGISTER_CAL_MC);
_bmp180_calib.md = i2c_read_int16(_address, BMP180_REGISTER_CAL_MD);
}
// Compute B5 coefficient used in temperature & pressure calcs.
// Based on Adafruit_BMP085_Unified library
long _computeB5(unsigned long t) {
long X1 = (t - (long)_bmp180_calib.ac6) * ((long)_bmp180_calib.ac5) >> 15;
long X2 = ((long)_bmp180_calib.mc << 11) / (X1+(long)_bmp180_calib.md);
return X1 + X2;
}
unsigned char _read() {
// Read raw temperature
i2c_write_uint8(_address, BMP180_REGISTER_CONTROL, BMP180_REGISTER_READTEMPCMD);
nice_delay(5);
unsigned long t = i2c_read_uint16(_address, BMP180_REGISTER_TEMPDATA);
// Compute B5 coeficient
long b5 = _computeB5(t);
// Final temperature
_temperature = ((double) ((b5 + 8) >> 4)) / 10.0;
// Read raw pressure
i2c_write_uint8(_address, BMP180_REGISTER_CONTROL, BMP180_REGISTER_READPRESSURECMD + (_mode << 6));
nice_delay(26);
unsigned long p1 = i2c_read_uint16(_address, BMP180_REGISTER_PRESSUREDATA);
unsigned long p2 = i2c_read_uint8(_address, BMP180_REGISTER_PRESSUREDATA+2);
long p = ((p1 << 8) + p2) >> (8 - _mode);
// Pressure compensation
long b6 = b5 - 4000;
long x1 = (_bmp180_calib.b2 * ((b6 * b6) >> 12)) >> 11;
long x2 = (_bmp180_calib.ac2 * b6) >> 11;
long x3 = x1 + x2;
long b3 = (((((int32_t) _bmp180_calib.ac1) * 4 + x3) << _mode) + 2) >> 2;
x1 = (_bmp180_calib.ac3 * b6) >> 13;
x2 = (_bmp180_calib.b1 * ((b6 * b6) >> 12)) >> 16;
x3 = ((x1 + x2) + 2) >> 2;
unsigned long b4 = (_bmp180_calib.ac4 * (uint32_t) (x3 + 32768)) >> 15;
unsigned long b7 = ((uint32_t) (p - b3) * (50000 >> _mode));
if (b7 < 0x80000000) {
p = (b7 << 1) / b4;
} else {
p = (b7 / b4) << 1;
}
x1 = (p >> 8) * (p >> 8);
x1 = (x1 * 3038) >> 16;
x2 = (-7357 * p) >> 16;
_pressure = p + ((x1 + x2 + 3791) >> 4);
return SENSOR_ERROR_OK;
}
// ---------------------------------------------------------------------
unsigned char _chip;
bool _run_init = false;
double _temperature = 0;
double _pressure = 0;
unsigned int _mode = BMP180_MODE;
typedef struct {
int16_t ac1;
int16_t ac2;
int16_t ac3;
uint16_t ac4;
uint16_t ac5;
uint16_t ac6;
int16_t b1;
int16_t b2;
int16_t mb;
int16_t mc;
int16_t md;
} bmp180_calib_t;
bmp180_calib_t _bmp180_calib;
};
// Static inizializations
unsigned char BMP180Sensor::addresses[1] = {0x77};
#endif // SENSOR_SUPPORT && BMP180_SUPPORT

+ 212
- 0
code/espurna/sensors/EZOPHSensor.h View File

@ -0,0 +1,212 @@
// -----------------------------------------------------------------------------
// EZO pH Circuit from Atlas Scientific
//
// Uses SoftwareSerial library
// Copyright (C) 2018 by Rui Marinho <ruipmarinho at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EZOPH_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <SoftwareSerial.h>
class EZOPHSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
EZOPHSensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_EZOPH_ID;
}
~EZOPHSensor() {
if (_serial) delete _serial;
}
// ---------------------------------------------------------------------
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setTX(unsigned char pin_tx) {
if (_pin_tx == pin_tx) return;
_pin_tx = pin_tx;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
unsigned char getTX() {
return _pin_tx;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_serial) delete _serial;
_serial = new SoftwareSerial(_pin_rx, _pin_tx);
_serial->enableIntTx(false);
_serial->begin(9600);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "EZOPH @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[6];
snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_PH;
return MAGNITUDE_NONE;
}
void tick() {
_setup();
_read();
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _ph;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _setup() {
if (_sync_responded) {
return;
}
_error = SENSOR_ERROR_WARM_UP;
String sync_serial = "";
sync_serial.reserve(30);
if (!_sync_requested) {
_serial->write(67); // C
_serial->write(44); // ,
_serial->write(63); // ?
_serial->write(13); // \r
_serial->flush();
_sync_requested = true;
}
while ((_serial->available() > 0)) {
char sync_char = (char)_serial->read();
sync_serial += sync_char;
if (sync_char == '\r') {
break;
}
}
if (sync_serial.startsWith("?C,")) {
_sync_interval = sync_serial.substring(sync_serial.indexOf(",") + 1).toInt() * 1000;
if (_sync_interval == 0) {
_error = SENSOR_ERROR_OTHER;
return;
}
}
if (sync_serial.startsWith("*OK")) {
_sync_responded = true;
}
if (!_sync_responded) {
return;
}
_error = SENSOR_ERROR_OK;
}
void _read() {
if (_error != SENSOR_ERROR_OK) {
return;
}
if (millis() - _ts <= _sync_interval) {
return;
}
_ts = millis();
String ph_serial = "";
ph_serial.reserve(30);
while ((_serial->available() > 0)) {
char ph_char = (char)_serial->read();
ph_serial += ph_char;
if (ph_char == '\r') {
break;
}
}
if (ph_serial == "*ER") {
_error = SENSOR_ERROR_OTHER;
return;
}
_ph = ph_serial.toFloat();
_error = SENSOR_ERROR_OK;
}
bool _sync_requested = false;
bool _sync_responded = false;
unsigned long _sync_interval = 100000; // Maximum continuous reading interval allowed is 99000 milliseconds.
unsigned long _ts = 0;
double _ph = 0;
unsigned int _pin_rx;
unsigned int _pin_tx;
SoftwareSerial * _serial = NULL;
};
#endif // SENSOR_SUPPORT && EZOPH_SUPPORT

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

@ -104,7 +104,7 @@ class GUVAS12SDSensor : public BaseSensor {
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_UV;
if (index == 0) return MAGNITUDE_UVI;
return MAGNITUDE_NONE;
}


+ 21
- 18
code/espurna/sensors/HLW8012Sensor.h View File

@ -9,8 +9,6 @@
#include "Arduino.h"
#include "BaseSensor.h"
#include <ESP8266WiFi.h>
#include <HLW8012.h>
class HLW8012Sensor : public BaseSensor {
@ -148,14 +146,10 @@ class HLW8012Sensor : public BaseSensor {
// Handle interrupts
#if HLW8012_USE_INTERRUPTS
_enableInterrupts(true);
#else
_onconnect_handler = WiFi.onStationModeGotIP([this](WiFiEventStationModeGotIP ipInfo) {
_enableInterrupts(true);
});
_ondisconnect_handler = WiFi.onStationModeDisconnected([this](WiFiEventStationModeDisconnected ipInfo) {
#if HLW8012_WAIT_FOR_WIFI == 0
_enableInterrupts(false);
});
_enableInterrupts(true);
#endif
#endif
_ready = true;
@ -205,6 +199,15 @@ class HLW8012Sensor : public BaseSensor {
return 0;
}
// Pre-read hook (usually to populate registers with up-to-date data)
#if HLW8012_USE_INTERRUPTS
#if HLW8012_WAIT_FOR_WIFI
void pre() {
_enableInterrupts(wifiConnected());
}
#endif
#endif
// Toggle between current and voltage monitoring
#if HLW8012_USE_INTERRUPTS == 0
// Post-read hook (usually to reset things)
@ -247,10 +250,15 @@ class HLW8012Sensor : public BaseSensor {
} else {
_detach(_cf);
_detach(_cf1);
_interrupt_cf = GPIO_NONE;
_interrupt_cf1 = GPIO_NONE;
if (GPIO_NONE != _interrupt_cf) {
_detach(_interrupt_cf);
_interrupt_cf = GPIO_NONE;
}
if (GPIO_NONE != _interrupt_cf1) {
_detach(_interrupt_cf1);
_interrupt_cf1 = GPIO_NONE;
}
}
@ -266,11 +274,6 @@ class HLW8012Sensor : public BaseSensor {
HLW8012 * _hlw8012 = NULL;
#if HLW8012_USE_INTERRUPTS == 0
WiFiEventHandler _onconnect_handler;
WiFiEventHandler _ondisconnect_handler;
#endif
};
// -----------------------------------------------------------------------------


+ 150
- 0
code/espurna/sensors/MAX6675.h View File

@ -0,0 +1,150 @@
// -----------------------------------------------------------------------------
// MAX6675 Sensor
// Uses MAX6675_Thermocouple library
// Copyright (C) 2017-2018 by Xose Pérez <andrade dot luciano at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && MAX6675_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <vector>
#include <MAX6675.h>
#define MAX6675_READ_INTERVAL 3000
class MAX6675Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
MAX6675Sensor(): BaseSensor() {
_sensor_id = SENSOR_MAX6675_ID;
}
~MAX6675Sensor() {
}
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
void setCS(unsigned char pin_cs) {
if (_pin_cs == pin_cs) return;
_pin_cs = pin_cs;
_dirty = true;
}
void setSO(unsigned char pin_so) {
if (_pin_so == pin_so) return;
_pin_so = pin_so;
_dirty = true;
}
void setSCK(unsigned char pin_sck) {
if (_pin_sck == pin_sck) return;
_pin_sck = pin_sck;
_dirty = true;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
//// MAX6675
int units = 1; // Units to readout temp (0 = raw, 1 = ˚C, 2 = ˚F)
if (_max) delete _max;
_max = new MAX6675(_pin_cs,_pin_so,_pin_sck,units);
_count = 1;
_ready = true;
_dirty = false;
}
// Loop-like method, call it in your main loop
void tick() {
static unsigned long last = 0;
if (millis() - last < MAX6675_READ_INTERVAL) return;
last = millis();
last_read = _max->read_temp();
}
// Descriptive name of the sensor
String description() {
char buffer[20];
//snprintf(buffer, sizeof(buffer), "MAX6675 @ CS %d", _gpio);
snprintf(buffer, sizeof(buffer), "MAX6675 ");
return String(buffer);
}
String address(unsigned char index){
return String("@ address");
}
// Address of the device
// Descriptive name of the slot # index
String slot(unsigned char index) {
if (index < _count) {
// char buffer[40];
// uint8_t * address = _devices[index].address;
// snprintf(buffer, sizeof(buffer), "%s (%02X%02X%02X%02X%02X%02X%02X%02X) @ GPIO%d",
// chipAsString(index).c_str(),
// address[0], address[1], address[2], address[3],
// address[4], address[5], address[6], address[7],
// _gpio
// );
return description();
}
return String();
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index < _count) return MAGNITUDE_TEMPERATURE;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
}
// Current value for slot # index
double value(unsigned char index) {
return last_read;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
unsigned int _pin_cs = MAX6675_CS_PIN;
unsigned int _pin_so = MAX6675_SO_PIN;
unsigned int _pin_sck = MAX6675_SCK_PIN;
bool _busy = false;
double last_read = 0;
MAX6675 * _max = NULL;
};
#endif // SENSOR_SUPPORT && MAX6675_SUPPORT

+ 13
- 7
code/espurna/sensors/PMSX003Sensor.h View File

@ -22,6 +22,7 @@
#define PMS_TYPE_X003_9 1
#define PMS_TYPE_5003T 2
#define PMS_TYPE_5003ST 3
#define PMS_TYPE_5003S 4
// Sensor type specified data
#define PMS_SLOT_MAX 4
@ -35,7 +36,8 @@ const static struct {
{"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}}
{"PMS5003ST", 17, 4, {MAGNITUDE_PM2dot5, MAGNITUDE_TEMPERATURE, MAGNITUDE_HUMIDITY, MAGNITUDE_HCHO}},
{"PMS5003S", 13, 3, {MAGNITUDE_PM2dot5, MAGNITUDE_PM10, MAGNITUDE_HCHO}},
};
// [MAGIC][LEN][DATA9|13|17][SUM]
@ -90,7 +92,7 @@ class PMSX003 {
int avail = _serial->available();
#if SENSOR_DEBUG
//debugSend("[SENSOR] PMS: Packet available = %d\n", avail);
//DEBUG_MSG("[SENSOR] PMS: Packet available = %d\n", avail);
#endif
if (avail < PMS_PACKET_SIZE(data_count)) {
break;
@ -102,7 +104,7 @@ class PMSX003 {
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));
DEBUG_MSG(("[SENSOR] PMS: Payload size: %d != %d.\n"), size, PMS_PAYLOAD_SIZE(data_count));
#endif
break;
}
@ -110,7 +112,7 @@ class PMSX003 {
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]);
//DEBUG_MSG(("[SENSOR] PMS: data[%d] = %d\n"), i, data[i]);
#endif
}
@ -119,7 +121,7 @@ class PMSX003 {
return true;
} else {
#if SENSOR_DEBUG
debugSend(("[SENSOR] PMS checksum: %04X != %04X\n"), sum, checksum);
DEBUG_MSG(("[SENSOR] PMS checksum: %04X != %04X\n"), sum, checksum);
#endif
}
break;
@ -282,7 +284,7 @@ class PMSX003Sensor : public BaseSensor, PMSX003 {
readCycle = _readCount % 30;
if (readCycle == 0) {
#if SENSOR_DEBUG
debugSend("[SENSOR] %s: Wake up: %d\n", pms_specs[_type].name, _readCount);
DEBUG_MSG("[SENSOR] %s: Wake up: %d\n", pms_specs[_type].name, _readCount);
#endif
wakeUp();
return;
@ -306,6 +308,10 @@ class PMSX003Sensor : public BaseSensor, PMSX003 {
_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_5003S) {
_slot_values[0] = data[4];
_slot_values[1] = data[5];
_slot_values[2] = (double)data[12] / 1000;
} else if (_type == PMS_TYPE_5003T) {
_slot_values[0] = data[4];
_slot_values[1] = (double)data[10] / 10;
@ -321,7 +327,7 @@ class PMSX003Sensor : public BaseSensor, PMSX003 {
if (readCycle == 6) {
sleep();
#if SENSOR_DEBUG
debugSend("[SENSOR] %s: Enter sleep mode: %d\n", pms_specs[_type].name, _readCount);
DEBUG_MSG("[SENSOR] %s: Enter sleep mode: %d\n", pms_specs[_type].name, _readCount);
#endif
return;
}


+ 188
- 24
code/espurna/sensors/PZEM004TSensor.h View File

@ -3,6 +3,48 @@
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
// Connection Diagram:
// -------------------
//
// Needed when connecting multiple PZEM004T devices on the same UART
// *You must set the PZEM004T device address prior using this configuration*
//
// +---------+
// | ESPurna | +VCC
// | Node | ^
// | G T R | |
// +-+--+--+-+ R (10K)
// | | | |
// | | +-----------------+---------------+---------------+
// | +-----------------+--|------------+--|------------+ |
// +-----------------+--|--|---------+--|--|---------+ | |
// | | | | | | | | |
// | | V | | V | | V
// | | - | | - | | -
// +-+--+--+-+ +-+--+--+-+ +-+--+--+-+
// | G R T | | G R T | | G R T |
// |PZEM-004T| |PZEM-004T| |PZEM-004T|
// | Module | | Module | | Module |
// +---------+ +---------+ +---------+
//
// Where:
// ------
// G = GND
// R = ESPurna UART RX
// T = ESPurna UART TX
// V = Small Signal Schottky Diode, like BAT43,
// Cathode to PZEM TX, Anode to Espurna RX
// R = Resistor to VCC, 10K
//
// More Info:
// ----------
// See ESPurna Wiki - https://github.com/xoseperez/espurna/wiki/Sensor-PZEM004T
//
// Reference:
// ----------
// UART/TTL-Serial network with single master and multiple slaves:
// http://cool-emerald.blogspot.com/2009/10/multidrop-network-for-rs232.html
#if SENSOR_SUPPORT && PZEM004T_SUPPORT
#pragma once
@ -12,6 +54,13 @@
#include <PZEM004T.h>
#define PZ_MAGNITUDE_COUNT 4
#define PZ_MAGNITUDE_CURRENT_INDEX 0
#define PZ_MAGNITUDE_VOLTAGE_INDEX 1
#define PZ_MAGNITUDE_POWER_ACTIVE_INDEX 2
#define PZ_MAGNITUDE_ENERGY_INDEX 3
class PZEM004TSensor : public BaseSensor {
public:
@ -21,9 +70,7 @@ class PZEM004TSensor : public BaseSensor {
// ---------------------------------------------------------------------
PZEM004TSensor(): BaseSensor() {
_count = 4;
_sensor_id = SENSOR_PZEM004T_ID;
_ip = IPAddress(192,168,1,1);
}
~PZEM004TSensor() {
@ -49,6 +96,53 @@ class PZEM004TSensor : public BaseSensor {
_dirty = true;
}
// Set the devices physical addresses managed by this sensor
void setAddresses(const char *addresses) {
char const * sep = " ";
char tokens[strlen(addresses) + 1];
strlcpy(tokens, addresses, sizeof(tokens));
char *address = tokens;
int i = 0;
address = strtok(address, sep);
while (address != 0 && i++ < PZEM004T_MAX_DEVICES) {
IPAddress addr;
reading_t reading;
reading.current = PZEM_ERROR_VALUE;
reading.voltage = PZEM_ERROR_VALUE;
reading.power = PZEM_ERROR_VALUE;
reading.energy = PZEM_ERROR_VALUE;
if (addr.fromString(address)) {
_devices.push_back(addr);
_energy_offsets.push_back(0);
_readings.push_back(reading);
}
address = strtok(0, sep);
}
_count = _devices.size() * PZ_MAGNITUDE_COUNT;
_dirty = true;
}
// Return the number of devices managed by this sensor
unsigned char getAddressesCount() {
return _devices.size();
}
// Get device physical address based on the device index
String getAddress(unsigned char dev) {
return _devices[dev].toString();
}
// Set the device physical address
bool setDeviceAddress(IPAddress *addr) {
while(_busy) { yield(); };
_busy = true;
bool res = _pzem->setAddress(*addr);
_busy = false;
return res;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
@ -61,12 +155,11 @@ class PZEM004TSensor : public BaseSensor {
// ---------------------------------------------------------------------
void resetEnergy(double value = 0) {
if (_ready) {
_energy_offset = value - (_pzem->energy(_ip) * 3600);
} else {
_energy_offset = value;
}
// If called with value = -1, the offset will be the last energy reading
// otherwise, it will be the value provided
float resetEnergy(unsigned char dev, float value = -1) {
_energy_offsets[dev] = value != -1 ? value : _readings[dev].energy;
return _energy_offsets[dev];
}
// ---------------------------------------------------------------------
@ -75,7 +168,6 @@ class PZEM004TSensor : public BaseSensor {
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_pzem) delete _pzem;
@ -84,16 +176,15 @@ class PZEM004TSensor : public BaseSensor {
} else {
_pzem = new PZEM004T(_pin_rx, _pin_tx);
}
_pzem->setAddress(_ip);
if(_devices.size() == 1) _pzem->setAddress(_devices[0]);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
char buffer[27];
if (_serial) {
snprintf(buffer, sizeof(buffer), "PZEM004T @ HwSerial");
} else {
@ -104,34 +195,99 @@ class PZEM004TSensor : public BaseSensor {
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
int dev = index / PZ_MAGNITUDE_COUNT;
char buffer[25];
snprintf(buffer, sizeof(buffer), "(%u/%s)", dev, getAddress(dev).c_str());
return description() + String(buffer);
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return _ip.toString();
int dev = index / PZ_MAGNITUDE_COUNT;
return _devices[dev].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;
int dev = index / PZ_MAGNITUDE_COUNT;
index = index - (dev * PZ_MAGNITUDE_COUNT);
if (index == PZ_MAGNITUDE_CURRENT_INDEX) return MAGNITUDE_CURRENT;
if (index == PZ_MAGNITUDE_VOLTAGE_INDEX) return MAGNITUDE_VOLTAGE;
if (index == PZ_MAGNITUDE_POWER_ACTIVE_INDEX) return MAGNITUDE_POWER_ACTIVE;
if (index == PZ_MAGNITUDE_ENERGY_INDEX) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
int dev = index / PZ_MAGNITUDE_COUNT;
index = index - (dev * PZ_MAGNITUDE_COUNT);
double response = 0;
if (index == 0) response = _pzem->current(_ip);
if (index == 1) response = _pzem->voltage(_ip);
if (index == 2) response = _pzem->power(_ip);
if (index == 3) response = _energy_offset + (_pzem->energy(_ip) * 3600);
if (index == PZ_MAGNITUDE_CURRENT_INDEX) response = _readings[dev].current;
if (index == PZ_MAGNITUDE_VOLTAGE_INDEX) response = _readings[dev].voltage;
if (index == PZ_MAGNITUDE_POWER_ACTIVE_INDEX) response = _readings[dev].power;
if (index == PZ_MAGNITUDE_ENERGY_INDEX) response = (_readings[dev].energy * 3600) - _energy_offsets[dev];
if (response < 0) response = 0;
return response;
}
// Post-read hook (usually to reset things)
void post() {
_error = SENSOR_ERROR_OK;
}
// Loop-like method, call it in your main loop
void tick() {
static unsigned char dev = 0;
static unsigned char magnitude = 0;
static unsigned long last_millis = 0;
if (_busy || millis() - last_millis < PZEM004T_READ_INTERVAL) return;
_busy = true;
// Clear buffer in case of late response(Timeout)
while(Serial.available() > 0) Serial.read();
float read;
float* readings_p;
switch(magnitude) {
case PZ_MAGNITUDE_CURRENT_INDEX:
read = _pzem->current(_devices[dev]);
readings_p = &_readings[dev].current;
break;
case PZ_MAGNITUDE_VOLTAGE_INDEX:
read = _pzem->voltage(_devices[dev]);
readings_p = &_readings[dev].voltage;
break;
case PZ_MAGNITUDE_POWER_ACTIVE_INDEX:
read = _pzem->power(_devices[dev]);
readings_p = &_readings[dev].power;
break;
case PZ_MAGNITUDE_ENERGY_INDEX:
read = _pzem->energy(_devices[dev]);
readings_p = &_readings[dev].energy;
break;
default:
_busy = false;
return;
}
if(read == PZEM_ERROR_VALUE) {
_error = SENSOR_ERROR_TIMEOUT;
} else {
*readings_p = read;
}
if(++dev == _devices.size()) {
dev = 0;
last_millis = millis();
if(++magnitude == PZ_MAGNITUDE_COUNT) {
magnitude = 0;
}
}
_busy = false;
}
protected:
// ---------------------------------------------------------------------
@ -140,10 +296,18 @@ class PZEM004TSensor : public BaseSensor {
unsigned int _pin_rx = PZEM004T_RX_PIN;
unsigned int _pin_tx = PZEM004T_TX_PIN;
IPAddress _ip;
bool _busy = false;
typedef struct {
float voltage;
float current;
float power;
float energy;
} reading_t;
std::vector<reading_t> _readings;
std::vector<float> _energy_offsets;
std::vector<IPAddress> _devices;
HardwareSerial * _serial = NULL;
PZEM004T * _pzem = NULL;
double _energy_offset = 0;
};


+ 231
- 0
code/espurna/sensors/PulseMeterSensor.h View File

@ -0,0 +1,231 @@
// -----------------------------------------------------------------------------
// Pulse Meter Power Monitor Sensor
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && PULSEMETER_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
class PulseMeterSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
PulseMeterSensor(): BaseSensor() {
_count = 2;
_sensor_id = SENSOR_PULSEMETER_ID;
}
~PulseMeterSensor() {
_enableInterrupts(false);
}
void resetEnergy(double value = 0) {
_energy = value;
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
if (_gpio == gpio) return;
_gpio = gpio;
_dirty = true;
}
void setEnergyRatio(unsigned long ratio) {
if (ratio > 0) _ratio = ratio;
}
void setDebounceTime(unsigned long debounce) {
_debounce = debounce;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
unsigned long getEnergyRatio() {
return _ratio;
}
unsigned long getDebounceTime() {
return _debounce;
}
// ---------------------------------------------------------------------
// Sensors API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
// Defined outside the class body
void begin() {
_enableInterrupts(true);
_ready = true;
}
// Descriptive name of the sensor
String description() {
char buffer[24];
snprintf(buffer, sizeof(buffer), "PulseMeter @ GPIO(%u)", _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);
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
unsigned long lapse = millis() - _previous_time;
_previous_time = millis();
unsigned long pulses = _pulses - _previous_pulses;
_previous_pulses = _pulses;
unsigned long _energy_delta = 1000 * 3600 * pulses / _ratio;
_energy += _energy_delta;
if (lapse > 0) _active = 1000 * _energy_delta / lapse;
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_POWER_ACTIVE;
if (index == 1) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _active;
if (index == 1) return _energy;
return 0;
}
// Handle interrupt calls
void ICACHE_RAM_ATTR handleInterrupt(unsigned char gpio) {
static unsigned long last = 0;
if (millis() - last > _debounce) {
last = millis();
_pulses++;
}
}
protected:
// ---------------------------------------------------------------------
// Interrupt management
// ---------------------------------------------------------------------
void _attach(PulseMeterSensor * instance, unsigned char gpio, unsigned char mode);
void _detach(unsigned char gpio);
void _enableInterrupts(bool value) {
if (value) {
if (_gpio != _previous) {
if (_previous != GPIO_NONE) _detach(_previous);
_attach(this, _gpio, PULSEMETER_INTERRUPT_ON);
_previous = _gpio;
}
} else {
_detach(_previous);
_previous = GPIO_NONE;
}
}
// ---------------------------------------------------------------------
unsigned char _previous = GPIO_NONE;
unsigned char _gpio = GPIO_NONE;
unsigned long _ratio = PULSEMETER_ENERGY_RATIO;
unsigned long _debounce = PULSEMETER_DEBOUNCE;
double _active = 0;
double _energy = 0;
volatile unsigned long _pulses = 0;
unsigned long _previous_pulses = 0;
unsigned long _previous_time = 0;
};
// -----------------------------------------------------------------------------
// Interrupt helpers
// -----------------------------------------------------------------------------
PulseMeterSensor * _pulsemeter_sensor_instance[10] = {NULL};
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr(unsigned char gpio) {
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_pulsemeter_sensor_instance[index]) {
_pulsemeter_sensor_instance[index]->handleInterrupt(gpio);
}
}
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_0() { _pulsemeter_sensor_isr(0); }
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_1() { _pulsemeter_sensor_isr(1); }
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_2() { _pulsemeter_sensor_isr(2); }
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_3() { _pulsemeter_sensor_isr(3); }
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_4() { _pulsemeter_sensor_isr(4); }
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_5() { _pulsemeter_sensor_isr(5); }
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_12() { _pulsemeter_sensor_isr(12); }
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_13() { _pulsemeter_sensor_isr(13); }
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_14() { _pulsemeter_sensor_isr(14); }
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_15() { _pulsemeter_sensor_isr(15); }
static void (*_pulsemeter_sensor_isr_list[10])() = {
_pulsemeter_sensor_isr_0, _pulsemeter_sensor_isr_1, _pulsemeter_sensor_isr_2,
_pulsemeter_sensor_isr_3, _pulsemeter_sensor_isr_4, _pulsemeter_sensor_isr_5,
_pulsemeter_sensor_isr_12, _pulsemeter_sensor_isr_13, _pulsemeter_sensor_isr_14,
_pulsemeter_sensor_isr_15
};
void PulseMeterSensor::_attach(PulseMeterSensor * instance, unsigned char gpio, unsigned char mode) {
if (!gpioValid(gpio)) return;
_detach(gpio);
unsigned char index = gpio > 5 ? gpio-6 : gpio;
_pulsemeter_sensor_instance[index] = instance;
attachInterrupt(gpio, _pulsemeter_sensor_isr_list[index], mode);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt attached to %s\n"), gpio, instance->description().c_str());
#endif
}
void PulseMeterSensor::_detach(unsigned char gpio) {
if (!gpioValid(gpio)) return;
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_pulsemeter_sensor_instance[index]) {
detachInterrupt(gpio);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt detached from %s\n"), gpio, _pulsemeter_sensor_instance[index]->description().c_str());
#endif
_pulsemeter_sensor_instance[index] = NULL;
}
}
#endif // SENSOR_SUPPORT && PULSEMETER_SUPPORT

+ 96
- 0
code/espurna/sensors/VEML6075Sensor.h View File

@ -0,0 +1,96 @@
// -----------------------------------------------------------------------------
// VEML6075 Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && VEML6075_SUPPORT
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
#include "SparkFun_VEML6075_Arduino_Library.h"
class VEML6075Sensor : public I2CSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
VEML6075Sensor(): I2CSensor() {
_count = 3;
_sensor_id = SENSOR_VEML6075_ID;
_veml6075 = new VEML6075();
}
~VEML6075Sensor() {
delete _veml6075;
}
void begin() {
if (!_veml6075->begin()) {
return;
};
_ready = true;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Descriptive name of the sensor
String description() {
char buffer[25];
snprintf(buffer, sizeof(buffer), "VEML6075 @ 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_UVA;
if (index == 1) return MAGNITUDE_UVB;
if (index == 2) return MAGNITUDE_UVI;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _veml6075->a();
if (index == 1) return _veml6075->b();
if (index == 2) return _veml6075->index();
return 0;
}
void setIntegrationTime(VEML6075::veml6075_uv_it_t integration_time) {
_veml6075->setIntegrationTime(integration_time);
}
void setDynamicMode(VEML6075::veml6075_hd_t dynamic_mode) {
_veml6075->setHighDynamic(dynamic_mode);
}
protected:
VEML6075 * _veml6075 = NULL;
};
#endif // SENSOR_SUPPORT && VEML6075_SUPPORT

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

@ -0,0 +1,119 @@
// -----------------------------------------------------------------------------
// VL53L1X Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && VL53L1X_SUPPORT
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
#include "VL53L1X.h"
class VL53L1XSensor : public I2CSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
VL53L1XSensor(): I2CSensor() {
_count = 1;
_sensor_id = SENSOR_VL53L1X_ID;
_vl53l1x = new VL53L1X();
}
~VL53L1XSensor() {
delete _vl53l1x;
}
// ---------------------------------------------------------------------
void setDistanceMode(VL53L1X::DistanceMode mode) {
_vl53l1x->setDistanceMode(mode);
}
void setMeasurementTimingBudget(uint32_t budget_us) {
_vl53l1x->setMeasurementTimingBudget(budget_us);
}
void setInterMeasurementPeriod(unsigned int period) {
if (_inter_measurement_period == period) return;
_inter_measurement_period = period;
_dirty = true;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
void begin() {
if (!_dirty) {
return;
}
// I2C auto-discover
unsigned char addresses[] = {0x29};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
_vl53l1x->setAddress(_address);
if (!_vl53l1x->init()) {
return;
};
_vl53l1x->startContinuous(_inter_measurement_period);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[21];
snprintf(buffer, sizeof(buffer), "VL53L1X @ 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_DISTANCE;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
if (!_vl53l1x->dataReady()) {
return;
}
_distance = (double) _vl53l1x->read(false) / 1000.00;
}
// Current value for slot # index
double value(unsigned char index) {
if (index != 0) return 0;
return _distance;
}
protected:
VL53L1X * _vl53l1x = NULL;
unsigned int _inter_measurement_period;
double _distance = 0;
};
#endif // SENSOR_SUPPORT && VL53L1X_SUPPORT

+ 9
- 297
code/espurna/settings.ino View File

@ -2,27 +2,12 @@
SETTINGS MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <EEPROM_Rotate.h>
#include <vector>
#include "libs/EmbedisWrap.h"
#include <Stream.h>
#include "libs/StreamInjector.h"
StreamInjector _serial = StreamInjector(TERMINAL_BUFFER_SIZE);
EmbedisWrap embedis(_serial, TERMINAL_BUFFER_SIZE);
#if TERMINAL_SUPPORT
#if SERIAL_RX_ENABLED
char _serial_rx_buffer[TERMINAL_BUFFER_SIZE];
static unsigned char _serial_rx_pointer = 0;
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
bool _settings_save = false;
// -----------------------------------------------------------------------------
// Reverse engineering EEPROM storage format
@ -108,215 +93,6 @@ std::vector<String> _settingsKeys() {
return keys;
}
// -----------------------------------------------------------------------------
// Commands
// -----------------------------------------------------------------------------
void _settingsHelpCommand() {
// Get sorted list of commands
std::vector<String> commands;
unsigned char size = embedis.getCommandCount();
for (unsigned int i=0; i<size; i++) {
String command = embedis.getCommandName(i);
bool inserted = false;
for (unsigned char j=0; j<commands.size(); j++) {
// Check if we have to insert it before the current element
if (commands[j].compareTo(command) > 0) {
commands.insert(commands.begin() + j, command);
inserted = true;
break;
}
}
// If we could not insert it, just push it at the end
if (!inserted) commands.push_back(command);
}
// Output the list
DEBUG_MSG_P(PSTR("Available commands:\n"));
for (unsigned char i=0; i<commands.size(); i++) {
DEBUG_MSG_P(PSTR("> %s\n"), (commands[i]).c_str());
}
}
void _settingsKeysCommand() {
// Get sorted list of keys
std::vector<String> keys = _settingsKeys();
// Write key-values
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());
}
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++) {
EEPROMr.write(i, 0xFF);
}
EEPROMr.commit();
}
void _settingsInitCommands() {
#if DEBUG_SUPPORT
settingsRegisterCommand(F("CRASH"), [](Embedis* e) {
debugDumpCrashInfo();
debugClearCrashInfo();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("COMMANDS"), [](Embedis* e) {
_settingsHelpCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
resetReason(CUSTOM_RESET_TERMINAL);
ESP.eraseConfig();
*((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"));
});
settingsRegisterCommand(F("GPIO"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
int pin = String(e->argv[1]).toInt();
//if (!gpioValid(pin)) {
// DEBUG_MSG_P(PSTR("-ERROR: Invalid GPIO\n"));
// return;
//}
if (e->argc > 2) {
bool state = String(e->argv[2]).toInt() == 1;
digitalWrite(pin, state);
}
DEBUG_MSG_P(PSTR("GPIO %d is %s\n"), pin, digitalRead(pin) == HIGH ? "HIGH" : "LOW");
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HEAP"), [](Embedis* e) {
infoMemory("Heap", getInitialFreeHeap(), getFreeHeap());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("STACK"), [](Embedis* e) {
infoMemory("Stack", 4096, getFreeStack());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HELP"), [](Embedis* e) {
_settingsHelpCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("INFO"), [](Embedis* e) {
info();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("KEYS"), [](Embedis* e) {
_settingsKeysCommand();
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) {
espurnaReload();
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"));
});
settingsRegisterCommand(F("CONFIG"), [](Embedis* e) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
settingsGetJson(root);
String output;
root.printTo(output);
DEBUG_MSG(output.c_str());
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
#if not SETTINGS_AUTOSAVE
settingsRegisterCommand(F("SAVE"), [](Embedis* e) {
_settings_save = true;
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
#endif
}
// -----------------------------------------------------------------------------
// Key-value API
// -----------------------------------------------------------------------------
@ -367,26 +143,21 @@ bool hasSetting(const String& key, unsigned int index) {
void saveSettings() {
#if not SETTINGS_AUTOSAVE
_settings_save = true;
eepromCommit();
#endif
}
void resetSettings() {
_settingsFactoryResetCommand();
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROMr.write(i, 0xFF);
}
EEPROMr.commit();
}
// -----------------------------------------------------------------------------
// Settings
// API
// -----------------------------------------------------------------------------
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;
if (size > SPI_FLASH_SEC_SIZE) size = SPI_FLASH_SEC_SIZE;
@ -440,80 +211,21 @@ void settingsGetJson(JsonObject& root) {
}
void settingsRegisterCommand(const String& name, void (*call)(Embedis*)) {
Embedis::command(name, call);
};
// -----------------------------------------------------------------------------
// Initialization
// -----------------------------------------------------------------------------
void settingsSetup() {
_serial.callback([](uint8_t ch) {
#if TELNET_SUPPORT
telnetWrite(ch);
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.write(ch);
#endif
});
Embedis::dictionary( F("EEPROM"),
SPI_FLASH_SEC_SIZE,
[](size_t pos) -> char { return EEPROMr.read(pos); },
[](size_t pos, char value) { EEPROMr.write(pos, value); },
#if SETTINGS_AUTOSAVE
[]() { _settings_save = true; }
[]() { eepromCommit(); }
#else
[]() {}
#endif
);
_settingsInitCommands();
#if TERMINAL_SUPPORT
#if SERIAL_RX_ENABLED
SERIAL_RX_PORT.begin(SERIAL_RX_BAUDRATE);
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
// Register loop
espurnaRegisterLoop(settingsLoop);
}
void settingsLoop() {
if (_settings_save) {
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
while (SERIAL_RX_PORT.available() > 0) {
char rc = Serial.read();
_serial_rx_buffer[_serial_rx_pointer++] = rc;
if ((_serial_rx_pointer == TERMINAL_BUFFER_SIZE) || (rc == 10)) {
settingsInject(_serial_rx_buffer, (size_t) _serial_rx_pointer);
_serial_rx_pointer = 0;
}
}
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
}
}

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


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


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


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


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


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


+ 56
- 14
code/espurna/system.ino View File

@ -10,8 +10,13 @@ Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if WIFI_SLEEP_MODE != WIFI_NONE_SLEEP
unsigned long _loop_delay = 0;
#endif
bool _system_send_heartbeat = false;
unsigned char _heartbeat_mode = HEARTBEAT_MODE;
unsigned long _heartbeat_interval = HEARTBEAT_INTERVAL;
// Calculated load average 0 to 100;
unsigned short int _load_average = 100;
@ -42,7 +47,7 @@ void systemCheck(bool stable) {
}
}
EEPROMr.write(EEPROM_CRASH_COUNTER, value);
EEPROMr.commit();
eepromCommit();
}
bool systemCheck() {
@ -66,17 +71,41 @@ void systemSendHeartbeat() {
_system_send_heartbeat = true;
}
bool systemGetHeartbeat() {
return _system_send_heartbeat;
}
#if WIFI_SLEEP_MODE != WIFI_NONE_SLEEP
unsigned long systemLoopDelay() {
return _loop_delay;
}
#endif
unsigned long systemLoadAverage() {
return _load_average;
}
void _systemSetupHeartbeat() {
_heartbeat_mode = getSetting("hbMode", HEARTBEAT_MODE).toInt();
_heartbeat_interval = getSetting("hbInterval", HEARTBEAT_INTERVAL).toInt();
}
#if WEB_SUPPORT
bool _systemWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "hb", 2) == 0);
}
#endif
void systemLoop() {
// -------------------------------------------------------------------------
// User requested reset
// -------------------------------------------------------------------------
if (checkNeedsReset()) {
reset();
}
// -------------------------------------------------------------------------
// Check system stability
// -------------------------------------------------------------------------
@ -89,19 +118,21 @@ void systemLoop() {
// Heartbeat
// -------------------------------------------------------------------------
#if HEARTBEAT_MODE == HEARTBEAT_ONCE
if (_system_send_heartbeat) {
_system_send_heartbeat = false;
heartbeat();
}
#elif HEARTBEAT_MODE == HEARTBEAT_REPEAT
if (_system_send_heartbeat && _heartbeat_mode == HEARTBEAT_ONCE) {
heartbeat();
_system_send_heartbeat = false;
} else if (_heartbeat_mode == HEARTBEAT_REPEAT || _heartbeat_mode == HEARTBEAT_REPEAT_STATUS) {
static unsigned long last_hbeat = 0;
if (_system_send_heartbeat || (last_hbeat == 0) || (millis() - last_hbeat > HEARTBEAT_INTERVAL)) {
_system_send_heartbeat = false;
#if NTP_SUPPORT
if ((_system_send_heartbeat && ntpSynced()) || (millis() - last_hbeat > _heartbeat_interval * 1000)) {
#else
if (_system_send_heartbeat || (millis() - last_hbeat > _heartbeat_interval * 1000)) {
#endif
last_hbeat = millis();
heartbeat();
_system_send_heartbeat = false;
}
#endif // HEARTBEAT_MODE == HEARTBEAT_REPEAT
}
// -------------------------------------------------------------------------
// Load Average calculation
@ -130,7 +161,9 @@ void systemLoop() {
// Power saving delay
// -------------------------------------------------------------------------
delay(_loop_delay);
#if WIFI_SLEEP_MODE != WIFI_NONE_SLEEP
delay(_loop_delay);
#endif
}
@ -161,14 +194,23 @@ void systemSetup() {
systemCheck(false);
#endif
#if WEB_SUPPORT
wsOnReceiveRegister(_systemWebSocketOnReceive);
#endif
// Init device-specific hardware
_systemSetupSpecificHardware();
// Cache loop delay value to speed things (recommended max 250ms)
_loop_delay = atol(getSetting("loopDelay", LOOP_DELAY_TIME).c_str());
_loop_delay = constrain(_loop_delay, 0, 300);
#if WIFI_SLEEP_MODE != WIFI_NONE_SLEEP
_loop_delay = atol(getSetting("loopDelay", LOOP_DELAY_TIME).c_str());
_loop_delay = constrain(_loop_delay, 0, 300);
#endif
// Register Loop
espurnaRegisterLoop(systemLoop);
// Cache Heartbeat values
_systemSetupHeartbeat();
}

+ 51
- 35
code/espurna/telnet.ino View File

@ -15,9 +15,9 @@ Parts of the code have been borrowed from Thomas Sarlandie's NetServer
AsyncServer * _telnetServer;
AsyncClient * _telnetClients[TELNET_MAX_CLIENTS];
bool _telnetFirst = true;
#if TELNET_PASSWORD
bool _authenticated[TELNET_MAX_CLIENTS];
#endif
bool _telnetAuth = TELNET_AUTHENTICATION;
bool _telnetClientsAuth[TELNET_MAX_CLIENTS];
// -----------------------------------------------------------------------------
// Private methods
@ -32,6 +32,7 @@ bool _telnetWebSocketOnReceive(const char * key, JsonVariant& value) {
void _telnetWebSocketOnSend(JsonObject& root) {
root["telnetVisible"] = 1;
root["telnetSTA"] = getSetting("telnetSTA", TELNET_STA).toInt() == 1;
root["telnetAuth"] = getSetting("telnetAuth", TELNET_AUTHENTICATION).toInt() == 1;
}
#endif
@ -44,32 +45,32 @@ void _telnetDisconnect(unsigned char clientId) {
DEBUG_MSG_P(PSTR("[TELNET] Client #%d disconnected\n"), clientId);
}
bool _telnetWrite(unsigned char clientId, void *data, size_t len) {
bool _telnetWrite(unsigned char clientId, const char *data, size_t len) {
if (_telnetClients[clientId] && _telnetClients[clientId]->connected()) {
return (_telnetClients[clientId]->write((const char*) data, len) > 0);
return (_telnetClients[clientId]->write(data, len) > 0);
}
return false;
}
unsigned char _telnetWrite(void *data, size_t len) {
unsigned char _telnetWrite(const char *data, size_t len) {
unsigned char count = 0;
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
// Do not send broadcast messages to unauthenticated clients
if (_telnetAuth && !_telnetClientsAuth[i]) {
continue;
}
#if TELNET_PASSWORD
// Do not send broadcast messages to unauthenticated clients
if (_authenticated[i]) {
if (_telnetWrite(i, data, len)) ++count;
}
#else
if (_telnetWrite(i, data, len)) ++count;
#endif
if (_telnetWrite(i, data, len)) ++count;
}
return count;
}
unsigned char _telnetWrite(const char *data) {
return _telnetWrite(data, strlen(data));
}
bool _telnetWrite(unsigned char clientId, const char * message) {
return _telnetWrite(clientId, (void *) message, strlen(message));
return _telnetWrite(clientId, message, strlen(message));
}
void _telnetData(unsigned char clientId, void *data, size_t len) {
@ -96,23 +97,29 @@ void _telnetData(unsigned char clientId, void *data, size_t len) {
return;
}
// Password
#if TELNET_PASSWORD
if (!_authenticated[clientId]) {
String password = getAdminPass();
if (strncmp(p, password.c_str(), password.length()) == 0) {
DEBUG_MSG_P(PSTR("[TELNET] Client #%d authenticated\n"), clientId);
_telnetWrite(clientId, "Welcome!\n");
_authenticated[clientId] = true;
} else {
_telnetWrite(clientId, "Password: ");
}
return;
// Password prompt (disable on CORE variant)
#ifdef ESPURNA_CORE
const bool authenticated = true;
#else
const bool authenticated = _telnetClientsAuth[clientId];
#endif
if (_telnetAuth && !authenticated) {
String password = getAdminPass();
if (strncmp(p, password.c_str(), password.length()) == 0) {
DEBUG_MSG_P(PSTR("[TELNET] Client #%d authenticated\n"), clientId);
_telnetWrite(clientId, "Welcome!\n");
_telnetClientsAuth[clientId] = true;
} else {
_telnetWrite(clientId, "Password: ");
}
#endif // TELNET_PASSWORD
return;
}
// Inject command
settingsInject(data, len);
#if TERMINAL_SUPPORT
terminalInject(data, len);
#endif
}
@ -171,13 +178,15 @@ void _telnetNewClient(AsyncClient *client) {
#if TERMINAL_SUPPORT == 0
info();
wifiDebug();
debugDumpCrashInfo();
debugClearCrashInfo();
crashDump();
crashClear();
#endif
#if TELNET_PASSWORD
_authenticated[i] = false;
_telnetWrite(i, "Password: ");
#ifdef ESPURNA_CORE
_telnetClientsAuth[i] = true;
#else
_telnetClientsAuth[i] = !_telnetAuth;
if (_telnetAuth) _telnetWrite(i, "Password: ");
#endif
_telnetFirst = true;
@ -214,6 +223,10 @@ unsigned char telnetWrite(unsigned char ch) {
return _telnetWrite(data, 1);
}
void _telnetConfigure() {
_telnetAuth = getSetting("telnetAuth", TELNET_AUTHENTICATION).toInt() == 1;
}
void telnetSetup() {
_telnetServer = new AsyncServer(TELNET_PORT);
@ -227,6 +240,9 @@ void telnetSetup() {
wsOnReceiveRegister(_telnetWebSocketOnReceive);
#endif
espurnaRegisterReload(_telnetConfigure);
_telnetConfigure();
DEBUG_MSG_P(PSTR("[TELNET] Listening on port %d\n"), TELNET_PORT);
}


+ 283
- 0
code/espurna/terminal.ino View File

@ -0,0 +1,283 @@
/*
TERMINAL MODULE
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if TERMINAL_SUPPORT
#include <vector>
#include "libs/EmbedisWrap.h"
#include <Stream.h>
#include "libs/StreamInjector.h"
StreamInjector _serial = StreamInjector(TERMINAL_BUFFER_SIZE);
EmbedisWrap embedis(_serial, TERMINAL_BUFFER_SIZE);
#if SERIAL_RX_ENABLED
char _serial_rx_buffer[TERMINAL_BUFFER_SIZE];
static unsigned char _serial_rx_pointer = 0;
#endif // SERIAL_RX_ENABLED
// -----------------------------------------------------------------------------
// Commands
// -----------------------------------------------------------------------------
void _terminalHelpCommand() {
// Get sorted list of commands
std::vector<String> commands;
unsigned char size = embedis.getCommandCount();
for (unsigned int i=0; i<size; i++) {
String command = embedis.getCommandName(i);
bool inserted = false;
for (unsigned char j=0; j<commands.size(); j++) {
// Check if we have to insert it before the current element
if (commands[j].compareTo(command) > 0) {
commands.insert(commands.begin() + j, command);
inserted = true;
break;
}
}
// If we could not insert it, just push it at the end
if (!inserted) commands.push_back(command);
}
// Output the list
DEBUG_MSG_P(PSTR("Available commands:\n"));
for (unsigned char i=0; i<commands.size(); i++) {
DEBUG_MSG_P(PSTR("> %s\n"), (commands[i]).c_str());
}
}
void _terminalKeysCommand() {
// Get sorted list of keys
std::vector<String> keys = _settingsKeys();
// Write key-values
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());
}
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 _terminalInitCommand() {
#if DEBUG_SUPPORT
terminalRegisterCommand(F("CRASH"), [](Embedis* e) {
crashDump();
crashClear();
terminalOK();
});
#endif
terminalRegisterCommand(F("COMMANDS"), [](Embedis* e) {
_terminalHelpCommand();
terminalOK();
});
terminalRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) {
terminalOK();
resetReason(CUSTOM_RESET_TERMINAL);
_eepromCommit();
ESP.eraseConfig();
*((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494
});
terminalRegisterCommand(F("FACTORY.RESET"), [](Embedis* e) {
resetSettings();
terminalOK();
});
terminalRegisterCommand(F("GPIO"), [](Embedis* e) {
if (e->argc < 2) {
terminalError(F("Wrong arguments"));
return;
}
int pin = String(e->argv[1]).toInt();
//if (!gpioValid(pin)) {
// terminalError(F("Invalid GPIO"));
// return;
//}
if (e->argc > 2) {
bool state = String(e->argv[2]).toInt() == 1;
digitalWrite(pin, state);
}
DEBUG_MSG_P(PSTR("GPIO %d is %s\n"), pin, digitalRead(pin) == HIGH ? "HIGH" : "LOW");
terminalOK();
});
terminalRegisterCommand(F("HEAP"), [](Embedis* e) {
infoMemory("Heap", getInitialFreeHeap(), getFreeHeap());
terminalOK();
});
terminalRegisterCommand(F("STACK"), [](Embedis* e) {
infoMemory("Stack", 4096, getFreeStack());
terminalOK();
});
terminalRegisterCommand(F("HELP"), [](Embedis* e) {
_terminalHelpCommand();
terminalOK();
});
terminalRegisterCommand(F("INFO"), [](Embedis* e) {
info();
terminalOK();
});
terminalRegisterCommand(F("KEYS"), [](Embedis* e) {
_terminalKeysCommand();
terminalOK();
});
terminalRegisterCommand(F("GET"), [](Embedis* e) {
if (e->argc < 2) {
terminalError(F("Wrong arguments"));
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());
}
terminalOK();
});
terminalRegisterCommand(F("RELOAD"), [](Embedis* e) {
espurnaReload();
terminalOK();
});
terminalRegisterCommand(F("RESET"), [](Embedis* e) {
terminalOK();
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
terminalRegisterCommand(F("RESET.SAFE"), [](Embedis* e) {
EEPROMr.write(EEPROM_CRASH_COUNTER, SYSTEM_CHECK_MAX);
terminalOK();
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
terminalRegisterCommand(F("UPTIME"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime());
terminalOK();
});
terminalRegisterCommand(F("CONFIG"), [](Embedis* e) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
settingsGetJson(root);
String output;
root.printTo(output);
DEBUG_MSG(output.c_str());
});
#if not SETTINGS_AUTOSAVE
terminalRegisterCommand(F("SAVE"), [](Embedis* e) {
eepromCommit();
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
#endif
}
void _terminalLoop() {
#if DEBUG_SERIAL_SUPPORT
while (DEBUG_PORT.available()) {
_serial.inject(DEBUG_PORT.read());
}
#endif
embedis.process();
#if SERIAL_RX_ENABLED
while (SERIAL_RX_PORT.available() > 0) {
char rc = Serial.read();
_serial_rx_buffer[_serial_rx_pointer++] = rc;
if ((_serial_rx_pointer == TERMINAL_BUFFER_SIZE) || (rc == 10)) {
terminalInject(_serial_rx_buffer, (size_t) _serial_rx_pointer);
_serial_rx_pointer = 0;
}
}
#endif // SERIAL_RX_ENABLED
}
// -----------------------------------------------------------------------------
// Pubic API
// -----------------------------------------------------------------------------
void terminalInject(void *data, size_t len) {
_serial.inject((char *) data, len);
}
Stream & terminalSerial() {
return (Stream &) _serial;
}
void terminalRegisterCommand(const String& name, void (*call)(Embedis*)) {
Embedis::command(name, call);
};
void terminalOK() {
DEBUG_MSG_P(PSTR("+OK\n"));
}
void terminalError(const String& error) {
DEBUG_MSG_P(PSTR("-ERROR: %s\n"), error.c_str());
}
void terminalSetup() {
_serial.callback([](uint8_t ch) {
#if TELNET_SUPPORT
telnetWrite(ch);
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.write(ch);
#endif
});
_terminalInitCommand();
#if SERIAL_RX_ENABLED
SERIAL_RX_PORT.begin(SERIAL_RX_BAUDRATE);
#endif // SERIAL_RX_ENABLED
// Register loop
espurnaRegisterLoop(_terminalLoop);
}
#endif // TERMINAL_SUPPORT

+ 37
- 16
code/espurna/thinkspeak.ino View File

@ -25,6 +25,8 @@ const char THINGSPEAK_REQUEST_TEMPLATE[] PROGMEM =
"%s\r\n";
bool _tspk_enabled = false;
bool _tspk_clear = false;
char * _tspk_queue[THINGSPEAK_FIELDS] = {NULL};
bool _tspk_flush = false;
@ -33,6 +35,25 @@ unsigned char _tspk_tries = 0;
// -----------------------------------------------------------------------------
#if BROKER_SUPPORT
void _tspkBrokerCallback(const unsigned char type, const char * topic, unsigned char id, const char * payload) {
// Process status messages
if (BROKER_MSG_TYPE_STATUS == type) {
tspkEnqueueRelay(id, (char *) payload);
tspkFlush();
}
// Porcess sensor messages
if (BROKER_MSG_TYPE_SENSOR == type) {
//tspkEnqueueMeasurement(id, (char *) payload);
//tspkFlush();
}
}
#endif // BROKER_SUPPORT
#if WEB_SUPPORT
bool _tspkWebSocketOnReceive(const char * key, JsonVariant& value) {
@ -45,6 +66,7 @@ void _tspkWebSocketOnSend(JsonObject& root) {
root["tspkEnabled"] = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
root["tspkKey"] = getSetting("tspkKey");
root["tspkClear"] = getSetting("tspkClear", THINGSPEAK_CLEAR_CACHE).toInt() == 1;
JsonArray& relays = root.createNestedArray("tspkRelays");
for (byte i=0; i<relayCount(); i++) {
@ -53,15 +75,8 @@ void _tspkWebSocketOnSend(JsonObject& root) {
if (relayCount() > 0) visible = 1;
#if SENSOR_SUPPORT
JsonArray& list = root.createNestedArray("tspkMagnitudes");
for (byte i=0; i<magnitudeCount(); i++) {
JsonObject& element = list.createNestedObject();
element["name"] = magnitudeName(i);
element["type"] = magnitudeType(i);
element["index"] = magnitudeIndex(i);
element["idx"] = getSetting("tspkMagnitude", i, 0).toInt();
}
if (magnitudeCount() > 0) visible = 1;
_sensorWebSocketMagnitudes(root, "tspk");
visible = visible || (magnitudeCount() > 0);
#endif
root["tspkVisible"] = visible;
@ -71,6 +86,7 @@ void _tspkWebSocketOnSend(JsonObject& root) {
#endif
void _tspkConfigure() {
_tspk_clear = getSetting("tspkClear", THINGSPEAK_CLEAR_CACHE).toInt() == 1;
_tspk_enabled = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
if (_tspk_enabled && (getSetting("tspkKey").length() == 0)) {
_tspk_enabled = false;
@ -221,10 +237,12 @@ void _tspkEnqueue(unsigned char index, char * payload) {
}
void _tspkClearQueue() {
for (unsigned char id=0; id<THINGSPEAK_FIELDS; id++) {
if (_tspk_queue[id] != NULL) {
free(_tspk_queue[id]);
_tspk_queue[id] = NULL;
if (_tspk_clear) {
for (unsigned char id=0; id<THINGSPEAK_FIELDS; id++) {
if (_tspk_queue[id] != NULL) {
free(_tspk_queue[id]);
_tspk_queue[id] = NULL;
}
}
}
}
@ -250,14 +268,13 @@ void _tspkFlush() {
}
}
// -----------------------------------------------------------------------------
bool tspkEnqueueRelay(unsigned char index, unsigned char status) {
bool tspkEnqueueRelay(unsigned char index, char * payload) {
if (!_tspk_enabled) return true;
unsigned char id = getSetting("tspkRelay", index, 0).toInt();
if (id > 0) {
char payload[3] = {0};
itoa(status ? 1 : 0, payload, 10);
_tspkEnqueue(id, payload);
return true;
}
@ -291,6 +308,10 @@ void tspkSetup() {
wsOnReceiveRegister(_tspkWebSocketOnReceive);
#endif
#if BROKER_SUPPORT
brokerRegister(_tspkBrokerCallback);
#endif
DEBUG_MSG_P(PSTR("[THINGSPEAK] Async %s, SSL %s\n"),
THINGSPEAK_USE_ASYNC ? "ENABLED" : "DISABLED",
THINGSPEAK_USE_SSL ? "ENABLED" : "DISABLED"


+ 143
- 53
code/espurna/utils.ino View File

@ -9,6 +9,8 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <Ticker.h>
Ticker _defer_reset;
uint8_t _reset_reason = 0;
String getIdentifier() {
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%s-%06X"), APP_NAME, ESP.getChipId());
@ -56,6 +58,14 @@ String getCoreRevision() {
#endif
}
unsigned char getHeartbeatMode() {
return getSetting("hbMode", HEARTBEAT_MODE).toInt();
}
unsigned char getHeartbeatInterval() {
return getSetting("hbInterval", HEARTBEAT_INTERVAL).toInt();
}
// WTF
// Calling ESP.getFreeHeap() is making the system crash on a specific
// AiLight bulb, but anywhere else...
@ -133,7 +143,60 @@ unsigned long getUptime() {
}
#if HEARTBEAT_MODE != HEARTBEAT_NONE
// -----------------------------------------------------------------------------
// Heartbeat helper
// -----------------------------------------------------------------------------
namespace Heartbeat {
enum Report : uint32_t {
Status = 1 << 1,
Ssid = 1 << 2,
Ip = 1 << 3,
Mac = 1 << 4,
Rssi = 1 << 5,
Uptime = 1 << 6,
Datetime = 1 << 7,
Freeheap = 1 << 8,
Vcc = 1 << 9,
Relay = 1 << 10,
Light = 1 << 11,
Hostname = 1 << 12,
App = 1 << 13,
Version = 1 << 14,
Board = 1 << 15,
Loadavg = 1 << 16,
Interval = 1 << 17,
Description = 1 << 18
};
constexpr uint32_t defaultValue() {
return (Status * (HEARTBEAT_REPORT_STATUS)) | \
(Ssid * (HEARTBEAT_REPORT_SSID)) | \
(Ip * (HEARTBEAT_REPORT_IP)) | \
(Mac * (HEARTBEAT_REPORT_MAC)) | \
(Rssi * (HEARTBEAT_REPORT_RSSI)) | \
(Uptime * (HEARTBEAT_REPORT_UPTIME)) | \
(Datetime * (HEARTBEAT_REPORT_DATETIME)) | \
(Freeheap * (HEARTBEAT_REPORT_FREEHEAP)) | \
(Vcc * (HEARTBEAT_REPORT_VCC)) | \
(Relay * (HEARTBEAT_REPORT_RELAY)) | \
(Light * (HEARTBEAT_REPORT_LIGHT)) | \
(Hostname * (HEARTBEAT_REPORT_HOSTNAME)) | \
(Description * (HEARTBEAT_REPORT_DESCRIPTION)) | \
(App * (HEARTBEAT_REPORT_APP)) | \
(Version * (HEARTBEAT_REPORT_VERSION)) | \
(Board * (HEARTBEAT_REPORT_BOARD)) | \
(Loadavg * (HEARTBEAT_REPORT_LOADAVG)) | \
(Interval * (HEARTBEAT_REPORT_INTERVAL));
}
uint32_t currentValue() {
const String cfg = getSetting("hbReport");
if (!cfg.length()) return defaultValue();
return strtoul(cfg.c_str(), NULL, 10);
}
}
void heartbeat() {
@ -141,6 +204,7 @@ void heartbeat() {
unsigned int free_heap = getFreeHeap();
#if MQTT_SUPPORT
unsigned char _heartbeat_mode = getHeartbeatMode();
bool serial = !mqttConnected();
#else
bool serial = true;
@ -161,63 +225,80 @@ void heartbeat() {
#endif
}
const uint32_t hb_cfg = Heartbeat::currentValue();
if (!hb_cfg) return;
// -------------------------------------------------------------------------
// MQTT
// -------------------------------------------------------------------------
#if MQTT_SUPPORT
if (!serial) {
#if (HEARTBEAT_REPORT_INTERVAL)
mqttSend(MQTT_TOPIC_INTERVAL, HEARTBEAT_INTERVAL / 1000);
#endif
#if (HEARTBEAT_REPORT_APP)
if (!serial && (_heartbeat_mode == HEARTBEAT_REPEAT || systemGetHeartbeat())) {
if (hb_cfg & Heartbeat::Interval)
mqttSend(MQTT_TOPIC_INTERVAL, String(getHeartbeatInterval() / 1000).c_str());
if (hb_cfg & Heartbeat::App)
mqttSend(MQTT_TOPIC_APP, APP_NAME);
#endif
#if (HEARTBEAT_REPORT_VERSION)
if (hb_cfg & Heartbeat::Version)
mqttSend(MQTT_TOPIC_VERSION, APP_VERSION);
#endif
#if (HEARTBEAT_REPORT_BOARD)
if (hb_cfg & Heartbeat::Board)
mqttSend(MQTT_TOPIC_BOARD, getBoardName().c_str());
#endif
#if (HEARTBEAT_REPORT_HOSTNAME)
mqttSend(MQTT_TOPIC_HOSTNAME, getSetting("hostname").c_str());
#endif
#if (HEARTBEAT_REPORT_IP)
if (hb_cfg & Heartbeat::Hostname)
mqttSend(MQTT_TOPIC_HOSTNAME, getSetting("hostname", getIdentifier()).c_str());
if (hb_cfg & Heartbeat::Description) {
if (hasSetting("desc")) {
mqttSend(MQTT_TOPIC_DESCRIPTION, getSetting("desc").c_str());
}
}
if (hb_cfg & Heartbeat::Ssid)
mqttSend(MQTT_TOPIC_SSID, WiFi.SSID().c_str());
if (hb_cfg & Heartbeat::Ip)
mqttSend(MQTT_TOPIC_IP, getIP().c_str());
#endif
#if (HEARTBEAT_REPORT_MAC)
if (hb_cfg & Heartbeat::Mac)
mqttSend(MQTT_TOPIC_MAC, WiFi.macAddress().c_str());
#endif
#if (HEARTBEAT_REPORT_RSSI)
if (hb_cfg & Heartbeat::Rssi)
mqttSend(MQTT_TOPIC_RSSI, String(WiFi.RSSI()).c_str());
#endif
#if (HEARTBEAT_REPORT_UPTIME)
if (hb_cfg & Heartbeat::Uptime)
mqttSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str());
#if NTP_SUPPORT
if ((hb_cfg & Heartbeat::Datetime) && (ntpSynced()))
mqttSend(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
#endif
#if (HEARTBEAT_REPORT_DATETIME) && (NTP_SUPPORT)
if (ntpSynced()) mqttSend(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
#endif
#if (HEARTBEAT_REPORT_FREEHEAP)
if (hb_cfg & Heartbeat::Freeheap)
mqttSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str());
#endif
#if (HEARTBEAT_REPORT_RELAY)
if (hb_cfg & Heartbeat::Relay)
relayMQTT();
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE)
if (hb_cfg & Heartbeat::Light)
lightMQTT();
#endif
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) & (HEARTBEAT_REPORT_LIGHT)
lightMQTT();
#endif
#if (HEARTBEAT_REPORT_VCC)
#if ADC_MODE_VALUE == ADC_VCC
if ((hb_cfg & Heartbeat::Vcc) && (ADC_MODE_VALUE == ADC_VCC))
mqttSend(MQTT_TOPIC_VCC, String(ESP.getVcc()).c_str());
#endif
#endif
#if (HEARTBEAT_REPORT_STATUS)
if (hb_cfg & Heartbeat::Status)
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
#endif
#if (LOADAVG_REPORT)
if (hb_cfg & Heartbeat::Loadavg)
mqttSend(MQTT_TOPIC_LOADAVG, String(systemLoadAverage()).c_str());
#endif
} else if (!serial && _heartbeat_mode == HEARTBEAT_REPEAT_STATUS) {
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
#endif
// -------------------------------------------------------------------------
@ -225,18 +306,18 @@ void heartbeat() {
// -------------------------------------------------------------------------
#if INFLUXDB_SUPPORT
#if (HEARTBEAT_REPORT_UPTIME)
if (hb_cfg & Heartbeat::Uptime)
idbSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str());
#endif
#if (HEARTBEAT_REPORT_FREEHEAP)
if (hb_cfg & Heartbeat::Freeheap)
idbSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str());
#endif
if (hb_cfg & Heartbeat::Rssi)
idbSend(MQTT_TOPIC_RSSI, String(WiFi.RSSI()).c_str());
#endif
}
#endif /// HEARTBEAT_MODE != HEARTBEAT_NONE
// -----------------------------------------------------------------------------
// INFO
// -----------------------------------------------------------------------------
@ -347,8 +428,7 @@ void info() {
// -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[MAIN] EEPROM sectors: %s\n"), (char *) eepromSectors().c_str());
DEBUG_MSG_P(PSTR("[MAIN] EEPROM current: %lu\n"), eepromCurrent());
eepromSectorsDebug();
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
@ -389,7 +469,9 @@ void info() {
#if ADC_MODE_VALUE == ADC_VCC
DEBUG_MSG_P(PSTR("[MAIN] Power: %u mV\n"), ESP.getVcc());
#endif
DEBUG_MSG_P(PSTR("[MAIN] Power saving delay value: %lu ms\n"), systemLoopDelay());
#if WIFI_SLEEP_MODE != WIFI_NONE_SLEEP
DEBUG_MSG_P(PSTR("[MAIN] Power saving delay value: %lu ms\n"), systemLoopDelay());
#endif
// -------------------------------------------------------------------------
@ -464,8 +546,9 @@ unsigned char resetReason() {
}
void resetReason(unsigned char reason) {
_reset_reason = reason;
EEPROMr.write(EEPROM_CUSTOM_RESET, reason);
EEPROMr.commit();
eepromCommit();
}
void reset() {
@ -473,8 +556,11 @@ void reset() {
}
void deferredReset(unsigned long delay, unsigned char reason) {
resetReason(reason);
_defer_reset.once_ms(delay, reset);
_defer_reset.once_ms(delay, resetReason, reason);
}
bool checkNeedsReset() {
return _reset_reason > 0;
}
// -----------------------------------------------------------------------------
@ -505,15 +591,19 @@ bool isNumber(const char * s) {
unsigned char len = strlen(s);
if (0 == len) return false;
bool decimal = false;
bool digit = false;
for (unsigned char i=0; i<len; i++) {
if (s[i] == '-') {
if (('-' == s[i]) || ('+' == s[i])) {
if (i>0) return false;
} else if (s[i] == '.') {
if (!digit) return false;
if (decimal) return false;
decimal = true;
} else if (!isdigit(s[i])) {
return false;
} else {
digit = true;
}
}
return true;
}
return digit;
}

+ 18
- 0
code/espurna/web.ino View File

@ -46,6 +46,7 @@ std::vector<uint8_t> * _webConfigBuffer;
bool _webConfigSuccess = false;
std::vector<web_request_callback_f> _web_request_callbacks;
std::vector<web_body_callback_f> _web_body_callbacks;
// -----------------------------------------------------------------------------
// HOOKS
@ -343,6 +344,17 @@ void _onRequest(AsyncWebServerRequest *request){
}
void _onBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
// Send request to subscribers
for (unsigned char i = 0; i < _web_body_callbacks.size(); i++) {
bool response = (_web_body_callbacks[i])(request, data, len, index, total);
if (response) return;
}
}
// -----------------------------------------------------------------------------
bool webAuthenticate(AsyncWebServerRequest *request) {
@ -362,6 +374,10 @@ AsyncWebServer * webServer() {
return _server;
}
void webBodyRegister(web_body_callback_f callback) {
_web_body_callbacks.push_back(callback);
}
void webRequestRegister(web_request_callback_f callback) {
_web_request_callbacks.push_back(callback);
}
@ -412,7 +428,9 @@ void webSetup() {
});
#endif
// Handle other requests, including 404
_server->onRequestBody(_onBody);
_server->onNotFound(_onRequest);
// Run server


+ 14
- 12
code/espurna/wifi.ino View File

@ -385,39 +385,39 @@ void _wifiDebugCallback(justwifi_messages_t code, char * parameter) {
void _wifiInitCommands() {
settingsRegisterCommand(F("WIFI"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI"), [](Embedis* e) {
wifiDebug();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
settingsRegisterCommand(F("WIFI.RESET"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.RESET"), [](Embedis* e) {
_wifiConfigure();
wifiDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
settingsRegisterCommand(F("WIFI.AP"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.AP"), [](Embedis* e) {
wifiStartAP();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
#if defined(JUSTWIFI_ENABLE_WPS)
settingsRegisterCommand(F("WIFI.WPS"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.WPS"), [](Embedis* e) {
wifiStartWPS();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
#endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
settingsRegisterCommand(F("WIFI.SMARTCONFIG"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.SMARTCONFIG"), [](Embedis* e) {
wifiStartSmartConfig();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
settingsRegisterCommand(F("WIFI.SCAN"), [](Embedis* e) {
terminalRegisterCommand(F("WIFI.SCAN"), [](Embedis* e) {
_wifiScan();
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
}
@ -469,6 +469,7 @@ void _wifiWebSocketOnAction(uint32_t client_id, const char * action, JsonObject&
void wifiDebug(WiFiMode_t modes) {
#if DEBUG_SUPPORT
bool footer = false;
if (((modes & WIFI_STA) > 0) && ((WiFi.getMode() & WIFI_STA) > 0)) {
@ -509,6 +510,7 @@ void wifiDebug(WiFiMode_t modes) {
if (footer) {
DEBUG_MSG_P(PSTR("[WIFI] ----------------------------------------------\n"));
}
#endif //DEBUG_SUPPORT
}


+ 139
- 65
code/espurna/ws.ino View File

@ -66,8 +66,6 @@ bool _wsAuth(AsyncWebSocketClient * client) {
}
if (index == WS_BUFFER_SIZE) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] Validation check failed\n"));
wsSend_P(client->id(), PSTR("{\"message\": 10}"));
return false;
}
@ -75,6 +73,27 @@ bool _wsAuth(AsyncWebSocketClient * client) {
}
#if DEBUG_WEB_SUPPORT
bool wsDebugSend(const char* prefix, const char* message) {
if (!wsConnected()) return false;
if (getFreeHeap() < (strlen(message) * 3)) return false;
DynamicJsonBuffer jsonBuffer;
JsonObject &root = jsonBuffer.createObject();
JsonObject &weblog = root.createNestedObject("weblog");
weblog.set("message", message);
if (prefix && (prefix[0] != '\0')) {
weblog.set("prefix", prefix);
}
wsSend(root);
return true;
}
#endif
// -----------------------------------------------------------------------------
#if MQTT_SUPPORT
@ -291,84 +310,134 @@ void _wsUpdate(JsonObject& root) {
#endif
}
void _wsDoUpdate(bool reset = false) {
static unsigned long last = 0;
if (reset) {
last = 0;
return;
}
if (millis() - last > WS_UPDATE_INTERVAL) {
last = millis();
wsSend(_wsUpdate);
}
}
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, "desc", 4) == 0) return true;
if (strncmp(key, "webPort", 7) == 0) return true;
return false;
}
void _wsOnStart(JsonObject& root) {
char chipid[7];
snprintf_P(chipid, sizeof(chipid), PSTR("%06X"), ESP.getChipId());
uint8_t * bssid = WiFi.BSSID();
char bssid_str[20];
snprintf_P(bssid_str, sizeof(bssid_str),
PSTR("%02X:%02X:%02X:%02X:%02X:%02X"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]
);
root["webMode"] = WEB_MODE_NORMAL;
root["app_name"] = APP_NAME;
root["app_version"] = APP_VERSION;
root["app_build"] = buildTime();
#if defined(APP_REVISION)
root["app_revision"] = APP_REVISION;
#endif
root["manufacturer"] = MANUFACTURER;
root["chipid"] = String(chipid);
root["mac"] = WiFi.macAddress();
root["bssid"] = String(bssid_str);
root["channel"] = WiFi.channel();
root["device"] = DEVICE;
root["hostname"] = getSetting("hostname");
root["desc"] = getSetting("desc");
root["network"] = getNetwork();
root["deviceip"] = getIP();
root["sketch_size"] = ESP.getSketchSize();
root["free_size"] = ESP.getFreeSketchSpace();
root["sdk"] = ESP.getSdkVersion();
root["core"] = getCoreVersion();
root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt();
root["webPort"] = getSetting("webPort", WEB_PORT).toInt();
root["wsAuth"] = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1;
#if TERMINAL_SUPPORT
root["cmdVisible"] = 1;
#endif
root["hbMode"] = getSetting("hbMode", HEARTBEAT_MODE).toInt();
root["hbInterval"] = getSetting("hbInterval", HEARTBEAT_INTERVAL).toInt();
_wsDoUpdate(true);
}
void wsSend(JsonObject& root) {
size_t len = root.measureLength();
AsyncWebSocketMessageBuffer* buffer = _ws.makeBuffer(len);
if (buffer) {
root.printTo(reinterpret_cast<char*>(buffer->get()), len + 1);
_ws.textAll(buffer);
}
}
void wsSend(uint32_t client_id, JsonObject& root) {
AsyncWebSocketClient* client = _ws.client(client_id);
if (client == nullptr) return;
size_t len = root.measureLength();
AsyncWebSocketMessageBuffer* buffer = _ws.makeBuffer(len);
if (buffer) {
root.printTo(reinterpret_cast<char*>(buffer->get()), len + 1);
client->text(buffer);
}
}
void _wsStart(uint32_t client_id) {
#if USE_PASSWORD && WEB_FORCE_PASS_CHANGE
bool changePassword = getAdminPass().equals(ADMIN_PASS);
#else
bool changePassword = false;
#endif
if (changePassword) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
if (changePassword) {
root["webMode"] = WEB_MODE_PASSWORD;
} else {
char chipid[7];
snprintf_P(chipid, sizeof(chipid), PSTR("%06X"), ESP.getChipId());
uint8_t * bssid = WiFi.BSSID();
char bssid_str[20];
snprintf_P(bssid_str, sizeof(bssid_str),
PSTR("%02X:%02X:%02X:%02X:%02X:%02X"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]
);
root["webMode"] = WEB_MODE_NORMAL;
root["app_name"] = APP_NAME;
root["app_version"] = APP_VERSION;
root["app_build"] = buildTime();
#if defined(APP_REVISION)
root["app_revision"] = APP_REVISION;
#endif
root["manufacturer"] = MANUFACTURER;
root["chipid"] = String(chipid);
root["mac"] = WiFi.macAddress();
root["bssid"] = String(bssid_str);
root["channel"] = WiFi.channel();
root["device"] = DEVICE;
root["hostname"] = getSetting("hostname");
root["network"] = getNetwork();
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["wsAuth"] = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1;
#if TERMINAL_SUPPORT
root["cmdVisible"] = 1;
#endif
wsSend(root);
return;
}
}
void _wsStart(uint32_t client_id) {
for (unsigned char i = 0; i < _ws_on_send_callbacks.size(); i++) {
wsSend(client_id, _ws_on_send_callbacks[i]);
for (auto& callback : _ws_on_send_callbacks) {
callback(root);
}
wsSend(client_id, root);
}
void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
if (type == WS_EVT_CONNECT) {
client->_tempObject = nullptr;
#ifndef NOWSAUTH
if (!_wsAuth(client)) return;
if (!_wsAuth(client)) {
wsSend_P(client->id(), PSTR("{\"message\": 10}"));
DEBUG_MSG_P(PSTR("[WEBSOCKET] Validation check failed\n"));
client->close();
return;
}
#endif
IPAddress ip = client->remoteIP();
@ -401,12 +470,8 @@ void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTy
}
void _wsLoop() {
static unsigned long last = 0;
if (!wsConnected()) return;
if (millis() - last > WS_UPDATE_INTERVAL) {
last = millis();
wsSend(_wsUpdate);
}
_wsDoUpdate();
}
// -----------------------------------------------------------------------------
@ -417,6 +482,10 @@ bool wsConnected() {
return (_ws.count() > 0);
}
bool wsConnected(uint32_t client_id) {
return _ws.hasClient(client_id);
}
void wsOnSendRegister(ws_on_send_callback_f callback) {
_ws_on_send_callbacks.push_back(callback);
}
@ -434,10 +503,8 @@ void wsSend(ws_on_send_callback_f callback) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
callback(root);
String output;
root.printTo(output);
jsonBuffer.clear();
_ws.textAll((char *) output.c_str());
wsSend(root);
}
}
@ -456,13 +523,20 @@ void wsSend_P(PGM_P payload) {
}
void wsSend(uint32_t client_id, ws_on_send_callback_f callback) {
AsyncWebSocketClient* client = _ws.client(client_id);
if (client == nullptr) return;
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
callback(root);
String output;
root.printTo(output);
jsonBuffer.clear();
_ws.text(client_id, (char *) output.c_str());
size_t len = root.measureLength();
AsyncWebSocketMessageBuffer* buffer = _ws.makeBuffer(len);
if (buffer) {
root.printTo(reinterpret_cast<char*>(buffer->get()), len + 1);
client->text(buffer);
}
}
void wsSend(uint32_t client_id, const char * payload) {


+ 5
- 6
code/gulpfile.js View File

@ -27,7 +27,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// -----------------------------------------------------------------------------
const gulp = require('gulp');
const runSequence = require('run-sequence');
const through = require('through2');
const htmlmin = require('gulp-htmlmin');
@ -191,15 +190,15 @@ gulp.task('webui_all', function() {
return buildWebUI('all');
});
gulp.task('webui', function(cb) {
runSequence([
gulp.task('webui',
gulp.parallel(
'webui_small',
'webui_sensor',
'webui_light',
'webui_rfbridge',
'webui_rfm69',
'webui_all'
], cb);
});
)
);
gulp.task('default', ['webui']);
gulp.task('default', gulp.series('webui'));

+ 77
- 41
code/html/custom.js View File

@ -47,7 +47,8 @@ function sensorName(id) {
"Events", "PMSX003", "BMX280", "MHZ19", "SI7021",
"SHT3X I2C", "BH1750", "PZEM004T", "AM2320 I2C", "GUVAS12SD",
"TMP3X", "Sonar", "SenseAir", "GeigerTicks", "GeigerCPM",
"NTC", "SDS011", "MICS2710", "MICS5525"
"NTC", "SDS011", "MICS2710", "MICS5525", "VL53L1X", "VEML6075",
"EZOPH"
];
if (1 <= id && id <= names.length) {
return names[id - 1];
@ -61,10 +62,10 @@ function magnitudeType(type) {
"Current", "Voltage", "Active Power", "Apparent Power",
"Reactive Power", "Power Factor", "Energy", "Energy (delta)",
"Analog", "Digital", "Event",
"PM1.0", "PM2.5", "PM10", "CO2", "Lux", "UV", "Distance" , "HCHO",
"PM1.0", "PM2.5", "PM10", "CO2", "Lux", "UVA", "UVB", "UV Index", "Distance" , "HCHO",
"Local Dose Rate", "Local Dose Rate",
"Count",
"NO2", "CO", "Resistance"
"NO2", "CO", "Resistance", "pH"
];
if (1 <= type && type <= types.length) {
return types[type - 1];
@ -656,7 +657,10 @@ function doScan() {
}
function doHAConfig() {
$("#haConfig").html("");
$("#haConfig")
.text("")
.height(0)
.show();
sendAction("haconfig", {});
return false;
}
@ -766,12 +770,13 @@ function createMagnitudeList(data, container, template_name) {
if (current > 0) { return; }
var template = $("#" + template_name + " .pure-g")[0];
for (var i in data) {
var magnitude = data[i];
var size = data.size;
for (var i=0; i<size; ++i) {
var line = $(template).clone();
$("label", line).html(magnitudeType(magnitude.type) + " #" + parseInt(magnitude.index, 10));
$("div.hint", line).html(magnitude.name);
$("input", line).attr("tabindex", 40 + i).val(magnitude.idx);
$("label", line).html(magnitudeType(data.type[i]) + " #" + parseInt(data.index[i], 10));
$("div.hint", line).html(data.name[i]);
$("input", line).attr("tabindex", 40 + i).val(data.idx[i]);
line.appendTo("#" + container);
}
@ -933,21 +938,33 @@ function createCheckboxes() {
function initRelayConfig(data) {
var current = $("#relayConfig > div").length;
var current = $("#relayConfig > legend").length; // there is a legend per relay
if (current > 0) { return; }
var size = data.size;
var start = data.start;
var template = $("#relayConfigTemplate").children();
for (var i in data) {
var relay = data[i];
for (var i=start; i<size; ++i) {
var line = $(template).clone();
$("span.gpio", line).html(relay.gpio);
$("span.id", line).html(i);
$("select[name='relayBoot']", line).val(relay.boot);
$("select[name='relayPulse']", line).val(relay.pulse);
$("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);
$("span.gpio", line).html(data.gpio[i]);
$("select[name='relayBoot']", line).val(data.boot[i]);
$("select[name='relayPulse']", line).val(data.pulse[i]);
$("input[name='relayTime']", line).val(data.pulse_time[i]);
if ("group" in data) {
$("input[name='mqttGroup']", line).val(data.group[i]);
}
if ("group_inv" in data) {
$("input[name='mqttGroupInv']", line).val(data.group_inv[i]);
}
if ("on_disc" in data) {
$("input[name='relayOnDisc']", line).val(data.on_disc[i]);
}
line.appendTo("#relayConfig");
}
@ -960,17 +977,19 @@ function initRelayConfig(data) {
<!-- removeIf(!sensor)-->
function initMagnitudes(data) {
// check if already initialized
// check if already initialized (each magnitude is inside div.pure-g)
var done = $("#magnitudes > div").length;
if (done > 0) { return; }
var size = data.size;
// add templates
var template = $("#magnitudeTemplate").children();
for (var i in data) {
var magnitude = data[i];
for (var i=0; i<size; ++i) {
var line = $(template).clone();
$("label", line).html(magnitudeType(magnitude.type) + " #" + parseInt(magnitude.index, 10));
$("div.hint", line).html(magnitude.description);
$("label", line).html(magnitudeType(data.type[i]) + " #" + parseInt(data.index[i], 10));
$("div.hint", line).html(data.description[i]);
$("input", line).attr("data", i);
line.appendTo("#magnitudes");
}
@ -1199,11 +1218,18 @@ function processData(data) {
}
if ("rfb" === key) {
var nodes = data.rfb;
for (i in nodes) {
var node = nodes[i];
$("input[name='rfbcode'][data-id='" + node.id + "'][data-status='" + node.status + "']").val(node.data);
var rfb = data.rfb;
var size = data.size;
var start = data.start;
var on = rfb["on"];
var off = rfb["off"];
for (var i=start; i<start+size; ++i) {
$("input[name='rfbcode'][data-id='" + i + "'][data-status='1']").val(on[i]);
$("input[name='rfbcode'][data-id='" + i + "'][data-status='0']").val(off[i]);
}
return;
}
<!-- endRemoveIf(!rfbridge)-->
@ -1318,15 +1344,13 @@ function processData(data) {
if ("magnitudes" === key) {
initMagnitudes(value);
for (i in value) {
var magnitude = value[i];
var error = magnitude.error || 0;
for (var i=0; i<value.size; ++i) {
var error = value.error[i] || 0;
var text = (0 === error) ?
magnitude.value + magnitude.units :
value.value[i] + value.units[i] :
magnitudeError(error);
var element = $("input[name='magnitude'][data='" + i + "']");
element.val(text);
$("div.hint", element.parent().parent()).html(magnitude.description);
}
return;
}
@ -1363,7 +1387,11 @@ function processData(data) {
// -----------------------------------------------------------------------------
if ("haConfig" === key) {
$("#haConfig").show();
websock.send("{}");
$("#haConfig")
.append(new Text(value))
.height($("#haConfig")[0].scrollHeight);
return;
}
// -----------------------------------------------------------------------------
@ -1375,13 +1403,13 @@ function processData(data) {
return;
}
if ("schedule" === key) {
for (i in value) {
var schedule = value[i];
var sch_line = addSchedule({ data: {schType: schedule["schType"] }});
if ("schedules" === key) {
for (var i=0; i<value.size; ++i) {
var sch_line = addSchedule({ data: {schType: value.schType[i] }});
Object.keys(schedule).forEach(function(key) {
var sch_value = schedule[key];
Object.keys(value).forEach(function(key) {
if ("size" == key) return;
var sch_value = value[key][i];
$("input[name='" + key + "']", sch_line).val(sch_value);
$("select[name='" + key + "']", sch_line).prop("value", sch_value);
$("input[type='checkbox'][name='" + key + "']", sch_line).prop("checked", sch_value);
@ -1456,7 +1484,13 @@ function processData(data) {
// Web log
if ("weblog" === key) {
$("#weblog").append(new Text(value));
websock.send("{}");
if (value.prefix) {
$("#weblog").append(new Text(value.prefix));
}
$("#weblog").append(new Text(value.message));
$("#weblog").scrollTop($("#weblog")[0].scrollHeight - $("#weblog").height());
return;
}
@ -1709,6 +1743,8 @@ $(function() {
$(document).on("change", "input", hasChanged);
$(document).on("change", "select", hasChanged);
$("textarea").on("dblclick", function() { this.select(); });
// don't autoconnect when opening from filesystem
if (window.location.protocol === "file:") { return; }


+ 106
- 16
code/html/index.html View File

@ -84,6 +84,7 @@
<span class="pure-menu-heading" name="hostname">HOSTNAME</span>
<span class="pure-menu-heading small" name="title">ESPurna 0.0.0</span>
<span class="pure-menu-heading small" name="desc"></span>
<ul class="pure-menu-list">
@ -127,6 +128,10 @@
<a href="#" class="pure-menu-link" data="panel-mqtt">MQTT</a>
</li>
<li class="pure-menu-item module module-nofuss">
<a href="#" class="pure-menu-link" data="panel-nofuss">NOFUSS</a>
</li>
<li class="pure-menu-item module module-ntp">
<a href="#" class="pure-menu-link" data="panel-ntp">NTP</a>
</li>
@ -338,6 +343,16 @@
</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Description</label>
<input name="desc" class="pure-u-1 pure-u-lg-3-4" maxlength="64" type="text" tabindex="2" />
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
Human-friendly name for your device. Will be reported with the heartbeat.<br />
You can use this to specify the location or some other identification information.
</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Double click delay</label>
<input name="btnDelay" class="pure-u-1 pure-u-lg-1-4" type="number" action="reboot" min="0" step="100" max="1000" tabindex="6" />
@ -608,17 +623,36 @@
<div class="pure-u-1 pure-u-lg-3-4 hint">Turn ON to be able to telnet to your device while connected to your home router.<br />TELNET is always enabled in AP mode.</div>
</div>
<div class="pure-g module module-telnet">
<label class="pure-u-1 pure-u-lg-1-4">TELNET Password</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="telnetAuth" /></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">Request password when starting telnet session</div>
</div>
<div class="pure-g module module-nofuss">
<label class="pure-u-1 pure-u-lg-1-4">Automatic remote updates (NoFUSS)</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="nofussEnabled" /></div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Heartbeat message</label>
<select class="pure-u-1 pure-u-lg-3-4" name="hbMode" tabindex="15" >
<option value="0">Disabled</option>
<option value="1">On device startup</option>
<option value="2">Repeat after defined interval</option>
<option value="3">Repeat only device status</option>
</select>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
Define when heartbeat message will be sent.
</div>
</div>
<div class="pure-g module module-nofuss">
<label class="pure-u-1 pure-u-lg-1-4">NoFUSS server</label>
<input name="nofussServer" class="pure-u-1 pure-u-lg-3-4" type="text" tabindex="15" />
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Heartbeat interval</label>
<input name="hbInterval" class="pure-u-1 pure-u-lg-1-4" type="number" tabindex="16" />
<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 address of the NoFUSS server for automatic remote updates (see https://bitbucket.org/xoseperez/nofuss).</div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
This is the interval in <strong>seconds</strong> how often to send the heartbeat message.
</div>
</div>
<div class="pure-g">
@ -630,7 +664,7 @@
<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" />
<input name="upgrade" type="file" tabindex="17" />
</div>
</fieldset>
@ -652,6 +686,11 @@
<legend>General</legend>
<div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Device hostname can be configured on the GENERAL tab.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Scan networks</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="wifiScan" tabindex="1" /></div>
@ -815,6 +854,11 @@
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">MQTT User</label>
<input class="pure-u-1 pure-u-lg-1-4" name="mqttUser" type="text" tabindex="23" placeholder="Leave blank if no user" autocomplete="off" />
<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">
You can use the following placeholders: {hostname}, {mac}
</div>
</div>
<div class="pure-g">
@ -829,7 +873,7 @@
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
If left empty the firmware will generate a client ID based on the serial number of the chip.
If left empty the firmware will generate a client ID based on the serial number of the chip. You can use the following placeholders: {hostname}, {mac}
</div>
</div>
@ -907,6 +951,33 @@
</div>
</form>
<form id="form-nofuss" class="pure-form form-settings">
<div class="panel" id="panel-nofuss">
<div class="header">
<h1>NoFUSS</h1>
<h2>Automatically upgrade the firmware (see <a href="https://bitbucket.org/xoseperez/nofuss">xoseperez/nofuss</a> for details)</h2>
</div>
<div class="page">
<fieldset>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Enable</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="nofussEnabled" /></div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Server</label>
<input name="nofussServer" class="pure-u-1 pure-u-lg-3-4" type="text" tabindex="15" />
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Address of the NoFUSS server</div>
</div>
</fieldset>
</div>
</div>
</form>
<form id="form-ntp" class="pure-form form-settings">
<div class="panel" id="panel-ntp">
@ -1048,7 +1119,7 @@
</div>
</div>
<div class="pure-g">
<span class="pure-u-1 terminal" id="haConfig" name="haConfig"></span>
<textarea class="pure-u-1 terminal" id="haConfig" name="haConfig" wrap="off" readonly></textarea>
</div>
@ -1079,9 +1150,21 @@
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="tspkEnabled" tabindex="30" /></div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Clear cache</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="tspkClear" tabindex="31" /></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">
With every POST to thinkspeak.com only enqueued fields are sent.
If you select to clear the cache after every sending this will result in only those fields that have changed will be posted.
If you want all fields to be sent with every POST do not clear the cache.
</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Thingspeak API Key</label>
<input class="pure-u-1 pure-u-lg-3-4" name="tspkKey" type="text" tabindex="31" />
<input class="pure-u-1 pure-u-lg-3-4" name="tspkKey" type="text" tabindex="32" />
</div>
<legend>Sensors &amp; actuators</legend>
@ -1329,20 +1412,27 @@
<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 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 its nominal power or use a multimeter.</div>
</div>
<div class="pure-g module module-pm">
<label class="pure-u-1 pure-u-lg-1-4">Energy Ratio</label>
<input class="pure-u-1 pure-u-lg-3-4" name="pwrRatioE" type="text" tabindex="55" placeholder="0" />
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Energy ratio in pulses/kWh.</div>
</div>
<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-1 pure-u-lg-1-4"><input type="checkbox" name="pwrResetCalibration" 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 revert to factory calibration values.</div>
</div>
<div class="pure-g module module-hlw module-cse module-emon">
<div class="pure-g module module-hlw module-cse module-emon module-pm">
<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-1 pure-u-lg-1-4"><input type="checkbox" name="pwrResetE" tabindex="57" /></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>
@ -1518,7 +1608,7 @@
</div>
<div id="relayConfigTemplate" class="template">
<legend>Switch #<span class="id"></span> (GPIO<span class="gpio"></span>)</legend>
<legend>Switch #<span class="id"></span> (<span class="gpio"></span>)</legend>
<div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Boot mode</label></div>
<select class="pure-u-1 pure-u-lg-3-4" name="relayBoot">


+ 27
- 25
code/memanalyzer.py View File

@ -19,15 +19,16 @@ import argparse
import os
import re
import shlex
import subprocess
import sys
from collections import OrderedDict
from sortedcontainers import SortedDict
import subprocess
if (sys.version_info > (3, 0)):
from subprocess import getstatusoutput as getstatusoutput
if sys.version_info > (3, 0):
from subprocess import getstatusoutput
else:
from commands import getstatusoutput as getstatusoutput
from commands import getstatusoutput
# -------------------------------------------------------------------------------
@ -55,7 +56,7 @@ def file_size(file):
def analyse_memory(elf_file):
command = "%s -t '%s'" % (objdump_binary, elf_file)
command = "{} -t '{}'".format(objdump_binary, elf_file)
response = subprocess.check_output(shlex.split(command))
if isinstance(response, bytes):
response = response.decode('utf-8')
@ -66,8 +67,8 @@ def analyse_memory(elf_file):
ret = {}
for (id_, _) in list(sections.items()):
section_start_token = " _%s_start" % id_
section_end_token = " _%s_end" % id_
section_start_token = " _{}_start".format(id_)
section_end_token = " _{}_end".format(id_)
section_start = -1
section_end = -1
for line in lines:
@ -92,24 +93,26 @@ def analyse_memory(elf_file):
# print("{0: >10}|{1: >30}|{2:12X}|{3:12X}|{4:8}".format(id_, descr, section_start, section_end, section_length))
# i += 1
# print("Total Used RAM : %d" % usedRAM)
# print("Free RAM : %d" % (TOTAL_DRAM - usedRAM))
# print("Free IRam : %d" % usedIRAM)
# print("Total Used RAM : {:d}".format(usedRAM))
# print("Free RAM : {:d}".format(TOTAL_DRAM - usedRAM))
# print("Free IRam : {:d}".format(usedIRAM))
return ret
def run(env_, modules_):
flags = ""
for item in modules_.items():
flags += "-D%s_SUPPORT=%d " % item
command = "ESPURNA_BOARD=\"WEMOS_D1_MINI_RELAYSHIELD\" ESPURNA_FLAGS=\"%s\" platformio run --silent --environment %s 2>/dev/null" % (flags, env_)
for k, v in modules_.items():
flags += "-D{}_SUPPORT={:d} ".format(k, v)
command = "ESPURNA_BOARD=\"WEMOS_D1_MINI_RELAYSHIELD\" ESPURNA_FLAGS=\"{}\" platformio run --silent --environment {} 2>/dev/null".format(flags, env_)
subprocess.check_call(command, shell=True)
def calc_free(module):
free = 80 * 1024 - module['data'] - module['rodata'] - module['bss']
free = free + (16 - free % 16)
module['free'] = free
def modules_get():
modules_ = SortedDict()
for line in open("espurna/config/arduino.h"):
@ -120,7 +123,8 @@ def modules_get():
del modules_['NETBIOS']
return modules_
try:
if __name__ == '__main__':
# Parse command line options
parser = argparse.ArgumentParser(description=description)
@ -160,7 +164,7 @@ try:
# Check test modules exist
for module in test_modules:
if module not in available_modules:
print("Module %s not found" % module)
print("Module {} not found".format(module))
sys.exit(2)
# Define base configuration
@ -173,9 +177,9 @@ try:
# Show init message
if len(test_modules) > 0:
print("Analyzing module(s) %s on top of %s configuration\n" % (", ".join(test_modules), "CORE" if args.core > 0 else "DEFAULT"))
print("Analyzing module(s) {} on top of {} configuration\n".format(", ".join(test_modules), "CORE" if args.core > 0 else "DEFAULT"))
else:
print("Analyzing %s configuration\n" % ("CORE" if args.core > 0 else "DEFAULT"))
print("Analyzing {} configuration\n".format("CORE" if args.core > 0 else "DEFAULT"))
output_format = "{:<20}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}"
print(output_format.format(
@ -211,8 +215,8 @@ try:
# Build the core without modules to get base memory usage
run(env, modules)
base = analyse_memory(".pioenvs/%s/firmware.elf" % env)
base['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
base = analyse_memory(".pioenvs/{}/firmware.elf".format(env))
base['size'] = file_size(".pioenvs/{}/firmware.bin".format(env))
calc_free(base)
print(output_format.format(
"CORE" if args.core == 1 else "DEFAULT",
@ -231,8 +235,8 @@ try:
modules[module] = 1
run(env, modules)
results[module] = analyse_memory(".pioenvs/%s/firmware.elf" % env)
results[module]['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
results[module] = analyse_memory(".pioenvs/{}/firmware.elf".format(env))
results[module]['size'] = file_size(".pioenvs/{}/firmware.bin".format(env))
calc_free(results[module])
modules[module] = 0
@ -253,8 +257,8 @@ try:
for module in test_modules:
modules[module] = 1
run(env, modules)
total = analyse_memory(".pioenvs/%s/firmware.elf" % env)
total['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
total = analyse_memory(".pioenvs/{}/firmware.elf".format(env))
total['size'] = file_size(".pioenvs/{}/firmware.bin".format(env))
calc_free(total)
if len(test_modules) > 1:
@ -279,7 +283,5 @@ try:
total['size'],
))
except:
raise
print("\n")

+ 59
- 48
code/ota.py View File

@ -8,14 +8,14 @@
# -------------------------------------------------------------------------------
from __future__ import print_function
import shutil
import argparse
import os
import re
import shutil
import socket
import subprocess
import sys
import time
import os
from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf
@ -33,6 +33,7 @@ description = "ESPurna OTA Manager v0.3"
devices = []
discover_last = 0
# -------------------------------------------------------------------------------
def on_service_state_change(zeroconf, service_type, name, state_change):
@ -55,13 +56,13 @@ def on_service_state_change(zeroconf, service_type, name, state_change):
'app_name': '',
'app_version': '',
'target_board': '',
'mem_size': '',
'sdk_size': '',
'free_space': '',
'mem_size': 0,
'sdk_size': 0,
'free_space': 0,
}
for key, item in info.properties.items():
device[key.decode('UTF-8')] = item.decode('UTF-8');
device[key.decode('UTF-8')] = item.decode('UTF-8')
# rename fields (needed for sorting by name)
device['app'] = device['app_name']
@ -75,35 +76,35 @@ def list_devices():
"""
Shows the list of discovered devices
"""
output_format="{:>3} {:<14} {:<15} {:<17} {:<12} {:<8} {:<25} {:<8} {:<8} {:<10}"
output_format = "{:>3} {:<14} {:<15} {:<17} {:<12} {:<12} {:<25} {:<8} {:<8} {:<10}"
print(output_format.format(
"#",
"HOSTNAME",
"IP",
"MAC",
"APP",
"VERSION",
"DEVICE",
"MEM_SIZE",
"SDK_SIZE",
"FREE_SPACE"
"#",
"HOSTNAME",
"IP",
"MAC",
"APP",
"VERSION",
"DEVICE",
"MEM_SIZE",
"SDK_SIZE",
"FREE_SPACE"
))
print("-" * 139)
index = 0
for device in devices:
index = index + 1
index += 1
print(output_format.format(
index,
device.get('hostname', ''),
device.get('ip', ''),
device.get('mac', ''),
device.get('app_name', ''),
device.get('app_version', ''),
device.get('target_board', ''),
device.get('mem_size', ''),
device.get('sdk_size', ''),
device.get('free_space', ''),
index,
device.get('hostname', ''),
device.get('ip', ''),
device.get('mac', ''),
device.get('app_name', ''),
device.get('app_version', ''),
device.get('target_board', ''),
device.get('mem_size', 0),
device.get('sdk_size', 0),
device.get('free_space', 0),
))
print()
@ -120,11 +121,13 @@ def get_boards():
boards.append(m.group(1))
return sorted(boards)
def get_device_size(device):
if device.get('mem_size', 0) == device.get('sdk_size', 0):
return int(device.get('mem_size', 0)) / 1024
return 0
def get_empty_board():
"""
Returns the empty structure of a board to flash
@ -132,12 +135,13 @@ def get_empty_board():
board = {'board': '', 'ip': '', 'size': 0, 'auth': '', 'flags': ''}
return board
def get_board_by_index(index):
"""
Returns the required data to flash a given board
"""
board = {}
if 1 <= index and index <= len(devices):
if 1 <= index <= len(devices):
device = devices[index - 1]
board['hostname'] = device.get('hostname')
board['board'] = device.get('target_board', '')
@ -145,6 +149,7 @@ def get_board_by_index(index):
board['size'] = get_device_size(device)
return board
def get_board_by_mac(mac):
"""
Returns the required data to flash a given board
@ -161,6 +166,7 @@ def get_board_by_mac(mac):
return board
return None
def get_board_by_hostname(hostname):
"""
Returns the required data to flash a given board
@ -178,6 +184,7 @@ def get_board_by_hostname(hostname):
return board
return None
def input_board():
"""
Grabs info from the user about what device to flash
@ -189,10 +196,10 @@ def input_board():
except ValueError:
index = 0
if index < 0 or len(devices) < index:
print("Board number must be between 1 and %s\n" % str(len(devices)))
print("Board number must be between 1 and {}\n".format(str(len(devices))))
return None
board = get_board_by_index(index);
board = get_board_by_index(index)
# Choose board type if none before
if len(board.get('board', '')) == 0:
@ -201,15 +208,15 @@ def input_board():
count = 1
boards = get_boards()
for name in boards:
print("%3d\t%s" % (count, name))
count = count + 1
print("{:3d}\t{}".format(count, name))
count += 1
print()
try:
index = int(input("Choose the board type you want to flash: "))
except ValueError:
index = 0
if index < 1 or len(boards) < index:
print("Board number must be between 1 and %s\n" % str(len(boards)))
print("Board number must be between 1 and {}\n".format(str(len(boards))))
return None
board['board'] = boards[index - 1]
@ -227,12 +234,14 @@ def input_board():
return board
def boardname(board):
return board.get('hostname', board['ip'])
def store(device, env):
source = ".pioenvs/%s/firmware.elf" % env
destination = ".pioenvs/elfs/%s.elf" % boardname(device).lower()
source = ".pioenvs/{}/firmware.elf".format(env)
destination = ".pioenvs/elfs/{}.elf".format(boardname(device).lower())
dst_dir = os.path.dirname(destination)
if not os.path.exists(dst_dir):
@ -240,6 +249,7 @@ def store(device, env):
shutil.move(source, destination)
def run(device, env):
print("Building and flashing image over-the-air...")
environ = os.environ.copy()
@ -253,6 +263,7 @@ def run(device, env):
store(device, env)
# -------------------------------------------------------------------------------
if __name__ == '__main__':
@ -272,13 +283,13 @@ if __name__ == '__main__':
print(description)
print()
# Look for sevices
# Look for services
zeroconf = Zeroconf()
browser = ServiceBrowser(zeroconf, "_arduino._tcp.local.", handlers=[on_service_state_change])
discover_last = time.time()
while time.time() < discover_last + DISCOVER_TIMEOUT:
None
#zeroconf.close()
pass
# zeroconf.close()
if len(devices) == 0:
print("Nothing found!\n")
@ -287,7 +298,7 @@ if __name__ == '__main__':
# Sort list
field = args.sort.lower()
if field not in devices[0]:
print("Unknown field '%s'\n" % field)
print("Unknown field '{}'\n".format(field))
sys.exit(1)
devices = sorted(devices, key=lambda device: device.get(field, ''))
@ -322,23 +333,23 @@ if __name__ == '__main__':
queue = sorted(queue, key=lambda device: device.get('board', ''))
# Flash eash board
# Flash each board
for board in queue:
# Flash core version?
if args.core > 0:
board['flags'] = "-DESPURNA_CORE " + board['flags']
env = "esp8266-%dm-ota" % board['size']
env = "esp8266-{:d}m-ota".format(board['size'])
# Summary
print()
print("HOST = %s" % boardname(board))
print("IP = %s" % board['ip'])
print("BOARD = %s" % board['board'])
print("AUTH = %s" % board['auth'])
print("FLAGS = %s" % board['flags'])
print("ENV = %s" % env)
print("HOST = {}".format(boardname(board)))
print("IP = {}".format(board['ip']))
print("BOARD = {}".format(board['board']))
print("AUTH = {}".format(board['auth']))
print("FLAGS = {}".format(board['flags']))
print("ENV = {}".format(env))
response = True
if args.yes == 0:


+ 2205
- 1381
code/package-lock.json
File diff suppressed because it is too large
View File


+ 2
- 5
code/package.json View File

@ -1,12 +1,11 @@
{
"name": "esp8266-filesystem-builder",
"version": "0.2.2",
"description": "Gulp based build system for ESP8266 file system files",
"main": "gulpfile.js",
"author": "Xose Pérez <xose.perez@gmail.com>",
"license": "GPL-3.0",
"devDependencies": {
"gulp": "^3.9.1",
"gulp": "^4.0.0",
"gulp-base64-favicon": "^1.0.2",
"gulp-crass": "^0.2.2",
"gulp-css-base64": "^1.3.4",
@ -17,8 +16,6 @@
"gulp-inline": "^0.1.1",
"gulp-remove-code": "^3.0.4",
"gulp-rename": "^1.4.0",
"gulp-replace": "^1.0.0",
"map-stream": "0.0.7",
"run-sequence": "^2.2.1"
"gulp-replace": "^1.0.0"
}
}

+ 470
- 19
code/platformio.ini View File

@ -7,19 +7,20 @@ data_dir = espurna/data
# ------------------------------------------------------------------------------
# PLATFORM:
# !! DO NOT confuse platformio's ESP8266 development platform with Arduino core for ESP8266
# We use platformIO 1.5.0 as default
# We use Arduino Core 2.3.0 (platformIO 1.5.0) as default
#
# platformIO 1.5.0 = arduino core 2.3.0
# platformIO 1.6.0 = arduino core 2.4.0
# platformIO 1.7.3 = arduino core 2.4.1
# platformIO 1.8.0 = arduino core 2.4.2
# arduino core 2.3.0 = platformIO 1.5.0
# arduino core 2.4.0 = platformIO 1.6.0
# arduino core 2.4.1 = platformIO 1.7.3
# arduino core 2.4.2 = platformIO 1.8.0
# ------------------------------------------------------------------------------
platform_150 = espressif8266@1.5.0
platform_160 = espressif8266@1.6.0
platform_173 = espressif8266@1.7.3
platform_180 = espressif8266@1.8.0
platform_latest = ${common.platform_180}
platform = ${common.platform_150}
arduino_core_2_3_0 = espressif8266@1.5.0
arduino_core_2_4_0 = espressif8266@1.6.0
arduino_core_2_4_1 = espressif8266@1.7.3
arduino_core_2_4_2 = espressif8266@1.8.0
platform = ${common.arduino_core_2_3_0}
platform_latest = ${common.arduino_core_2_4_2}
# ------------------------------------------------------------------------------
# FLAGS: DEBUG
@ -33,6 +34,7 @@ debug_flags = -DDEBUG_ESP_CORE -DDEBUG_ESP_SSL -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP
# build flags for 512k/1m ESP's are different because we use Linker Scripts to adjust flash split
# build_flags_512k ( 512 KB) = 487 KB sketch, 4 KB eeprom, 16 KB reserved
# build_flags_1m0m (1024 KB) = 999 KB sketch, 4 KB eeprom, 16 KB reserved
# build_flags_2m1m (2048 KB) = 1019 KB sketch, 16 KB eeprom, 992 KB spiffs, 16 KB reserved
# build_flags_4m1m (4096 KB) = 1019 KB sketch, 16 KB eeprom, 992 KB spiffs, 16 KB reserved, 2048 KB empty/ota?
# build_flags_4m3m (4096 KB) = 1019 KB sketch, 16 KB eeprom, 3040 KB spiffs, 16 KB reserved
#
@ -41,7 +43,7 @@ debug_flags = -DDEBUG_ESP_CORE -DDEBUG_ESP_SSL -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP
# -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY = v2 Lower Memory
# -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH = v2 Higher Bandwidth
# ------------------------------------------------------------------------------
build_flags = -g -w -DMQTT_MAX_PACKET_SIZE=400 -DNO_GLOBAL_EEPROM ${sysenv.ESPURNA_FLAGS} -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH
build_flags = -g -w -DMQTT_MAX_PACKET_SIZE=400 -DNO_GLOBAL_EEPROM ${sysenv.ESPURNA_FLAGS} -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH
build_flags_512k = ${common.build_flags} -Wl,-Teagle.flash.512k0m1s.ld
build_flags_1m0m = ${common.build_flags} -Wl,-Teagle.flash.1m0m1s.ld
build_flags_2m1m = ${common.build_flags} -Wl,-Teagle.flash.2m1m4s.ld
@ -75,14 +77,14 @@ lib_deps =
ArduinoJson
https://github.com/marvinroger/async-mqtt-client#v0.8.1
Brzo I2C
https://github.com/xoseperez/debounceevent.git#2.0.4
https://github.com/xoseperez/eeprom_rotate#0.9.1
https://github.com/xoseperez/debounceevent.git#2.0.5
https://github.com/xoseperez/eeprom_rotate#0.9.2
Embedis
Encoder
https://github.com/plerup/espsoftwareserial#3.4.1
https://github.com/me-no-dev/ESPAsyncTCP#55cd520
https://github.com/me-no-dev/ESPAsyncWebServer#05306e4
https://bitbucket.org/xoseperez/fauxmoesp.git#3.0.1
https://bitbucket.org/xoseperez/fauxmoesp.git#3.1.0
https://github.com/xoseperez/hlw8012.git#1.1.0
https://github.com/markszabo/IRremoteESP8266#v2.2.0
https://github.com/xoseperez/justwifi.git#2.0.2
@ -97,6 +99,9 @@ lib_deps =
https://github.com/LowPowerLab/RFM69#1.1.3
https://github.com/xoseperez/Time
NewPing
https://github.com/sparkfun/SparkFun_VEML6075_Arduino_Library#V_1.0.3
https://github.com/pololu/vl53l1x-arduino#1.0.1
https://github.com/mcleng/MAX6675-Library#2.0.1
lib_ignore =
# ------------------------------------------------------------------------------
@ -430,6 +435,17 @@ build_flags = ${common.build_flags_1m0m} -DITEAD_SONOFF_BASIC -DDHT_SUPPORT=1
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:itead-sonoff-basic-r2-dht]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DITEAD_SONOFF_BASIC -DDHT_SUPPORT=1 -DDHT_PIN=2
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:itead-sonoff-basic-dht-ota]
platform = ${common.platform}
framework = ${common.framework}
@ -455,6 +471,17 @@ build_flags = ${common.build_flags_1m0m} -DITEAD_SONOFF_BASIC -DDALLAS_SUPPORT=1
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:itead-sonoff-basic-r2-dallas]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DITEAD_SONOFF_BASIC -DDALLAS_SUPPORT=1 -DDALLAS_PIN=2
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:itead-sonoff-rf]
platform = ${common.platform}
framework = ${common.framework}
@ -1202,6 +1229,31 @@ upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:lyasi-rgb-light]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DLYASI_LIGHT
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:lyasi-rgb-light-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DLYASI_LIGHT
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:magichome-led-controller]
platform = ${common.platform}
framework = ${common.framework}
@ -1251,6 +1303,54 @@ upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:magichome-zj-wfmn-a-11]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DMAGICHOME_ZJ_WFMN_A_11
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:magichome-zj-wfmn-a-11-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DMAGICHOME_ZJ_WFMN_A_11
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:magichome-zj-wfmn-b-11]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DMAGICHOME_ZJ_WFMN_B_11
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:magichome-zj-wfmn-b-11-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DMAGICHOME_ZJ_WFMN_B_11
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:huacanxing-h801]
platform = ${common.platform}
framework = ${common.framework}
@ -1351,6 +1451,31 @@ upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:arilux-al-lc02-v14]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DARILUX_AL_LC02_V14
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:arilux-al-lc02-v14-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DARILUX_AL_LC02_V14
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:arilux-al-lc06]
platform = ${common.platform}
framework = ${common.framework}
@ -1889,6 +2014,31 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:euromate-wifi-stecker-shuko-v2]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_2m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_2m1m} -DEUROMATE_WIFI_STECKER_SCHUKO_V2
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:euromate-wifi-stecker-shuko-v2-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_2m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_2m1m} -DEUROMATE_WIFI_STECKER_SCHUKO_V2
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:tonbux-powerstrip02]
platform = ${common.platform}
framework = ${common.framework}
@ -2010,6 +2160,30 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:maxcio-wde004]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DMAXCIO_WDE004
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:maxcio-wde004-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DMAXCIO_WDE004
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:yidian-xsssa05]
platform = ${common.platform}
framework = ${common.framework}
@ -2034,6 +2208,55 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:oukitel-p1]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DOUKITEL_P1
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:oukitel-p1-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DOUKITEL_P1
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:tonbux-xsssa01]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_4m1m} -DTONBUX_XSSSA01
upload_speed = ${common.upload_speed_fast}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:tonbux-xsssa01-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_4m1m} -DTONBUX_XSSSA01
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:tonbux-xsssa06]
platform = ${common.platform}
framework = ${common.framework}
@ -2253,6 +2476,55 @@ upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:lombex-lux-nova2-tunable-white]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DLOMBEX_LUX_NOVA2_TUNABLE_WHITE
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:lombex-lux-nova2-tunable-white-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DLOMBEX_LUX_NOVA2_TUNABLE_WHITE
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:lombex-lux-nova2-white-color]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DLOMBEX_LUX_NOVA2_WHITE_COLOR
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:lombex-lux-nova2-white-color-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DLOMBEX_LUX_NOVA2_WHITE_COLOR
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
# ------------------------------------------------------------------------------
# GENERIC OTA ENVIRONMENTS
@ -2269,6 +2541,17 @@ build_flags = ${common.build_flags_1m0m} -DGENERIC_ESP01S_RELAY_V40
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:generic-esp01s-relay-40-inv]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGENERIC_ESP01S_RELAY_V40 -DRELAY1_TYPE=1
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:generic-esp01s-relay-40-ota]
platform = ${common.platform}
framework = ${common.framework}
@ -2282,6 +2565,19 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:generic-esp01s-relay-40-inv-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGENERIC_ESP01S_RELAY_V40 -DRELAY1_TYPE=1
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:generic-esp01s-rgbled-10]
platform = ${common.platform}
framework = ${common.framework}
@ -2476,26 +2772,130 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:blitzwolf-bwshp2]
[env:blitzwolf-bwshpx]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHP2
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHPX
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:blitzwolf-bwshp2-ota]
[env:blitzwolf-bwshpx-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHP2
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHPX
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:blitzwolf-bwshpx-v23]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHPX_V23
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:blitzwolf-bwshpx-v23-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHPX_V23
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:teckin-sp22-v14]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DTECKIN_SP22_V14
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:teckin-sp22-v14-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DTECKIN_SP22_V14
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:gosund-ws1]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGOSUND_WS1
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:gosund-ws1-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGOSUND_WS1
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:gosund-sp1-v23]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGOSUND_SP1_V23
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:gosund-sp1-v23-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGOSUND_SP1_V23
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
@ -2727,3 +3127,54 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:gblife-rgbw-socket]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGBLIFE_RGBW_SOCKET
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:gblife-rgbw-socket-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGBLIFE_RGBW_SOCKET
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:smartlife-mini-smart-socket]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DSMARTLIFE_MINI_SMART_SOCKET
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:smartlife-mini-smart-socket-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DSMARTLIFE_MINI_SMART_SOCKET
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}

BIN
images/devices/lombex-lux-nova-flash-2.jpg View File

Before After
Width: 2940  |  Height: 1724  |  Size: 1022 KiB

BIN
images/devices/lombex-lux-nova-flash.jpg View File

Before After
Width: 2718  |  Height: 2751  |  Size: 1.5 MiB

BIN
images/devices/lombex-lux-nova.jpg View File

Before After
Width: 300  |  Height: 400  |  Size: 5.6 KiB

BIN
images/devices/schuko-wifi-plug-v2.jpg View File

Before After
Width: 415  |  Height: 415  |  Size: 12 KiB

BIN
images/devices/tonbux-xs-ssa01.jpg View File

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

+ 27
- 11
pre-commit View File

@ -15,16 +15,18 @@ Copy this file to .git/hooks/
"""
import os
import sys
import re
import string
import sys
from subprocess import call, check_output
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse
from fileinput import FileInput
# https://github.com/python/cpython/commit/6cb7b659#diff-78790b53ff259619377058acd4f74672
if sys.version_info[0] < 3:
class FileInputCtx(FileInput):
@ -34,9 +36,18 @@ if sys.version_info[0] < 3:
def __exit__(self, type, value, traceback):
self.close()
FileInput = FileInputCtx
class CustomFormatter(string.Formatter):
def format_field(self, value, spec):
if spec == "escape_hyphen":
return value.replace("-", "--")
else:
return super(CustomFormatter, self).format_field(value, spec)
def run(cmd, cwd=None):
out = check_output(cmd, cwd=cwd)
out = out.decode("latin1").strip()
@ -79,17 +90,18 @@ def espurna_get_version(base, version_h="code/espurna/config/version.h"):
return version
TEMPLATES = {
"![travis]": "[![travis](https://travis-ci.org/{USER}/{REPO}.svg?branch={BRANCH})]" \
"(https://travis-ci.org/{USER}/{REPO})\n",
"![version]": "[![version](https://img.shields.io/badge/version-{VERSION}-brightgreen.svg)](CHANGELOG.md)\n",
"![branch]": "[![branch](https://img.shields.io/badge/branch-{BRANCH}-orange.svg)]" \
"(https://github.com/{USER}/{REPO}/tree/{BRANCH}/)\n"
"![travis]": "[![travis](https://travis-ci.org/{USER}/{REPO}.svg?branch={BRANCH})]"
"(https://travis-ci.org/{USER}/{REPO})\n",
"![version]": "[![version](https://img.shields.io/badge/version-{VERSION:escape_hyphen}-brightgreen.svg)]"
"(CHANGELOG.md)\n",
"![branch]": "[![branch](https://img.shields.io/badge/branch-{BRANCH:escape_hyphen}-orange.svg)]"
"(https://github.com/{USER}/{REPO}/tree/{BRANCH}/)\n",
}
README = "README.md"
if __name__ == "__main__":
base = os.getcwd()
@ -98,13 +110,16 @@ if __name__ == "__main__":
"USER": user,
"REPO": repo,
"BRANCH": git_branch(),
"VERSION": espurna_get_version(base)
"VERSION": espurna_get_version(base),
}
formatter = CustomFormatter()
templates = [
(k, tmpl.format(**fmt))
(k, formatter.format(tmpl, **fmt))
for k, tmpl in TEMPLATES.items()
]
def fmt_line(line):
for match, tmpl in templates:
if match in line:
@ -112,6 +127,7 @@ if __name__ == "__main__":
return line
path = os.path.join(base, README)
with FileInput(path, inplace=True) as readme:
@ -121,4 +137,4 @@ if __name__ == "__main__":
if call(["git", "add", README]):
sys.exit(1)
sys.exit(0);
sys.exit(0)

Loading…
Cancel
Save