Browse Source

Merge pull request #1 from xoseperez/dev

merging back from Xose
rules-rpn
Colin Shorts 5 years ago
committed by GitHub
parent
commit
6c2d191df9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
161 changed files with 30788 additions and 21217 deletions
  1. +2
    -0
      .gitattributes
  2. +48
    -0
      .github/ISSUE_TEMPLATE/bug_report.md
  3. +26
    -0
      .github/ISSUE_TEMPLATE/feature_request.md
  4. +42
    -0
      .github/ISSUE_TEMPLATE/questions---troubleshooting.md
  5. +1
    -1
      .travis.yml
  6. +193
    -0
      CHANGELOG.md
  7. +16
    -0
      CONTRIBUTING.md
  8. +113
    -40
      README.md
  9. +8
    -0
      SUPPORT.md
  10. +2
    -0
      code/.gitignore
  11. +25
    -7
      code/build.sh
  12. +7
    -6
      code/debug.sh
  13. +46
    -4
      code/espurna/alexa.ino
  14. +13
    -4
      code/espurna/api.ino
  15. +7
    -7
      code/espurna/broker.ino
  16. +79
    -18
      code/espurna/button.ino
  17. +1
    -0
      code/espurna/config/all.h
  18. +113
    -75
      code/espurna/config/arduino.h
  19. +19
    -1
      code/espurna/config/dependencies.h
  20. +9
    -0
      code/espurna/config/deprecated.h
  21. +124
    -14
      code/espurna/config/general.h
  22. +739
    -46
      code/espurna/config/hardware.h
  23. +46
    -10
      code/espurna/config/progmem.h
  24. +22
    -4
      code/espurna/config/prototypes.h
  25. +302
    -42
      code/espurna/config/sensors.h
  26. +66
    -35
      code/espurna/config/types.h
  27. +1
    -1
      code/espurna/config/version.h
  28. +11
    -0
      code/espurna/config/webui.h
  29. +157
    -0
      code/espurna/crash.ino
  30. BIN
      code/espurna/data/index.all.html.gz
  31. BIN
      code/espurna/data/index.light.html.gz
  32. BIN
      code/espurna/data/index.lightfox.html.gz
  33. BIN
      code/espurna/data/index.rfbridge.html.gz
  34. BIN
      code/espurna/data/index.rfm69.html.gz
  35. BIN
      code/espurna/data/index.sensor.html.gz
  36. BIN
      code/espurna/data/index.small.html.gz
  37. +45
    -174
      code/espurna/debug.ino
  38. +107
    -16
      code/espurna/domoticz.ino
  39. +53
    -10
      code/espurna/eeprom.ino
  40. +2
    -2
      code/espurna/encoder.ino
  41. +12
    -7
      code/espurna/espurna.ino
  42. +1
    -1
      code/espurna/filters/BaseFilter.h
  43. +1
    -1
      code/espurna/filters/LastFilter.h
  44. +1
    -1
      code/espurna/filters/MaxFilter.h
  45. +1
    -1
      code/espurna/filters/MedianFilter.h
  46. +1
    -1
      code/espurna/filters/MovingAverageFilter.h
  47. +1
    -1
      code/espurna/gpio.ino
  48. +107
    -31
      code/espurna/homeassistant.ino
  49. +19
    -1
      code/espurna/i2c.ino
  50. +16
    -1
      code/espurna/influxdb.ino
  51. +13
    -26
      code/espurna/ir.ino
  52. +52
    -16
      code/espurna/led.ino
  53. +1
    -1
      code/espurna/libs/RFM69Wrap.h
  54. +1
    -1
      code/espurna/libs/StreamInjector.h
  55. +162
    -77
      code/espurna/light.ino
  56. +107
    -0
      code/espurna/lightfox.ino
  57. +1
    -1
      code/espurna/llmnr.ino
  58. +1
    -1
      code/espurna/mdns.ino
  59. +99
    -3
      code/espurna/migrate.ino
  60. +34
    -23
      code/espurna/mqtt.ino
  61. +1
    -1
      code/espurna/netbios.ino
  62. +8
    -4
      code/espurna/nofuss.ino
  63. +7
    -4
      code/espurna/ntp.ino
  64. +38
    -5
      code/espurna/ota.ino
  65. +229
    -134
      code/espurna/relay.ino
  66. +0
    -192
      code/espurna/rf.ino
  67. +262
    -95
      code/espurna/rfbridge.ino
  68. +7
    -4
      code/espurna/rfm69.ino
  69. +33
    -18
      code/espurna/scheduler.ino
  70. +301
    -39
      code/espurna/sensor.ino
  71. +43
    -2
      code/espurna/sensors/AnalogSensor.h
  72. +1
    -1
      code/espurna/sensors/BH1750Sensor.h
  73. +253
    -0
      code/espurna/sensors/BMP180Sensor.h
  74. +1
    -1
      code/espurna/sensors/BMX280Sensor.h
  75. +1
    -1
      code/espurna/sensors/BaseSensor.h
  76. +20
    -7
      code/espurna/sensors/CSE7766Sensor.h
  77. +1
    -1
      code/espurna/sensors/DHTSensor.h
  78. +1
    -1
      code/espurna/sensors/DallasSensor.h
  79. +1
    -1
      code/espurna/sensors/DigitalSensor.h
  80. +1
    -1
      code/espurna/sensors/ECH1560Sensor.h
  81. +212
    -0
      code/espurna/sensors/EZOPHSensor.h
  82. +1
    -1
      code/espurna/sensors/EmonADC121Sensor.h
  83. +1
    -1
      code/espurna/sensors/EmonADS1X15Sensor.h
  84. +1
    -1
      code/espurna/sensors/EmonAnalogSensor.h
  85. +1
    -1
      code/espurna/sensors/EmonSensor.h
  86. +1
    -1
      code/espurna/sensors/EventSensor.h
  87. +2
    -2
      code/espurna/sensors/GUVAS12SDSensor.h
  88. +22
    -19
      code/espurna/sensors/HLW8012Sensor.h
  89. +1
    -1
      code/espurna/sensors/I2CSensor.h
  90. +143
    -0
      code/espurna/sensors/MAX6675Sensor.h
  91. +10
    -2
      code/espurna/sensors/MHZ19Sensor.h
  92. +189
    -0
      code/espurna/sensors/MICS2710Sensor.h
  93. +144
    -0
      code/espurna/sensors/MICS5525Sensor.h
  94. +1
    -1
      code/espurna/sensors/NTCSensor.h
  95. +13
    -7
      code/espurna/sensors/PMSX003Sensor.h
  96. +193
    -25
      code/espurna/sensors/PZEM004TSensor.h
  97. +231
    -0
      code/espurna/sensors/PulseMeterSensor.h
  98. +1
    -1
      code/espurna/sensors/SDS011Sensor.h
  99. +1
    -1
      code/espurna/sensors/SHT3XI2CSensor.h
  100. +1
    -1
      code/espurna/sensors/SI7021Sensor.h

+ 2
- 0
.gitattributes View File

@ -0,0 +1,2 @@
*.gz.h -diff -merge
*.gz.h linguist-generated=true

+ 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


+ 193
- 0
CHANGELOG.md View File

@ -3,6 +3,199 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.13.6] Unreleased
### Fixed
### Added
### Changed
## [1.13.5] 2019-02-27
### Fixed
- Revert loopDelay dependency on wifi sleep mode (#1574)
- Fix hardcoded serial objects in _debugSendSerial, terminalLoop and PZEM sensor (#1573)
- Fix RFBridge not showing codes in web UI as per @mcspr suggested change (#1571)
- Fix BSSIDs in scan output (#1567)
- Fix PZEM004TSensor pointer use
- RFBridge: fix webui codes parsing
- Avoid websocket ping back on fw upgrade via web UI form (#1574)
- Revert loopDelay dependency on wifi sleep mode
- Removing line break before templated variable to fix issue with Windows Arduino IDE (#1579, thanks to @AlbertWeterings)
- Send brightness to websocket
### Added
- Relay MQTT group receive-only sync mode setting
- Set wifi sleep mode from settings
- Add unique id and device support for better HA UI integration (#1547, thanks to @abmantis)
- Improved inline documentation of BMX280 settings (#1585, thanks to CraigMarkwardt)
## [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)
- Fix custom_crash_callback declaration for Arduino IDE 1.8.6 (#1169)
- Fix eneUnits key in web UI (#1177)
- Fix HA names (#1183)
- API is now restful (issue a PUT to change a relay status). It can be disabled from web UI (#1192)
- Remove static array to prevent out of bound in relay.ino (#1217)
- Remove duplicate call to EEPROMr.begin (#1214)
- Fix issue when SPIFFS_SUPPORT is enabled (#1225)
- Fix quoting units_of_measurement in HA config output (#1227)
- Fix "Clear counts" on rfm69 does not reset node count properly (thanks to @Trickx, #1239)
- Fix homecube 3rd led setting (thanks to @mcspr)
- Fix typo in static IP hint text (@thanks to @zafrirron)
- Fix hostname/password length requirements (thanks to @mcspr and @djwmarcx)
- Do not quote numbers in MQTT JSON payloads
- Fix telnet client object deletion (thanks to @mcspr)
- Call wakeUp PMS on first reading cycle to avoid not data in a long period (thanks to @Yonsm)
- Small fixes and windows support for ESPurna OTA Manager (thanks to @mcspr)
- Fix for YiDian XS-SSA05 configs (thanks to @ducky64)
- Send MQTT messages only for button events with assigned actions (thanks to @Valcob)
- Avoid EEPROM commits on callbacks (#1214)
### Added
- Option to report energy based on delta since last report (#369)
- Support for IR-MQTT bridge, also in RAW mode (#556, #907)
- Allow faster sensor reading intervals, down to 1 second (#848)
- Support for Xiaomi Smart Desk Lamp (#884)
- Retry up to 3 times on bad response to Thingspeak server (#1213)
- Support for apparent power and power factor on CSE7/XX sensor (#1215)
- Support for encoders
- Support for Allterco Shelly2
- Added SDS011 sensor support (thanks to @derlucas)
- Added password check to telnet (option to disable it)
- Added PHYX support (thanks to @whitebird)
- Added config command that outputs the configuration in JSON
- Support for MICS-2710, MICS-5525 and MICS-4514, gas sensors
- Support for iWoole LED Table Lamp (thanks to @CollinShorts)
- Command to output free stack
- Password management from web UI (thanks to @mcspr)
- Added BESTEK MRJ1011 support (thanks to @InduPrakash)
- Support for EXS WiFi Relay 5.0 (thanks to @cheise, #1218)
- Allowing disabling or single heartbeat on MQTT connect or repeat (default) (#1196)
- Command to save settings when SETTINGS_AUTOSAVE is off
### Changed
- Upgraded to JustWifi 2.0.2
- Upgraded to FauxmoESP 3.0.1
- Upgraded to DebounceEvent 2.0.4 to properly support BUTTON_SWITCH
- Split `info` command output into `info` and `wifi`. Refactor output.
- Custom HA payloads (thanks to @Yonsm)
## [1.13.2] 2018-08-27
### Fixed
- Fix relay overflow window length


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

+ 113
- 40
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.3a-brightgreen.svg)](CHANGELOG.md)
[![version](https://img.shields.io/badge/version-1.13.6--dev-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)
[![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE)
[![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 November 2018, Max Prokhorov (@mcspr) is also actively working 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.
@ -77,20 +82,27 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Support for [different **sensors**](Sensors)
* Environment
* **DHT11 / DHT12 / DHT22 / DHT21 / AM2301 / Itead's SI7021**
* **BMP280** and **BME280** temperature, humidity (BME280) and pressure sensor by Bosch
* **BMP180**, **BMP280** and **BME280** pressure, humidity (BME280) and temperature (BMP280 & BME280) sensor by Bosch
* **TMP35** and **TMP36** analog temperature sensors
* **MAX6675** temperature sensor
* **NTC** temperature sensors
* **SI7021** temperature and humidity sensor
* **SHT3X** temperature and humidity sensor over I2C (Wemos shield)
* **AM2320** temperature and humidity sensor over I2C
* **Dallas OneWire sensors** like the DS18B20
* **MHZ19** CO2 sensor
* **MICS2710** CO2 & NO2 sensor
* **MICS5525** CO & CO2 sensor
* **SenseAir S8** CO2 sensor
* **PMSX003/PMS5003T/ST** dust sensors
* **SDS011** dust sensor
* **BH1750** luminosity sensor
* **GUVAS12SD** UV sensor
* **GEIGER COUNTER** by RH Electronics
* **VEML6075** UV Sensor
* **EZO pH Circuit**
* **Geiger counter** by RH Electronics
* **HC-SR04**, **SRF05**, **SRF06**, **DYP-ME007**, **JSN-SR04T** & **Parallax PING)))™** distance sensors
* **VL53L1X** distance sensor
* Power monitoring
* **HLW8012** using the [HLW8012 Library](https://bitbucket.org/xoseperez/hlw8012) (Sonoff POW)
* **CSE7766** and **CSE7759B** power monitor chips
@ -215,73 +227,134 @@ For more information please refer to the [ESPurna Wiki](https://github.com/xosep
Here is the list of supported hardware. For more information please refer to the [ESPurna Wiki Hardware page](https://github.com/xoseperez/espurna/wiki/Hardware).
### Power monitoring devices
||||
|---|---|---|
|![BlitzWolf BW-SHP6](images/devices/blitzwolf-bw-shp6.jpg)|![BlitzWolf BW-SHP2](images/devices/blitzwolf-bw-shp2.jpg)|![Power meters based on V9261F](images/devices/generic-v9261f.jpg)|
|**Blitzwolf BW-SHP6**|**Blitzwolf BW-SHP2<br />(also by Coosa, Goosund, HomeCube, Teckin)**|**Power meters based on V9261F**|
|![Itead Sonoff POW](images/devices/itead-sonoff-pow.jpg)|![Itead Sonoff POW](images/devices/itead-sonoff-pow-r2.jpg)|![Itead Sonoff S31](images/devices/itead-sonoff-s31.jpg)|
|**Itead Sonoff POW**|**Itead Sonoff POW R2**|**Itead Sonoff S31**|
|![Smartlife Mini Smart Socket](images/devices/smartlife-mini-smart-socket.jpg)|![Teckin SP20](images/devices/teckin-sp20.jpg)|![Digoo NX SP202](images/devices/digoo-nx-sp202.jpg)|
|**Smartlife (NETVIP) Mini Smart Socket**|**Teckin SP20**|**Digoo NX SP202**|
|![Vanzavanzu Smart WiFi Plug Mini](images/devices/vanzavanzu-smart-wifi-plug-mini.jpg)|||
|**Vanzavanzu Smart WiFi Plug Mini**|||
### Embedded switches
||||
|---|---|---|
|![Tinkerman Espurna H](images/devices/tinkerman-espurna-h.jpg)|![Tinkerman RFM69GW](images/devices/tinkerman-rfm69gw.jpg)||
|**Tinkerman ESPurna H**|**Tinkerman RFM69GW**||
|![Itead Sonoff RF Bridge](images/devices/itead-sonoff-rfbridge.jpg)|![Itead Sonoff RF](images/devices/itead-sonoff-rf.jpg)|![Itead Sonoff 4CH](images/devices/itead-sonoff-4ch.jpg)|
|**Itead Sonoff RF Bridge**|**Itead Sonoff RF**|**Itead Sonoff 4CH**|
|![Itead Sonoff 4CH Pro](images/devices/itead-sonoff-4ch-pro.jpg)|||
|**Itead Sonoff 4CH Pro**|||
|![Itead Sonoff S31](images/devices/itead-sonoff-s31.jpg)|![BlitzWolf BW-SPP2](images/devices/blitzwolf-bw-shp2.jpg)|![Power meters based on V9261F](images/devices/generic-v9261f.jpg)|
|**Itead Sonoff S31**|**Blitzwolf BW-SHP2<br />(also by Coosa, Goosund, HomeCube, Teckin)**|**Power meters based on V9261F**|
|![Itead Sonoff POW](images/devices/itead-sonoff-pow.jpg)|![Itead Sonoff POW](images/devices/itead-sonoff-pow-r2.jpg)|![Vanzavanzu Smart WiFi Plug Mini](images/devices/vanzavanzu-smart-wifi-plug-mini.jpg)|
|**Itead Sonoff POW**|**Itead Sonoff POW R2**|**Vanzavanzu Smart WiFi Plug Mini**|
|![Itead Sonoff Basic](images/devices/itead-sonoff-basic.jpg)|![Itead Sonoff Dual/Dual R2](images/devices/itead-sonoff-dual.jpg)|![Itead Sonoff TH10/TH16](images/devices/itead-sonoff-th.jpg)|
|**Itead Sonoff Basic**|**Itead Sonoff Dual/Dual R2**|**Itead Sonoff TH10/TH16**|
|![Electrodragon WiFi IOT](images/devices/electrodragon-wifi-iot.jpg)|![OpenEnergyMonitor WiFi MQTT Relay / Thermostat](images/devices/openenergymonitor-mqtt-relay.jpg)||
|**Electrodragon WiFi IOT**|**OpenEnergyMonitor WiFi MQTT Relay / Thermostat**||
|![Itead S20](images/devices/itead-s20.jpg)|![Itead S20](images/devices/itead-s26.jpg)|![Neo Coolcam NAS WR01W](images/devices/neo-coolcam-wifi.jpg)|
|![Itead Sonoff 4CH](images/devices/itead-sonoff-4ch.jpg)|![Itead Sonoff 4CH Pro](images/devices/itead-sonoff-4ch-pro.jpg)||
|**Itead Sonoff 4CH**|**Itead Sonoff 4CH Pro**||
|![Allterco Shelly1](images/devices/allterco-shelly1.jpg)|![Allterco Shelly2](images/devices/allterco-shelly2.jpg)|![Jan Goedeke Wifi Relay (NO/NC)](images/devices/jangoe-wifi-relay.jpg)|
|**Alterco Shelly1**|**Alterco Shelly2**|**Jan Goedeke Wifi Relay (NO/NC)**|
|![EXS Wifi Relay v3.1](images/devices/exs-wifi-relay-v31.jpg)|![EXS Wifi Relay v5.0](images/devices/exs-wifi-relay-v50.jpg)|![Jorge García Wifi + Relays Board Kit](images/devices/jorgegarcia-wifi-relays.jpg)|
|**EXS Wifi Relay v3.1**|**EXS Wifi Relay v5.0**|**Jorge García Wifi + Relays Board Kit**|
|![Allnet ESP8266-UP-Relay](images/devices/allnet-esp8266-up-relay.jpg)|![Bruno Horta's OnOfre](images/devices/bh-onofre.jpg)|![Luani HVIO](images/devices/luani-hvio.jpg)|
|**Allnet ESP8266-UP-Relay**|**Bruno Horta's OnOfre**|**Luani HVIO**|
### Wall Sockets
||||
|---|---|---|
|![Itead S20](images/devices/itead-s20.jpg)|![Itead S26](images/devices/itead-s26.jpg)|![Neo Coolcam NAS WR01W](images/devices/neo-coolcam-wifi.jpg)|
|**Itead S20**|**Itead S26**|**Neo Coolcam NAS WR01W**|
|![Schuko Wifi Plug](images/devices/schuko-wifi-plug.jpg)|![KMC 70011](images/devices/kmc-70011.jpg)|![Xenon SM-PW702U](images/devices/xenon-sm-pw702u.jpg)|
|**Schuko Wifi Plug**|**KMC 70011**|**Xenon SM-PW702U**|
|![Maxcio W-US002S](images/devices/maxcio-w-us002s.jpg)|![HEYGO HY02](images/devices/heygo-hy02.jpg)|![YiDian XS-SSA05](images/devices/yidian-xs-ssa05.jpg)|
|**Maxcio W-US002S**|**HEYGO HY02**|**YiDian XS-SSA05**|
|![WiOn 50055](images/devices/wion-50055.jpg)|![LINGAN SWA1](images/devices/lingan-swa1.jpg)|![HomeCube 16A](images/devices/homecube-16a.jpg)|
|**WiOn 50055**|**LINGAN SWA1**|**HomeCube 16A**|
|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg)|||
|**WorkChoice EcoPlug**|||
|![Tonbux PowerStrip02](images/devices/tonbux-powerstrip02.jpg)|![ForNorm Power Strip](images/devices/fornorm-power-strip.jpg)|![Zhilde ZLD-EU55-W](images/devices/zhilde-zld-eu55-w.jpg)|
|**Tonbux PowerStrip02**|**Fornorm Power Strip**|**Zhilde ZLD-EU55-W**|
|![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**|
|![Schuko Wifi Plug](images/devices/schuko-wifi-plug.jpg)|![Schuko Wifi Plug V2](images/devices/schuko-wifi-plug-v2.jpg)|![KMC 70011](images/devices/kmc-70011.jpg)|
|**Schuko Wifi Plug**|**Schuko Wifi Plug V2**|**KMC 70011**|
|![Xenon SM-PW702U](images/devices/xenon-sm-pw702u.jpg)|![Orvibo B25](images/devices/orvibo-b25.jpg)|![Oukitel P1](images/devices/oukitel-p1.jpg)|
|**Xenon SM-PW702U**|**Orvibo B25**|**Oukitel P1**|
|![Tonbux XS-SSA06](images/devices/tonbux-xs-ssa06.jpg)|![Litesun LA-WF3](images/devices/litesun-la-wf3.jpg)|![Maxcio W DE-004](images/devices/maxcio-w-de004.jpg)|
|**Tonbux XS-SSA06**|**Litesun LA-WF3**|**Maxcio W DE-004**|
|![Hama WiFi Steckdose](images/devices/hama-wifi-steckdose.jpg)|![GBLife RGBW Socket](images/devices/gblife-rgbw-socket.jpg)||
|**Hama WiFi Steckdose**|**GBLife RGBW Socket**||
### Wall switches
||||
|---|---|---|
|![Itead Sonoff Touch](images/devices/itead-sonoff-touch.jpg)|![Itead Sonoff T1](images/devices/itead-sonoff-t1.jpg)|![YJZK switch](images/devices/yjzk-2gang-switch.jpg)|
|**Itead Sonoff Touch**|**Itead Sonoff T1**|**YJZK 1/2/3-gangs switch**|
|![Gosund WS1 / KS-602S](images/devices/gosund-ws1.jpg)|||
|**Gosund WS1 / KS-602S**|||
### Power strips
||||
|---|---|---|
|![Tonbux PowerStrip02](images/devices/tonbux-powerstrip02.jpg)|![ForNorm ZLD-34EU](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**|
### Smart lights
||||
|---|---|---|
|![Itead Slampher](images/devices/itead-slampher.jpg)|![Arilux E27](images/devices/arilux-e27.jpg)|![Itead Sonoff B1](images/devices/itead-sonoff-b1.jpg)|
|**Itead Slampher**|**Arilux E27**|**Itead Sonoff B1**|
|![AI-Thinker Wifi Light / Noduino OpenLight](images/devices/aithinker-ai-light.jpg)|![Authometion LYT8266](images/devices/authometion-lyt8266.jpg)|![AG-L4](images/devices/ag-l4.jpg)|
|**AI-Thinker Wifi Light / Noduino OpenLight**|**Authometion LYT8266**|**AG-L4**|
|![Lohas 9W](images/devices/lohas-9w.jpg)|||
|**Lohas 9W**|||
|![Itead Sonoff LED](images/devices/itead-sonoff-led.jpg)|![Itead BN-SZ01](images/devices/itead-bn-sz01.jpg)|![InterMitTech QuinLED 2.6](images/devices/intermittech-quinled-2.6.jpg)|
|**Itead Sonoff LED**|**Itead BN-SZ01**|**InterMitTech QuinLED 2.6**|
|![Lohas 9W](images/devices/lohas-9w.jpg)|![Xiaomi Smart Desk Lamp](images/devices/xiaomi-smart-desk-lamp.jpg)|![iWoole LED Table Lamp](images/devices/iwoole-led-table-lamp.jpg)|
|**Lohas 9W**|**Xiaomi Smart Desk Lamp**|**iWoole LED Table Lamp**|
|![Itead Sonoff LED](images/devices/itead-sonoff-led.jpg)|![Itead BN-SZ01](images/devices/itead-bn-sz01.jpg)|![Lombox LUX Nova 2](images/devices/lombex-lux-nova2.jpg)|
|**Itead Sonoff LED**|**Itead BN-SZ01**|**Lombex LUX Nova 2 (white and color)**|
|![Arilux AL-LC01 (RGB)](images/devices/arilux-al-lc01.jpg)|![Arilux AL-LC02 (RGBW)](images/devices/arilux-al-lc02.jpg)|![Arilux AL-LC06 (RGBWWCW)](images/devices/arilux-al-lc06.jpg)|
|**Arilux AL-LC01 (RGB)**|**Arilux AL-LC02 (RGBW)**|**Arilux AL-LC06 (RGBWWCW)**|
|![Arilux AL-LC11 (RGBWWW) & RF](images/devices/arilux-al-lc11.jpg)|![MagicHome LED Controller (1.0 and 2.x)](images/devices/magichome-led-controller.jpg)|![Huacanxing H801/802](images/devices/huacanxing-h801.jpg)|
|**Arilux AL-LC11 (RGBWWW) & RF**|**MagicHome LED Controller (1.0/2.x)**|**Huacanxing H801/802**|
|**Arilux AL-LC11 (RGBWWW) & RF**|**MagicHome LED Controller (1.0/2.x, also ZJ WFMN A/B11)**|**Huacanxing H801/802**|
### Radio links / gateways
||||
|---|---|---|
|![Tinkerman RFM69GW](images/devices/tinkerman-rfm69gw.jpg)|![Itead Sonoff RF Bridge](images/devices/itead-sonoff-rfbridge.jpg)|![Itead Sonoff RF](images/devices/itead-sonoff-rf.jpg)|
|**Tinkerman RFM69GW**|**Itead Sonoff RF Bridge**|**Itead Sonoff RF**|
### Other devices
||||
|---|---|---|
|![Tonbux Mosquito Killer](images/devices/tonbux-mosquito-killer.jpg)|![Itead Sonoff IFAN02](images/devices/itead-sonoff-ifan02.jpg)||
|**Tonbux Mosquito Killer**|**Itead Sonoff IFAN02**|||
### Custom & Development boards
||||
|---|---|---|
|![Tinkerman Espurna H](images/devices/tinkerman-espurna-h.jpg)||![NodeMCU](images/devices/nodemcu-lolin-v3.jpg)|
|**Tinkerman ESPurna H**||**NodeMCU Lolin V3**|
|![Itead Sonoff SV](images/devices/itead-sonoff-sv.jpg)|![Itead 1CH Inching](images/devices/itead-1ch-inching.jpg)|![Itead Motor Clockwise/Anticlockwise](images/devices/itead-motor.jpg)|
|**Itead Sonoff SV**|**Itead 1CH Inching**|**Itead Motor Clockwise/Anticlockwise**|
|![Jan Goedeke Wifi Relay (NO/NC)](images/devices/jangoe-wifi-relay.jpg)|![Jorge García Wifi + Relays Board Kit](images/devices/jorgegarcia-wifi-relays.jpg)|![EXS Wifi Relay v3.1](images/devices/exs-wifi-relay-v31.jpg)|
|**Jan Goedeke Wifi Relay (NO/NC)**|**Jorge García Wifi + Relays Board Kit**|**EXS Wifi Relay v3.1**|
|![Allterco Shelly1](images/devices/allterco-shelly1.jpg)|||
|**Alterco Shelly1**|||
|![ManCaveMade ESP-Live](images/devices/mancavemade-esp-live.jpg)|![Wemos D1 Mini Relay Shield](images/devices/wemos-d1-mini-relayshield.jpg)|![Witty Cloud](images/devices/witty-cloud.jpg)|
|**ManCaveMade ESP-Live**|**Wemos D1 Mini Relay Shield**|**Witty Cloud**|
|![ManCaveMade ESP-Live](images/devices/mancavemade-esp-live.jpg)|![Wemos D1 Mini Relay Shield](images/devices/wemos-d1-relayshield.jpg)|![Gizwits Witty Cloud](images/devices/witty-cloud.jpg)|
|**ManCaveMade ESP-Live**|**Wemos D1 Mini Relay Shield**|**Gizwits Witty Cloud**|
|![IKE ESPike](images/devices/ike-espike.jpg)|![Pilotak ESP DIN](images/devices/pilotak-esp-din.jpg)|![Arniex Swifitch](images/devices/arniex-swifitch.jpg)|
|**IKE ESPike**|**Pilotak ESP DIN|**Arniex Swifitch**|
|![Heltec Touch Relay](images/devices/heltec-touch-relay.jpg)|![Generic Relay v4.0](images/devices/generic-relay-40.jpg)|![Generic RGBLed v1.0](images/devices/generic-rgbled-10.jpg)|
|**Heltec Touch Relay**|**Generic Relay v4.0**|**Generic RGBLed v1.0**|
|![Generic DHT11 v1.0](images/devices/generic-dht11-10.jpg)|![Generic DS18B20 v1.0](images/devices/generic-ds18b20-10.jpg)|![Bruno Horta's OnOfre](images/devices/bh-onofre.jpg)|
|**Generic DHT11 v1.0**|**Generic DS18B20 v1.0**|**Bruno Horta's OnOfre**|
|![Allnet ESP8266-UP-Relay](images/devices/allnet-esp8266-up-relay.jpg)|![RH Electronics Geiger Counter](images/devices/generic-geiger-diy.png)|![Luani HVIO](images/devices/luani-hvio.jpg)|
|**Allnet ESP8266-UP-Relay**|**RH Electronics Geiger Counter**|**Luani HVIO**|
|![Tonbux Mosquito Killer](images/devices/tonbux-mosquito-killer.jpg)|![Itead Sonoff IFAN02](images/devices/itead-sonoff-ifan02.jpg)||
|**Tonbux Mosquito Killer**|**Itead Sonoff IFAN02**|||
|![Generic DHT11 v1.0](images/devices/generic-dht11-10.jpg)|![Generic DS18B20 v1.0](images/devices/generic-ds18b20-10.jpg)|![InterMitTech QuinLED 2.6](images/devices/intermittech-quinled-2.6.jpg)|
|**Generic DHT11 v1.0**|**Generic DS18B20 v1.0**|**InterMitTech QuinLED 2.6**|
|![Phyx ESP12 RGBW](images/devices/phyx-esp12-rgbw.jpg)|![RH Electronics Geiger Counter](images/devices/generic-geiger-diy.png)|![Green ESP Relay](images/devices/green-esp-relay.jpg)|
|**Phyx ESP12 RGBW**|**RH Electronics Geiger Counter**|**Green ESP Relay**|
|![Foxel Lightfox Dual](images/devices/foxel-lightfox-dual.jpg)|||
|**Foxel Lightfox Dual**|||
**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
Copyright (C) 2016-2018 by Xose Pérez (@xoseperez)
Copyright (C) 2016-2019 by Xose Pérez (@xoseperez)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by


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

+ 2
- 0
code/.gitignore View File

@ -11,3 +11,5 @@ custom.h
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.pioenvs
.piolibdeps

+ 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


+ 46
- 4
code/espurna/alexa.ino View File

@ -2,7 +2,7 @@
ALEXA 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>
*/
@ -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);
}


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

@ -2,7 +2,7 @@
API 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>
*/
@ -31,6 +31,11 @@ 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"] = getSetting("apiRestFul", API_RESTFUL).toInt() == 1;
}
void _apiConfigure() {
// Nothing to do
}
// -----------------------------------------------------------------------------
@ -158,9 +163,11 @@ bool _apiRequestCallback(AsyncWebServerRequest *request) {
// Check if its a PUT
if (api.putFn != NULL) {
if (request->hasParam("value", request->method() == HTTP_PUT)) {
AsyncWebParameter* p = request->getParam("value", request->method() == HTTP_PUT);
(api.putFn)((p->value()).c_str());
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());
}
}
}
@ -212,9 +219,11 @@ void apiRegister(const char * key, api_get_callback_f getFn, api_put_callback_f
}
void apiSetup() {
_apiConfigure();
wsOnSendRegister(_apiWebSocketOnSend);
wsOnReceiveRegister(_apiWebSocketOnReceive);
webRequestRegister(_apiRequestCallback);
espurnaRegisterReload(_apiConfigure);
}
#endif // API_SUPPORT

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

@ -2,7 +2,7 @@
BROKER MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -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

+ 79
- 18
code/espurna/button.ino View File

@ -2,7 +2,7 @@
BUTTON 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>
*/
@ -97,54 +97,92 @@ void buttonEvent(unsigned int id, unsigned char event) {
DEBUG_MSG_P(PSTR("[BUTTON] Button #%u event %u\n"), id, event);
if (event == 0) return;
unsigned char action = buttonAction(id, event);
#if MQTT_SUPPORT
buttonMQTT(id, event);
if (action != BUTTON_MODE_NONE || BUTTON_MQTT_SEND_ALL_EVENTS) {
buttonMQTT(id, event);
}
#endif
unsigned char action = buttonAction(id, event);
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() {
#ifdef ITEAD_SONOFF_DUAL
#if defined(ITEAD_SONOFF_DUAL)
unsigned int actions = buttonStore(BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE);
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 1});
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 2});
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, BUTTON3_RELAY});
#elif defined(FOXEL_LIGHTFOX_DUAL)
unsigned int actions = buttonStore(BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE);
unsigned int btn1Relay = getSetting("btnRelay", 0, BUTTON1_RELAY - 1).toInt() + 1;
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, btn1Relay});
unsigned int btn2Relay = getSetting("btnRelay", 1, BUTTON2_RELAY - 1).toInt() + 1;
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, btn2Relay});
unsigned int btn3Relay = getSetting("btnRelay", 2, BUTTON3_RELAY - 1).toInt() + 1;
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, btn3Relay});
unsigned int btn4Relay = getSetting("btnRelay", 3, BUTTON4_RELAY - 1).toInt() + 1;
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, btn4Relay});
#else
unsigned long btnDelay = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt();
@ -214,7 +252,7 @@ void buttonSetup() {
void buttonLoop() {
#ifdef ITEAD_SONOFF_DUAL
#if defined(ITEAD_SONOFF_DUAL)
if (Serial.available() >= 4) {
if (Serial.read() == 0xA0) {
@ -254,6 +292,29 @@ void buttonLoop() {
}
}
#elif defined(FOXEL_LIGHTFOX_DUAL)
if (Serial.available() >= 4) {
if (Serial.read() == 0xA0) {
if (Serial.read() == 0x04) {
unsigned char value = Serial.read();
if (Serial.read() == 0xA1) {
DEBUG_MSG_P(PSTR("[BUTTON] [LIGHTFOX] Received buttons mask: %d\n"), value);
for (unsigned int i=0; i<_buttons.size(); i++) {
bool clicked = (value & (1 << i)) > 0;
if (clicked) {
buttonEvent(i, BUTTON_EVENT_CLICK);
}
}
}
}
}
}
#else
for (unsigned int i=0; i < _buttons.size(); i++) {


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


+ 113
- 75
code/espurna/config/arduino.h View File

@ -7,98 +7,128 @@
// Hardware
//--------------------------------------------------------------------------------
//#define NODEMCU_LOLIN
//#define WEMOS_D1_MINI
//#define WEMOS_D1_MINI_RELAYSHIELD
//#define TINKERMAN_ESPURNA_H06
//#define TINKERMAN_ESPURNA_H08
//#define TINKERMAN_RFM69GW
//#define ITEAD_SONOFF_BASIC
//#define ITEAD_SONOFF_RF
//#define ITEAD_SONOFF_TH
//#define ITEAD_SONOFF_SV
//#define ITEAD_SLAMPHER
//#define AITHINKER_AI_LIGHT
//#define ALLNET_4DUINO_IOT_WLAN_RELAIS
//#define ALLTERCO_SHELLY1
//#define ALLTERCO_SHELLY2
//#define ARILUX_AL_LC01
//#define ARILUX_AL_LC02
//#define ARILUX_AL_LC02_V14
//#define ARILUX_AL_LC06
//#define ARILUX_AL_LC11
//#define ARILUX_E27
//#define ARNIEX_SWIFITCH
//#define AUTHOMETION_LYT8266
//#define BESTEK_MRJ1011
//#define BH_ONOFRE
//#define BLITZWOLF_BWSHPX
//#define BLITZWOLF_BWSHPX_V23
//#define DIGOO_NX_SP202
//#define ELECTRODRAGON_WIFI_IOT
//#define ESPURNA_CORE
//#define EUROMATE_WIFI_STECKER_SCHUKO
//#define EUROMATE_WIFI_STECKER_SCHUKO_V2
//#define EXS_WIFI_RELAY_V31
//#define EXS_WIFI_RELAY_V50
//#define FORNORM_ZLD_34EU
//#define FOXEL_LIGHTFOX_DUAL
//#define GBLIFE_RGBW_SOCKET
//#define GENERIC_8CH
//#define GENERIC_AG_L4
//#define GENERIC_ECH1560
//#define GENERIC_ESP01S_DHT11_V10
//#define GENERIC_ESP01S_DS18B20_V10
//#define GENERIC_ESP01S_RELAY_V40
//#define GENERIC_ESP01S_RGBLED_V10
//#define GENERIC_V9261F
//#define GIZWITS_WITTY_CLOUD
//#define GOSUND_WS1
//#define GREEN_ESP8266RELAY
//#define HAMA_WIFI_STECKDOSE_00176533
//#define HELTEC_TOUCHRELAY
//#define HEYGO_HY02
//#define HOMECUBE_16A
//#define HUACANXING_H801
//#define HUACANXING_H802
//#define IKE_ESPIKE
//#define INTERMITTECH_QUINLED
//#define ITEAD_1CH_INCHING
//#define ITEAD_BNSZ01
//#define ITEAD_MOTOR
//#define ITEAD_S20
//#define ITEAD_SONOFF_TOUCH
//#define ITEAD_SONOFF_POW
//#define ITEAD_SONOFF_POW_R2
//#define ITEAD_SONOFF_DUAL
//#define ITEAD_SONOFF_DUAL_R2
//#define ITEAD_SLAMPHER
//#define ITEAD_SONOFF_4CH
//#define ITEAD_SONOFF_4CH_PRO
//#define ITEAD_1CH_INCHING
//#define ITEAD_MOTOR
//#define ITEAD_SONOFF_BNSZ01
//#define ITEAD_SONOFF_RFBRIDGE
//#define ITEAD_SONOFF_B1
//#define ITEAD_SONOFF_BASIC
//#define ITEAD_SONOFF_BNSZ01
//#define ITEAD_SONOFF_DUAL
//#define ITEAD_SONOFF_DUAL_R2
//#define ITEAD_SONOFF_IFAN02
//#define ITEAD_SONOFF_LED
//#define ITEAD_SONOFF_POW
//#define ITEAD_SONOFF_POW_R2
//#define ITEAD_SONOFF_RF
//#define ITEAD_SONOFF_RFBRIDGE
//#define ITEAD_SONOFF_S31
//#define ITEAD_SONOFF_SV
//#define ITEAD_SONOFF_T1_1CH
//#define ITEAD_SONOFF_T1_2CH
//#define ITEAD_SONOFF_T1_3CH
//#define ITEAD_SONOFF_S31
//#define YJZK_SWITCH_2CH
//#define ELECTRODRAGON_WIFI_IOT
//#define WORKCHOICE_ECOPLUG
//#define AITHINKER_AI_LIGHT
//#define MAGICHOME_LED_CONTROLLER
//#define MAGICHOME_LED_CONTROLLER_20
//#define HUACANXING_H801
//#define HUACANXING_H802
//#define ITEAD_SONOFF_TH
//#define ITEAD_SONOFF_TOUCH
//#define IWOOLE_LED_TABLE_LAMP
//#define JANGOE_WIFI_RELAY_NC
//#define JANGOE_WIFI_RELAY_NO
//#define JORGEGARCIA_WIFI_RELAYS
//#define OPENENERGYMONITOR_MQTT_RELAY
//#define WION_50055
//#define EXS_WIFI_RELAY_V31
//#define GENERIC_V9261F
//#define GENERIC_ECH1560
//#define MANCAVEMADE_ESPLIVE
//#define INTERMITTECH_QUINLED
//#define ARILUX_AL_LC06
//#define ARILUX_E27
//#define XENON_SM_PW702U
//#define AUTHOMETION_LYT8266
//#define KMC_70011
//#define GENERIC_8CH
//#define ARILUX_AL_LC01
//#define ARILUX_AL_LC11
//#define ARILUX_AL_LC02
//#define WEMOS_D1_TARPUNA_SHIELD
//#define GIZWITS_WITTY_CLOUD
//#define EUROMATE_WIFI_STECKER_SCHUKO
//#define TONBUX_POWERSTRIP02
//#define LINGAN_SWA1
//#define HEYGO_HY02
//#define MAXCIO_WUS002S
//#define YIDIAN_XSSSA05
//#define TONBUX_XSSSA06
//#define GREEN_ESP8266RELAY
//#define IKE_ESPIKE
//#define ARNIEX_SWIFITCH
//#define GENERIC_ESP01S_RELAY_V40
//#define GENERIC_ESP01S_RGBLED_V10
//#define GENERIC_ESP01S_DHT11_V10
//#define GENERIC_ESP01S_DS18B20_V10
//#define HELTEC_TOUCHRELAY
//#define ZHILDE_EU44_W
//#define LITESUN_LA_WF3
//#define LOHAS_9W
//#define LOMBEX_LUX_NOVA2_TUNABLE_WHITE
//#define LOMBEX_LUX_NOVA2_WHITE_COLOR
//#define LUANI_HVIO
//#define ALLNET_4DUINO_IOT_WLAN_RELAIS
//#define TONBUX_MOSQUITO_KILLER
//#define LYASI_LIGHT
//#define MAGICHOME_LED_CONTROLLER
//#define MAGICHOME_LED_CONTROLLER_20
//#define MAGICHOME_ZJ_WFMN_A_11
//#define MAGICHOME_ZJ_WFMN_B_11
//#define MANCAVEMADE_ESPLIVE
//#define MAXCIO_WDE004
//#define MAXCIO_WUS002S
//#define NEO_COOLCAM_NAS_WR01W
//#define ESTINK_WIFI_POWER_STRIP
//#define NODEMCU_BASIC
//#define NODEMCU_LOLIN
//#define OPENENERGYMONITOR_MQTT_RELAY
//#define ORVIBO_B25
//#define OUKITEL_P1
//#define PHYX_ESP12_RGB
//#define PILOTAK_ESP_DIN_V1
//#define BLITZWOLF_BWSHP2
//#define BH_ONOFRE
//#define ITEAD_SONOFF_IFAN02
//#define GENERIC_AG_L4
//#define ALLTERCO_SHELLY1
//#define LOHAS_9W
//#define SMARTLIFE_MINI_SMART_SOCKET
//#define STM_RELAY
//#define TECKIN_SP20
//#define TECKIN_SP22_V14
//#define TINKERMAN_ESPURNA_H06
//#define TINKERMAN_ESPURNA_H08
//#define TINKERMAN_ESPURNA_SWITCH
//#define TINKERMAN_RFM69GW
//#define TONBUX_MOSQUITO_KILLER
//#define TONBUX_POWERSTRIP02
//#define TONBUX_XSSSA01
//#define TONBUX_XSSSA06
//#define VANZAVANZU_SMART_WIFI_PLUG_MINI
//#define WEMOS_D1_MINI
//#define WEMOS_D1_MINI_RELAYSHIELD
//#define WEMOS_D1_TARPUNA_SHIELD
//#define WION_50055
//#define WORKCHOICE_ECOPLUG
//#define XENON_SM_PW702U
//#define XIAOMI_SMART_DESK_LAMP
//#define YIDIAN_XSSSA05
//#define YJZK_SWITCH_1CH
//#define YJZK_SWITCH_2CH
//#define YJZK_SWITCH_3CH
//#define XIAOMI_SMART_DESK_LAMP
//#define ALLTERCO_SHELLY2
//#define PHYX_ESP12_RGB
//#define IWOOLE_LED_TABLE_LAMP
//#define ZHILDE_EU44_W
//--------------------------------------------------------------------------------
// Features (values below are non-default values)
@ -126,8 +156,8 @@
//#define NETBIOS_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define NOFUSS_SUPPORT 1
//#define NTP_SUPPORT 0
//#define RFM69_SUPPORT 1
//#define RF_SUPPORT 1
//#define RFM69_SUPPORT 1
//#define SCHEDULER_SUPPORT 0
//#define SENSOR_SUPPORT 1
//#define SPIFFS_SUPPORT 1
@ -145,6 +175,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
@ -155,12 +186,17 @@
//#define EMON_ADS1X15_SUPPORT 1
//#define EMON_ANALOG_SUPPORT 1
//#define EVENTS_SUPPORT 1
//#define EZOPH_SUPPORT 1
//#define GEIGER_SUPPORT 1
//#define GUVAS12SD_SUPPORT 1
//#define HLW8012_SUPPORT 1
//#define MAX6675_SUPPORT 1
//#define MHZ19_SUPPORT 1
//#define MICS2710_SUPPORT 1
//#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
@ -169,3 +205,5 @@
//#define SONAR_SUPPORT 1
//#define TMP3X_SUPPORT 1
//#define V9261F_SUPPORT 1
//#define VEML6075_SUPPORT 1
//#define VL53L1X_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

+ 124
- 14
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
@ -148,7 +148,8 @@
// EEPROM
//------------------------------------------------------------------------------
#define EEPROM_SIZE 4096 // EEPROM size in bytes
#define EEPROM_SIZE SPI_FLASH_SEC_SIZE // EEPROM size in bytes (1 sector = 4096 bytes)
//#define EEPROM_RORATE_SECTORS 2 // Number of sectors to use for EEPROM rotation
// If not defined the firmware will use a number based
// on the number of available sectors
@ -165,32 +166,98 @@
// HEARTBEAT
//------------------------------------------------------------------------------
#ifndef HEARTBEAT_ENABLED
#define HEARTBEAT_ENABLED 1
#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)
#define HEARTBEAT_MODE HEARTBEAT_NONE
#endif
#ifndef HEARTBEAT_MODE
#define HEARTBEAT_MODE HEARTBEAT_REPEAT
#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
@ -200,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
//------------------------------------------------------------------------------
@ -228,6 +291,11 @@
#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
//------------------------------------------------------------------------------
// ENCODER
//------------------------------------------------------------------------------
@ -422,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.
#define WEB_REMOTE_DOMAIN "http://tinkerman.cat"
#ifndef WEB_REMOTE_DOMAIN
#define WEB_REMOTE_DOMAIN "http://espurna.io"
#endif
// -----------------------------------------------------------------------------
// WEBSOCKETS
@ -458,6 +528,11 @@
#define API_ENABLED 0 // Do not enable API by default
#endif
#ifndef API_RESTFUL
#define API_RESTFUL 1 // A restful API requires changes to be issued as PUT requests
// Setting this to 0 will allow using GET to change relays, for instance
#endif
#ifndef API_BUFFER_SIZE
#define API_BUFFER_SIZE 15 // Size of the buffer for HTTP GET API responses
#endif
@ -514,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"
// -----------------------------------------------------------------------------
@ -714,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"
@ -726,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"
@ -739,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"
@ -749,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)
@ -781,7 +864,7 @@
// -----------------------------------------------------------------------------
#ifndef SETTINGS_AUTOSAVE
#define SETTINGS_AUTOSAVE 1 // Autosave settings o force manual commit
#define SETTINGS_AUTOSAVE 1 // Autosave settings or force manual commit
#endif
#define SETTINGS_MAX_LIST_COUNT 10 // Maximum index for settings lists
@ -804,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
@ -908,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
@ -976,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
@ -1054,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
// -----------------------------------------------------------------------------
@ -1318,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
@ -1335,6 +1441,10 @@
#define RFM69_MAX_TOPICS 50
#endif
#ifndef RFM69_MAX_NODES
#define RFM69_MAX_NODES 255
#endif
#ifndef RFM69_DEFAULT_TOPIC
#define RFM69_DEFAULT_TOPIC "/rfm69gw/{node}/{key}"
#endif


+ 739
- 46
code/espurna/config/hardware.h View File

@ -34,20 +34,22 @@
#define MANUFACTURER "ESPRESSIF"
#define DEVICE "ESPURNA_CORE"
// UI modules
#define BUTTON_SUPPORT 0
#define MDNS_SERVER_SUPPORT 0
#define NTP_SUPPORT 0
#define WEB_SUPPORT 0
// Disable non-core modules
#define ALEXA_SUPPORT 0
#define BROKER_SUPPORT 0
#define BUTTON_SUPPORT 0
#define DOMOTICZ_SUPPORT 0
#define HOMEASSISTANT_SUPPORT 0
#define I2C_SUPPORT 0
#define MDNS_SERVER_SUPPORT 0
#define MQTT_SUPPORT 0
#define NTP_SUPPORT 0
#define SCHEDULER_SUPPORT 0
#define SENSOR_SUPPORT 0
#define THINGSPEAK_SUPPORT 0
#define WEB_SUPPORT 0
// Extra light-weight image
//#define DEBUG_SERIAL_SUPPORT 0
@ -70,7 +72,7 @@
// Buttons
#define BUTTON1_PIN 0
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
#define BUTTON1_RELAY 1
// Hidden button will enter AP mode if dblclick and reset the device when long-long-clicked
#define RELAY1_PIN 12
@ -123,7 +125,7 @@
#define RELAY1_PIN 5
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
// LED
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
@ -226,7 +228,6 @@
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_INVERSE
// Check http://tinkerman.cat/rfm69-wifi-gateway/
#elif defined(TINKERMAN_RFM69GW)
@ -930,6 +931,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 +1152,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 +1236,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
// -----------------------------------------------------------------------------
@ -1385,6 +1494,36 @@
#define RELAY1_TYPE RELAY_TYPE_LATCHED
#define RELAY1_RESET_PIN 12
// -----------------------------------------------------------------------------
// EX-Store Wifi Relay v5.0
// -----------------------------------------------------------------------------
#elif defined(EXS_WIFI_RELAY_V50)
// Info
#define MANUFACTURER "EXS"
#define DEVICE "WIFI_RELAY_V50"
// Buttons
#define BUTTON1_PIN 5
#define BUTTON2_PIN 4
#define BUTTON1_RELAY 1
#define BUTTON2_RELAY 2
#define BUTTON1_MODE BUTTON_SWITCH | BUTTON_DEFAULT_HIGH | BUTTON_SET_PULLUP
#define BUTTON2_MODE BUTTON_SWITCH | BUTTON_DEFAULT_HIGH | BUTTON_SET_PULLUP
// Relays
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_LATCHED
#define RELAY1_RESET_PIN 16
#define RELAY2_PIN 13
#define RELAY2_TYPE RELAY_TYPE_LATCHED
#define RELAY2_RESET_PIN 12
// LEDs
#define LED1_PIN 15
#define LED1_PIN_INVERSE 0
// -----------------------------------------------------------------------------
// V9261F
// -----------------------------------------------------------------------------
@ -1521,6 +1660,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
@ -1701,7 +1860,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
// -----------------------------------------------------------------------------
@ -1731,6 +1890,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
// -----------------------------------------------------------------------------
@ -1890,6 +2079,57 @@
#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 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
// -----------------------------------------------------------------------------
#elif defined(OUKITEL_P1)
// 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
// -----------------------------------------------------------------------------
@ -1902,7 +2142,7 @@
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
@ -1910,8 +2150,13 @@
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 4
#define LED1_PIN_INVERSE 0
#define LED1_PIN 0 // red
#define LED1_PIN_INVERSE 1
#define LED1_MODE LED_MODE_WIFI
#define LED2_PIN 15 // blue
#define LED2_PIN_INVERSE 1
#define LED2_MODE LED_MODE_RELAY
// HLW8012
#ifndef HLW8012_SUPPORT
@ -1921,8 +2166,34 @@
#define HLW8012_CF1_PIN 14
#define HLW8012_CF_PIN 5
#define HLW8012_CURRENT_R 0.001 // Current resistor
#define HLW8012_VOLTAGE_R_UP ( 2 * 1200000 ) // Upstream voltage resistor
#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
// -----------------------------------------------------------------------------
// 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
@ -2057,7 +2328,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
@ -2368,17 +2641,16 @@
// ------------------------------------------------------------------------------
// Estink Wifi Power Strip
// https://www.amazon.de/Steckdosenleiste-Ladeger%C3%A4t-Sprachsteuerung-SmartphonesTablets-Android/dp/B0796W5FZY
// Fornorm Wi-Fi USB Extension Socket (ZLD-34EU)
// https://www.aliexpress.com/item/Fornorm-WiFi-Extension-Socket-with-Surge-Protector-Smart-Power-Strip-3-Outlets-and-4-USB-Charging/32849743948.html
// Also: Estink Wifi Power Strip
// -----------------------------------------------------------------------------
#elif defined(ESTINK_WIFI_POWER_STRIP)
#elif defined(FORNORM_ZLD_34EU)
// Info
#define MANUFACTURER "ESTINK"
#define DEVICE "WIFI_POWER_STRIP"
#define MANUFACTURER "FORNORM"
#define DEVICE "ZLD_34EU"
// Disable UART noise since this board uses GPIO3
#define DEBUG_SERIAL_SUPPORT 0
@ -2447,19 +2719,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
@ -2492,39 +2765,148 @@
#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)
// -----------------------------------------------------------------------------
// 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 "HOMECUBE"
#define DEVICE "16A"
#define MANUFACTURER "BLITZWOLF"
#define DEVICE "BWSHPX_V23"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_PIN 3
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 15
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
//LED Pin 4 - ESP8266 onboard LED
//Red LED: 0
//Green LED: 12
//Blue LED: 2
// Blue
#define LED1_PIN 2
#define LED1_PIN_INVERSE 0
// Green
#define LED2_PIN 12
#define LED1_PIN 1
#define LED1_PIN_INVERSE 1
#define LED2_PIN 13
#define LED2_PIN_INVERSE 1
#define LED2_MODE LED_MODE_RELAY
#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
// Also model number KS-602S
// -----------------------------------------------------------------------------
#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
// ----------------------------------------------------------------------------------------
// 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
#define MANUFACTURER "HOMECUBE"
#define DEVICE "16A"
// 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
//LED Pin 4 - ESP8266 onboard LED
//Red LED: 0
//Green LED: 12
//Blue LED: 2
// Blue
#define LED1_PIN 2
#define LED1_PIN_INVERSE 0
// Green
#define LED2_PIN 12
#define LED2_PIN_INVERSE 1
#define LED2_MODE LED_MODE_RELAY
// Red
#define LED3_PIN 0
@ -2724,6 +3106,7 @@
#define BUTTON2_LNGLNGCLICK BUTTON_MODE_RESET
// Light
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
#define LIGHT_STEP 8
@ -2790,6 +3173,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
@ -2814,6 +3244,252 @@
#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
// Also labeled NETVIP
// https://www.amazon.es/Inteligente-NETVIP-Inal%C3%A1mbrico-Interruptor-Funciona/dp/B07KH8YWS5
// ----------------------------------------------------------------------------------------
#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
// ----------------------------------------------------------------------------------------
// Hama WiFi Steckdose (00176533)
// https://at.hama.com/00176533/hama-wifi-steckdose-3500w-16a
// ----------------------------------------------------------------------------------------
#elif defined(HAMA_WIFI_STECKDOSE_00176533)
// Info
#define MANUFACTURER "HAMA"
#define DEVICE "WIFI_STECKDOSE_00176533"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 4
#define LED1_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// Oxaoxe NX-SP202
// Digoo NX-SP202 (not tested)
// Digoo DG-SP202 (not tested)
// https://github.com/xoseperez/espurna/issues/1502
// -----------------------------------------------------------------------------
#elif defined(DIGOO_NX_SP202)
// Info
#define MANUFACTURER "DIGOO"
#define DEVICE "NX_SP202"
// Buttons
#define BUTTON1_PIN 0
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
#define BUTTON2_PIN 16
#define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#define BUTTON2_RELAY 2
// Relays
#define RELAY1_PIN 15
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#define RELAY2_PIN 14
#define RELAY2_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 13
#define LED1_PIN_INVERSE 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 23296
#define HLW8012_VOLTAGE_RATIO 310085
#define HLW8012_POWER_RATIO 3368471
#define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// Foxel's LightFox dual
// https://github.com/foxel/esp-dual-rf-switch
// -----------------------------------------------------------------------------
#elif defined(FOXEL_LIGHTFOX_DUAL)
// Info
#define MANUFACTURER "FOXEL"
#define DEVICE "LIGHTFOX_DUAL"
#define SERIAL_BAUDRATE 19200
#define RELAY_PROVIDER RELAY_PROVIDER_DUAL
#define DUMMY_RELAY_COUNT 2
#define DEBUG_SERIAL_SUPPORT 0
// Buttons
#define BUTTON1_RELAY 1
#define BUTTON2_RELAY 2
#define BUTTON3_RELAY 2
#define BUTTON4_RELAY 1
// -----------------------------------------------------------------------------
// Teckin SP20
// -----------------------------------------------------------------------------
#elif defined(TECKIN_SP20)
// Info
#define MANUFACTURER "TECKIN"
#define DEVICE "SP20"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 4
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
#define LED2_PIN 0
#define LED2_PIN_INVERSE 1
#define LED2_MODE LED_MODE_FINDME
#define LED2_RELAY 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
// -----------------------------------------------------------------------------
// Charging Essentials / LITESUN LA-WF3
// -----------------------------------------------------------------------------
#elif defined(LITESUN_LA_WF3)
// Info
#define MANUFACTURER "LITESUN"
#define DEVICE "LA_WF3"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 4 // 4 blue led
#define LED1_MODE LED_MODE_WIFI
#define LED1_PIN_INVERSE 1
#define LED2_PIN 5 // 5 red led
#define LED2_MODE LED_MODE_RELAY
#define LED2_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// TEST boards (do not use!!)
// -----------------------------------------------------------------------------
@ -2850,6 +3526,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
@ -2857,8 +3534,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
@ -2890,6 +3568,18 @@
#define ECH1560_MISO_PIN 11
#define ECH1560_INVERTED 12
// MICS-2710 & MICS-5525 test
#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
@ -2990,10 +3680,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


+ 46
- 10
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
@ -187,12 +190,21 @@ PROGMEM const char espurna_sensors[] =
#if MHZ19_SUPPORT
"MHZ19 "
#endif
#if MICS2710_SUPPORT
"MICS2710 "
#endif
#if MICS5525_SUPPORT
"MICS5525 "
#endif
#if NTC_SUPPORT
"NTC "
#endif
#if PMSX003_SUPPORT
"PMSX003 "
#endif
#if PULSEMETER_SUPPORT
"PULSEMETER "
#endif
#if PZEM004T_SUPPORT
"PZEM004T "
#endif
@ -217,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
"";
@ -226,9 +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, 0, 3 // NO2, CO, Ohms, pH
};
PROGMEM const char magnitude_unknown_topic[] = "unknown";
@ -251,12 +275,18 @@ 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]
PROGMEM const char magnitude_geiger_sv_topic[] = "ldr_uSvh"; // local dose rate [µSievert per hour]
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,
@ -265,10 +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_count_topic,
magnitude_no2_topic, magnitude_co_topic, magnitude_resistance_topic, magnitude_ph_topic
};
PROGMEM const char magnitude_empty[] = "";
@ -285,11 +317,11 @@ PROGMEM const char magnitude_kwh[] = "kWh";
PROGMEM const char magnitude_ugm3[] = "µg/m³";
PROGMEM const char magnitude_ppm[] = "ppm";
PROGMEM const char magnitude_lux[] = "lux";
PROGMEM const char magnitude_uv[] = "uv";
PROGMEM const char magnitude_distance[] = "m";
PROGMEM const char magnitude_mgm3[] = "mg/m³";
PROGMEM const char magnitude_geiger_cpm[] = "cpm"; // Counts per Minute: Unit of local dose rate (Geiger counting)
PROGMEM const char magnitude_geiger_sv[] = "µSv/h"; // µSievert per hour: 2nd unit of local dose rate (Geiger counting)
PROGMEM const char magnitude_resistance[] = "ohm";
PROGMEM const char* const magnitude_units[] = {
@ -299,10 +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_geiger_cpm, magnitude_geiger_sv, // Geiger counter units
magnitude_empty, //
magnitude_ppm, magnitude_ppm, // NO2 & CO2
magnitude_resistance,
magnitude_empty // pH
};
#endif

+ 22
- 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,15 +142,22 @@ 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
// -----------------------------------------------------------------------------
char * ltrim(char * s);
void nice_delay(unsigned long ms);
bool inline eraseSDKConfig();
#define ARRAYINIT(type, name, ...) type name[] = {__VA_ARGS__};
@ -165,7 +174,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 +185,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 +194,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 +210,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();

+ 302
- 42
code/espurna/config/sensors.h View File

@ -99,6 +99,25 @@
#define ANALOG_DELAY 0 // Delay between samples in micros
#endif
//Use the following to perform scaling of raw analog values
// scaledRead = ( factor * rawRead ) + offset
//
//Please take note that the offset is not affected by the scaling factor
#ifndef ANALOG_FACTOR
#define ANALOG_FACTOR 1.0 // Multiply raw reading by this factor
#endif
#ifndef ANALOG_OFFSET
#define ANALOG_OFFSET 0.0 // Add this offset to *scaled* value
#endif
// Round to this number of decimals
#ifndef ANALOG_DECIMALS
#define ANALOG_DECIMALS 2
#endif
//------------------------------------------------------------------------------
// BH1750
// Enable support by passing BH1750_SUPPORT=1 build flag
@ -115,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
@ -134,9 +168,27 @@
// 6 for 10ms, 7 for 20ms
#define BMX280_FILTER 0 // 0 for OFF, 1 for 2 values, 2 for 4 values, 3 for 8 values and 4 for 16 values
#define BMX280_TEMPERATURE 1 // Oversampling for temperature (set to 0 to disable magnitude)
// 0b000 = 0 = Skip measurement
// 0b001 = 1 = 1x 16bit/0.0050C resolution
// 0b010 = 2 = 2x 17bit/0.0025C
// 0b011 = 3 = 4x 18bit/0.0012C
// 0b100 = 4 = 8x 19bit/0.0006C
// 0b101 = 5 = 16x 20bit/0.0003C
#define BMX280_HUMIDITY 1 // Oversampling for humidity (set to 0 to disable magnitude, only for BME280)
// 0b000 = 0 = Skip measurement
// 0b001 = 1 = 1x 0.07% resolution
// 0b010 = 2 = 2x 0.05%
// 0b011 = 3 = 4x 0.04%
// 0b100 = 4 = 8x 0.03%
// 0b101 = 5 = 16x 0.02%
#define BMX280_PRESSURE 1 // Oversampling for pressure (set to 0 to disable magnitude)
// 0b000 = 0 = Skipped
// 0b001 = 1 = 1x 16bit/2.62 Pa resolution
// 0b010 = 2 = 2x 17bit/1.31 Pa
// 0b011 = 3 = 4x 18bit/0.66 Pa
// 0b100 = 4 = 8x 19bit/0.33 Pa
// 0b101 = 5 = 16x 20bit/0.16 Pa
//------------------------------------------------------------------------------
// Dallas OneWire temperature sensors
// Enable support by passing DALLAS_SUPPORT=1 build flag
@ -243,12 +295,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
@ -407,6 +475,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
@ -430,6 +503,45 @@
#define MHZ19_TX_PIN 15
#endif
//------------------------------------------------------------------------------
// MICS-2710 (and MICS-4514) NO2 sensor
// Enable support by passing MICS2710_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef MICS2710_SUPPORT
#define MICS2710_SUPPORT 0
#endif
#ifndef MICS2710_NOX_PIN
#define MICS2710_NOX_PIN 0
#endif
#ifndef MICS2710_PRE_PIN
#define MICS2710_PRE_PIN 4
#endif
#define MICS2710_PREHEAT_TIME 10000 // 10s preheat for NOX read
#define MICS2710_RL 820 // RL, load resistor
#define MICS2710_R0 2200 // R0 calibration value for NO2 sensor,
// Typical value as per datasheet
//------------------------------------------------------------------------------
// MICS-5525 (and MICS-4514) CO sensor
// Enable support by passing MICS5525_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef MICS5525_SUPPORT
#define MICS5525_SUPPORT 0
#endif
#ifndef MICS5525_RED_PIN
#define MICS5525_RED_PIN 0
#endif
#define MICS5525_RL 820 // RL, load resistor
#define MICS5525_R0 750000 // R0 calibration value for NO2 sensor,
// Typical value as per datasheet
//------------------------------------------------------------------------------
// NTC sensor
// Enable support by passing NTC_SUPPORT=1 build flag
@ -467,40 +579,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
@ -522,20 +600,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
@ -561,6 +665,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
@ -651,6 +801,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
// =============================================================================
@ -660,6 +881,7 @@
AM2320_SUPPORT || \
ANALOG_SUPPORT || \
BH1750_SUPPORT || \
BMP180_SUPPORT || \
BMX280_SUPPORT || \
CSE7766_SUPPORT || \
DALLAS_SUPPORT || \
@ -673,17 +895,23 @@
GEIGER_SUPPORT || \
GUVAS12SD_SUPPORT || \
HLW8012_SUPPORT || \
MICS2710_SUPPORT || \
MICS5525_SUPPORT || \
MHZ19_SUPPORT || \
NTC_SUPPORT || \
SDS011_SUPPORT || \
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
@ -737,6 +965,10 @@
#include "../sensors/BH1750Sensor.h"
#endif
#if BMP180_SUPPORT
#include "../sensors/BMP180Sensor.h"
#endif
#if BMX280_SUPPORT
#include "../sensors/BMX280Sensor.h"
#endif
@ -777,6 +1009,10 @@
#include "../sensors/EventSensor.h"
#endif
#if EZOPH_SUPPORT
#include "../sensors/EZOPHSensor.h"
#endif
#if GEIGER_SUPPORT
#include "../sensors/GeigerSensor.h"
#endif
@ -789,10 +1025,22 @@
#include "../sensors/HLW8012Sensor.h"
#endif
#if MAX6675_SUPPORT
#include "../sensors/MAX6675Sensor.h"
#endif
#if MHZ19_SUPPORT
#include "../sensors/MHZ19Sensor.h"
#endif
#if MICS2710_SUPPORT
#include "../sensors/MICS2710Sensor.h"
#endif
#if MICS5525_SUPPORT
#include "../sensors/MICS5525Sensor.h"
#endif
#if NTC_SUPPORT
#include "../sensors/NTCSensor.h"
#endif
@ -809,6 +1057,10 @@
#include "../sensors/PMSX003Sensor.h"
#endif
#if PULSEMETER_SUPPORT
#include "../sensors/PulseMeterSensor.h"
#endif
#if PZEM004T_SUPPORT
#include "../sensors/PZEM004TSensor.h"
#endif
@ -833,4 +1085,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

+ 66
- 35
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
@ -84,6 +96,10 @@
#define RELAY_PROVIDER_RFBRIDGE 3
#define RELAY_PROVIDER_STM 4
#define RELAY_GROUP_SYNC_NORMAL 0
#define RELAY_GROUP_SYNC_INVERSE 1
#define RELAY_GROUP_SYNC_RECEIVEONLY 2
//------------------------------------------------------------------------------
// UDP SYSLOG
//------------------------------------------------------------------------------
@ -250,36 +266,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_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
@ -302,11 +327,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_MAX 26
#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.3a"
#define APP_VERSION "1.13.6-dev"
#define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat"
#define CFG_VERSION 3

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

@ -7,6 +7,7 @@
#define WEBUI_IMAGE_SENSOR 2
#define WEBUI_IMAGE_RFBRIDGE 4
#define WEBUI_IMAGE_RFM69 8
#define WEBUI_IMAGE_LIGHTFOX 16
#define WEBUI_IMAGE_FULL 15
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
@ -45,6 +46,13 @@
#endif
#endif
#if defined(FOXEL_LIGHTFOX_DUAL)
#ifdef WEBUI_IMAGE
#undef WEBUI_IMAGE
#endif
#define WEBUI_IMAGE WEBUI_IMAGE_LIGHTFOX
#endif
#ifndef WEBUI_IMAGE
#define WEBUI_IMAGE WEBUI_IMAGE_SMALL
#endif
@ -67,6 +75,9 @@ PROGMEM const char espurna_webui[] =
#if WEBUI_IMAGE == WEBUI_IMAGE_RFM69
"RFM69"
#endif
#if WEBUI_IMAGE == WEBUI_IMAGE_LIGHTFOX
"LIGHTFOX"
#endif
#if WEBUI_IMAGE == WEBUI_IMAGE_FULL
"FULL"
#endif


+ 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.lightfox.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


+ 45
- 174
code/espurna/debug.ino View File

@ -2,20 +2,12 @@
DEBUG 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>
*/
#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')) {
DEBUG_PORT.print(prefix);
}
DEBUG_PORT.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

+ 107
- 16
code/espurna/domoticz.ino View File

@ -2,7 +2,7 @@
DOMOTICZ 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>
*/
@ -11,12 +11,13 @@ 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
//------------------------------------------------------------------------------
unsigned char _domoticzRelay(unsigned int idx) {
int _domoticzRelay(unsigned int idx) {
for (unsigned char relayID=0; relayID<relayCount(); relayID++) {
if (domoticzIdx(relayID) == idx) {
return relayID;
@ -36,6 +37,65 @@ 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);
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
void _domoticzLight(unsigned int idx, const JsonObject& root) {
if (!lightHasColor()) return;
JsonObject& color = root["Color"];
if (!color.success()) return;
// m field contains information about color mode (enum ColorMode from domoticz ColorSwitch.h):
unsigned int cmode = color["m"];
if (cmode == 3 || cmode == 4) { // ColorModeRGB or ColorModeCustom - see domoticz ColorSwitch.h
lightChannel(0, color["r"]);
lightChannel(1, color["g"]);
lightChannel(2, color["b"]);
// WARM WHITE (or MONOCHROME WHITE) and COLD WHITE are always sent.
// Apply only when supported.
if (lightChannels() > 3) {
lightChannel(3, color["ww"]);
}
if (lightChannels() > 4) {
lightChannel(4, color["cw"]);
}
// domoticz uses 100 as maximum value while we're using LIGHT_MAX_BRIGHTNESS
unsigned int brightness = (root["Level"].as<uint8_t>() / 100.0) * LIGHT_MAX_BRIGHTNESS;
lightBrightness(brightness);
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received rgb:%u,%u,%u ww:%u,cw:%u brightness:%u for IDX %u\n"),
color["r"].as<uint8_t>(),
color["g"].as<uint8_t>(),
color["b"].as<uint8_t>(),
color["ww"].as<uint8_t>(),
color["cw"].as<uint8_t>(),
brightness,
idx
);
lightUpdate(true, mqttForward());
}
}
#endif
void _domoticzMqtt(unsigned int type, const char * topic, const char * payload) {
if (!_dcz_enabled) return;
@ -67,11 +127,19 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
// IDX
unsigned int idx = root["idx"];
unsigned char relayID = _domoticzRelay(idx);
String stype = root["stype"];
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (stype.startsWith("RGB") && (domoticzIdx(0) == idx)) {
_domoticzLight(idx, root);
}
#endif
int 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);
_domoticzStatus(relayID, value >= 1);
}
}
@ -80,6 +148,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 +172,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 +181,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 +197,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 +224,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 +252,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);
}

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

@ -2,7 +2,7 @@
ENCODER MODULE
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2018-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -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 {


+ 12
- 7
code/espurna/espurna.ino View File

@ -2,7 +2,7 @@
ESPurna
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>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -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
@ -187,6 +189,9 @@ void setup() {
#if UART_MQTT_SUPPORT
uartmqttSetup();
#endif
#ifdef FOXEL_LIGHTFOX_DUAL
lightfoxSetup();
#endif
// 3rd party code hook


+ 1
- 1
code/espurna/filters/BaseFilter.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Base Filter (other filters inherit from this)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT


+ 1
- 1
code/espurna/filters/LastFilter.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Last Filter
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT


+ 1
- 1
code/espurna/filters/MaxFilter.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Max Filter
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT


+ 1
- 1
code/espurna/filters/MedianFilter.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Median Filter
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT


+ 1
- 1
code/espurna/filters/MovingAverageFilter.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Moving Average Filter
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT


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

@ -2,7 +2,7 @@
GPIO MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/


+ 107
- 31
code/espurna/homeassistant.ino View File

@ -2,13 +2,14 @@
HOME ASSISTANT MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2019 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;
@ -36,11 +37,10 @@ void _haSendMagnitude(unsigned char i, JsonObject& config) {
config["name"] = _haFixName(getSetting("hostname") + String(" ") + magnitudeTopic(type));
config.set("platform", "mqtt");
config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
config["unit_of_measurement"] = "\"" + magnitudeUnits(type) + "\"";
config["unit_of_measurement"] = magnitudeUnits(type);
}
void _haSendMagnitudes() {
void _haSendMagnitudes(const JsonObject& deviceConfig) {
for (unsigned char i=0; i<magnitudeCount(); i++) {
@ -54,6 +54,9 @@ void _haSendMagnitudes() {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
config["uniq_id"] = getIdentifier() + "_" + magnitudeTopic(magnitudeType(i)) + "_" + String(i);
config["device"] = deviceConfig;
config.printTo(output);
jsonBuffer.clear();
}
@ -114,7 +117,7 @@ void _haSendSwitch(unsigned char i, JsonObject& config) {
}
void _haSendSwitches() {
void _haSendSwitches(const JsonObject& deviceConfig) {
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light");
@ -134,6 +137,9 @@ void _haSendSwitches() {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
config["uniq_id"] = getIdentifier() + "_" + type + "_" + String(i);
config["device"] = deviceConfig;
config.printTo(output);
jsonBuffer.clear();
}
@ -147,9 +153,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 +167,16 @@ String _haGetConfig() {
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
output += 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,12 +184,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 += "\"}";
}
output += "\n";
jsonBuffer.clear();
printer(output);
}
#if SENSOR_SUPPORT
@ -188,8 +209,16 @@ String _haGetConfig() {
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
output += "sensor:\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 += " - ";
@ -197,18 +226,36 @@ String _haGetConfig() {
} else {
output += " ";
}
output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
String value = kv.value.as<String>();
value.replace("%", "'%'");
output += kv.key;
output += ": ";
output += value;
output += "\n";
}
output += " ";
if (wrapJson) {
output += "\"}";
}
output += "\n";
jsonBuffer.clear();
printer(output);
}
#endif
}
return output;
void _haGetDeviceConfig(JsonObject& config) {
String identifier = getIdentifier();
config.createNestedArray("identifiers").add(identifier);
config["name"] = getSetting("desc", getSetting("hostname"));
config["manufacturer"] = String(MANUFACTURER);
config["model"] = String(DEVICE);
config["sw_version"] = String(APP_NAME) + " " + String(APP_VERSION) + " (" + getCoreVersion() + ")";
}
void _haSend() {
@ -221,12 +268,18 @@ void _haSend() {
DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n"));
// Get common device config
DynamicJsonBuffer jsonBuffer;
JsonObject& deviceConfig = jsonBuffer.createObject();
_haGetDeviceConfig(deviceConfig);
// Send messages
_haSendSwitches();
_haSendSwitches(deviceConfig);
#if SENSOR_SUPPORT
_haSendMagnitudes();
_haSendMagnitudes(deviceConfig);
#endif
jsonBuffer.clear();
_haSendFlag = false;
}
@ -240,6 +293,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);
}
@ -252,11 +307,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);
}
}
@ -265,32 +316,56 @@ void _haWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& d
#if TERMINAL_SUPPORT
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();
});
}
#endif
// -----------------------------------------------------------------------------
#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();
@ -299,6 +374,7 @@ void haSetup() {
wsOnSendRegister(_haWebSocketOnSend);
wsOnActionRegister(_haWebSocketOnAction);
wsOnReceiveRegister(_haWebSocketOnReceive);
espurnaRegisterLoop(_haLoop);
#endif
#if TERMINAL_SUPPORT


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

@ -2,7 +2,7 @@
I2C MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -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


+ 16
- 1
code/espurna/influxdb.ino View File

@ -2,7 +2,7 @@
INFLUXDB MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -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);


+ 13
- 26
code/espurna/ir.ino View File

@ -3,8 +3,8 @@
IR MODULE
Copyright (C) 2018 by Alexander Kolesnikov (raw and MQTT implementation)
Copyright (C) 2017-2018 by François Déchery
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2019 by François Déchery
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
-----------------------------------------------------------------------------
Configuration
@ -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


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

@ -2,7 +2,7 @@
LED 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>
*/
@ -51,6 +51,16 @@ void _ledMode(unsigned char id, unsigned char mode) {
_leds[id].mode = mode;
}
unsigned char _ledRelay(unsigned char id) {
if (id >= _ledCount()) return false;
return _leds[id].relay;
}
void _ledRelay(unsigned char id, unsigned char relay) {
if (id >= _ledCount()) return;
_leds[id].relay = relay;
}
void _ledBlink(unsigned char id, unsigned long delayOff, unsigned long delayOn) {
if (id >= _ledCount()) return;
static unsigned long next = millis();
@ -68,11 +78,29 @@ bool _ledWebSocketOnReceive(const char * key, JsonVariant& value) {
void _ledWebSocketOnSend(JsonObject& root) {
if (_ledCount() == 0) return;
root["ledVisible"] = 1;
root["ledMode0"] = _ledMode(0);
JsonArray& leds = root.createNestedArray("ledConfig");
for (byte i=0; i<_ledCount(); i++) {
JsonObject& led = leds.createNestedObject();
led["mode"] = getSetting("ledMode", i, "").toInt();
led["relay"] = getSetting("ledRelay", i, "").toInt();
}
}
#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) {
@ -120,6 +148,7 @@ unsigned char _ledCount() {
void _ledConfigure() {
for (unsigned int i=0; i < _leds.size(); i++) {
_ledMode(i, getSetting("ledMode", i, _ledMode(i)).toInt());
_ledRelay(i, getSetting("ledRelay", i, _ledRelay(i)).toInt());
}
_led_update = true;
}
@ -133,32 +162,34 @@ void ledUpdate(bool value) {
void ledSetup() {
#if LED1_PIN != GPIO_NONE
_leds.push_back((led_t) { LED1_PIN, LED1_PIN_INVERSE, LED1_MODE, LED1_RELAY });
_leds.push_back((led_t) { LED1_PIN, LED1_PIN_INVERSE, LED1_MODE, LED1_RELAY - 1 });
#endif
#if LED2_PIN != GPIO_NONE
_leds.push_back((led_t) { LED2_PIN, LED2_PIN_INVERSE, LED2_MODE, LED2_RELAY });
_leds.push_back((led_t) { LED2_PIN, LED2_PIN_INVERSE, LED2_MODE, LED2_RELAY - 1 });
#endif
#if LED3_PIN != GPIO_NONE
_leds.push_back((led_t) { LED3_PIN, LED3_PIN_INVERSE, LED3_MODE, LED3_RELAY });
_leds.push_back((led_t) { LED3_PIN, LED3_PIN_INVERSE, LED3_MODE, LED3_RELAY - 1 });
#endif
#if LED4_PIN != GPIO_NONE
_leds.push_back((led_t) { LED4_PIN, LED4_PIN_INVERSE, LED4_MODE, LED4_RELAY });
_leds.push_back((led_t) { LED4_PIN, LED4_PIN_INVERSE, LED4_MODE, LED4_RELAY - 1 });
#endif
#if LED5_PIN != GPIO_NONE
_leds.push_back((led_t) { LED5_PIN, LED5_PIN_INVERSE, LED5_MODE, LED5_RELAY });
_leds.push_back((led_t) { LED5_PIN, LED5_PIN_INVERSE, LED5_MODE, LED5_RELAY - 1 });
#endif
#if LED6_PIN != GPIO_NONE
_leds.push_back((led_t) { LED6_PIN, LED6_PIN_INVERSE, LED6_MODE, LED6_RELAY });
_leds.push_back((led_t) { LED6_PIN, LED6_PIN_INVERSE, LED6_MODE, LED6_RELAY - 1 });
#endif
#if LED7_PIN != GPIO_NONE
_leds.push_back((led_t) { LED7_PIN, LED7_PIN_INVERSE, LED7_MODE, LED7_RELAY });
_leds.push_back((led_t) { LED7_PIN, LED7_PIN_INVERSE, LED7_MODE, LED7_RELAY - 1 });
#endif
#if LED8_PIN != GPIO_NONE
_leds.push_back((led_t) { LED8_PIN, LED8_PIN_INVERSE, LED8_MODE, LED8_RELAY });
_leds.push_back((led_t) { LED8_PIN, LED8_PIN_INVERSE, LED8_MODE, LED8_RELAY - 1 });
#endif
for (unsigned int i=0; i < _leds.size(); i++) {
pinMode(_leds[i].pin, OUTPUT);
setSetting("ledMode", i, _leds[i].mode);
setSetting("ledRelay", i, _leds[i].relay);
_ledStatus(i, false);
}
@ -173,6 +204,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
@ -207,13 +243,13 @@ void ledLoop() {
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(_leds[i].relay-1)) {
if (relayStatus(_leds[i].relay)) {
_ledBlink(i, 4900, 100);
} else {
_ledBlink(i, 100, 4900);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(_leds[i].relay-1)) {
if (relayStatus(_leds[i].relay)) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 100, 900);
@ -229,13 +265,13 @@ void ledLoop() {
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(_leds[i].relay-1)) {
if (relayStatus(_leds[i].relay)) {
_ledBlink(i, 100, 4900);
} else {
_ledBlink(i, 4900, 100);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(_leds[i].relay-1)) {
if (relayStatus(_leds[i].relay)) {
_ledBlink(i, 100, 900);
} else {
_ledBlink(i, 900, 100);
@ -250,11 +286,11 @@ void ledLoop() {
if (!_led_update) continue;
if (_ledMode(i) == LED_MODE_FOLLOW) {
_ledStatus(i, relayStatus(_leds[i].relay-1));
_ledStatus(i, relayStatus(_leds[i].relay));
}
if (_ledMode(i) == LED_MODE_FOLLOW_INVERSE) {
_ledStatus(i, !relayStatus(_leds[i].relay-1));
_ledStatus(i, !relayStatus(_leds[i].relay));
}
if (_ledMode(i) == LED_MODE_FINDME) {


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

@ -3,7 +3,7 @@
RFM69Wrap
RFM69 by Felix Ruso (http://LowPowerLab.com/contact) wrapper for ESP8266
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>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by


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

@ -2,7 +2,7 @@
StreamInjector
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>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by


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

@ -2,7 +2,7 @@
LIGHT 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>
*/
@ -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);
@ -796,6 +843,20 @@ void _lightWebSocketOnSend(JsonObject& root) {
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) {
if (_light_has_color) {
@ -844,9 +905,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 +918,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 +950,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 +960,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 +987,25 @@ 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);
const String value(e->argv[1]);
if( value.length() > 0 ) {
if( value[0] == '+' || value[0] == '-' ) {
lightBrightness(lightBrightness()+String(e->argv[1]).toInt());
} else {
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 +1014,45 @@ 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);
const String value(e->argv[1]);
String color = String("M");
if( value.length() > 0 ) {
if( value[0] == '+' || value[0] == '-' ) {
color += String(_light_mireds + String(e->argv[1]).toInt());
} else {
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();
});
}


+ 107
- 0
code/espurna/lightfox.ino View File

@ -0,0 +1,107 @@
/*
LightFox module
Copyright (C) 2019 by Andrey F. Kupreychik <foxle@quickfox.ru>
*/
#ifdef FOXEL_LIGHTFOX_DUAL
// -----------------------------------------------------------------------------
// DEFINITIONS
// -----------------------------------------------------------------------------
#define LIGHTFOX_CODE_START 0xA0
#define LIGHTFOX_CODE_LEARN 0xF1
#define LIGHTFOX_CODE_CLEAR 0xF2
#define LIGHTFOX_CODE_STOP 0xA1
// -----------------------------------------------------------------------------
// PUBLIC
// -----------------------------------------------------------------------------
void lightfoxLearn() {
Serial.write(LIGHTFOX_CODE_START);
Serial.write(LIGHTFOX_CODE_LEARN);
Serial.write(0x00);
Serial.write(LIGHTFOX_CODE_STOP);
Serial.println();
Serial.flush();
DEBUG_MSG_P(PSTR("[LIGHTFOX] Learn comman sent\n"));
}
void lightfoxClear() {
Serial.write(LIGHTFOX_CODE_START);
Serial.write(LIGHTFOX_CODE_CLEAR);
Serial.write(0x00);
Serial.write(LIGHTFOX_CODE_STOP);
Serial.println();
Serial.flush();
DEBUG_MSG_P(PSTR("[LIGHTFOX] Clear comman sent\n"));
}
// -----------------------------------------------------------------------------
// WEB
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
void _lightfoxWebSocketOnSend(JsonObject& root) {
root["lightfoxVisible"] = 1;
uint8_t buttonsCount = _buttons.size();
root["lightfoxRelayCount"] = relayCount();
JsonArray& rfb = root.createNestedArray("lightfoxButtons");
for (byte id=0; id<buttonsCount; id++) {
JsonObject& node = rfb.createNestedObject();
node["id"] = id;
node["relay"] = getSetting("btnRelay", id, "0");
}
}
void _lightfoxWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "lightfoxLearn") == 0) lightfoxLearn();
if (strcmp(action, "lightfoxClear") == 0) lightfoxClear();
}
#endif
// -----------------------------------------------------------------------------
// TERMINAL
// -----------------------------------------------------------------------------
#if TERMINAL_SUPPORT
void _lightfoxInitCommands() {
terminalRegisterCommand(F("LIGHTFOX.LEARN"), [](Embedis* e) {
lightfoxLearn();
DEBUG_MSG_P(PSTR("+OK\n"));
});
terminalRegisterCommand(F("LIGHTFOX.CLEAR"), [](Embedis* e) {
lightfoxClear();
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
#endif
// -----------------------------------------------------------------------------
// SETUP & LOOP
// -----------------------------------------------------------------------------
void lightfoxSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_lightfoxWebSocketOnSend);
wsOnActionRegister(_lightfoxWebSocketOnAction);
#endif
#if TERMINAL_SUPPORT
_lightfoxInitCommands();
#endif
}
#endif

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

@ -2,7 +2,7 @@
LLMNR MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/


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

@ -2,7 +2,7 @@
MDNS MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/


+ 99
- 3
code/espurna/migrate.ino View File

@ -2,7 +2,7 @@
MIGRATE 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>
*/
@ -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);
@ -975,7 +986,7 @@ void migrate() {
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(ESTINK_WIFI_POWER_STRIP)
#elif defined(FORNORM_ZLD_34EU)
setSetting("board", 77);
setSetting("btnGPIO", 0, 16);
@ -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);
@ -1204,6 +1215,91 @@ void migrate() {
setSetting("chLogic", 3, 0);
setSetting("relays", 1);
#elif defined(EXS_WIFI_RELAY_V50)
setSetting("board", 91);
setSetting("btnGPIO", 0, 5);
setSetting("btnGPIO", 1, 4);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("relayGPIO", 0, 14);
setSetting("relayGPIO", 1, 13);
setSetting("relayResetGPIO", 0, 16);
setSetting("relayResetGPIO", 1, 12);
setSetting("relayType", 0, RELAY_TYPE_LATCHED);
setSetting("relayType", 0, RELAY_TYPE_LATCHED);
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);
#elif defined(DIGOO_NX_SP202)
setSetting("board", 95);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("btnGPIO", 1, 16);
setSetting("btnRelay", 1, 1);
setSetting("relayGPIO", 0, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayGPIO", 1, 14);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 12);
setSetting("cf1GPIO", 5);
setSetting("cfGPIO", 4);
setSetting("pwrRatioC", 23296);
setSetting("pwrRatioV", 310085);
setSetting("pwrRatioP", 3368471);
setSetting("hlwSelC", LOW);
setSetting("hlwIntM", FALLING);
#elif defined(FOXEL_LIGHTFOX_DUAL)
setSetting("board", 96);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("btnRelay", 2, 1);
setSetting("btnRelay", 3, 0);
setSetting("relayProvider", RELAY_PROVIDER_DUAL);
setSetting("relays", 2);
#else
// Allow users to define new settings without migration config


+ 34
- 23
code/espurna/mqtt.ino View File

@ -2,7 +2,7 @@
MQTT 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>
*/
@ -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);
EEPROMr.commit();
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;
}


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

@ -2,7 +2,7 @@
NETBIOS MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/


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

@ -2,7 +2,7 @@
NOFUSS 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>
*/
@ -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);
}
});


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

@ -2,7 +2,7 @@
NTP 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>
*/
@ -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) {


+ 38
- 5
code/espurna/ota.ino View File

@ -2,7 +2,7 @@
OTA 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>
*/
@ -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);


+ 229
- 134
code/espurna/relay.ino View File

@ -2,7 +2,7 @@
RELAY 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>
*/
@ -67,6 +67,8 @@ void _relayProviderStatus(unsigned char id, bool status) {
if (_relays[i].current_status) mask = mask + (1 << i);
}
DEBUG_MSG_P(PSTR("[RELAY] [DUAL] Sending relay mask: %d\n"), mask);
// Send it to F330
Serial.flush();
Serial.write(0xA0);
@ -83,37 +85,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 +150,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 +171,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 +184,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
@ -173,30 +193,20 @@ void _relayProcess(bool mode) {
#endif
if (!_relayRecursive) {
relayPulse(id);
_relaySaveTicker.once_ms(RELAY_SAVE_DELAY, relaySave);
// We will trigger a commit only if
// we care about current relay status on boot
unsigned char boot_mode = getSetting("relayBoot", id, RELAY_BOOT_MODE).toInt();
bool do_commit = ((RELAY_BOOT_SAME == boot_mode) || (RELAY_BOOT_TOGGLE == boot_mode));
_relaySaveTicker.once_ms(RELAY_SAVE_DELAY, relaySave, do_commit);
#if WEB_SUPPORT
wsSend(_relayWebSocketUpdate);
#endif
}
#if DOMOTICZ_SUPPORT
domoticzSendRelay(id);
#endif
#if INFLUXDB_SUPPORT
relayInfluxDB(id);
#endif
#if THINGSPEAK_SUPPORT
tspkEnqueueRelay(id, target);
tspkFlush();
#endif
// Flag relay-based LEDs to update status
#if LED_SUPPORT
ledUpdate(true);
#endif
}
_relays[id].report = false;
_relays[id].group_report = false;
@ -383,16 +393,41 @@ void relaySync(unsigned char id) {
}
void relaySave() {
void relaySave(bool do_commit) {
// Relay status is stored in a single byte
// This means that, atm,
// we are only storing the status of the first 8 relays.
unsigned char bit = 1;
unsigned char mask = 0;
for (unsigned int i=0; i < _relays.size(); i++) {
unsigned char count = _relays.size();
if (count > 8) count = 8;
for (unsigned int i=0; i < count; i++) {
if (relayStatus(i)) mask += bit;
bit += bit;
}
EEPROMr.write(EEPROM_RELAY_STATUS, mask);
DEBUG_MSG_P(PSTR("[RELAY] Saving mask: %d\n"), mask);
EEPROMr.commit();
DEBUG_MSG_P(PSTR("[RELAY] Setting relay mask: %d\n"), mask);
// The 'do_commit' flag controls wether we are commiting this change or not.
// It is useful to set it to 'false' if the relay change triggering the
// save involves a relay whose boot mode is independent from current mode,
// thus storing the last relay value is not absolutely necessary.
// Nevertheless, we store the value in the EEPROM buffer so it will be written
// on the next commit.
if (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
eepromCommit();
}
}
void relaySave() {
relaySave(true);
}
void relayToggle(unsigned char id, bool report, bool group_report) {
@ -445,6 +480,12 @@ unsigned char relayParsePayload(const char * payload) {
// BACKWARDS COMPATIBILITY
void _relayBackwards() {
for (unsigned int i=0; i<_relays.size(); i++) {
if (!hasSetting("mqttGroupInv", i)) continue;
setSetting("mqttGroupSync", i, getSetting("mqttGroupInv", i));
delSetting("mqttGroupInv", i);
}
byte relayMode = getSetting("relayMode", RELAY_BOOT_MODE).toInt();
byte relayPulseMode = getSetting("relayPulseMode", RELAY_PULSE_MODE).toInt();
float relayPulseTime = getSetting("relayPulseTime", RELAY_PULSE_TIME).toFloat();
@ -474,27 +515,34 @@ void _relayBoot() {
DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %d\n"), mask);
// Walk the relays
bool status = false;
bool status;
for (unsigned int i=0; i<_relays.size(); i++) {
unsigned char boot_mode = getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt();
DEBUG_MSG_P(PSTR("[RELAY] Relay #%d boot mode %d\n"), i, boot_mode);
status = false;
switch (boot_mode) {
case RELAY_BOOT_SAME:
status = ((mask & bit) == bit);
if (i < 8) {
status = ((mask & bit) == bit);
}
break;
case RELAY_BOOT_TOGGLE:
status = ((mask & bit) != bit);
mask ^= bit;
trigger_save = true;
if (i < 8) {
status = ((mask & bit) != bit);
mask ^= bit;
trigger_save = true;
}
break;
case RELAY_BOOT_ON:
status = true;
break;
case RELAY_BOOT_OFF:
default:
status = false;
break;
}
_relays[i].current_status = !status;
_relays[i].target_status = status;
#if RELAY_PROVIDER == RELAY_PROVIDER_STM
@ -508,7 +556,7 @@ void _relayBoot() {
// Save if there is any relay in the RELAY_BOOT_TOGGLE mode
if (trigger_save) {
EEPROMr.write(EEPROM_RELAY_STATUS, mask);
EEPROMr.commit();
eepromCommit();
}
_relayRecursive = false;
@ -517,6 +565,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);
@ -525,8 +578,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();
}
}
@ -543,34 +594,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_sync = relays.createNestedArray("group_sync");
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_sync.add(getSetting("mqttGroupSync", i, 0).toInt());
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);
@ -707,6 +815,18 @@ void relaySetupAPI() {
#if MQTT_SUPPORT
void _relayMQTTGroup(unsigned char id) {
String topic = getSetting("mqttGroup", id, "");
if (!topic.length()) return;
unsigned char mode = getSetting("mqttGroupSync", id, RELAY_GROUP_SYNC_NORMAL).toInt();
if (mode == RELAY_GROUP_SYNC_RECEIVEONLY) return;
bool status = relayStatus(id);
if (mode == RELAY_GROUP_SYNC_INVERSE) status = !status;
mqttSendRaw(topic.c_str(), status ? RELAY_MQTT_ON : RELAY_MQTT_OFF);
}
void relayMQTT(unsigned char id) {
if (id >= _relays.size()) return;
@ -720,12 +840,7 @@ void relayMQTT(unsigned char id) {
// Check group topic
if (_relays[id].group_report) {
_relays[id].group_report = false;
String t = getSetting("mqttGroup", id, "");
if (t.length() > 0) {
bool status = relayStatus(id);
if (getSetting("mqttGroupInv", id, 0).toInt() == 1) status = !status;
mqttSendRaw(t.c_str(), status ? RELAY_MQTT_ON : RELAY_MQTT_OFF);
}
_relayMQTTGroup(id);
}
// Send speed for IFAN02
@ -766,7 +881,7 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
if (type == MQTT_CONNECT_EVENT) {
// Send status on connect
#if not HEARTBEAT_REPORT_RELAY
#if (HEARTBEAT_MODE == HEARTBEAT_NONE) or (not HEARTBEAT_REPORT_RELAY)
relayMQTT();
#endif
@ -852,7 +967,7 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
if (value == 0xFF) return;
if (value < 2) {
if (getSetting("mqttGroupInv", i, 0).toInt() == 1) {
if (getSetting("mqttGroupSync", i, RELAY_GROUP_SYNC_NORMAL).toInt() == RELAY_GROUP_SYNC_INVERSE) {
value = 1 - value;
}
}
@ -894,20 +1009,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
//------------------------------------------------------------------------------
@ -916,9 +1017,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();
@ -941,7 +1042,7 @@ void _relayInitCommands() {
DEBUG_MSG_P(PSTR("Pulse time: %d\n"), _relays[id].pulse_ms);
}
DEBUG_MSG_P(PSTR("+OK\n"));
terminalOK();
});
}
@ -959,44 +1060,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
unsigned int _delay_on[8] = {RELAY1_DELAY_ON, RELAY2_DELAY_ON, RELAY3_DELAY_ON, RELAY4_DELAY_ON, RELAY5_DELAY_ON, RELAY6_DELAY_ON, RELAY7_DELAY_ON, RELAY8_DELAY_ON};
unsigned int _delay_off[8] = {RELAY1_DELAY_OFF, RELAY2_DELAY_OFF, RELAY3_DELAY_OFF, RELAY4_DELAY_OFF, RELAY5_DELAY_OFF, RELAY6_DELAY_OFF, RELAY7_DELAY_OFF, RELAY8_DELAY_OFF};
for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL,0,_delay_on[i], _delay_off[i]});
}
#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

+ 262
- 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-2019 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,36 @@ 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(root);
}
void _rfbWebSocketSendCode(unsigned char id) {
_rfbWebSocketSendCodeArray(id, 1);
}
void _rfbWebSocketSendCodes() {
_rfbWebSocketSendCodeArray(0, relayCount());
}
void _rfbWebSocketOnSend(JsonObject& root) {
root["rfbVisible"] = 1;
@ -96,15 +123,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 +132,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 +147,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 +168,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 +253,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 +268,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 +288,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 +302,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 +327,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 +342,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 +369,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 +387,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 +412,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 +439,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 +468,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 +536,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 +545,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 +730,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 +740,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 +763,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


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

@ -25,7 +25,7 @@ struct _node_t {
unsigned char lastPacketID = 0;
};
_node_t _rfm69_node_info[255];
_node_t _rfm69_node_info[RFM69_MAX_NODES];
unsigned char _rfm69_node_count;
unsigned long _rfm69_packet_count;
@ -115,9 +115,11 @@ void _rfm69Debug(const char * level, packet_t * data) {
void _rfm69Process(packet_t * data) {
// Count seen nodes and packets
// Is node beyond RFM69_MAX_NODES?
if (data->senderID >= RFM69_MAX_NODES) return;
// 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
@ -235,9 +237,10 @@ void _rfm69Loop() {
}
void _rfm69Clear() {
for(unsigned int i=0; i<255; i++) {
for(unsigned int i=0; i<RFM69_MAX_NODES; i++) {
_rfm69_node_info[i].duplicates = 0;
_rfm69_node_info[i].missing = 0;
_rfm69_node_info[i].count = 0;
}
_rfm69_node_count = 0;
_rfm69_packet_count = 0;


+ 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


+ 301
- 39
code/espurna/sensor.ino View File

@ -2,7 +2,7 @@
SENSOR 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>
*/
@ -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();
// -----------------------------------------------------------------------------
@ -52,6 +56,7 @@ unsigned char _magnitudeDecimals(unsigned char type) {
// Hardcoded decimals (these should be linked to the unit, instead of the magnitude)
if (type == MAGNITUDE_ANALOG) return ANALOG_DECIMALS;
if (type == MAGNITUDE_ENERGY ||
type == MAGNITUDE_ENERGY_DELTA) {
if (_sensor_energy_units == ENERGY_KWH) return 3;
@ -97,6 +102,31 @@ 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;
@ -111,37 +141,52 @@ void _sensorWebSocketSendData(JsonObject& root) {
char buffer[10];
bool hasTemperature = false;
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;
if (magnitude.type == MAGNITUDE_HUMIDITY) hasHumidity = true;
#if MICS2710_SUPPORT || MICS5525_SUPPORT
if (magnitude.type == MAGNITUDE_CO || magnitude.type == MAGNITUDE_NO2) hasMICS = true;
#endif
}
magnitudes["size"] = size;
if (hasTemperature) root["temperatureVisible"] = 1;
if (hasHumidity) root["humidityVisible"] = 1;
if (hasMICS) root["micsVisible"] = 1;
}
@ -192,9 +237,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;
@ -255,7 +307,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"),
@ -266,8 +318,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
@ -343,6 +452,9 @@ void _sensorLoad() {
AnalogSensor * sensor = new AnalogSensor();
sensor->setSamples(ANALOG_SAMPLES);
sensor->setDelay(ANALOG_DELAY);
//CICM For analog scaling
sensor->setFactor(ANALOG_FACTOR);
sensor->setOffset(ANALOG_OFFSET);
_sensors.push_back(sensor);
}
#endif
@ -356,6 +468,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();
@ -518,15 +638,27 @@ void _sensorLoad() {
MHZ19Sensor * sensor = new MHZ19Sensor();
sensor->setRX(MHZ19_RX_PIN);
sensor->setTX(MHZ19_TX_PIN);
if (getSetting("mhz19CalibrateAuto", 0).toInt() == 1)
sensor->setCalibrateAuto(true);
_sensors.push_back(sensor);
}
#endif
#if SDS011_SUPPORT
#if MICS2710_SUPPORT
{
SDS011Sensor * sensor = new SDS011Sensor();
sensor->setRX(SDS011_RX_PIN);
sensor->setTX(SDS011_TX_PIN);
MICS2710Sensor * sensor = new MICS2710Sensor();
sensor->setAnalogGPIO(MICS2710_NOX_PIN);
sensor->setPreHeatGPIO(MICS2710_PRE_PIN);
sensor->setRL(MICS2710_RL);
_sensors.push_back(sensor);
}
#endif
#if MICS5525_SUPPORT
{
MICS5525Sensor * sensor = new MICS5525Sensor();
sensor->setAnalogGPIO(MICS5525_RED_PIN);
sensor->setRL(MICS5525_RL);
_sensors.push_back(sensor);
}
#endif
@ -545,15 +677,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 PMSX003_SUPPORT
{
PMSX003Sensor * sensor = new PMSX003Sensor();
@ -568,15 +691,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
@ -614,6 +772,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) {
@ -698,6 +893,20 @@ void _sensorInit() {
// Custom initializations
#if MICS2710_SUPPORT
if (_sensors[i]->getID() == SENSOR_MICS2710_ID) {
MICS2710Sensor * sensor = (MICS2710Sensor *) _sensors[i];
sensor->setR0(getSetting("snsR0", MICS2710_R0).toInt());
}
#endif // MICS2710_SUPPORT
#if MICS5525_SUPPORT
if (_sensors[i]->getID() == SENSOR_MICS5525_ID) {
MICS5525Sensor * sensor = (MICS5525Sensor *) _sensors[i];
sensor->setR0(getSetting("snsR0", MICS5525_R0).toInt());
}
#endif // MICS5525_SUPPORT
#if EMON_ANALOG_SUPPORT
if (_sensors[i]->getID() == SENSOR_EMON_ANALOG_ID) {
@ -758,6 +967,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
}
}
@ -779,6 +995,30 @@ void _sensorConfigure() {
// Specific sensor settings
for (unsigned char i=0; i<_sensors.size(); i++) {
#if MICS2710_SUPPORT
if (_sensors[i]->getID() == SENSOR_MICS2710_ID) {
if (getSetting("snsResetCalibration", 0).toInt() == 1) {
MICS2710Sensor * sensor = (MICS2710Sensor *) _sensors[i];
sensor->calibrate();
setSetting("snsR0", sensor->getR0());
}
}
#endif // MICS2710_SUPPORT
#if MICS5525_SUPPORT
if (_sensors[i]->getID() == SENSOR_MICS5525_ID) {
if (getSetting("snsResetCalibration", 0).toInt() == 1) {
MICS5525Sensor * sensor = (MICS5525Sensor *) _sensors[i];
sensor->calibrate();
setSetting("snsR0", sensor->getR0());
}
}
#endif // MICS5525_SUPPORT
#if EMON_ANALOG_SUPPORT
if (_sensors[i]->getID() == SENSOR_EMON_ANALOG_ID) {
@ -909,6 +1149,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
@ -922,6 +1191,7 @@ void _sensorConfigure() {
}
// Save settings
delSetting("snsResetCalibration");
delSetting("pwrExpectedP");
delSetting("pwrExpectedC");
delSetting("pwrExpectedV");
@ -940,7 +1210,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
@ -959,14 +1229,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
@ -1142,7 +1404,7 @@ void sensorLoop() {
// Get the first relay state
#if SENSOR_POWER_CHECK_STATUS
bool relay_off = (relayCount() > 0) && (relayStatus(0) == 0);
bool relay_off = (relayCount() == 1) && (relayStatus(0) == 0);
#endif
// Get readings


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Analog Sensor (maps to an analogRead)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && (ANALOG_SUPPORT || NTC_SUPPORT)
@ -35,6 +35,16 @@ class AnalogSensor : public BaseSensor {
_micros = micros;
}
void setFactor(double factor) {
//DEBUG_MSG(("[ANALOG_SENSOR] Factor set to: %s \n"), String(factor,6).c_str());
_factor = factor;
}
void setOffset(double offset) {
//DEBUG_MSG(("[ANALOG_SENSOR] Factor set to: %s \n"), String(offset,6).c_str());
_offset = offset;
}
// ---------------------------------------------------------------------
unsigned int getSamples() {
@ -45,6 +55,14 @@ class AnalogSensor : public BaseSensor {
return _micros;
}
double getFactor() {
return _factor;
}
double getOffset() {
return _offset;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
@ -77,6 +95,7 @@ class AnalogSensor : public BaseSensor {
}
// Current value for slot # index
// Changed return type as moving to scaled value
double value(unsigned char index) {
if (index == 0) return _read();
return 0;
@ -84,7 +103,9 @@ class AnalogSensor : public BaseSensor {
protected:
unsigned int _read() {
//CICM: this should be for raw values
// renaming protected function "_read" to "_rawRead"
unsigned int _rawRead() {
if (1 == _samples) return analogRead(0);
unsigned long sum = 0;
for (unsigned int i=0; i<_samples; i++) {
@ -94,8 +115,28 @@ class AnalogSensor : public BaseSensor {
return sum / _samples;
}
//CICM: and proper read should be scalable and thus needs sign
//and decimal part
double _read() {
//Raw measure could also be a class variable with getter so that can
//be reported through MQTT, ...
unsigned int rawValue;
double scaledValue;
// Debugging doubles to string
//DEBUG_MSG(("[ANALOG_SENSOR] Started standard read, factor: %s , offset: %s, decimals: %d \n"), String(_factor).c_str(), String(_offset).c_str(), ANALOG_DECIMALS);
rawValue = _rawRead();
//DEBUG_MSG(("[ANALOG_SENSOR] Raw read received: %d \n"), rawValue);
scaledValue = _factor*rawValue + _offset;
//DEBUG_MSG(("[ANALOG_SENSOR] Scaled value result: %s \n"), String(scaledValue).c_str());
return scaledValue;
}
unsigned int _samples = 1;
unsigned long _micros = 0;
//CICM: for scaling and offset, also with getters and setters
double _factor = 1.0;
double _offset = 0.0;
};


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// BH1750 Liminosity sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && BH1750_SUPPORT


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

@ -0,0 +1,253 @@
// -----------------------------------------------------------------------------
// BMP085/BMP180 Sensor over I2C
// Copyright (C) 2019 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

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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// BME280/BMP280 Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && BMX280_SUPPORT


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Abstract sensor class (other sensor classes extend this class)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT


+ 20
- 7
code/espurna/sensors/CSE7766Sensor.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// CSE7766 based power monitor
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
// http://www.chipsea.com/UploadFiles/2017/08/11144342F01B5662.pdf
// -----------------------------------------------------------------------------
@ -161,9 +161,10 @@ class CSE7766Sensor : public BaseSensor {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
if (index == 3) return MAGNITUDE_POWER_APPARENT;
if (index == 4) return MAGNITUDE_POWER_FACTOR;
if (index == 5) return MAGNITUDE_ENERGY;
if (index == 3) return MAGNITUDE_POWER_REACTIVE;
if (index == 4) return MAGNITUDE_POWER_APPARENT;
if (index == 5) return MAGNITUDE_POWER_FACTOR;
if (index == 6) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
@ -172,9 +173,10 @@ class CSE7766Sensor : public BaseSensor {
if (index == 0) return _current;
if (index == 1) return _voltage;
if (index == 2) return _active;
if (index == 3) return _voltage * _current;
if (index == 4) return ((_voltage > 0) && (_current > 0)) ? 100 * _active / _voltage / _current : 100;
if (index == 5) return _energy;
if (index == 3) return _reactive;
if (index == 4) return _voltage * _current;
if (index == 5) return ((_voltage > 0) && (_current > 0)) ? 100 * _active / _voltage / _current : 100;
if (index == 6) return _energy;
return 0;
}
@ -273,6 +275,16 @@ class CSE7766Sensor : public BaseSensor {
}
}
// Calculate reactive power
_reactive = 0;
unsigned int active = _active;
unsigned int apparent = _voltage * _current;
if (apparent > active) {
_reactive = sqrt(apparent * apparent - active * active);
} else {
_reactive = 0;
}
// Calculate energy
unsigned int difference;
static unsigned int cf_pulses_last = 0;
@ -367,6 +379,7 @@ class CSE7766Sensor : public BaseSensor {
SoftwareSerial * _serial = NULL;
double _active = 0;
double _reactive = 0;
double _voltage = 0;
double _current = 0;
double _energy = 0;


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// DHTXX Sensor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && DHT_SUPPORT


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

@ -1,7 +1,7 @@
// -----------------------------------------------------------------------------
// Dallas OneWire Sensor
// Uses OneWire library
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && DALLAS_SUPPORT


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Digital Sensor (maps to a digitalRead)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && DIGITAL_SUPPORT


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// ECH1560 based power monitor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && ECH1560_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/EmonADC121Sensor.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// ADS121-based Energy Monitor Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EMON_ADC121_SUPPORT


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// ADS1X15-based Energy Monitor Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EMON_ADS1X15_SUPPORT


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Energy Monitor Sensor using builtin ADC
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EMON_ANALOG_SUPPORT


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Abstract Energy Monitor Sensor (other EMON sensors extend this class)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Event Counter Sensor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EVENTS_SUPPORT


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// GUVA-S12SD UV Sensor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// by Mustafa Tufan
// -----------------------------------------------------------------------------
@ -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;
}


+ 22
- 19
code/espurna/sensors/HLW8012Sensor.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Event Counter Sensor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && HLW8012_SUPPORT
@ -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
};
// -----------------------------------------------------------------------------


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Abstract I2C sensor class (other sensor classes extend this class)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && ( I2C_SUPPORT || EMON_ANALOG_SUPPORT )


+ 143
- 0
code/espurna/sensors/MAX6675Sensor.h View File

@ -0,0 +1,143 @@
// -----------------------------------------------------------------------------
// MAX6675 Sensor
// Uses MAX6675_Thermocouple library
// Copyright (C) 2017-2019 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;
_count = 1;
}
~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);
_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

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

@ -3,7 +3,7 @@
// Based on: https://github.com/nara256/mhz19_uart
// http://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf
// Uses SoftwareSerial library
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && MHZ19_SUPPORT
@ -78,7 +78,7 @@ class MHZ19Sensor : public BaseSensor {
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32);
_serial->enableIntTx(false);
_serial->begin(9600);
calibrateAuto(false);
calibrateAuto(_calibrateAuto);
_ready = true;
_dirty = false;
@ -139,6 +139,13 @@ class MHZ19Sensor : public BaseSensor {
_write(buffer);
}
void setCalibrateAuto(boolean value) {
_calibrateAuto = value;
if (_ready) {
calibrateAuto(value);
}
}
protected:
// ---------------------------------------------------------------------
@ -214,6 +221,7 @@ class MHZ19Sensor : public BaseSensor {
double _co2 = 0;
unsigned int _pin_rx;
unsigned int _pin_tx;
bool _calibrateAuto = false;
SoftwareSerial * _serial = NULL;
};


+ 189
- 0
code/espurna/sensors/MICS2710Sensor.h View File

@ -0,0 +1,189 @@
// -----------------------------------------------------------------------------
// MICS-2710 (and MICS-4514) NO2 Analog Sensor
// Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && MICS2710_SUPPORT
#pragma once
// Set ADC to TOUT pin
#undef ADC_MODE_VALUE
#define ADC_MODE_VALUE ADC_TOUT
#include "Arduino.h"
#include "BaseSensor.h"
extern "C" {
#include "../libs/fs_math.h"
}
class MICS2710Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
MICS2710Sensor(): BaseSensor() {
_count = 2;
_sensor_id = SENSOR_MICS2710_ID;
}
void calibrate() {
setR0(_getResistance());
}
// ---------------------------------------------------------------------
void setAnalogGPIO(unsigned char gpio) {
_noxGPIO = gpio;
}
unsigned char getAnalogGPIO() {
return _noxGPIO;
}
void setPreHeatGPIO(unsigned char gpio) {
_preGPIO = gpio;
}
unsigned char getPreHeatGPIO() {
return _preGPIO;
}
void setRL(unsigned long Rl) {
if (Rl > 0) _Rl = Rl;
}
unsigned long getRL() {
return _Rl;
}
void setR0(unsigned long R0) {
if (R0 > 0) _R0 = R0;
}
unsigned long getR0() {
return _R0;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
// Set NOX as analog input
pinMode(_noxGPIO, INPUT);
// Start pre-heating
pinMode(_preGPIO, OUTPUT);
digitalWrite(_preGPIO, HIGH);
_heating = true;
_start = millis();
_ready = true;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
// Check pre-heat time
if (_heating && (millis() - _start > MICS2710_PREHEAT_TIME)) {
digitalWrite(_preGPIO, LOW);
_heating = false;
}
if (_ready) {
_Rs = _getResistance();
}
}
// Descriptive name of the sensor
String description() {
return String("MICS-2710 @ TOUT");
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String("0");
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (0 == index) return MAGNITUDE_RESISTANCE;
if (1 == index) return MAGNITUDE_NO2;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (0 == index) return _Rs;
if (1 == index) return _getPPM();
return 0;
}
private:
unsigned long _getReading() {
return analogRead(_noxGPIO);
}
double _getResistance() {
// get voltage (1 == reference) from analog pin
double voltage = (float) _getReading() / 1024.0;
// schematic: 3v3 - Rs - P - Rl - GND
// V(P) = 3v3 * Rl / (Rs + Rl)
// Rs = 3v3 * Rl / V(P) - Rl = Rl * ( 3v3 / V(P) - 1)
// 3V3 voltage is cancelled
double resistance = (voltage > 0) ? _Rl * ( 1 / voltage - 1 ) : 0;
return resistance;
}
double _getPPM() {
// According to the datasheet (https://www.cdiweb.com/datasheets/e2v/mics-2710.pdf)
// there is an almost linear relation between log(Rs/R0) and log(ppm).
// Regression parameters have been calculated based on the graph
// in the datasheet with these readings:
//
// Rs/R0 NO2(ppm)
// 23 0.20
// 42 0.30
// 90 0.40
// 120 0.50
// 200 0.60
// 410 0.90
// 500 1.00
// 1000 1.30
// 10000 5.00
return fs_pow(10, 0.5170 * fs_log10(_Rs / _R0) - 1.3954);
}
bool _heating = false;
unsigned long _start = 0; // monitors the pre-heating time
unsigned long _R0 = MICS2710_R0; // R0, calikbration value at 25º
unsigned long _Rl = MICS2710_RL; // RL, load resistance
unsigned long _Rs = 0; // cached resistance
unsigned char _noxGPIO = MICS2710_PRE_PIN;
unsigned char _preGPIO = MICS2710_NOX_PIN;
};
#endif // SENSOR_SUPPORT && MICS2710_SUPPORT

+ 144
- 0
code/espurna/sensors/MICS5525Sensor.h View File

@ -0,0 +1,144 @@
// -----------------------------------------------------------------------------
// MICS-5525 (and MICS-4514) CO Analog Sensor
// Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && MICS5525_SUPPORT
#pragma once
// Set ADC to TOUT pin
#undef ADC_MODE_VALUE
#define ADC_MODE_VALUE ADC_TOUT
#include "Arduino.h"
#include "BaseSensor.h"
extern "C" {
#include "../libs/fs_math.h"
}
class MICS5525Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
MICS5525Sensor(): BaseSensor() {
_count = 2;
_sensor_id = SENSOR_MICS5525_ID;
}
void calibrate() {
setR0(_getResistance());
}
// ---------------------------------------------------------------------
void setAnalogGPIO(unsigned char gpio) {
_redGPIO = gpio;
}
unsigned char getAnalogGPIO() {
return _redGPIO;
}
void setRL(unsigned long Rl) {
if (Rl > 0) _Rl = Rl;
}
unsigned long getRL() {
return _Rl;
}
void setR0(unsigned long R0) {
if (R0 > 0) _R0 = R0;
}
unsigned long getR0() {
return _R0;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
pinMode(_redGPIO, INPUT);
_ready = true;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_Rs = _getResistance();
}
// Descriptive name of the sensor
String description() {
return String("MICS-5525 @ TOUT");
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String("0");
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (0 == index) return MAGNITUDE_RESISTANCE;
if (1 == index) return MAGNITUDE_CO;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (0 == index) return _Rs;
if (1 == index) return _getPPM();
return 0;
}
private:
unsigned long _getReading() {
return analogRead(_redGPIO);
}
double _getResistance() {
// get voltage (1 == reference) from analog pin
double voltage = (float) _getReading() / 1024.0;
// schematic: 3v3 - Rs - P - Rl - GND
// V(P) = 3v3 * Rl / (Rs + Rl)
// Rs = 3v3 * Rl / V(P) - Rl = Rl * ( 3v3 / V(P) - 1)
// 3V3 voltage is cancelled
double resistance = (voltage > 0) ? _Rl * ( 1 / voltage - 1 ) : 0;
return resistance;
}
double _getPPM() {
// According to the datasheet (https://airqualityegg.wikispaces.com/file/view/mics-5525-CO.pdf)
return 764.2976 * fs_pow(2.71828, -7.6389 * ((float) _Rs / _R0));
}
unsigned long _R0 = MICS5525_R0; // R0, calibration value at 25º on air
unsigned long _Rl = MICS5525_RL; // RL, load resistance
unsigned long _Rs = 0; // cached resistance
unsigned char _redGPIO = MICS5525_RED_PIN;
};
#endif // SENSOR_SUPPORT && MICS5525_SUPPORT

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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// NTC Sensor (maps to a NTCSensor)
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && NTC_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;
}


+ 193
- 25
code/espurna/sensors/PZEM004TSensor.h View File

@ -1,8 +1,50 @@
// -----------------------------------------------------------------------------
// PZEM004T based power monitor
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2019 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,103 @@ 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)
if (_serial) {
while(_serial->available() > 0) _serial->read();
} else {
// This we cannot do it from outside the library
}
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 +300,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) 2019 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

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

@ -1,5 +1,5 @@
// -----------------------------------------------------------------------------
// SDS011 particulates sensor
// SDS011 dust sensor
// Based on: https://github.com/ricki-z/SDS011
//
// Uses SoftwareSerial library


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// SHT3X Sensor over I2C (Wemos)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && SHT3X_I2C_SUPPORT


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// SI7021 / HTU21D Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && SI7021_SUPPORT


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

Loading…
Cancel
Save