Browse Source

Merge dev

fastled
Xose Pérez 6 years ago
parent
commit
b4028c8166
220 changed files with 25102 additions and 9012 deletions
  1. +41
    -0
      .github/stale.yml
  2. +4
    -1
      .gitignore
  3. +30
    -0
      .travis.yml
  4. +374
    -122
      CHANGELOG.md
  5. +159
    -48
      README.md
  6. +8
    -3
      code/.gitignore
  7. +0
    -65
      code/.travis.yml
  8. +28
    -6
      code/build.sh
  9. +0
    -51
      code/core_version.py
  10. +4
    -4
      code/eagle.flash.1m0m1s.ld
  11. +19
    -0
      code/eagle.flash.1m0m2s.ld
  12. +21
    -0
      code/eagle.flash.4m1m4s.ld
  13. +20
    -0
      code/eagle.flash.4m3m4e.ld
  14. +18
    -0
      code/eagle.flash.512k0m1s.ld
  15. +25
    -14
      code/espurna/alexa.ino
  16. +14
    -4
      code/espurna/api.ino
  17. +32
    -0
      code/espurna/broker.ino
  18. +55
    -34
      code/espurna/button.ino
  19. +29
    -15
      code/espurna/config/all.h
  20. +47
    -3
      code/espurna/config/arduino.h
  21. +5
    -0
      code/espurna/config/build.h
  22. +17
    -0
      code/espurna/config/debug.h
  23. +118
    -15
      code/espurna/config/defaults.h
  24. +50
    -0
      code/espurna/config/dependencies.h
  25. +687
    -281
      code/espurna/config/general.h
  26. +1129
    -51
      code/espurna/config/hardware.h
  27. +280
    -0
      code/espurna/config/progmem.h
  28. +52
    -10
      code/espurna/config/prototypes.h
  29. +275
    -183
      code/espurna/config/sensors.h
  30. +303
    -0
      code/espurna/config/types.h
  31. +2
    -1
      code/espurna/config/version.h
  32. BIN
      code/espurna/data/index.html.gz
  33. +139
    -65
      code/espurna/debug.ino
  34. +38
    -38
      code/espurna/domoticz.ino
  35. +86
    -0
      code/espurna/eeprom.ino
  36. +59
    -319
      code/espurna/espurna.ino
  37. +1
    -1
      code/espurna/filters/BaseFilter.h
  38. +1
    -1
      code/espurna/filters/MaxFilter.h
  39. +1
    -1
      code/espurna/filters/MedianFilter.h
  40. +1
    -1
      code/espurna/filters/MovingAverageFilter.h
  41. +6
    -6
      code/espurna/gpio.ino
  42. +306
    -0
      code/espurna/homeassistant.ino
  43. +0
    -101
      code/espurna/homeassitant.ino
  44. +182
    -4
      code/espurna/i2c.ino
  45. +47
    -33
      code/espurna/influxdb.ino
  46. +19
    -3
      code/espurna/ir.ino
  47. +71
    -28
      code/espurna/led.ino
  48. +2
    -2
      code/espurna/libs/EmbedisWrap.h
  49. +0
    -376
      code/espurna/libs/SSDPDevice.cpp
  50. +0
    -198
      code/espurna/libs/SSDPDevice.h
  51. +57
    -30
      code/espurna/libs/StreamInjector.h
  52. +1
    -3
      code/espurna/libs/WebSocketIncommingBuffer.h
  53. +636
    -0
      code/espurna/libs/fs_math.c
  54. +116
    -0
      code/espurna/libs/fs_math.h
  55. +0
    -1
      code/espurna/libs/pwm.c
  56. +1
    -1
      code/espurna/libs/pwm.h
  57. +439
    -282
      code/espurna/light.ino
  58. +1
    -1
      code/espurna/llmnr.ino
  59. +81
    -18
      code/espurna/mdns.ino
  60. +323
    -2
      code/espurna/migrate.ino
  61. +508
    -275
      code/espurna/mqtt.ino
  62. +1
    -1
      code/espurna/netbios.ino
  63. +35
    -2
      code/espurna/nofuss.ino
  64. +123
    -37
      code/espurna/ntp.ino
  65. +202
    -9
      code/espurna/ota.ino
  66. +319
    -121
      code/espurna/relay.ino
  67. +148
    -62
      code/espurna/rf.ino
  68. +310
    -109
      code/espurna/rfbridge.ino
  69. +227
    -0
      code/espurna/scheduler.ino
  70. +537
    -104
      code/espurna/sensor.ino
  71. +198
    -0
      code/espurna/sensors/AM2320Sensor.h
  72. +17
    -6
      code/espurna/sensors/AnalogSensor.h
  73. +30
    -47
      code/espurna/sensors/BH1750Sensor.h
  74. +265
    -75
      code/espurna/sensors/BMX280Sensor.h
  75. +21
    -8
      code/espurna/sensors/BaseSensor.h
  76. +378
    -0
      code/espurna/sensors/CSE7766Sensor.h
  77. +26
    -14
      code/espurna/sensors/DHTSensor.h
  78. +42
    -39
      code/espurna/sensors/DallasSensor.h
  79. +12
    -5
      code/espurna/sensors/DigitalSensor.h
  80. +39
    -20
      code/espurna/sensors/ECH1560Sensor.h
  81. +52
    -47
      code/espurna/sensors/EmonADC121Sensor.h
  82. +36
    -91
      code/espurna/sensors/EmonADS1X15Sensor.h
  83. +12
    -9
      code/espurna/sensors/EmonAnalogSensor.h
  84. +32
    -10
      code/espurna/sensors/EmonSensor.h
  85. +12
    -5
      code/espurna/sensors/EventSensor.h
  86. +174
    -0
      code/espurna/sensors/GUVAS12SDSensor.h
  87. +298
    -0
      code/espurna/sensors/GeigerSensor.h
  88. +119
    -0
      code/espurna/sensors/HCSR04Sensor.h
  89. +26
    -12
      code/espurna/sensors/HLW8012Sensor.h
  90. +49
    -15
      code/espurna/sensors/I2CSensor.h
  91. +21
    -8
      code/espurna/sensors/MHZ19Sensor.h
  92. +219
    -50
      code/espurna/sensors/PMSX003Sensor.h
  93. +133
    -0
      code/espurna/sensors/PZEM004TSensor.h
  94. +15
    -45
      code/espurna/sensors/SHT3XI2CSensor.h
  95. +88
    -96
      code/espurna/sensors/SI7021Sensor.h
  96. +233
    -0
      code/espurna/sensors/SenseAirSensor.h
  97. +94
    -0
      code/espurna/sensors/TMP3XSensor.h
  98. +22
    -10
      code/espurna/sensors/V9261FSensor.h
  99. +321
    -250
      code/espurna/settings.ino
  100. +64
    -21
      code/espurna/ssdp.ino

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

@ -0,0 +1,41 @@
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 120
# Number of days of inactivity before a stale Issue or Pull Request is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 30
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- enhancement
- bug
- staged for release
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed in 30 days if no further activity occurs.
Thank you for your contributions.
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue will be auto-closed because there hasn't been any activity for a few months. Feel free to open a new one if you still experience this problem.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
only: issues

+ 4
- 1
.gitignore View File

@ -8,7 +8,10 @@ firmware*
.gcc-flags.json
.sconsign.dblite
credentials.h
.build
node_modules
code/utils
custom.h
.python
.env
.DS_Store
.vscode

+ 30
- 0
.travis.yml View File

@ -0,0 +1,30 @@
language: python
python:
- '2.7'
sudo: false
cache:
directories:
- "~/.platformio"
install:
- pip install -U platformio
- cd code ; npm install --only=dev ; cd ..
script:
- cd code && ./build.sh && cd ..
before_deploy:
- mv firmware/*/espurna-*.bin firmware/
deploy:
provider: releases
api_key:
secure: LMCdaQnCxSQ5EuKhqcFR6VTfDCWc06jwD4fdHfkmBaeWBMMzdoZEqN26AwdTnoLLlQJTR9l21NypgGybssBr69Md/ZinMahWCJJ4gVzPe9Adr9ijRbzj/wckirLBVZjRWn8fxTjJgjpu1ten2CgBfNcc/roN3fI6DV/1Fvv1REfihND4EeIermsxIVRXOyluu1vnPV9ZM28XBNch9XfkKwIpLEaHNNtkzlSEua39U34WpZShrxxEVoZhiF/R1ZF+NiAnursPcPsn5hdrXyHFLFT2dVDnlpdIp79c6SGkCZ636//5erB6hgBHshbwuT9TYXxtwLyL7AeN/MfCg0gtZSsDII8mLKzytW1tl7r1W9l7s21z/55tljSz2Z6dcbJC3bxucgcLxM9R6PrsPdDrPQTd0QwYqYalMNlfuA8KGcesZueqI9Q4uw7uHFUwIp7FIaoKhwhjTKe/ZHZ75zU4wdBTXof4dnSQOudoGGjRyyj7V0eR/Mhuni45N2Bldy9hDaldgtWMuEdx3ABW1IrPXtPDVTXM6QjUHm0nj5zzCgoCvQEw3jycA7Wlgbzo8MQkKH3u4MtupJcEGHJAqMbk+JlRpIfdWTiqtI697CReRGYSWT6YxF68TeJo77JPPph724BIBMtKtIk2+4RkHxz5RqM8O2vt+AFNjnWNIvVQtY8=
file_glob: true
file: firmware/espurna-*.bin
skip_cleanup: true
on:
all_branches: true
tags: true
notifications:
pushover:
api_key:
secure: wtAleSibUqDOH7KskYMIFXwNqqZMA/2flCJzu835hSFWDFTjdXGeYLmQxCI7FGTWHcvWgf5pt3KR3OXtABVDfwqLj0HZjEubIypm56mZKsEaP32JlQiAw+Qz1KSU/5gEKEI3NU5MuL1rIebSEo9iVgwVDMn51GI9qv0LbjLKfXpxBmJXL/OmBRggVMswScCppXJLVlyMSRj9qT0Ds/lz+SL1g6vZHPO8r3z1BSOMA0eWNUYd/Huhe1XAHYzS4mS3aybPsPKldpv0igsvE5BHT37rb43QA0pHxv2M09QlBRNBI6kHgxzCw2OJ4uz5/K2U/4LBMDJrAj6rUGAhKn8Wuiw4B/kZb+WsvRCcCTHGYTc20KWrrV4aOojQRLht1FgNg7Ub+NNm5T9LBG+XKKHARTv1Kv09nCpPVD8NvF5YPRsSb7ZL6s/wUGSNuu365aVs3BnnazXb+ha33Hg0bCBEwmlW6FKdQ7S8OYsoIobTUcporRs8p089EQ8vNxN0osHnKPX5M0ZOxbBOIt5fQO2B+Cdn+m4hx/DETM5HMLZ/GpsbY9eiN7HWaaQPicSrSLprY6tzfvcHePk9Y6t8rjCKzehwlYtrfiMRvrPhZuOLcB4s3OmJngxvOCGxdWkh/6F2CY6sDslyviaK0ZwX93Gn7uNThAviFpZNUlPWP6jHIkA=
users:
secure: atCRvGYPuAT1DH5UupNIV80sqrIgwdYrxO69tq+lIBfi3BIJ4LLlvUQwigWak6otfJbEqAQKVNi8I8J4g7ASHx1wn335vpAqLAbhJqfbLznAcU9nZ+bdc8+NJ57qY66ZZJNpljbMC0HWgw2kgyiCLJ1wS3zsQAyuzyGuMcmc43zFOQMA7QUaefE6LGlHPn0i6Ub04QgQ1413IEu2/FR5i4hVXrgRQzaPy07pSPbvFTvoxfWDgWjTQM+R2AG8uStesO+2yzeb8Nu2pJDvFf3R1N8P2e3zg5YN86DLNRQ+Kxl7M1FJ/txbJ/4D1jdNwAmtUzEoYnsKCcCnMHWGuSlJF8fgXSVBDi9KVH7Y6rlWXzMcqesR5lzNTlD7JQ01yw2WrO6Nj7fvan+QNGp3d5F4Kf3WE+5rkmM5Sbo5rb9YOGt688i1qJ3Xf1MTkQNCzDiAg3qf1hu8j+AALblY73gNSAgv4F+/SnmnfKy5Cz/oRQQfboiC/VphNa25Fzq3s3uahQfLha4tyHrc1LltSM6bztErRSPqDp96qVTQORVHr6jqJhl6c9R0XXrnc0Pc5lpBoPKOqug1yPp15k9AAssGxtXZqg2loHjwmS+Qm1i77mNGKNhzqDpmLHEzmIejc4n4lIZmze+dLucStiNnkN6TF3nvIh37CNjH6slT5t7WfJg=

+ 374
- 122
CHANGELOG.md View File

@ -3,18 +3,270 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.12.6] 2018-05-02
### Fixed
- Check NTP_SUPPORT for sensors (thanks to @mcspr)
- Fix AM2302 sensor
- Fix hostname truncated to 20 chars when advertised to DHCP ([#774](https://github.com/xoseperez/espurna/issues/774))
- Decouple Serial object from Terminal, Debug modules ([#787](https://github.com/xoseperez/espurna/issues/787))
- Fix Arilux LC-01 definitions ([#797](https://github.com/xoseperez/espurna/issues/797))
- Do not uppercase hostname in web interface ([#799](https://github.com/xoseperez/espurna/issues/799))
- Ensure scheduler has access to all channels independently of the color mode ([#807](https://github.com/xoseperez/espurna/issues/807))
### Added
- Support for IteadStudio Sonoff S31 ([#497](https://github.com/xoseperez/espurna/issues/497))
- Option to ignore daylight saving in scheduler ([#783](https://github.com/xoseperez/espurna/issues/783))
- Report last energy reset datetime in web interface ([#784](https://github.com/xoseperez/espurna/issues/784))
- Added captive portal in AP mode
- Support for IR toggle mode (thanks to @darshkpatel)
- Support for IteadStudio Sonoff POW R2 (thanks to @ColinShorts)
- Support for Luani HVIO (thanks to @wildwiz)
- Support for Zhilde ZLD-EU55-W power strip (thanks to @wildwiz)
- Support for RFB_DIRECT Sonoff Bridge EFM8BB1 bypass hack (thanks to @wildwiz)
- Support for SenseAir S8 CO2 sensor (thanks to @Yonsm)
- Support for PMS5003T/ST sensors (thanks to @Yonsm)
### Changed
- Updated JustWifi Library
- Some cleanup in the web interface
- Refactored configuration files (thanks to @lobradov, @mcspr)
- Changes pre-commit hook (thanks to @mcspr)
## [1.12.5] 2018-04-08
### Fixed
- Fixed expected power calibration ([#676](https://github.com/xoseperez/espurna/issues/676))
- Do not show empty time strings ([#691](https://github.com/xoseperez/espurna/issues/691), thanks to @PieBru)
- Fix load average calculation when system check is disabled ([#707](https://github.com/xoseperez/espurna/issues/707))
- Fixed unstability issues with NtpClientLib using temporary fork ([#743](https://github.com/xoseperez/espurna/issues/743))
- Fixed typos in homeassistant module (thanks to @Cabalist)
- Fixed default HLW8012 calibration for KMC devices (thanks to @gn0st1c)
- Fix MQTT query request
- Fix scheduler debug message
- Fix NTP offset value
### Added
- Option to change NTP timeout via compile-time setting ([#452](https://github.com/xoseperez/espurna/issues/452))
- Added humidity correction to web UI ([#626](https://github.com/xoseperez/espurna/issues/626), tahnks to @ManuelW77)
- Added support for USA DST calculation ([#664](https://github.com/xoseperez/espurna/issues/664))
- Option to reset energy count ([#671](https://github.com/xoseperez/espurna/issues/671))
- Added Sonoff SV prebuild image ([#698](https://github.com/xoseperez/espurna/issues/698), thanks to @akasma74)
- Check and remove unused config keys ([#730](https://github.com/xoseperez/espurna/issues/730))
- Visual Studio metadata files added to .gitignore ([#731](https://github.com/xoseperez/espurna/issues/731), thanks to @gn0st1c)
- Added default MQTT and SSL settings to web UI ([#732](https://github.com/xoseperez/espurna/issues/732), thanks to @mcspr)
- Added option to the web UI to set the light transition length in milliseconds ([#739](https://github.com/xoseperez/espurna/issues/739))
- Improved testing with Travis (thanks to @lobradov)
- Change dimmers using schedule (thanks to @wysiwyng)
- Debug console in web UI (thanks to @lobradov), including command execution
- Option to reset relays in MQTT disconection (thanks to @a-tom-s)
- Option to disable system check from custom header (thanks to @phuonglm)
- Added "board" topic to the heartbeat messages (thanks to @mcspr)
- Added methods to create hierarchical MQTT JSON responses
- Added RESET.SAFE command to reboot into safe mode
- Added SDK and Core versions to the web UI
- Added revision to web UI (only when built from build.sh)
- Support for OBI Powerplug Adapter ([#622](https://github.com/xoseperez/espurna/issues/622), thanks to @Geitde)
- Support for Tunbox Powerstrip02 (thanks to @gn0st1c)
- Support for Lingan SWA1 (thanks to @gn0st1c)
- Support for Heygo HY02 (thanks to @gn0st1c)
- Support for Maxcio WUS0025 (thanks to @gn0st1c)
- Support for Yidian XSSSA05 SWA1 (thanks to @gn0st1c)
- Support for ArnieX Swifitch (thanks to @LubergAlexander)
- Support for IKE ESPIKE board
- Support for AM2320 sensors via I2C (thanks to @gn0st1c)
- Support for GUVAS12SD sensor (thanks to @gn0st1c)
### Changed
- Removed hostname size limit ([#576](https://github.com/xoseperez/espurna/issues/576), [#659](https://github.com/xoseperez/espurna/issues/659))
- Reworked RGBW implementation (thanks to @Skaronator)
- Several web UI layout changes (thanks to @lobradov & @mcspr)
- Button MQTT messages will not have the retain flag (thanks to @lobradov)
- Remove unnecessary code from boot log (thanks to @gn0st1c)
- Updated logo and favicon, added gitter channel
- Force reporting power values as 0 if relay is off
- Using gulp-crass for CSS minification
- Using WIFI_NONE_SLEEP by default
## [1.12.4] 2018-03-05
### Fixed
- Adding a 1ms delay after UDP send to avoid loosing packets ([#438](https://github.com/xoseperez/espurna/issues/438))
- Fixed void return in BMX280 sensor ([#489](https://github.com/xoseperez/espurna/issues/489))
- Fix MQTT keep alive cannot be more than 255 seconds ([#515](https://github.com/xoseperez/espurna/issues/515))
- Do not show scheduler tab in Web UI if build without scheduler support ([#527](https://github.com/xoseperez/espurna/issues/527))
- Fix inline documentation for Sonoff 4CH Pro button modes ([#551](https://github.com/xoseperez/espurna/issues/551))
- Prevent resending messages from rfin in RF Bridge ([#561](https://github.com/xoseperez/espurna/issues/561))
- Fix AnalogSensor description ([#601](https://github.com/xoseperez/espurna/issues/601))
- Fixed missing setting in HASS WS callback (thanks to @mcspr)
- ECH1560 call sync from tick method
- Fixed several issues reported by codacy
### Added
- UART to MQTT module (thanks to Albert Weterings, [#529](https://github.com/xoseperez/espurna/issues/529))
- Added option to show HASS configuration code in ESPurna web UI ([#616](https://github.com/xoseperez/espurna/issues/616))
- OTA upgrade via terminal (using 'ota' command, with SSL support)
- Added I2C scan and clear commands to terminal (only when I2C enabled)
- Added new relay & wifi led mode ([#604](https://github.com/xoseperez/espurna/issues/604))
- Option to enable/disable web auth from web UI
- Added "Reset to factory settings" in web UI (thanks to Teo Pavel, [#569](https://github.com/xoseperez/espurna/issues/569))
- Added {magnitude} placeholder to MQTT root topic
- Option to report energy in kWh and power in kW ([#523](https://github.com/xoseperez/espurna/issues/523))
- Check upgrade file size and signature in web UI
- Automatically dump info on telnet connection if TERMINAL_SUPPORT is disabled
- Two different ESPURNA_CORE images for 1MB and 4MB boards, freeing GPIOs ([#557](https://github.com/xoseperez/espurna/issues/557))
- Initial support for PZEM004T sensor (still beta)
- Support for STM_RELAY board (thanks to Maciej Czerniak)
- Support for KMC 70011 energy monitor (thanks to Wayne Manion, [#598](https://github.com/xoseperez/espurna/issues/598))
- Support for Wifi Stecker Shuko device (thanks to @Geitde, [#622](https://github.com/xoseperez/espurna/issues/622))
- Support for GizWits Witty Cloud device (thanks to Theonedemon)
### Changed
- BMX280 changes to allow for hot-plug ([#353](https://github.com/xoseperez/espurna/issues/353))
- Increase the initial check interval for NTP ([#452](https://github.com/xoseperez/espurna/issues/452))
- Force turning relays off before turning others on when synced ([#491](https://github.com/xoseperez/espurna/issues/491))
- Publish slampher as light to Home Assistant ([#494](https://github.com/xoseperez/espurna/issues/494))
- Force API to return the target status of the relay ([#548](https://github.com/xoseperez/espurna/issues/548))
- Increasing max number of messages in JSON payload to 20 ([#588](https://github.com/xoseperez/espurna/issues/588))
- Change copy from 'Use colorpicker' to 'Use color'. Better hint. ([#590](https://github.com/xoseperez/espurna/issues/590))
- Completely reworked the RF module to use the same web UI as the RFBridge module to learn new codes ([#594](https://github.com/xoseperez/espurna/issues/594))
- Several spelling and grammar changes by Lee Marlow
- Always enabled telnet access in ESPURNA_CORE image
- Updated ESPSoftwareSerial, ESPAsyncTCP and ESPAsyncWebServer libraries
### Removed
- Remove dependency from gulp-util ([#493](https://github.com/xoseperez/espurna/issues/493))
- Removed specific support for Magic Home LED Controller 2.3 ([#512](https://github.com/xoseperez/espurna/issues/512))
- Disabled floating point support when building against Arduino Core 2.4.0 with PIO
- Removed WiFi distance calculation
## [1.12.3] 2018-01-29
### Fixed
- Fix telnet crash due to local reference ([#487](https://github.com/xoseperez/espurna/issues/487))
## [1.12.2] 2018-01-29
### Added
- Repository migrated over to GitHub
- Travis CI build test and deploy
- Pre-commit hook to change README.md file depending on the branch
- {hostname} and {mac} placeholders for MQTT root topic
- Added support for timezones with minutes ([#265](https://github.com/xoseperez/espurna/issues/265))
- SSDP support ([#282](https://github.com/xoseperez/espurna/issues/282), [#423](https://github.com/xoseperez/espurna/issues/423), disabled by default since current implementation is not compatible with Alexa [#479](https://github.com/xoseperez/espurna/issues/479))
- HA auto-discover for multi-relay boards and sensors ([#392](https://github.com/xoseperez/espurna/issues/392), [#465](https://github.com/xoseperez/espurna/issues/465))
- Reset the pulse timeout every time an MQTT message is sent with the non-normal payload value ([#454](https://github.com/xoseperez/espurna/issues/454))
- Option to disable schedules without deleting them ([#453](https://github.com/xoseperez/espurna/issues/453))
- Added LED_MODE_STATUS ([#458](https://github.com/xoseperez/espurna/issues/458))
- Support to set on/off state per channel using switches ([#457](https://github.com/xoseperez/espurna/issues/457))
- Added support for MagicHome LED Controller 2.3
- Alexa message queue (thanks to Qubeck)
- Secondary Serial RX port for H801 and H802 boards ([#386](https://github.com/xoseperez/espurna/issues/386), thanks to Pablo Pousada Rial)
- Added compatibility with https://github.com/rhx/RF-Bridge-EFM8BB1 to RF Bridge (Thanks to Rene Hexel)
- Added message queue to RF Bridge
- Added MAC to mDNS text fields
- Added wifi.ap command to go into AP mode
- Added message id on MQTT JSON payloads
- Added hooks for 3rd party code (custom modules)
- Local broker to broadcast messages internally
- Added timestamp to debug output
- Common I2C interface to abstract backend library (Wire or Brzo I2C)
- Added espurnaLoopRegister
### Fixed
- Fixed support for 4CH Pro different modes ([#333](https://github.com/xoseperez/espurna/issues/333))
- Fixed several sensor modules to enable hot-unplug-plug ([#398](https://github.com/xoseperez/espurna/issues/398))
- Fixed crash when calling idbSend from an MQTT callback ([#410](https://github.com/xoseperez/espurna/issues/410))
- Checking trailing slash in mqttTopic ([#422](https://github.com/xoseperez/espurna/issues/422))
- Fixed pulse and pulse_ms order in relay_t structure ([#424](https://github.com/xoseperez/espurna/issues/424))
- Use same buffer size across all terminal-realted classes/methods. Set to 128 chars ([#477](https://github.com/xoseperez/espurna/issues/477), [#478](https://github.com/xoseperez/espurna/issues/478))
- Fix WiFi scan status in web UI
- Several code quality fixes (thanks to @lobradov)
- Fixed error message on first command over telnet
### Changed
- BMX280 sensor module now doesn't depend on third party libraries
- Changed time management in ntp, mqtt and scheduler modules
## Deprecated
- {identifier} placeholder for MQTT root topic
## [1.12.1] 2018-01-14
### Added
- Option to perform a WiFi network scan from web UI
- Added hostname to web UI side menu ([#404](https://github.com/xoseperez/espurna/issues/404))
- Option to flash multiple devices with ESPurna OTA Manager
### Fixed
- Fix web UI layout so signature does not overlay buttons ([#396](https://github.com/xoseperez/espurna/issues/396))
- Option to disable network scan and allow connecting to hidden SSID ([#392](https://github.com/xoseperez/espurna/issues/392), [#399](https://github.com/xoseperez/espurna/issues/399))
- Fix crash caused by a delay in UDP debugging code ([#397](https://github.com/xoseperez/espurna/issues/397))
- Fix memory leak in influxDB module ([#410](https://github.com/xoseperez/espurna/issues/410))
- Fix typos in web UI ([#394](https://github.com/xoseperez/espurna/issues/394), [#421](https://github.com/xoseperez/espurna/issues/421))
### Changed
- Updated to fauxmoESP 2.4.2
- Changed default I2C GPIO for Wemos D1 ([#420](https://github.com/xoseperez/espurna/issues/420))
- Some terminal commands have changed. See docs or type "help".
## [1.12.0] 2018-01-11
### Added
- Scheduler (contributed by Stefano Cotterli, thank you!, [#131](https://github.com/xoseperez/espurna/issues/131))
- Added "wifi.scan" command to terminal
- Added ESPurna Switch board support
- Added support for python3 in memanalyzer and ota scripts (thanks to @Cabalist)
- Added BSSID, RSSI, channels and distance to web UI status tab
- Added mDNS name resolving to MQTT, InfluxDB and NoFUSS modules ([#129](https://github.com/xoseperez/espurna/issues/129), disabled by default)
### Fixed
- Update FauxmoESP library to 2.4.1, solves dependency issue ([#388](https://github.com/xoseperez/espurna/issues/388))
- Fixed hardware definition in Sonoff Basic and Dual R2 causing wrong relay state on boot ([#365](https://github.com/xoseperez/espurna/issues/365))
### Changed
- Removed auto-recursion check in Domoticz module ([#379](https://github.com/xoseperez/espurna/issues/379))
- Rename terminal commands: reset.wifi to wifi.reset, reset.mqtt to mqtt.reset.
- Update JustWifi library to 1.1.6 (support for multiple SSIDs with the same name)
- Changed the way Home Assistant module handles disabling auto-discovery ([#383](https://github.com/xoseperez/espurna/issues/383))
## [1.11.4] 2018-01-09
### Fixed
- Fix bug in RF Bridge when RF code contains the stop byte. Check overflow ([#357](https://github.com/xoseperez/espurna/issues/357))
- Fixed typos in code and wiki (Thanks to @Cabalist)
- Fix bug in magnitude topic and units ([#355](https://github.com/xoseperez/espurna/issues/355))
### Added
- Small core build to allow two-step flashing method for big binaries
- Thingspeak support ([#371](https://github.com/xoseperez/espurna/issues/371), disabled by default)
- Color synchronization between lights using MQTT ([#362](https://github.com/xoseperez/espurna/issues/362))
- Support for Arilux AL-LC02 ([#347](https://github.com/xoseperez/espurna/issues/347))
- Support for Tarpuna Shield for Wemos D1
- Build option to disable password checking ([#373](https://github.com/xoseperez/espurna/issues/373))
- Option to report sensor address via MQTT ([#377](https://github.com/xoseperez/espurna/issues/377), I2C address, GPIO, Dallas address,...)
- Added binary size to memanalyzer script
- Option to specify custom client ID for MQTT connection ([#368](https://github.com/xoseperez/espurna/issues/368))
- Cross-platform ESPurna OTA Manager implemented in python (untested)
- Terminal command to get or set digital GPIO
### Changed
- Using 2.3.0 for prebuilt binaries
- Fix delay in DHT sensor
- Allow MQTT keep alive value of up to 3600s
- Changed Sonoff 4CH Pro definitions to support built-in interlock mode ([#333](https://github.com/xoseperez/espurna/issues/333))
## [1.11.3] 2018-01-02
### Fixed
- Fix uninitialized PWM channels bug ([#356](https://github.com/xoseperez/espurna/issues/356))
### Added
- Added memory analyzer
## [1.11.2] 2017-12-30
### Fixed
- Fix my92xx and pwm references for Arduino IDE (#346)
- Fix SHT3X I2C sensor magnitude count (#337)
- Fix timing for DHT11 sensors (#294)
- Fix overflow in relayParsePayload with long MQTT messages (#344)
- Fix loading of Dallas and DHT sensors for Sonoff TH images (#352)
- Fix my92xx and pwm references for Arduino IDE ([#346](https://github.com/xoseperez/espurna/issues/346))
- Fix SHT3X I2C sensor magnitude count ([#337](https://github.com/xoseperez/espurna/issues/337))
- Fix timing for DHT11 sensors ([#294](https://github.com/xoseperez/espurna/issues/294))
- Fix overflow in relayParsePayload with long MQTT messages ([#344](https://github.com/xoseperez/espurna/issues/344))
- Fix loading of Dallas and DHT sensors for Sonoff TH images ([#352](https://github.com/xoseperez/espurna/issues/352))
- Subscribe to Domoticz MQTT topics only if Domotic< is enabled
### Added
- Added option to change MQTT retain flag, QoS and keepalive time from webUI (#321)
- Added LED modes "always off" and "always on" (#348)
- Added option to change MQTT retain flag, QoS and keepalive time from webUI ([#321](https://github.com/xoseperez/espurna/issues/321))
- Added LED modes "always off" and "always on" ([#348](https://github.com/xoseperez/espurna/issues/348))
- Defined new ESPurna switch (no HLW8012 support & touch button ready)
### Changed
@ -31,12 +283,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.11.0] 2017-12-28
### Fixed
- Fixed Arduino IDE compilation issues (#330)
- Fixed Arduino IDE compilation issues ([#330](https://github.com/xoseperez/espurna/issues/330))
- Fixed issues with IE
- Fixed websocket auth issue with Safari (temporary)
- Fixed MQTT group sync when different switches share same group
- Fixed casting issue in buttonStore (#327)
- Fixed crash in InfluxDB initial heartbeat (#318)
- Fixed casting issue in buttonStore ([#327](https://github.com/xoseperez/espurna/issues/327))
- Fixed crash in InfluxDB initial heartbeat ([#318](https://github.com/xoseperez/espurna/issues/318))
- Fixed LED logic for ESPurna H08 board
### Added
@ -47,54 +299,54 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Option to select sensor read interval and report interval from web UI
- Itead RF Bridge
+ Match MQTT RFOUT codes to relays
+ Force RFBridge to send messages even if switch is already in requested state (#324)
+ Force RFBridge to send messages even if switch is already in requested state ([#324](https://github.com/xoseperez/espurna/issues/324))
+ Implemented RFbridge message queue asynchronously
- Added option to load config via HTTP POST & reset (#335)
- Added option to define behaviour of the first LED between WIFI, MQTT, FIND-ME (#317)
- Added option to load config via HTTP POST & reset ([#335](https://github.com/xoseperez/espurna/issues/335))
- Added option to define behaviour of the first LED between WIFI, MQTT, FIND-ME ([#317](https://github.com/xoseperez/espurna/issues/317))
- Added HTML linter to gulp builder
- Added Help command on terminal (#338)
- Added preliminary support for SSDP (untested, disabled by default) (#282)
- Added Help command on terminal ([#338](https://github.com/xoseperez/espurna/issues/338))
- Added preliminary support for SSDP (untested, disabled by default) ([#282](https://github.com/xoseperez/espurna/issues/282))
- Reporting NTP datetime on MQTT heartbeat (thanks to Eldon R. Brown)
- Added version tracking and migration code
- I2C and GPIO locking features
- Changed default button action for touch button devices (TOUCH and T1) (#327)
- Generic 8 channel board (#336)
- Changed default button action for touch button devices (TOUCH and T1) ([#327](https://github.com/xoseperez/espurna/issues/327))
- Generic 8 channel board ([#336](https://github.com/xoseperez/espurna/issues/336))
### Changed
- Added more sensor data filters (Max, MobileAverage)
- Changed max pulse time to 1h (#316)
- Renamed "reset" to "reboot" for clarity (#315)
- Changed max pulse time to 1h ([#316](https://github.com/xoseperez/espurna/issues/316))
- Renamed "reset" to "reboot" for clarity ([#315](https://github.com/xoseperez/espurna/issues/315))
- UI refactor
- Change apiRegister signature
## [1.10.1] 2017-12-05
### Fixed
- Fix Sonoff RFBridge learn message from web UI (#287)
- Fix unstability in "one and just one" sync mode (#290)
- Fix unnecessary inclusion of my92xx library (#293)
- Limit the MQTT queue to 10 messages when "Use JSON payload" enabled (#296)
- Fix Sonoff RFBridge OFF button toggling switch (#303)
- Allow defining only ON or OFF codes in Sonoff RFBridge (#304)
- Disabled terminal support for Sonoff Dual (#310)
- Fix Sonoff RFBridge learn message from web UI ([#287](https://github.com/xoseperez/espurna/issues/287))
- Fix unstability in "one and just one" sync mode ([#290](https://github.com/xoseperez/espurna/issues/290))
- Fix unnecessary inclusion of my92xx library ([#293](https://github.com/xoseperez/espurna/issues/293))
- Limit the MQTT queue to 10 messages when "Use JSON payload" enabled ([#296](https://github.com/xoseperez/espurna/issues/296))
- Fix Sonoff RFBridge OFF button toggling switch ([#303](https://github.com/xoseperez/espurna/issues/303))
- Allow defining only ON or OFF codes in Sonoff RFBridge ([#304](https://github.com/xoseperez/espurna/issues/304))
- Disabled terminal support for Sonoff Dual ([#310](https://github.com/xoseperez/espurna/issues/310))
### Added
- Support for SI7021-based sensor by Itead Studio compatible with Sonoff TH (#216)
- Support for Sonoff Dual R2 (#286)
- MQTT group topics (sync two or more switches from different devices, #300)
- Support for SI7021-based sensor by Itead Studio compatible with Sonoff TH ([#216](https://github.com/xoseperez/espurna/issues/216))
- Support for Sonoff Dual R2 ([#286](https://github.com/xoseperez/espurna/issues/286))
- MQTT group topics (sync two or more switches from different devices, [#300](https://github.com/xoseperez/espurna/issues/300))
- Color transitions (enabled by default, can be disabled from web UI)
- Option to disable MQTT support at build time
### Changed
- Decreased PWM frequency for dimmer lights
- Changed password policy (#297)
- Changed password policy ([#297](https://github.com/xoseperez/espurna/issues/297))
## [1.10.0] 2017-11-26
### Fixed
- Temperatures with 1 decimal resolution
- Issues with Sonoff B1 due to bad driver management (using my92xx library now)
- Avoid recursive messages on Domoticz (#272)
- Avoid recursive messages on Domoticz ([#272](https://github.com/xoseperez/espurna/issues/272))
- Fixed Sonoff T1 configuration
- Simplify and fix web auth (#284)
- Simplify and fix web auth ([#284](https://github.com/xoseperez/espurna/issues/284))
- Fix Embedis custom parser
### Added
@ -102,16 +354,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Option to disable system check on build time
- Power saving features (loopDelay and wifi sleep)
- Added Sonoff TH build environment
- Send Home Assistant auto discover messages on connect (#279)
- Implemented Home Assistant availability topic (#280)
- Send Home Assistant auto discover messages on connect ([#279](https://github.com/xoseperez/espurna/issues/279))
- Implemented Home Assistant availability topic ([#280](https://github.com/xoseperez/espurna/issues/280))
- Update time, uptime and heap on webUI every heartbeat
- Support for LLMNR and NetBIOS (#282)
- Support for LLMNR and NetBIOS ([#282](https://github.com/xoseperez/espurna/issues/282))
- Added I2C clean bus code
- Added realm to auth challenge
### Changed
- Changed default hostname to "ESPURNA_XXXXXX"
- Binaries built against stable core (~40Kb less, #274)
- Binaries built against stable core (~40Kb less, [#274](https://github.com/xoseperez/espurna/issues/274))
- Enabled TERMINAL_SUPPORT for Sonoff Dual (only available via TELNET)
- Dinamically resize debug strings (now messages are not cropped)
- MQTT: unsubscribe to '#' before subscribing
@ -125,7 +377,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Fixed bug in MY9291-based light bulbs at full brightness
### Added
- RFBridge: toggle when RF codes for ON and OFF are the same (#270)
- RFBridge: toggle when RF codes for ON and OFF are the same ([#270](https://github.com/xoseperez/espurna/issues/270))
- Support for HSV color schema (MQTT, API and webUI via a selector)
### Changed
@ -133,9 +385,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.9.8] 2017-11-08
### Fixed
- Removed dimmer lights flicker when saving to EEPROM (#191)
- Fixed low brightness in dimmer lights (#157)
- Fixed blank fields in energy (#258, #259)
- Removed dimmer lights flicker when saving to EEPROM ([#191](https://github.com/xoseperez/espurna/issues/191))
- Fixed low brightness in dimmer lights ([#157](https://github.com/xoseperez/espurna/issues/157))
- Fixed blank fields in energy ([#258](https://github.com/xoseperez/espurna/issues/258), [#259](https://github.com/xoseperez/espurna/issues/259))
- Fixed support for Arilux AL-LC06
- Updated fauxmoESP library with support for GetBinaryState actions
@ -154,42 +406,42 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.9.7] 2017-10-25
### Fixed
- Fix Alexa interface switching on all lights (#256)
- Fix Alexa interface switching on all lights ([#256](https://github.com/xoseperez/espurna/issues/256))
## [1.9.6] 2017-10-23
### Fixed
- Fix power report in Domoticz (#236)
- Fix Sonoff POW in AP mode (#241)
- Fix Home Automation auto-discovery (support for single relay switches and RGB lights, #235)
- Fix power report in Domoticz ([#236](https://github.com/xoseperez/espurna/issues/236))
- Fix Sonoff POW in AP mode ([#241](https://github.com/xoseperez/espurna/issues/241))
- Fix Home Automation auto-discovery (support for single relay switches and RGB lights, [#235](https://github.com/xoseperez/espurna/issues/235))
- Check WS authentication only on start event
### Added
- Support for 2.4.0 RC2 Arduino Core that fixes KRACK vulnerablity (pre-built images are compiled against this, #242)
- Support for 2.4.0 RC2 Arduino Core that fixes KRACK vulnerablity (pre-built images are compiled against this, [#242](https://github.com/xoseperez/espurna/issues/242))
- Support for ManCaveMade ESPLive board (thanks to Michael A. Cox)
- Support for InterMIT Tech QuinLED 2.6 (thanks to Colin Shorts)
- Support for Magic Home LED Controller 2.0 (thanks to users @gimi87 and @soif, #231)
- Support for Magic Home LED Controller 2.0 (thanks to users @gimi87 and @soif, [#231](https://github.com/xoseperez/espurna/issues/231))
- Support for Arilux AL-LC06 (thanks to Martijn Kruissen)
- Support for Xenon SM-PW702U Wifi boards (thanks to Joshua Harden, #212)
- Support for Authometion LYT8266 (testing, thanks to Joe Blellik, #213)
- Support for an external button for D1 Mini boards (thanks to user @PieBru, #239)
- Support for Xenon SM-PW702U Wifi boards (thanks to Joshua Harden, [#212](https://github.com/xoseperez/espurna/issues/212))
- Support for Authometion LYT8266 (testing, thanks to Joe Blellik, [#213](https://github.com/xoseperez/espurna/issues/213))
- Support for an external button for D1 Mini boards (thanks to user @PieBru, [#239](https://github.com/xoseperez/espurna/issues/239))
- Option to query relay status via MQTT or WS (thanks to Wesley Tuzza)
- Automatically install dependencies for web interface builder (thanks to Hermann Kraus)
- Support for HSV and IR for Magic Home LED Controller (optional, disabled by default, thanks to Wesley Tuzza)
- Added option to report DS18B20 temperatures based on changes (thanks to Michael A. Cox)
- Safer buffer handling for websocket data (thanks to Hermann Kraus & Björn Bergman)
- Updates HL8012 library with energy counting support (thanks to Hermann Kraus)
- Added option to disable light color persistence to avoid flickering (#191)
- Option to enable TELNET in STA mode from web UI (#203)
- Added option to disable light color persistence to avoid flickering ([#191](https://github.com/xoseperez/espurna/issues/191))
- Option to enable TELNET in STA mode from web UI ([#203](https://github.com/xoseperez/espurna/issues/203))
### Changed
- Changed default MQTT base topic to "{identifier}" (no leading slashes, #208)
- Prevent reconnecting when in AP mode if a web session or a telnet session is active (#244)
- Web UI checks for pending changes before reset/reconnect options (#226)
- Changed default MQTT base topic to "{identifier}" (no leading slashes, [#208](https://github.com/xoseperez/espurna/issues/208))
- Prevent reconnecting when in AP mode if a web session or a telnet session is active ([#244](https://github.com/xoseperez/espurna/issues/244))
- Web UI checks for pending changes before reset/reconnect options ([#226](https://github.com/xoseperez/espurna/issues/226))
- Increase WIFI connect timeout and reconnect interval
## [1.9.5] 2017-09-28
### Fixed
- Revert to JustWifi 1.1.4 (#228)
- Revert to JustWifi 1.1.4 ([#228](https://github.com/xoseperez/espurna/issues/228))
## [1.9.4] 2017-09-22
### Added
@ -200,14 +452,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Support for powermeters based on ECH1560 IC (beta, untested)
### Changed
- Changed behaviour on MQTT connection failure (#215)
- Changed behaviour on MQTT connection failure ([#215](https://github.com/xoseperez/espurna/issues/215))
- Removed boot delay
- Refactor power modules
- Updated JustWifi library
### Fixed
- Set all esp8285 devices to use esp01_1m (#210, #225)
- Removed wifi gain option since it prevents some devices to connect (#204)
- Set all esp8285 devices to use esp01_1m ([#210](https://github.com/xoseperez/espurna/issues/210), [#225](https://github.com/xoseperez/espurna/issues/225))
- Removed wifi gain option since it prevents some devices to connect ([#204](https://github.com/xoseperez/espurna/issues/204))
## [1.9.3] 2017-09-04
### Added
@ -220,13 +472,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Updated fauxmoESP library to 2.2.0
### Fixed
- Fix HLW8012 calibration (#194)
- Fix HLW8012 calibration ([#194](https://github.com/xoseperez/espurna/issues/194))
- Fix telnet dropping connection
- Fix WiFiSecureClient connection with PubSubClient (#64)
- Fix WiFiSecureClient connection with PubSubClient ([#64](https://github.com/xoseperez/espurna/issues/64))
## [1.9.2] 2017-08-31
### Added
- System stability check (turns off everything except WIFI AP, OTA and telnet if there is a boot crash loop) (#196)
- System stability check (turns off everything except WIFI AP, OTA and telnet if there is a boot crash loop) ([#196](https://github.com/xoseperez/espurna/issues/196))
- Telnet support (enabled by default only on AP interface)
- Option to set WiFi gain from web UI
- Option to disable MQTT from web UI
@ -241,7 +493,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Changes in terminal commands ("reconnect" is now "reset.wifi", also new commands added)
### Fixed
- Crash in settings saving (#190) and fixed UDP debug conditional build clauses
- Crash in settings saving ([#190](https://github.com/xoseperez/espurna/issues/190)) and fixed UDP debug conditional build clauses
## [1.9.1] 2017-08-27
### Added
@ -255,44 +507,44 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- Fixed build flags for DHT and DS18B20 in platformio.ini file
- Fixed Itead Sonoff B1 by updating the my9291 library
- Fixed light status on boot (#157)
- Fixed light status on boot ([#157](https://github.com/xoseperez/espurna/issues/157))
- Fixed CSS bug cause by a bad merge
## [1.9.0] 2017-08-25
### Added
- Support for IteadStudio BN-SZ01 Ceiling Light (#132)
- Support for IteadStudio Sonoff RF Bridge (#173)
- Support for IteadStudio Sonoff 4CH Pro (#174)
- Support for IteadStudio BN-SZ01 Ceiling Light ([#132](https://github.com/xoseperez/espurna/issues/132))
- Support for IteadStudio Sonoff RF Bridge ([#173](https://github.com/xoseperez/espurna/issues/173))
- Support for IteadStudio Sonoff 4CH Pro ([#174](https://github.com/xoseperez/espurna/issues/174))
- Support for IteadStudio Sonoff B1
- Support for IteadStudio Sonoff LED
- Support for IteadStudio Sonoff T1 wall switches (1, 2 and 4 channels)
- Support for WiOn 50055 WiFi Wall Outlet & Tap
- Support for EXS WiFi Relay v3.1 (and other future latching relay boards) (#152)
- TLS/SSL support for MQTT (caution: eats a lot of memory, do not use with web interface) (#64)
- Add support for delayed ON/OFF switches (#123, #161, #188)
- Added ON and OFF actions for button events (previously only TOGGLE available) (#182)
- Support for EXS WiFi Relay v3.1 (and other future latching relay boards) ([#152](https://github.com/xoseperez/espurna/issues/152))
- TLS/SSL support for MQTT (caution: eats a lot of memory, do not use with web interface) ([#64](https://github.com/xoseperez/espurna/issues/64))
- Add support for delayed ON/OFF switches ([#123](https://github.com/xoseperez/espurna/issues/123), [#161](https://github.com/xoseperez/espurna/issues/161), [#188](https://github.com/xoseperez/espurna/issues/188))
- Added ON and OFF actions for button events (previously only TOGGLE available) ([#182](https://github.com/xoseperez/espurna/issues/182))
- Sliders in web interface to control dimmer channels independently (also for brightness)
- Debug info about MQTT disconnect reason
### Changed
- MQTT setters ending with "/set" by default
- Using DOUT flash mode on all devices (#167)
- Using DOUT flash mode on all devices ([#167](https://github.com/xoseperez/espurna/issues/167))
- Longer timeout for WiFi connection (better chances for Sonoff Basic to connect)
- Changed MQTT topics for light devices (COLOR, BRIGHTNESS, MIRED, KELVIN, CHANNEL) (#144)
- Changed MQTT topics for light devices (COLOR, BRIGHTNESS, MIRED, KELVIN, CHANNEL) ([#144](https://github.com/xoseperez/espurna/issues/144))
- Changed the way light devices are defined (see LIGHT_PROVIDER_DIMMER)
- Allow to disable color picker in web interface
- API returns processed values for HLW8012 sensor (not raw values anymore) (#176)
- API returns processed values for HLW8012 sensor (not raw values anymore) ([#176](https://github.com/xoseperez/espurna/issues/176))
- Major refactoring of settings
### Fixed
- Discard MQTT messages with empty payload (#185)
- Discard MQTT messages with empty payload ([#185](https://github.com/xoseperez/espurna/issues/185))
- Wifi connection issue (https://github.com/esp8266/Arduino/issues/2186)
- Alexa connection issue
## [1.8.3] 2017-07-23
### Added
- Issue #85 and #90. Option to report MQTT messages with JSON payloads
- Issue #170. Updated DebouceEvent library to allow disabling double click and get faster click responses
- Issue [#85](https://github.com/xoseperez/espurna/issues/85) and [#90](https://github.com/xoseperez/espurna/issues/90). Option to report MQTT messages with JSON payloads
- Issue [#170](https://github.com/xoseperez/espurna/issues/170). Updated DebouceEvent library to allow disabling double click and get faster click responses
- Using memory layout with no SPIFFS for 1Mb devices
### Changed
@ -300,8 +552,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Return times in ISO8601 format
### Fixed
- Issue #168. Added H801 to arduino.h file
- Issue #171. Fix corrupted will message
- Issue [#168](https://github.com/xoseperez/espurna/issues/168). Added H801 to arduino.h file
- Issue [#171](https://github.com/xoseperez/espurna/issues/171). Fix corrupted will message
## [1.8.2] 2017-07-16
### Added
@ -310,45 +562,45 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Enable WIFI debug on hardware reset (button long click)
### Changed
- Issue #159. Allow decimals in relay pulse interval
- Issue [#159](https://github.com/xoseperez/espurna/issues/159). Allow decimals in relay pulse interval
- Updated HLW8012 library
### Fixed
- Issue #148. Fix bug in conditional compilation check
- Issue #149. Using different pulse counters for each relay (thanks to Lauris Ieviņš)
- Issue #141. Limit relay pulse interval to 60s
- Issue [#148](https://github.com/xoseperez/espurna/issues/148). Fix bug in conditional compilation check
- Issue [#149](https://github.com/xoseperez/espurna/issues/149). Using different pulse counters for each relay (thanks to Lauris Ieviņš)
- Issue [#141](https://github.com/xoseperez/espurna/issues/141). Limit relay pulse interval to 60s
- Fixed units for apparent & reactive power (thanks to Lauris Ieviņš)
- Fixed mDNS setup when using custom HTTP port for web interface
## [1.8.1] 2017-05-22
### Fixed
- Issue #140. Fix no relay control bug in Sonoff Dual
- Issue [#140](https://github.com/xoseperez/espurna/issues/140). Fix no relay control bug in Sonoff Dual
## [1.8.0] 2017-05-21
### Added
- Added gamma correction to RGB strips. Thanks to Chris Ward.
- Added support for Huacanxing H801 WiFi LED Controller. Thanks to Minh Phuong Ly.
- Issue #138. Added NTP configuration from web interface
- Issue #128. Report color when booting and in heartbeat stream.
- Issue #126. Show NTP status in web interface.
- Issue [#138](https://github.com/xoseperez/espurna/issues/138). Added NTP configuration from web interface
- Issue [#128](https://github.com/xoseperez/espurna/issues/128). Report color when booting and in heartbeat stream.
- Issue [#126](https://github.com/xoseperez/espurna/issues/126). Show NTP status in web interface.
- Added filter limits on POW readings.
- Added color temperature to RGB calculation. Thanks to Sacha Telgenhof.
- Issue #120. Added relay flood protection. Thanks to Izik Dubnov.
- Issue [#120](https://github.com/xoseperez/espurna/issues/120). Added relay flood protection. Thanks to Izik Dubnov.
- Support for "#RRGGBB", "RRR,GGG,BBB" and "WWW" color formats.
- Issue #117. Added build date & time to web interface.
- Issue [#117](https://github.com/xoseperez/espurna/issues/117). Added build date & time to web interface.
### Fixed
- Fix MQTT_RELAY board conifugration. Thanks to Denis French.
- Issue #125. Fix bug in relay status reading from EEPROM
- Issue #127. Fix button action in DUAL.
- Issue [#125](https://github.com/xoseperez/espurna/issues/125). Fix bug in relay status reading from EEPROM
- Issue [#127](https://github.com/xoseperez/espurna/issues/127). Fix button action in DUAL.
- Fix bug in Sonoff POW current reading. Thanks to Emmanuel Tatto.
- Minimizing my9291 flickering when booting.
- Fix conditional flags in hardware.ino to support Arduino IDE.
## [1.7.1] 2017-03-28
### Fixed
- Issue #113. Fix restoring color from EEPROM upon reboot
- Issue #113. Fix bug in API handlers
- Issue [#113](https://github.com/xoseperez/espurna/issues/113). Fix restoring color from EEPROM upon reboot
- Issue [#113](https://github.com/xoseperez/espurna/issues/113). Fix bug in API handlers
## [1.7.0] 2017-03-27
### Added
@ -367,10 +619,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Prevent the SDK from saving WiFi configuration to flash
### Fixed
- Issue #113. Fix light bulb state to OFF in library prevented the bulb from turning on
- Issue #58. Added code to handle spurious readings
- Issue [#113](https://github.com/xoseperez/espurna/issues/113). Fix light bulb state to OFF in library prevented the bulb from turning on
- Issue [#58](https://github.com/xoseperez/espurna/issues/58). Added code to handle spurious readings
- Fix bug in HLW8012 calibration current parameter casting to int instead of float
- Issue #115. Removed local declaration of _mqttForward variable. Thanks to Paweł Fiedor
- Issue [#115](https://github.com/xoseperez/espurna/issues/115). Removed local declaration of _mqttForward variable. Thanks to Paweł Fiedor
- Fix MQTT will topic. Thanks to Asbjorn Tronhus
## [1.6.9] 2017-03-12
@ -387,27 +639,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.6.8] 2017-03-01
### Added
- Issue #85. Heartbeat reports now free heap, uptime and VCC every 5 minutes
- Issue [#85](https://github.com/xoseperez/espurna/issues/85). Heartbeat reports now free heap, uptime and VCC every 5 minutes
### Changed
- Wait two minutes instead of one in AP mode before trying to reconnect to the router
- Issue #92. Debug log enabled by default in Arduino IDE
- Issue #91. Using AsyncMqttClient as default MQTT client again
- Issue [#92](https://github.com/xoseperez/espurna/issues/92). Debug log enabled by default in Arduino IDE
- Issue [#91](https://github.com/xoseperez/espurna/issues/91). Using AsyncMqttClient as default MQTT client again
### Fixed
- Report data from all sensors via websocket even if no MQTT connection
- Issue #92. Fix unknown reference in Arduino IDE
- Issue [#92](https://github.com/xoseperez/espurna/issues/92). Fix unknown reference in Arduino IDE
- Split data.h contents into 1k lines, otherwise Arduino IDE chokes on them
- Discard empty MQTT topic while subscribing
## [1.6.7] 2017-02-25
### Added
- Support for OpenLight / AI-Light by AI-Thinker based on MY9291 LED driver
- Issue #87. Factory reset when physical button pressed for >10 seconds
- Issue [#87](https://github.com/xoseperez/espurna/issues/87). Factory reset when physical button pressed for >10 seconds
## [1.6.6] 2017-02-23
### Fixed
- Issue #82. Fix critical bug on Sonoff Dual
- Issue [#82](https://github.com/xoseperez/espurna/issues/82). Fix critical bug on Sonoff Dual
## [1.6.5] 2017-02-22
### Added
@ -419,8 +671,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Double & long clicks do nothing except for the first defined button
### Fixed
- Issue #79. Fix bug in WiFi led notification & MQTT connectivity (using PubSubClient)
- Issue #73. Fix bug when building without Domoticz support
- Issue [#79](https://github.com/xoseperez/espurna/issues/79). Fix bug in WiFi led notification & MQTT connectivity (using PubSubClient)
- Issue [#73](https://github.com/xoseperez/espurna/issues/73). Fix bug when building without Domoticz support
- Fix Gulp tasks dependencies
## [1.6.4] 2017-02-20
@ -430,8 +682,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Support for PROGMEM debug messages (only wifi module has been changed)
- Option to disable mDNS, enabled by default
- Show current web server port in debug log
- Issue #75. Link relays to LEDs
- Issue #76. Using http://espurna.local when in AP mode
- Issue [#75](https://github.com/xoseperez/espurna/issues/75). Link relays to LEDs
- Issue [#76](https://github.com/xoseperez/espurna/issues/76). Using http://espurna.local when in AP mode
### Changed
- Images and favicon is now embedded in the HTML
@ -441,16 +693,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- Memory leak in MQTT connection method
- Wait 60 seconds before retrying to connect when in AP mode
- Issue #24 & #74. Update ESPAsyncTCP and ESPAsyncWebServer to latest GIT version that supports MSS defragmenting
- Issue #73. Fixes for windows machines
- Issue [#24](https://github.com/xoseperez/espurna/issues/24) & [#74](https://github.com/xoseperez/espurna/issues/74). Update ESPAsyncTCP and ESPAsyncWebServer to latest GIT version that supports MSS defragmenting
- Issue [#73](https://github.com/xoseperez/espurna/issues/73). Fixes for windows machines
### Removed
- Captive portal removed, mDNS resolution for AP mode too
## [1.6.3] 2017-02-15
### Added
- Issue #69. Temperature unit configuration from the web interface
- Issue #55. WebServer port configurable from the web interface, defaults to 80
- Issue [#69](https://github.com/xoseperez/espurna/issues/69). Temperature unit configuration from the web interface
- Issue [#55](https://github.com/xoseperez/espurna/issues/55). WebServer port configurable from the web interface, defaults to 80
- Expand network configuration when adding a new network
### Changed
@ -459,7 +711,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Scan for strongest network only if more than 1 network configured
### Fixed
- Issue #71. Added default values for netmask and DNS in web configuration
- Issue [#71](https://github.com/xoseperez/espurna/issues/71). Added default values for netmask and DNS in web configuration
- Fixed Itead 1CH self-locking/inching board definition
- Fixed PlatformIO environments for ESP8285 boards (4CH and Touch)
@ -491,11 +743,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.5.4] 2017-02-03
### Fixed
- Issue #50. Fix type bug in window variable when calculating energy for HLW8012 devices (Sonoff POW)
- Issue [#50](https://github.com/xoseperez/espurna/issues/50). Fix type bug in window variable when calculating energy for HLW8012 devices (Sonoff POW)
## [1.5.3] 2017-02-02
### Fixed
- Issue #50 and #54. Fixed domoticz MQTT message format
- Issue [#50](https://github.com/xoseperez/espurna/issues/50) and [#54](https://github.com/xoseperez/espurna/issues/54). Fixed domoticz MQTT message format
### Added
- Energy calculation and aggregation. API entry points and MQTT messages.
@ -528,9 +780,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- MQTT will topic
- Crash with HLW812 interrupts while trying to create a WIFI connection
- Issue #20 Better inline documentation for Alexa and Domoticz default settings
- Issue #39 Fixed autoconnect issue with static IP (fixed in JustWifi library)
- Issue #41 Added password requirements to initial password change page
- Issue [#20](https://github.com/xoseperez/espurna/issues/20) Better inline documentation for Alexa and Domoticz default settings
- Issue [#39](https://github.com/xoseperez/espurna/issues/39) Fixed autoconnect issue with static IP (fixed in JustWifi library)
- Issue [#41](https://github.com/xoseperez/espurna/issues/41) Added password requirements to initial password change page
### Changed
- Changed LED pattern for WIFI notifications (shorter pulses)
@ -540,7 +792,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Adding current, voltage, apparent and reactive power reports to Sonoff POW (Web & MQTT)
### Fixed
- Issue #35 Fixed frequent MQTT connection drops after WIFI reconnect
- Issue [#35](https://github.com/xoseperez/espurna/issues/35) Fixed frequent MQTT connection drops after WIFI reconnect
- Defer wifi disconnection from web interface to allow request to return
### Changed
@ -549,14 +801,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.4.3] 2017-01-11
### Fixed
- Issue #6 Using forked Time library to prevent conflict with Arduino Core for ESP8266 time.h file in windows machines
- Issue [#6](https://github.com/xoseperez/espurna/issues/6) Using forked Time library to prevent conflict with Arduino Core for ESP8266 time.h file in windows machines
## [1.4.2] 2017-01-09
### Added
- Support for inverse logic relays
### Fixed
- Issue #31. Fixed error in relay identification from MQTT messages
- Issue [#31](https://github.com/xoseperez/espurna/issues/31). Fixed error in relay identification from MQTT messages
## [1.4.1] 2017-01-05
### Added
@ -572,7 +824,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Support for static IP connections
### Fixed
- Issue #16. Enforce minimum password strength in web interface
- Issue [#16](https://github.com/xoseperez/espurna/issues/16). Enforce minimum password strength in web interface
### Changed
- Using default client_id provided by AsyncMqttClient
@ -609,9 +861,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Skipping retained MQTT messages (configurable)
### Fixed
- Issue #11 Compile error when building sonoff-dual-debug
- Issue #14 MQTT Connection with Username an Password not working
- Issue #17 Moved static variable 'pending' to class variable
- Issue [#11](https://github.com/xoseperez/espurna/issues/11) Compile error when building sonoff-dual-debug
- Issue [#14](https://github.com/xoseperez/espurna/issues/14) MQTT Connection with Username an Password not working
- Issue [#17](https://github.com/xoseperez/espurna/issues/17) Moved static variable 'pending' to class variable
## [1.1.0] 2016-12-06
### Added


+ 159
- 48
README.md View File

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

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

+ 0
- 65
code/.travis.yml View File

@ -1,65 +0,0 @@
# Continuous Integration (CI) is the practice, in software
# engineering, of merging all developer working copies with a shared mainline
# several times a day < http://docs.platformio.org/en/latest/ci/index.html >
#
# Documentation:
#
# * Travis CI Embedded Builds with PlatformIO
# < https://docs.travis-ci.com/user/integration/platformio/ >
#
# * PlatformIO integration with Travis CI
# < http://docs.platformio.org/en/latest/ci/travis.html >
#
# * User Guide for `platformio ci` command
# < http://docs.platformio.org/en/latest/userguide/cmd_ci.html >
#
#
# Please choice one of the following templates (proposed below) and uncomment
# it (remove "# " before each line) or use own configuration according to the
# Travis CI documentation (see above).
#
#
# Template #1: General project. Test it using existing `platformio.ini`.
#
language: python
python:
- "2.7"
sudo: false
cache:
directories:
- "~/.platformio"
install:
- pip install -U platformio
script:
- platformio run
#
# Template #2: The project is intended to by used as a library with examples
#
# language: python
# python:
# - "2.7"
#
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# env:
# - PLATFORMIO_CI_SRC=path/to/test/file.c
# - PLATFORMIO_CI_SRC=examples/file.ino
# - PLATFORMIO_CI_SRC=path/to/test/directory
#
# install:
# - pip install -U platformio
#
# script:
# - platformio ci --lib="." --board=TYPE_1 --board=TYPE_2 --board=TYPE_N

+ 28
- 6
code/build.sh View File

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

+ 0
- 51
code/core_version.py View File

@ -1,51 +0,0 @@
#!/bin/python
import json
import commands
import subprocess
import os
import sys
def core_version(env):
# Get the core folder
fwdir = env["FRAMEWORK_ARDUINOESP8266_DIR"]
# Get the core version
with open(fwdir + '/package.json') as data_file:
data = json.load(data_file)
core_version = data["version"].upper().replace(".", "_").replace("-", "_")
print "CORE VERSION: %s" % core_version
# Get git version
pr = subprocess.Popen(
"git --git-dir .git rev-parse --short=8 HEAD 2>/dev/null || echo ffffffff",
cwd = fwdir,
shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE )
(out, error) = pr.communicate()
git_version = str(out).replace('\n', "")
print "GIT VERSION: %s" % git_version
#env["BUILD_FLAGS"][0] += str(" -DARDUINO_ESP8266_RELEASE=" + core_version)
#env["BUILD_FLAGS"][0] += str(" -DARDUINO_ESP8266_RELEASE_" + core_version)
#env["BUILD_FLAGS"][0] += str(" -DARDUINO_ESP8266_GIT_VER=" + git_version)
with open('espurna/config/core_version.h', 'w') as the_file:
the_file.write('#define ARDUINO_ESP8266_RELEASE "%s"\n' % core_version)
the_file.write('#define ARDUINO_ESP8266_RELEASE_%s\n' % core_version)
the_file.write('#define ARDUINO_ESP8266_GIT_VER "%s"\n' % git_version)
#env.Append(
# CFLAGS = [
# str("-DARDUINO_ESP8266_RELEASE=" + core_version),
# str("-DARDUINO_ESP8266_RELEASE_" + core_version),
# str("-DARDUINO_ESP8266_GIT_VER=" + git_version)
# ]
#)
#print " -DARDUINO_ESP8266_RELEASE=" + core_version +
# " -DARDUINO_ESP8266_RELEASE_" + core_version +
# " -DARDUINO_ESP8266_GIT_VER=" + git_version
Import('env')
core_version(env)

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

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

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

@ -0,0 +1,19 @@
/* Flash Split for 1M chips, no SPIFFS */
/* sketch 995KB */
/* eeprom 8KB */
/* reserved 16KB */
MEMORY
{
dport0_0_seg : org = 0x3FF00000, len = 0x10
dram0_0_seg : org = 0x3FFE8000, len = 0x14000
iram1_0_seg : org = 0x40100000, len = 0x8000
irom0_0_seg : org = 0x40201010, len = 0xf8ff0
}
PROVIDE ( _SPIFFS_start = 0x402FA000 );
PROVIDE ( _SPIFFS_end = 0x402FA000 );
PROVIDE ( _SPIFFS_page = 0 );
PROVIDE ( _SPIFFS_block = 0 );
INCLUDE "../ld/eagle.app.v6.common.ld"

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

@ -0,0 +1,21 @@
/* Flash Split for 4M chips */
/* sketch 1019KB */
/* empty/ota? 2048KB */
/* spiffs 992KB */
/* eeprom 16KB */
/* reserved 16KB */
MEMORY
{
dport0_0_seg : org = 0x3FF00000, len = 0x10
dram0_0_seg : org = 0x3FFE8000, len = 0x14000
iram1_0_seg : org = 0x40100000, len = 0x8000
irom0_0_seg : org = 0x40201010, len = 0xfeff0
}
PROVIDE ( _SPIFFS_start = 0x40500000 );
PROVIDE ( _SPIFFS_end = 0x405F8000 );
PROVIDE ( _SPIFFS_page = 0x100 );
PROVIDE ( _SPIFFS_block = 0x2000 );
INCLUDE "../ld/eagle.app.v6.common.ld"

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

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

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

@ -0,0 +1,18 @@
/* Flash Split for 512K chips */
/* sketch 487KB */
/* eeprom 20KB */
MEMORY
{
dport0_0_seg : org = 0x3FF00000, len = 0x10
dram0_0_seg : org = 0x3FFE8000, len = 0x14000
iram1_0_seg : org = 0x40100000, len = 0x8000
irom0_0_seg : org = 0x40201010, len = 0x79ff0
}
PROVIDE ( _SPIFFS_start = 0x4027B000 );
PROVIDE ( _SPIFFS_end = 0x4027B000 );
PROVIDE ( _SPIFFS_page = 0x0 );
PROVIDE ( _SPIFFS_block = 0x0 );
INCLUDE "../ld/eagle.app.v6.common.ld"

+ 25
- 14
code/espurna/alexa.ino View File

@ -2,23 +2,30 @@
ALEXA MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if ALEXA_SUPPORT
#include <fauxmoESP.h>
fauxmoESP alexa;
struct AlexaDevChange {
AlexaDevChange(unsigned char device_id, bool state) : device_id(device_id), state(state) {};
unsigned char device_id = 0;
bool state = false;
};
#include <queue>
static std::queue<AlexaDevChange> _alexa_dev_changes;
// -----------------------------------------------------------------------------
// ALEXA
// -----------------------------------------------------------------------------
bool _alexa_change = false;
unsigned int _alexa_device_id = 0;
bool _alexa_state = false;
bool _alexaWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "alexa", 5) == 0);
}
void _alexaWebSocketOnSend(JsonObject& root) {
root["alexaVisible"] = 1;
@ -44,6 +51,7 @@ void alexaSetup() {
// Websockets
wsOnSendRegister(_alexaWebSocketOnSend);
wsOnAfterParseRegister(_alexaConfigure);
wsOnReceiveRegister(_alexaWebSocketOnReceive);
#endif
@ -57,26 +65,29 @@ void alexaSetup() {
}
}
alexa.onSetState([relays](unsigned char device_id, const char * name, bool state) {
_alexa_change = true;
_alexa_device_id = device_id;
_alexa_state = state;
alexa.onSetState([&](unsigned char device_id, const char * name, bool state) {
AlexaDevChange change(device_id, state);
_alexa_dev_changes.push(change);
});
alexa.onGetState([relays](unsigned char device_id, const char * name) {
alexa.onGetState([](unsigned char device_id, const char * name) {
return relayStatus(device_id);
});
// Register loop
espurnaRegisterLoop(alexaLoop);
}
void alexaLoop() {
alexa.handle();
if (_alexa_change) {
DEBUG_MSG_P(PSTR("[ALEXA] Device #%d state: %s\n"), _alexa_device_id, _alexa_state ? "ON" : "OFF");
_alexa_change = false;
relayStatus(_alexa_device_id, _alexa_state);
while (!_alexa_dev_changes.empty()) {
AlexaDevChange& change = _alexa_dev_changes.front();
DEBUG_MSG_P(PSTR("[ALEXA] Device #%u state: %s\n"), change.device_id, change.state ? "ON" : "OFF");
relayStatus(change.device_id, change.state);
_alexa_dev_changes.pop();
}
}


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

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

+ 32
- 0
code/espurna/broker.ino View File

@ -0,0 +1,32 @@
/*
BROKER MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if BROKER_SUPPORT
#include <vector>
std::vector<void (*)(const char *, unsigned char, const char *)> _broker_callbacks;
// -----------------------------------------------------------------------------
void brokerRegister(void (*callback)(const char *, unsigned char, const char *)) {
_broker_callbacks.push_back(callback);
}
void brokerPublish(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);
}
}
void brokerPublish(const char * topic, const char * message) {
brokerPublish(topic, 0, message);
}
#endif // BROKER_SUPPORT

+ 55
- 34
code/espurna/button.ino View File

@ -2,7 +2,7 @@
BUTTON MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -22,14 +22,22 @@ typedef struct {
std::vector<button_t> _buttons;
#if MQTT_SUPPORT
#ifdef MQTT_TOPIC_BUTTON
void buttonMQTT(unsigned char id, uint8_t event) {
if (id >= _buttons.size()) return;
char payload[2];
snprintf_P(payload, sizeof(payload), PSTR("%d"), event);
mqttSend(MQTT_TOPIC_BUTTON, id, payload);
itoa(event, payload, 10);
mqttSend(MQTT_TOPIC_BUTTON, id, payload, false, false); // 1st bool = force, 2nd = retain
}
#endif
#if WEB_SUPPORT
bool _buttonWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "btn", 3) == 0);
}
#endif
int buttonFromRelay(unsigned int relayID) {
@ -52,16 +60,18 @@ unsigned char buttonAction(unsigned char id, unsigned char event) {
if (event == BUTTON_EVENT_DBLCLICK) return (actions >> 8) & 0x0F;
if (event == BUTTON_EVENT_LNGCLICK) return (actions >> 12) & 0x0F;
if (event == BUTTON_EVENT_LNGLNGCLICK) return (actions >> 16) & 0x0F;
if (event == BUTTON_EVENT_TRIPLECLICK) return (actions >> 20) & 0x0F;
return BUTTON_MODE_NONE;
}
unsigned long buttonStore(unsigned long pressed, unsigned long click, unsigned long dblclick, unsigned long lngclick, unsigned long lnglngclick) {
unsigned long buttonStore(unsigned long pressed, unsigned long click, unsigned long dblclick, unsigned long lngclick, unsigned long lnglngclick, unsigned long tripleclick) {
unsigned int value;
value = pressed;
value += click << 4;
value += dblclick << 8;
value += lngclick << 12;
value += lnglngclick << 16;
value += tripleclick << 20;
return value;
}
@ -69,25 +79,25 @@ uint8_t mapEvent(uint8_t event, uint8_t count, uint16_t length) {
if (event == EVENT_PRESSED) return BUTTON_EVENT_PRESSED;
if (event == EVENT_CHANGED) return BUTTON_EVENT_CLICK;
if (event == EVENT_RELEASED) {
if (count == 1) {
if (1 == count) {
if (length > BUTTON_LNGLNGCLICK_DELAY) return BUTTON_EVENT_LNGLNGCLICK;
if (length > BUTTON_LNGCLICK_DELAY) return BUTTON_EVENT_LNGCLICK;
return BUTTON_EVENT_CLICK;
}
if (count == 2) return BUTTON_EVENT_DBLCLICK;
if (2 == count) return BUTTON_EVENT_DBLCLICK;
if (3 == count) return BUTTON_EVENT_TRIPLECLICK;
}
return BUTTON_EVENT_NONE;
}
void buttonEvent(unsigned int id, unsigned char event) {
DEBUG_MSG_P(PSTR("[BUTTON] Pressed #%d, event: %d\n"), id, event);
DEBUG_MSG_P(PSTR("[BUTTON] Button #%u event %u\n"), id, event);
if (event == 0) return;
#if MQTT_SUPPORT
#ifdef MQTT_TOPIC_BUTTON
buttonMQTT(id, event);
#endif
#endif
unsigned char action = buttonAction(id, event);
@ -106,13 +116,19 @@ void buttonEvent(unsigned int id, unsigned char event) {
relayStatus(_buttons[id].relayID - 1, false);
}
}
if (action == BUTTON_MODE_AP) createAP();
if (action == BUTTON_MODE_AP) wifiStartAP();
#if defined(JUSTWIFI_ENABLE_WPS)
if (action == BUTTON_MODE_WPS) wifiStartWPS();
#endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
if (action == BUTTON_MODE_SMART_CONFIG) wifiStartSmartConfig();
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
if (action == BUTTON_MODE_RESET) {
deferredReset(100, CUSTOM_RESET_HARDWARE);
}
if (action == BUTTON_MODE_FACTORY) {
DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
settingsFactoryReset();
resetSettings();
deferredReset(100, CUSTOM_RESET_FACTORY);
}
@ -122,7 +138,7 @@ void buttonSetup() {
#ifdef ITEAD_SONOFF_DUAL
unsigned int actions = buttonStore(BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE);
unsigned int actions = buttonStore(BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE);
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 1});
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 2});
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, BUTTON3_RELAY});
@ -131,58 +147,66 @@ void buttonSetup() {
unsigned long btnDelay = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt();
#ifdef BUTTON1_PIN
#if BUTTON1_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON1_PRESS, BUTTON1_CLICK, BUTTON1_DBLCLICK, BUTTON1_LNGCLICK, BUTTON1_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON1_PRESS, BUTTON1_CLICK, BUTTON1_DBLCLICK, BUTTON1_LNGCLICK, BUTTON1_LNGLNGCLICK, BUTTON1_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON1_PIN, BUTTON1_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON1_RELAY});
}
#endif
#ifdef BUTTON2_PIN
#if BUTTON2_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON2_PRESS, BUTTON2_CLICK, BUTTON2_DBLCLICK, BUTTON2_LNGCLICK, BUTTON2_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON2_PRESS, BUTTON2_CLICK, BUTTON2_DBLCLICK, BUTTON2_LNGCLICK, BUTTON2_LNGLNGCLICK, BUTTON2_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON2_PIN, BUTTON2_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON2_RELAY});
}
#endif
#ifdef BUTTON3_PIN
#if BUTTON3_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON3_PRESS, BUTTON3_CLICK, BUTTON3_DBLCLICK, BUTTON3_LNGCLICK, BUTTON3_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON3_PRESS, BUTTON3_CLICK, BUTTON3_DBLCLICK, BUTTON3_LNGCLICK, BUTTON3_LNGLNGCLICK, BUTTON3_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON3_PIN, BUTTON3_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON3_RELAY});
}
#endif
#ifdef BUTTON4_PIN
#if BUTTON4_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON4_PRESS, BUTTON4_CLICK, BUTTON4_DBLCLICK, BUTTON4_LNGCLICK, BUTTON4_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON4_PRESS, BUTTON4_CLICK, BUTTON4_DBLCLICK, BUTTON4_LNGCLICK, BUTTON4_LNGLNGCLICK, BUTTON4_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON4_PIN, BUTTON4_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON4_RELAY});
}
#endif
#ifdef BUTTON5_PIN
#if BUTTON5_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON5_PRESS, BUTTON5_CLICK, BUTTON5_DBLCLICK, BUTTON5_LNGCLICK, BUTTON5_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON5_PRESS, BUTTON5_CLICK, BUTTON5_DBLCLICK, BUTTON5_LNGCLICK, BUTTON5_LNGLNGCLICK, BUTTON5_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON5_PIN, BUTTON5_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON5_RELAY});
}
#endif
#ifdef BUTTON6_PIN
#if BUTTON6_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON6_PRESS, BUTTON6_CLICK, BUTTON6_DBLCLICK, BUTTON6_LNGCLICK, BUTTON6_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON6_PRESS, BUTTON6_CLICK, BUTTON6_DBLCLICK, BUTTON6_LNGCLICK, BUTTON6_LNGLNGCLICK, BUTTON6_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON6_PIN, BUTTON6_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON6_RELAY});
}
#endif
#ifdef BUTTON7_PIN
#if BUTTON7_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON7_PRESS, BUTTON7_CLICK, BUTTON7_DBLCLICK, BUTTON7_LNGCLICK, BUTTON7_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON7_PRESS, BUTTON7_CLICK, BUTTON7_DBLCLICK, BUTTON7_LNGCLICK, BUTTON7_LNGLNGCLICK, BUTTON7_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON7_PIN, BUTTON7_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON7_RELAY});
}
#endif
#ifdef BUTTON8_PIN
#if BUTTON8_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON8_PRESS, BUTTON8_CLICK, BUTTON8_DBLCLICK, BUTTON8_LNGCLICK, BUTTON8_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON8_PRESS, BUTTON8_CLICK, BUTTON8_DBLCLICK, BUTTON8_LNGCLICK, BUTTON8_LNGLNGCLICK, BUTTON8_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON8_PIN, BUTTON8_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON8_RELAY});
}
#endif
#endif
DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %d\n"), _buttons.size());
DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), _buttons.size());
// Websocket Callbacks
#if WEB_SUPPORT
wsOnReceiveRegister(_buttonWebSocketOnReceive);
#endif
// Register loop
espurnaRegisterLoop(buttonLoop);
}
@ -191,11 +215,9 @@ void buttonLoop() {
#ifdef ITEAD_SONOFF_DUAL
if (Serial.available() >= 4) {
unsigned char value;
if (Serial.read() == 0xA0) {
if (Serial.read() == 0x04) {
value = Serial.read();
unsigned char value = Serial.read();
if (Serial.read() == 0xA1) {
// RELAYs and BUTTONs are synchonized in the SIL F330
@ -228,7 +250,6 @@ void buttonLoop() {
}
}
}
}
#else


+ 29
- 15
code/espurna/config/all.h View File

@ -1,26 +1,40 @@
/*
If you want to modify the stock configuration but you don't want to touch
the repo files you can define USE_CUSTOM_H in your build settings.
Arduino IDE:
define it in your boards.txt for the board of your choice.
For instance, for the "Generic ESP8266 Module" with prefix "generic" just add:
generic.build.extra_flags=-DESP8266 -DUSE_CUSTOM_H
PlatformIO:
add the setting to your environment or just define global PLATFORMIO_BUILD_FLAGS
export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'"
Check https://github.com/xoseperez/espurna/issues/104
for an example on how to use this file.
*/
#ifdef USE_CUSTOM_H
#include "custom.h"
#endif
#include "version.h"
#include "types.h"
#include "arduino.h"
#include "hardware.h"
#include "defaults.h"
#include "general.h"
#include "prototypes.h"
#include "sensors.h"
#include "dependencies.h"
#include "progmem.h"
#include "debug.h"
#ifdef USE_CORE_VERSION_H
#include "core_version.h"
#endif
/*
If you want to modify the stock configuration but you don't want to touch
the repo files you can either define USE_CUSTOM_H or remove the
"#ifdef USE_CUSTOM_H" & "#endif" lines and add a "custom.h"
file to this same folder.
Check https://bitbucket.org/xoseperez/espurna/issues/104/general_customh
for an example on how to use this file.
(Define USE_CUSTOM_H on commandline for platformio:
export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'" )
*/
#ifdef USE_CUSTOM_H
#include "custom.h"
#endif

+ 47
- 3
code/espurna/config/arduino.h View File

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

+ 5
- 0
code/espurna/config/build.h View File

@ -0,0 +1,5 @@
// DO NOT EDIT THIS FILE MANUALLY
// This file is modified by PlatformIO
// This file should not be pushed when modified, untrack changes with:
// git update-index --assume-unchanged code/espurna/config/build.h
#define APP_BUILD_FLAGS ""

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

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

+ 118
- 15
code/espurna/config/defaults.h View File

@ -2,10 +2,37 @@
// Hardware default values
// -----------------------------------------------------------------------------
#define GPIO_NONE 0x99
// -----------------------------------------------------------------------------
// Buttons
// -----------------------------------------------------------------------------
#ifndef BUTTON1_PIN
#define BUTTON1_PIN GPIO_NONE
#endif
#ifndef BUTTON2_PIN
#define BUTTON2_PIN GPIO_NONE
#endif
#ifndef BUTTON3_PIN
#define BUTTON3_PIN GPIO_NONE
#endif
#ifndef BUTTON4_PIN
#define BUTTON4_PIN GPIO_NONE
#endif
#ifndef BUTTON5_PIN
#define BUTTON5_PIN GPIO_NONE
#endif
#ifndef BUTTON6_PIN
#define BUTTON6_PIN GPIO_NONE
#endif
#ifndef BUTTON7_PIN
#define BUTTON7_PIN GPIO_NONE
#endif
#ifndef BUTTON8_PIN
#define BUTTON8_PIN GPIO_NONE
#endif
#ifndef BUTTON1_PRESS
#define BUTTON1_PRESS BUTTON_MODE_NONE
#endif
@ -81,6 +108,31 @@
#define BUTTON8_DBLCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON1_TRIPLECLICK
#define BUTTON1_TRIPLECLICK BUTTON_MODE_SMART_CONFIG
#endif
#ifndef BUTTON2_TRIPLECLICK
#define BUTTON2_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON3_TRIPLECLICK
#define BUTTON3_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON4_TRIPLECLICK
#define BUTTON4_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON5_TRIPLECLICK
#define BUTTON5_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON6_TRIPLECLICK
#define BUTTON6_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON7_TRIPLECLICK
#define BUTTON7_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON8_TRIPLECLICK
#define BUTTON8_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON1_LNGCLICK
#define BUTTON1_LNGCLICK BUTTON_MODE_RESET
#endif
@ -160,6 +212,35 @@
// Relays
// -----------------------------------------------------------------------------
#ifndef DUMMY_RELAY_COUNT
#define DUMMY_RELAY_COUNT 0
#endif
#ifndef RELAY1_PIN
#define RELAY1_PIN GPIO_NONE
#endif
#ifndef RELAY2_PIN
#define RELAY2_PIN GPIO_NONE
#endif
#ifndef RELAY3_PIN
#define RELAY3_PIN GPIO_NONE
#endif
#ifndef RELAY4_PIN
#define RELAY4_PIN GPIO_NONE
#endif
#ifndef RELAY5_PIN
#define RELAY5_PIN GPIO_NONE
#endif
#ifndef RELAY6_PIN
#define RELAY6_PIN GPIO_NONE
#endif
#ifndef RELAY7_PIN
#define RELAY7_PIN GPIO_NONE
#endif
#ifndef RELAY8_PIN
#define RELAY8_PIN GPIO_NONE
#endif
#ifndef RELAY1_TYPE
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#endif
@ -175,7 +256,7 @@
#ifndef RELAY5_TYPE
#define RELAY5_TYPE RELAY_TYPE_NORMAL
#endif
#ifndef RELAY6_TYPE
#ifndef RELAY6_TYPE
#define RELAY6_TYPE RELAY_TYPE_NORMAL
#endif
#ifndef RELAY7_TYPE
@ -186,28 +267,28 @@
#endif
#ifndef RELAY1_RESET_PIN
#define RELAY1_RESET_PIN 0
#define RELAY1_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY2_RESET_PIN
#define RELAY2_RESET_PIN 0
#define RELAY2_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY3_RESET_PIN
#define RELAY3_RESET_PIN 0
#define RELAY3_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY4_RESET_PIN
#define RELAY4_RESET_PIN 0
#define RELAY4_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY5_RESET_PIN
#define RELAY5_RESET_PIN 0
#define RELAY5_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY6_RESET_PIN
#define RELAY6_RESET_PIN 0
#define RELAY6_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY7_RESET_PIN
#define RELAY7_RESET_PIN 0
#define RELAY7_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY8_RESET_PIN
#define RELAY8_RESET_PIN 0
#define RELAY8_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY1_DELAY_ON
@ -264,6 +345,31 @@
// LEDs
// -----------------------------------------------------------------------------
#ifndef LED1_PIN
#define LED1_PIN GPIO_NONE
#endif
#ifndef LED2_PIN
#define LED2_PIN GPIO_NONE
#endif
#ifndef LED3_PIN
#define LED3_PIN GPIO_NONE
#endif
#ifndef LED4_PIN
#define LED4_PIN GPIO_NONE
#endif
#ifndef LED5_PIN
#define LED5_PIN GPIO_NONE
#endif
#ifndef LED6_PIN
#define LED6_PIN GPIO_NONE
#endif
#ifndef LED7_PIN
#define LED7_PIN GPIO_NONE
#endif
#ifndef LED8_PIN
#define LED8_PIN GPIO_NONE
#endif
#ifndef LED1_MODE
#define LED1_MODE LED_MODE_WIFI
#endif
@ -318,12 +424,9 @@
// General
// -----------------------------------------------------------------------------
// Needed for ESP8285 boards under Windows using PlatformIO (?)
#ifndef BUTTON_PUSHBUTTON
#define BUTTON_PUSHBUTTON 0
#define BUTTON_SWITCH 1
#define BUTTON_DEFAULT_HIGH 2
#define BUTTON_SET_PULLUP 4
// Default hostname will be ESPURNA-XXXXXX, where XXXXXX is last 3 octets of chipID
#ifndef HOSTNAME
#define HOSTNAME ""
#endif
// Relay providers


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

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

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


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


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

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

+ 52
- 10
code/espurna/config/prototypes.h View File

@ -7,6 +7,12 @@ extern "C" {
#include "user_interface.h"
}
// -----------------------------------------------------------------------------
// EEPROM_ROTATE
// -----------------------------------------------------------------------------
#include <EEPROM_Rotate.h>
EEPROM_Rotate EEPROMr;
// -----------------------------------------------------------------------------
// WebServer
// -----------------------------------------------------------------------------
@ -27,34 +33,72 @@ typedef std::function<void(JsonObject&)> ws_on_send_callback_f;
void wsOnSendRegister(ws_on_send_callback_f callback);
void wsSend(ws_on_send_callback_f sender);
typedef std::function<void(const char *, JsonObject&)> ws_on_action_callback_f;
typedef std::function<void(uint32_t, const char *, JsonObject&)> ws_on_action_callback_f;
void wsOnActionRegister(ws_on_action_callback_f callback);
typedef std::function<void(void)> ws_on_after_parse_callback_f;
void wsOnAfterParseRegister(ws_on_after_parse_callback_f callback);
typedef std::function<bool(const char *, JsonVariant&)> ws_on_receive_callback_f;
void wsOnReceiveRegister(ws_on_receive_callback_f callback);
// -----------------------------------------------------------------------------
// WIFI
// -----------------------------------------------------------------------------
#include "JustWifi.h"
typedef std::function<void(justwifi_messages_t code, char * parameter)> wifi_callback_f;
void wifiRegister(wifi_callback_f callback);
// -----------------------------------------------------------------------------
// MQTT
// -----------------------------------------------------------------------------
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
void mqttRegister(mqtt_callback_f callback);
String mqttSubtopic(char * topic);
String mqttMagnitude(char * topic);
// -----------------------------------------------------------------------------
// Broker
// -----------------------------------------------------------------------------
void brokerRegister(void (*)(const char *, unsigned char, const char *));
// -----------------------------------------------------------------------------
// Settings
// -----------------------------------------------------------------------------
#include <Embedis.h>
template<typename T> bool setSetting(const String& key, T value);
template<typename T> bool setSetting(const String& key, unsigned int index, T value);
template<typename T> String getSetting(const String& key, T defaultValue);
template<typename T> String getSetting(const String& key, unsigned int index, T defaultValue);
bool settingsRestore(JsonObject& data);
void settingsGetJson(JsonObject& data);
bool settingsRestoreJson(JsonObject& data);
void settingsRegisterCommand(const String& name, void (*call)(Embedis*));
void settingsInject(void *data, size_t len);
Stream & settingsSerial();
// -----------------------------------------------------------------------------
// I2C
// -----------------------------------------------------------------------------
unsigned char i2cFindAndLock(size_t size, unsigned char * addresses);
void i2cScan();
void i2cClearBus();
bool i2cGetLock(unsigned char address);
bool i2cReleaseLock(unsigned char address);
unsigned char i2cFindAndLock(size_t size, unsigned char * addresses);
void i2c_wakeup(uint8_t address);
uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len);
uint8_t i2c_write_uint8(uint8_t address, uint8_t value);
uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value);
uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value1, uint8_t value2);
uint8_t i2c_write_uint16(uint8_t address, uint16_t value);
uint8_t i2c_write_uint16(uint8_t address, uint8_t reg, uint16_t value);
uint8_t i2c_read_uint8(uint8_t address);
uint8_t i2c_read_uint8(uint8_t address, uint8_t reg);
uint16_t i2c_read_uint16(uint8_t address);
uint16_t i2c_read_uint16(uint8_t address, uint8_t reg);
uint16_t i2c_read_uint16_le(uint8_t address, uint8_t reg);
int16_t i2c_read_int16(uint8_t address, uint8_t reg);
int16_t i2c_read_int16_le(uint8_t address, uint8_t reg);
void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len);
// -----------------------------------------------------------------------------
// GPIO
@ -75,13 +119,11 @@ void debugSend_P(PGM_P format, ...);
template<typename T> void domoticzSend(const char * key, T value);
template<typename T> void domoticzSend(const char * key, T nvalue, const char * svalue);
// -----------------------------------------------------------------------------
// InfluxDB
// -----------------------------------------------------------------------------
template<typename T> bool idbSend(const char * topic, T payload);
template<typename T> bool idbSend(const char * topic, unsigned char id, T payload);
// -----------------------------------------------------------------------------
// Utils
// -----------------------------------------------------------------------------
char * ltrim(char * s);
void nice_delay(unsigned long ms);
#define ARRAYINIT(type, name, ...) \
type name[] = {__VA_ARGS__};

+ 275
- 183
code/espurna/config/sensors.h View File

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


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

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

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

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

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


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

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

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

@ -2,7 +2,7 @@
DOMOTICZ MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -11,16 +11,13 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h>
bool _dcz_enabled = false;
unsigned long _dcz_skip_time = 0;
unsigned long _dcz_last_idx = 0;
unsigned long _dcz_last_time = 0;
//------------------------------------------------------------------------------
// Private methods
//------------------------------------------------------------------------------
int _domoticzRelay(unsigned int idx) {
for (int relayID=0; relayID<relayCount(); relayID++) {
unsigned char _domoticzRelay(unsigned int idx) {
for (unsigned char relayID=0; relayID<relayCount(); relayID++) {
if (domoticzIdx(relayID) == idx) {
return relayID;
}
@ -28,13 +25,6 @@ int _domoticzRelay(unsigned int idx) {
return -1;
}
bool _domoticzSkip(unsigned long idx) {
if (idx == _dcz_last_idx && (millis() - _dcz_last_time < _dcz_skip_time)) return true;
_dcz_last_idx = idx;
_dcz_last_time = millis();
return false;
}
void _domoticzMqttSubscribe(bool value) {
String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
@ -53,7 +43,13 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
if (type == MQTT_CONNECT_EVENT) {
// Subscribe to domoticz action topics
mqttSubscribeRaw(dczTopicOut.c_str());
// Send relays state on connection
domoticzSendRelays();
}
if (type == MQTT_MESSAGE_EVENT) {
@ -70,17 +66,12 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
}
// IDX
unsigned long idx = root["idx"];
int relayID = _domoticzRelay(idx);
unsigned int idx = root["idx"];
unsigned char relayID = _domoticzRelay(idx);
if (relayID >= 0) {
// Skip message if recursive
if (_domoticzSkip(idx)) return;
unsigned long value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %d for IDX %d\n"), value, idx);
unsigned char value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx);
relayStatus(relayID, value == 1);
}
}
@ -89,16 +80,21 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
};
#if WEB_SUPPORT
bool _domoticzWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "dcz", 3) == 0);
}
void _domoticzWebSocketOnSend(JsonObject& root) {
root["dczVisible"] = 1;
root["dczEnabled"] = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
root["dczSkip"] = getSetting("dczSkip", DOMOTICZ_SKIP_TIME);
root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC);
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
JsonArray& relays = root.createNestedArray("dczRelayIdx");
for (byte i=0; i<relayCount(); i++) {
JsonArray& relays = root.createNestedArray("dczRelays");
for (unsigned char i=0; i<relayCount(); i++) {
relays.add(domoticzIdx(i));
}
@ -115,10 +111,12 @@ void _domoticzWebSocketOnSend(JsonObject& root) {
}
#endif // WEB_SUPPORT
void _domoticzConfigure() {
_dcz_enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
_dcz_skip_time = 1000 * getSetting("dczSkip", DOMOTICZ_SKIP_TIME).toInt();
_domoticzMqttSubscribe(_dcz_enabled);
bool enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
if (enabled != _dcz_enabled) _domoticzMqttSubscribe(enabled);
_dcz_enabled = enabled;
}
//------------------------------------------------------------------------------
@ -129,14 +127,9 @@ template<typename T> void domoticzSend(const char * key, T nvalue, const char *
if (!_dcz_enabled) return;
unsigned int idx = getSetting(key).toInt();
if (idx > 0) {
// Skip message if recursive
if (_domoticzSkip(idx)) return;
char payload[128];
snprintf(payload, sizeof(payload), "{\"idx\": %d, \"nvalue\": %s, \"svalue\": \"%s\"}", idx, String(nvalue).c_str(), svalue);
snprintf(payload, sizeof(payload), "{\"idx\": %u, \"nvalue\": %s, \"svalue\": \"%s\"}", idx, String(nvalue).c_str(), svalue);
mqttSendRaw(getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC).c_str(), payload);
}
}
@ -144,16 +137,22 @@ template<typename T> void domoticzSend(const char * key, T nvalue) {
domoticzSend(key, nvalue, "");
}
void domoticzSendRelay(unsigned int relayID) {
void domoticzSendRelay(unsigned char relayID) {
if (!_dcz_enabled) return;
char buffer[15];
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%d"), relayID);
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%u"), relayID);
domoticzSend(buffer, relayStatus(relayID) ? "1" : "0");
}
int domoticzIdx(unsigned int relayID) {
void domoticzSendRelays() {
for (uint8_t relayID=0; relayID < relayCount(); relayID++) {
domoticzSendRelay(relayID);
}
}
unsigned int domoticzIdx(unsigned char relayID) {
char buffer[15];
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%d"), relayID);
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%u"), relayID);
return getSetting(buffer).toInt();
}
@ -162,6 +161,7 @@ void domoticzSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_domoticzWebSocketOnSend);
wsOnAfterParseRegister(_domoticzConfigure);
wsOnReceiveRegister(_domoticzWebSocketOnReceive);
#endif
mqttRegister(_domoticzMqtt);
}


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

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

+ 59
- 319
code/espurna/espurna.ino View File

@ -2,7 +2,7 @@
ESPurna
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
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
@ -20,321 +20,95 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config/all.h"
#include <EEPROM.h>
#include <vector>
std::vector<void (*)()> _loop_callbacks;
// -----------------------------------------------------------------------------
// METHODS
// REGISTER
// -----------------------------------------------------------------------------
unsigned long _loopDelay = 0;
void hardwareSetup() {
EEPROM.begin(EEPROM_SIZE);
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.begin(SERIAL_BAUDRATE);
#if DEBUG_ESP_WIFI
DEBUG_PORT.setDebugOutput(true);
#endif
#elif defined(SERIAL_BAUDRATE)
Serial.begin(SERIAL_BAUDRATE);
#endif
#if SPIFFS_SUPPORT
SPIFFS.begin();
#endif
#if defined(ESPLIVE)
//The ESPLive has an ADC MUX which needs to be configured.
pinMode(16, OUTPUT);
digitalWrite(16, HIGH); //Defualt CT input (pin B, solder jumper B)
#endif
}
void hardwareLoop() {
// Heartbeat
static unsigned long last_uptime = 0;
static bool on_connect = true;
bool send = (last_uptime == 0);
send = send || (millis() - last_uptime > HEARTBEAT_INTERVAL);
if (mqttConnected()) {
send = send || on_connect;
} else {
on_connect = true;
}
if (send) {
last_uptime = millis();
if (mqttConnected()) on_connect = false;
heartbeat();
}
void espurnaRegisterLoop(void (*callback)()) {
_loop_callbacks.push_back(callback);
}
// -----------------------------------------------------------------------------
// BOOTING
// -----------------------------------------------------------------------------
unsigned int sectors(size_t size) {
return (int) (size + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE;
}
void welcome() {
DEBUG_MSG_P(PSTR("\n\n"));
DEBUG_MSG_P(PSTR("[INIT] %s %s\n"), (char *) APP_NAME, (char *) APP_VERSION);
DEBUG_MSG_P(PSTR("[INIT] %s\n"), (char *) APP_AUTHOR);
DEBUG_MSG_P(PSTR("[INIT] %s\n\n"), (char *) APP_WEBSITE);
DEBUG_MSG_P(PSTR("[INIT] CPU chip ID: 0x%06X\n"), ESP.getChipId());
DEBUG_MSG_P(PSTR("[INIT] CPU frequency: %d MHz\n"), ESP.getCpuFreqMHz());
DEBUG_MSG_P(PSTR("[INIT] SDK version: %s\n"), ESP.getSdkVersion());
DEBUG_MSG_P(PSTR("[INIT] Core version: %s\n"), getCoreVersion().c_str());
DEBUG_MSG_P(PSTR("[INIT] Core revision: %s\n"), getCoreRevision().c_str());
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
FlashMode_t mode = ESP.getFlashChipMode();
DEBUG_MSG_P(PSTR("[INIT] Flash chip ID: 0x%06X\n"), ESP.getFlashChipId());
DEBUG_MSG_P(PSTR("[INIT] Flash speed: %u Hz\n"), ESP.getFlashChipSpeed());
DEBUG_MSG_P(PSTR("[INIT] Flash mode: %s\n"), mode == FM_QIO ? "QIO" : mode == FM_QOUT ? "QOUT" : mode == FM_DIO ? "DIO" : mode == FM_DOUT ? "DOUT" : "UNKNOWN");
DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[INIT] Flash sector size: %8u bytes\n"), SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("[INIT] Flash size (CHIP): %8u bytes\n"), ESP.getFlashChipRealSize());
DEBUG_MSG_P(PSTR("[INIT] Flash size (SDK): %8u bytes / %4d sectors\n"), ESP.getFlashChipSize(), sectors(ESP.getFlashChipSize()));
DEBUG_MSG_P(PSTR("[INIT] Firmware size: %8u bytes / %4d sectors\n"), ESP.getSketchSize(), sectors(ESP.getSketchSize()));
DEBUG_MSG_P(PSTR("[INIT] OTA size: %8u bytes / %4d sectors\n"), ESP.getFreeSketchSpace(), sectors(ESP.getFreeSketchSpace()));
#if SPIFFS_SUPPORT
FSInfo fs_info;
bool fs = SPIFFS.info(fs_info);
if (fs) {
DEBUG_MSG_P(PSTR("[INIT] SPIFFS size: %8u bytes / %4d sectors\n"), fs_info.totalBytes, sectors(fs_info.totalBytes));
}
#else
DEBUG_MSG_P(PSTR("[INIT] SPIFFS size: %8u bytes / %4d sectors\n"), 0, 0);
#endif
DEBUG_MSG_P(PSTR("[INIT] EEPROM size: %8u bytes / %4d sectors\n"), settingsMaxSize(), sectors(settingsMaxSize()));
DEBUG_MSG_P(PSTR("[INIT] Empty space: %8u bytes / 4 sectors\n"), 4 * SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
#if SPIFFS_SUPPORT
if (fs) {
DEBUG_MSG_P(PSTR("[INIT] SPIFFS total size: %8u bytes\n"), fs_info.totalBytes);
DEBUG_MSG_P(PSTR("[INIT] used size: %8u bytes\n"), fs_info.usedBytes);
DEBUG_MSG_P(PSTR("[INIT] block size: %8u bytes\n"), fs_info.blockSize);
DEBUG_MSG_P(PSTR("[INIT] page size: %8u bytes\n"), fs_info.pageSize);
DEBUG_MSG_P(PSTR("[INIT] max files: %8u\n"), fs_info.maxOpenFiles);
DEBUG_MSG_P(PSTR("[INIT] max length: %8u\n"), fs_info.maxPathLength);
} else {
DEBUG_MSG_P(PSTR("[INIT] No SPIFFS partition\n"));
}
DEBUG_MSG_P(PSTR("\n"));
#endif
void setup() {
// -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[INIT] MANUFACTURER: %s\n"), MANUFACTURER);
DEBUG_MSG_P(PSTR("[INIT] DEVICE: %s\n"), DEVICE);
DEBUG_MSG_P(PSTR("[INIT] SUPPORT:"));
#if ALEXA_SUPPORT
DEBUG_MSG_P(PSTR(" ALEXA"));
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_SERIAL"));
#endif
#if DEBUG_TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_TELNET"));
#endif
#if DEBUG_UDP_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_UDP"));
#endif
#if DOMOTICZ_SUPPORT
DEBUG_MSG_P(PSTR(" DOMOTICZ"));
#endif
#if HOMEASSISTANT_SUPPORT
DEBUG_MSG_P(PSTR(" HOMEASSISTANT"));
#endif
#if I2C_SUPPORT
DEBUG_MSG_P(PSTR(" I2C"));
#endif
#if INFLUXDB_SUPPORT
DEBUG_MSG_P(PSTR(" INFLUXDB"));
#endif
#if LLMNR_SUPPORT
DEBUG_MSG_P(PSTR(" LLMNR"));
#endif
#if MDNS_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS"));
#endif
#if NETBIOS_SUPPORT
DEBUG_MSG_P(PSTR(" NETBIOS"));
#endif
#if NOFUSS_SUPPORT
DEBUG_MSG_P(PSTR(" NOFUSS"));
#endif
#if NTP_SUPPORT
DEBUG_MSG_P(PSTR(" NTP"));
#endif
#if RF_SUPPORT
DEBUG_MSG_P(PSTR(" RF"));
#endif
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR(" SENSOR"));
#endif
#if SPIFFS_SUPPORT
DEBUG_MSG_P(PSTR(" SPIFFS"));
#endif
#if SSDP_SUPPORT
DEBUG_MSG_P(PSTR(" SSDP"));
#endif
#if TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" TELNET"));
#endif
#if TERMINAL_SUPPORT
DEBUG_MSG_P(PSTR(" TERMINAL"));
#endif
#if WEB_SUPPORT
DEBUG_MSG_P(PSTR(" WEB"));
#endif
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n[INIT] SENSORS:"));
#if ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" ANALOG"));
#endif
#if BMX280_SUPPORT
DEBUG_MSG_P(PSTR(" BMX280"));
#endif
#if DALLAS_SUPPORT
DEBUG_MSG_P(PSTR(" DALLAS"));
#endif
#if DHT_SUPPORT
DEBUG_MSG_P(PSTR(" DHTXX"));
#endif
#if DIGITAL_SUPPORT
DEBUG_MSG_P(PSTR(" DIGITAL"));
#endif
#if ECH1560_SUPPORT
DEBUG_MSG_P(PSTR(" ECH1560"));
#endif
#if EMON_ADC121_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ADC121"));
#endif
#if EMON_ADS1X15_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ADX1X15"));
#endif
#if EMON_ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ANALOG"));
#endif
#if EVENTS_SUPPORT
DEBUG_MSG_P(PSTR(" EVENTS"));
#endif
#if HLW8012_SUPPORT
DEBUG_MSG_P(PSTR(" HLW8012"));
#endif
#if MHZ19_SUPPORT
DEBUG_MSG_P(PSTR(" MHZ19"));
#endif
#if PMSX003_SUPPORT
DEBUG_MSG_P(PSTR(" PMSX003"));
#endif
#if SHT3X_I2C_SUPPORT
DEBUG_MSG_P(PSTR(" SHT3X_I2C"));
#endif
#if SI7021_SUPPORT
DEBUG_MSG_P(PSTR(" SI7021"));
#endif
#if V9261F_SUPPORT
DEBUG_MSG_P(PSTR(" V9261F"));
#endif
#endif // SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n\n"));
// Basic modules, will always run
// -------------------------------------------------------------------------
unsigned char reason = resetReason();
if (reason > 0) {
char buffer[32];
strcpy_P(buffer, custom_reset_string[reason-1]);
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), buffer);
} else {
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), (char *) ESP.getResetReason().c_str());
}
DEBUG_MSG_P(PSTR("[INIT] Free heap: %u bytes\n"), getFreeHeap());
#if ADC_VCC_ENABLED
DEBUG_MSG_P(PSTR("[INIT] Power: %d mV\n"), ESP.getVcc());
// Serial debug
#if DEBUG_SUPPORT
debugSetup();
#endif
DEBUG_MSG_P(PSTR("[INIT] Power saving delay value: %lu ms\n"), _loopDelay);
DEBUG_MSG_P(PSTR("\n"));
}
void setup() {
// Init EEPROM, Serial and SPIFFS
hardwareSetup();
// Question system stability
#if SYSTEM_CHECK_ENABLED
systemCheck(false);
#endif
// Init EEPROM
eepromSetup();
// Init Serial, SPIFFS and system check
systemSetup();
// Init persistance and terminal features
settingsSetup();
// Hostname & board name initialization
if (getSetting("hostname").length() == 0) {
setSetting("hostname", getIdentifier());
setDefaultHostname();
}
// Cache loop delay value to speed things (recommended max 250ms)
_loopDelay = atol(getSetting("loopDelay", LOOP_DELAY_TIME).c_str());
setBoardName();
// Show welcome message and system configuration
welcome();
info();
// Basic modules, will always run
wifiSetup();
otaSetup();
#if TELNET_SUPPORT
telnetSetup();
#endif
// Do not run the next services if system is flagged stable
// -------------------------------------------------------------------------
// Check if system is stable
// -------------------------------------------------------------------------
#if SYSTEM_CHECK_ENABLED
if (!systemCheck()) return;
#endif
// -------------------------------------------------------------------------
// Next modules will be only loaded if system is flagged as stable
// -------------------------------------------------------------------------
// Init webserver required before any module that uses API
#if WEB_SUPPORT
webSetup();
wsSetup();
apiSetup();
#if DEBUG_WEB_SUPPORT
debugWebSetup();
#endif
#endif
// lightSetup must be called before relaySetup
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
lightSetup();
#endif
relaySetup();
buttonSetup();
ledSetup();
#if MQTT_SUPPORT
mqttSetup();
#endif
#if MDNS_SUPPORT
mdnsSetup();
#if MDNS_SERVER_SUPPORT
mdnsServerSetup();
#endif
#if MDNS_CLIENT_SUPPORT
mdnsClientSetup();
#endif
#if LLMNR_SUPPORT
llmnrSetup();
@ -350,12 +124,7 @@ void setup() {
#endif
#if I2C_SUPPORT
i2cSetup();
#if I2C_CLEAR_BUS
i2cClearBus();
#endif
i2cScan();
#endif
#ifdef ITEAD_SONOFF_RFBRIDGE
rfbSetup();
#endif
@ -368,6 +137,9 @@ void setup() {
#if INFLUXDB_SUPPORT
idbSetup();
#endif
#if THINGSPEAK_SUPPORT
tspkSetup();
#endif
#if RF_SUPPORT
rfSetup();
#endif
@ -383,6 +155,18 @@ void setup() {
#if SENSOR_SUPPORT
sensorSetup();
#endif
#if SCHEDULER_SUPPORT
schSetup();
#endif
#if UART_MQTT_SUPPORT
uartmqttSetup();
#endif
// 3rd party code hook
#if USE_EXTRA
extraSetup();
#endif
// Prepare configuration for version 2.0
migrate();
@ -393,53 +177,9 @@ void setup() {
void loop() {
hardwareLoop();
settingsLoop();
wifiLoop();
otaLoop();
#if SYSTEM_CHECK_ENABLED
systemCheckLoop();
// Do not run the next services if system is flagged stable
if (!systemCheck()) return;
#endif
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
lightLoop();
#endif
relayLoop();
buttonLoop();
ledLoop();
#if MQTT_SUPPORT
mqttLoop();
#endif
#ifdef ITEAD_SONOFF_RFBRIDGE
rfbLoop();
#endif
#if SSDP_SUPPORT
ssdpLoop();
#endif
#if NTP_SUPPORT
ntpLoop();
#endif
#if ALEXA_SUPPORT
alexaLoop();
#endif
#if NOFUSS_SUPPORT
nofussLoop();
#endif
#if RF_SUPPORT
rfLoop();
#endif
#if IR_SUPPORT
irLoop();
#endif
#if SENSOR_SUPPORT
sensorLoop();
#endif
// Power saving delay
delay(_loopDelay);
// Call registered loop callbacks
for (unsigned char i = 0; i < _loop_callbacks.size(); i++) {
(_loop_callbacks[i])();
}
}

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

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


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

@ -2,14 +2,14 @@
GPIO MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
unsigned int _gpio_locked = 0;
bool gpioValid(unsigned char gpio) {
if (0 <= gpio && gpio <= 5) return true;
if (gpio <= 5) return true;
if (12 <= gpio && gpio <= 15) return true;
return false;
}
@ -19,11 +19,11 @@ bool gpioGetLock(unsigned char gpio) {
unsigned int mask = 1 << gpio;
if ((_gpio_locked & mask) == 0) {
_gpio_locked |= mask;
DEBUG_MSG_P(PSTR("[GPIO] GPIO%d locked\n"), gpio);
DEBUG_MSG_P(PSTR("[GPIO] GPIO%u locked\n"), gpio);
return true;
}
}
DEBUG_MSG_P(PSTR("[GPIO] Failed getting lock for GPIO%d\n"), gpio);
DEBUG_MSG_P(PSTR("[GPIO] Failed getting lock for GPIO%u\n"), gpio);
return false;
}
@ -31,9 +31,9 @@ bool gpioReleaseLock(unsigned char gpio) {
if (gpioValid(gpio)) {
unsigned int mask = 1 << gpio;
_gpio_locked &= ~mask;
DEBUG_MSG_P(PSTR("[GPIO] GPIO%d lock released\n"), gpio);
DEBUG_MSG_P(PSTR("[GPIO] GPIO%u lock released\n"), gpio);
return true;
}
DEBUG_MSG_P(PSTR("[GPIO] Failed releasing lock for GPIO%d\n"), gpio);
DEBUG_MSG_P(PSTR("[GPIO] Failed releasing lock for GPIO%u\n"), gpio);
return false;
}

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

@ -0,0 +1,306 @@
/*
HOME ASSISTANT MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if HOMEASSISTANT_SUPPORT
#include <ArduinoJson.h>
bool _haEnabled = false;
bool _haSendFlag = false;
// -----------------------------------------------------------------------------
// SENSORS
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
void _haSendMagnitude(unsigned char i, JsonObject& config) {
unsigned char type = magnitudeType(i);
config["name"] = getSetting("hostname") + String(" ") + magnitudeTopic(type);
config.set("platform", "mqtt");
config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
config["unit_of_measurement"] = magnitudeUnits(type);
}
void _haSendMagnitudes() {
for (unsigned char i=0; i<magnitudeCount(); i++) {
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/sensor/" +
getSetting("hostname") + "_" + String(i) +
"/config";
String output;
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
config.printTo(output);
jsonBuffer.clear();
}
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
}
#endif // SENSOR_SUPPORT
// -----------------------------------------------------------------------------
// SWITCHES & LIGHTS
// -----------------------------------------------------------------------------
void _haSendSwitch(unsigned char i, JsonObject& config) {
String name = getSetting("hostname");
if (relayCount() > 1) {
name += String(" #") + String(i);
}
config.set("name", name);
config.set("platform", "mqtt");
if (relayCount()) {
config["state_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, false);
config["command_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, true);
config["payload_on"] = String("1");
config["payload_off"] = String("0");
config["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false);
config["payload_available"] = String("1");
config["payload_not_available"] = String("0");
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (i == 0) {
if (lightHasColor()) {
config["brightness_state_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, false);
config["brightness_command_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, true);
config["rgb_state_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, false);
config["rgb_command_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, true);
config["color_temp_command_topic"] = mqttTopic(MQTT_TOPIC_MIRED, true);
}
if (lightChannels() > 3) {
config["white_value_state_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, false);
config["white_value_command_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, true);
}
}
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
}
void _haSendSwitches() {
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light");
#else
String type = String("switch");
#endif
for (unsigned char i=0; i<relayCount(); i++) {
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/" + type +
"/" + getSetting("hostname") + "_" + String(i) +
"/config";
String output;
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
config.printTo(output);
jsonBuffer.clear();
}
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
}
// -----------------------------------------------------------------------------
String _haGetConfig() {
String output;
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light");
#else
String type = String("switch");
#endif
for (unsigned char i=0; i<relayCount(); i++) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
output += type + ":\n";
bool first = true;
for (auto kv : config) {
if (first) {
output += " - ";
first = false;
} else {
output += " ";
}
output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
}
output += "\n";
jsonBuffer.clear();
}
#if SENSOR_SUPPORT
for (unsigned char i=0; i<magnitudeCount(); i++) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
output += "sensor:\n";
bool first = true;
for (auto kv : config) {
if (first) {
output += " - ";
first = false;
} else {
output += " ";
}
output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
}
output += "\n";
jsonBuffer.clear();
}
#endif
return output;
}
void _haSend() {
// Pending message to send?
if (!_haSendFlag) return;
// Are we connected?
if (!mqttConnected()) return;
DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n"));
// Send messages
_haSendSwitches();
#if SENSOR_SUPPORT
_haSendMagnitudes();
#endif
_haSendFlag = false;
}
void _haConfigure() {
bool enabled = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
_haSendFlag = (enabled != _haEnabled);
_haEnabled = enabled;
_haSend();
}
#if WEB_SUPPORT
bool _haWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "ha", 2) == 0);
}
void _haWebSocketOnSend(JsonObject& root) {
root["haVisible"] = 1;
root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX);
root["haEnabled"] = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
}
void _haWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "haconfig") == 0) {
String output = _haGetConfig();
output.replace(" ", "&nbsp;");
output.replace("\n", "<br />");
output = String("{\"haConfig\": \"") + output + String("\"}");
wsSend(client_id, output.c_str());
}
}
#endif
#if TERMINAL_SUPPORT
void _haInitCommands() {
settingsRegisterCommand(F("HA.CONFIG"), [](Embedis* e) {
DEBUG_MSG(_haGetConfig().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) {
setSetting("haEnabled", "1");
_haConfigure();
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
#endif
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
setSetting("haEnabled", "0");
_haConfigure();
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
#endif
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
#endif
// -----------------------------------------------------------------------------
void haSetup() {
_haConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_haWebSocketOnSend);
wsOnAfterParseRegister(_haConfigure);
wsOnActionRegister(_haWebSocketOnAction);
wsOnReceiveRegister(_haWebSocketOnReceive);
#endif
// On MQTT connect check if we have something to send
mqttRegister([](unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) _haSend();
});
#if TERMINAL_SUPPORT
_haInitCommands();
#endif
}
#endif // HOMEASSISTANT_SUPPORT

+ 0
- 101
code/espurna/homeassitant.ino View File

@ -1,101 +0,0 @@
/*
HOME ASSISTANT MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if HOMEASSISTANT_SUPPORT
#include <ArduinoJson.h>
bool _haEnabled = false;
// -----------------------------------------------------------------------------
void _haWebSocketOnSend(JsonObject& root) {
root["haVisible"] = 1;
root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX);
}
void _haConfigure() {
bool enabled = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
if (enabled != _haEnabled) haSend(enabled);
_haEnabled = enabled;
}
// -----------------------------------------------------------------------------
void haSend(bool add) {
DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n"));
String output;
if (add) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
root["name"] = getSetting("hostname");
root["platform"] = "mqtt";
if (relayCount()) {
root["state_topic"] = getTopic(MQTT_TOPIC_RELAY, 0, false);
root["command_topic"] = getTopic(MQTT_TOPIC_RELAY, 0, true);
root["payload_on"] = String("1");
root["payload_off"] = String("0");
root["availability_topic"] = getTopic(MQTT_TOPIC_STATUS, false);
root["payload_available"] = String("1");
root["payload_not_available"] = String("0");
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (lightHasColor()) {
root["brightness_state_topic"] = getTopic(MQTT_TOPIC_BRIGHTNESS, false);
root["brightness_command_topic"] = getTopic(MQTT_TOPIC_BRIGHTNESS, true);
root["rgb_state_topic"] = getTopic(MQTT_TOPIC_COLOR_RGB, false);
root["rgb_command_topic"] = getTopic(MQTT_TOPIC_COLOR_RGB, true);
root["color_temp_command_topic"] = getTopic(MQTT_TOPIC_MIRED, true);
}
if (lightChannels() > 3) {
root["white_value_state_topic"] = getTopic(MQTT_TOPIC_CHANNEL, 3, false);
root["white_value_command_topic"] = getTopic(MQTT_TOPIC_CHANNEL, 3, true);
}
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
root.printTo(output);
}
#if LIGHT_PROVIDER == LIGHT_PROVIDER_NONE
String component = String("switch");
#else
String component = String("light");
#endif
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/" + component +
"/" + getSetting("hostname") +
"/config";
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
void haSetup() {
_haConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_haWebSocketOnSend);
wsOnAfterParseRegister(_haConfigure);
#endif
mqttRegister([](unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) haSend(_haEnabled);
});
}
#endif // HOMEASSISTANT_SUPPORT

+ 182
- 4
code/espurna/i2c.ino View File

@ -2,7 +2,7 @@
I2C MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -32,7 +32,7 @@ int _i2cClearbus(int sda, int scl) {
pinMode(sda, INPUT_PULLUP);
pinMode(scl, INPUT_PULLUP);
delay(2500);
nice_delay(2500);
// Wait 2.5 secs. This is strictly only necessary on the first power
// up of the DS3231 module to allow it to initialize properly,
// but is also assists in reliable programming of FioV3 boards as it gives the
@ -68,7 +68,7 @@ int _i2cClearbus(int sda, int scl) {
int counter = 20;
while (scl_low && (counter > 0)) {
counter--;
delay(100);
nice_delay(100);
scl_low = (digitalRead(scl) == LOW);
}
@ -104,6 +104,176 @@ int _i2cClearbus(int sda, int scl) {
}
// ---------------------------------------------------------------------
// I2C API
// ---------------------------------------------------------------------
#if I2C_USE_BRZO
void i2c_wakeup(uint8_t address) {
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_end_transaction();
}
uint8_t i2c_write_uint8(uint8_t address, uint8_t value) {
uint8_t buffer[1] = {value};
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_write_uint8(buffer, 1, false);
return brzo_i2c_end_transaction();
}
uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) {
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_write_uint8(buffer, len, false);
return brzo_i2c_end_transaction();
}
uint8_t i2c_read_uint8(uint8_t address) {
uint8_t buffer[1] = {reg};
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_read(buffer, 1, false);
brzo_i2c_end_transaction();
return buffer[0];
};
uint8_t i2c_read_uint8(uint8_t address, uint8_t reg) {
uint8_t buffer[1] = {reg};
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_write_uint8(buffer, 1, false);
brzo_i2c_read(buffer, 1, false);
brzo_i2c_end_transaction();
return buffer[0];
};
uint16_t i2c_read_uint16(uint8_t address) {
uint8_t buffer[2] = {reg, 0};
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_read(buffer, 2, false);
brzo_i2c_end_transaction();
return (buffer[0] * 256) | buffer[1];
};
uint16_t i2c_read_uint16(uint8_t address, uint8_t reg) {
uint8_t buffer[2] = {reg, 0};
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_write_uint8(buffer, 1, false);
brzo_i2c_read(buffer, 2, false);
brzo_i2c_end_transaction();
return (buffer[0] * 256) | buffer[1];
};
void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len) {
brzo_i2c_start_transaction(address, _i2c_scl_frequency);
brzo_i2c_read(buffer, len, false);
brzo_i2c_end_transaction();
}
#else // not I2C_USE_BRZO
void i2c_wakeup(uint8_t address) {
Wire.beginTransmission((uint8_t) address);
Wire.endTransmission();
}
uint8_t i2c_write_uint8(uint8_t address, uint8_t value) {
Wire.beginTransmission((uint8_t) address);
Wire.write((uint8_t) value);
return Wire.endTransmission();
}
uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) {
Wire.beginTransmission((uint8_t) address);
Wire.write(buffer, len);
return Wire.endTransmission();
}
uint8_t i2c_read_uint8(uint8_t address) {
uint8_t value;
Wire.beginTransmission((uint8_t) address);
Wire.requestFrom((uint8_t) address, (uint8_t) 1);
value = Wire.read();
Wire.endTransmission();
return value;
};
uint8_t i2c_read_uint8(uint8_t address, uint8_t reg) {
uint8_t value;
Wire.beginTransmission((uint8_t) address);
Wire.write((uint8_t) reg);
Wire.endTransmission();
Wire.requestFrom((uint8_t) address, (uint8_t) 1);
value = Wire.read();
Wire.endTransmission();
return value;
};
uint16_t i2c_read_uint16(uint8_t address) {
uint16_t value;
Wire.beginTransmission((uint8_t) address);
Wire.requestFrom((uint8_t) address, (uint8_t) 2);
value = (Wire.read() * 256) | Wire.read();
Wire.endTransmission();
return value;
};
uint16_t i2c_read_uint16(uint8_t address, uint8_t reg) {
uint16_t value;
Wire.beginTransmission((uint8_t) address);
Wire.write((uint8_t) reg);
Wire.endTransmission();
Wire.requestFrom((uint8_t) address, (uint8_t) 2);
value = (Wire.read() * 256) | Wire.read();
Wire.endTransmission();
return value;
};
void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len) {
Wire.beginTransmission((uint8_t) address);
Wire.requestFrom(address, (uint8_t) len);
for (int i=0; i<len; i++) buffer[i] = Wire.read();
Wire.endTransmission();
}
#endif // I2C_USE_BRZO
uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value) {
uint8_t buffer[2] = {reg, value};
return i2c_write_buffer(address, buffer, 2);
}
uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value1, uint8_t value2) {
uint8_t buffer[3] = {reg, value1, value2};
return i2c_write_buffer(address, buffer, 3);
}
uint8_t i2c_write_uint16(uint8_t address, uint8_t reg, uint16_t value) {
uint8_t buffer[3];
buffer[0] = reg;
buffer[1] = (value >> 8) & 0xFF;
buffer[2] = (value >> 0) & 0xFF;
return i2c_write_buffer(address, buffer, 3);
}
uint8_t i2c_write_uint16(uint8_t address, uint16_t value) {
uint8_t buffer[2];
buffer[0] = (value >> 8) & 0xFF;
buffer[1] = (value >> 0) & 0xFF;
return i2c_write_buffer(address, buffer, 2);
}
uint16_t i2c_read_uint16_le(uint8_t address, uint8_t reg) {
uint16_t temp = i2c_read_uint16(address, reg);
return (temp / 256) | (temp * 256);
};
int16_t i2c_read_int16(uint8_t address, uint8_t reg) {
return (int16_t) i2c_read_uint16(address, reg);
};
int16_t i2c_read_int16_le(uint8_t address, uint8_t reg) {
return (int16_t) i2c_read_uint16_le(address, reg);
};
// -----------------------------------------------------------------------------
// Utils
// -----------------------------------------------------------------------------
@ -194,7 +364,15 @@ void i2cSetup() {
Wire.begin(sda, scl);
#endif
DEBUG_MSG_P(PSTR("[I2C] Using GPIO%d for SDA and GPIO%d for SCL\n"), sda, scl);
DEBUG_MSG_P(PSTR("[I2C] Using GPIO%u for SDA and GPIO%u for SCL\n"), sda, scl);
#if I2C_CLEAR_BUS
i2cClearBus();
#endif
#if I2C_PERFORM_SCAN
i2cScan();
#endif
}


+ 47
- 33
code/espurna/influxdb.ino View File

@ -1,8 +1,8 @@
/*
I2C MODULE
INFLUXDB MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -16,6 +16,10 @@ SyncClient _idb_client;
// -----------------------------------------------------------------------------
bool _idbWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "idb", 3) == 0);
}
void _idbWebSocketOnSend(JsonObject& root) {
root["idbVisible"] = 1;
root["idbEnabled"] = getSetting("idbEnabled", INFLUXDB_ENABLED).toInt() == 1;
@ -36,50 +40,59 @@ void _idbConfigure() {
// -----------------------------------------------------------------------------
template<typename T> bool idbSend(const char * topic, T payload) {
bool idbSend(const char * topic, const char * payload) {
if (!_idb_enabled) return true;
if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return true;
char host[64];
getSetting("idbHost", INFLUXDB_HOST).toCharArray(host, sizeof(host));
int port = getSetting("idbPort", INFLUXDB_PORT).toInt();
String h = getSetting("idbHost", INFLUXDB_HOST);
#if MDNS_CLIENT_SUPPORT
h = mdnsResolve(h);
#endif
char * host = strdup(h.c_str());
unsigned int port = getSetting("idbPort", INFLUXDB_PORT).toInt();
DEBUG_MSG("[INFLUXDB] Sending to %s:%u\n", host, port);
bool success = false;
DEBUG_MSG("[INFLUXDB] Sending to %s:%d\n", host, port);
_idb_client.setTimeout(2);
if (!_idb_client.connect(host, port)) {
if (_idb_client.connect((const char *) host, port)) {
char data[128];
snprintf(data, sizeof(data), "%s,device=%s value=%s", topic, getSetting("hostname").c_str(), String(payload).c_str());
DEBUG_MSG("[INFLUXDB] Data: %s\n", data);
char request[256];
snprintf(request, sizeof(request), "POST /write?db=%s&u=%s&p=%s HTTP/1.1\r\nHost: %s:%u\r\nContent-Length: %d\r\n\r\n%s",
getSetting("idbDatabase", INFLUXDB_DATABASE).c_str(),
getSetting("idbUsername", INFLUXDB_USERNAME).c_str(), getSetting("idbPassword", INFLUXDB_PASSWORD).c_str(),
host, port, strlen(data), data);
if (_idb_client.printf(request) > 0) {
while (_idb_client.connected() && _idb_client.available() == 0) delay(1);
while (_idb_client.available()) _idb_client.read();
if (_idb_client.connected()) _idb_client.stop();
success = true;
} else {
DEBUG_MSG("[INFLUXDB] Sent failed\n");
}
_idb_client.stop();
while (_idb_client.connected()) yield();
} else {
DEBUG_MSG("[INFLUXDB] Connection failed\n");
return false;
}
char data[128];
snprintf(data, sizeof(data), "%s,device=%s value=%s", topic, getSetting("hostname").c_str(), String(payload).c_str());
DEBUG_MSG("[INFLUXDB] Data: %s\n", data);
char request[256];
snprintf(request, sizeof(request), "POST /write?db=%s&u=%s&p=%s HTTP/1.1\r\nHost: %s:%d\r\nContent-Length: %d\r\n\r\n%s",
getSetting("idbDatabase", INFLUXDB_DATABASE).c_str(),
getSetting("idbUsername", INFLUXDB_USERNAME).c_str(), getSetting("idbPassword", INFLUXDB_PASSWORD).c_str(),
host, port, strlen(data), data);
if (_idb_client.printf(request) > 0) {
while (_idb_client.connected() && _idb_client.available() == 0) delay(1);
while (_idb_client.available()) _idb_client.read();
if (_idb_client.connected()) _idb_client.stop();
return true;
}
_idb_client.stop();
DEBUG_MSG("[INFLUXDB] Sent failed\n");
while (_idb_client.connected()) delay(0);
return false;
free(host);
return success;
}
template<typename T> bool idbSend(const char * topic, unsigned char id, T payload) {
bool idbSend(const char * topic, unsigned char id, const char * payload) {
char measurement[64];
snprintf_P(measurement, sizeof(measurement), PSTR("%s,id=%d"), topic, id);
return idbSend(topic, payload);
snprintf(measurement, sizeof(measurement), "%s,id=%d", topic, id);
return idbSend(measurement, payload);
}
bool idbEnabled() {
@ -91,6 +104,7 @@ void idbSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_idbWebSocketOnSend);
wsOnAfterParseRegister(_idbConfigure);
wsOnReceiveRegister(_idbWebSocketOnReceive);
#endif
}


+ 19
- 3
code/espurna/ir.ino View File

@ -2,8 +2,8 @@
IR MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017 by François Déchery
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2018 by François Déchery
*/
@ -14,6 +14,7 @@ Copyright (C) 2017 by François Déchery
IRrecv * _ir_recv;
decode_results _ir_results;
unsigned long _ir_last_toggle = 0;
// -----------------------------------------------------------------------------
// PRIVATE
@ -43,11 +44,21 @@ void _irProcessCode(unsigned long code) {
relayStatus(0, button_value);
}
if (button_mode == IR_BUTTON_MODE_TOGGLE) {
if (millis() - _ir_last_toggle > 250){
relayToggle(button_value);
_ir_last_toggle = millis();
} else {
DEBUG_MSG_P(PSTR("[IR] Ignoring repeated code\n"));
}
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (button_mode == IR_BUTTON_MODE_BRIGHTER) {
lightBrightnessStep(button_value ? 1 : -1);
delay(150); //debounce
nice_delay(150); //debounce
}
if (button_mode == IR_BUTTON_MODE_RGB) {
@ -92,8 +103,13 @@ void _irProcessCode(unsigned long code) {
// -----------------------------------------------------------------------------
void irSetup() {
_ir_recv = new IRrecv(IR_PIN);
_ir_recv->enableIRIn();
// Register loop
espurnaRegisterLoop(irLoop);
}
void irLoop() {


+ 71
- 28
code/espurna/led.ino View File

@ -2,7 +2,7 @@
LED MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -30,7 +30,6 @@ bool _ledStatus(unsigned char id) {
bool _ledStatus(unsigned char id, bool status) {
if (id >=_ledCount()) return false;
bool s = _leds[id].reverse ? !status : status;
digitalWrite(_leds[id].pin, _leds[id].reverse ? !status : status);
return status;
}
@ -59,18 +58,22 @@ void _ledBlink(unsigned char id, unsigned long delayOff, unsigned long delayOn)
}
#if WEB_SUPPORT
bool _ledWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "led", 3) == 0);
}
void _ledWebSocketOnSend(JsonObject& root) {
if (_ledCount() == 0) return;
root["ledVisible"] = 1;
root["ledMode0"] = _ledMode(0);
}
#endif
#if MQTT_SUPPORT
void _ledMQTTCallback(unsigned int type, const char * topic, const char * payload) {
static bool isFirstMessage = true;
if (type == MQTT_CONNECT_EVENT) {
char buffer[strlen(MQTT_TOPIC_LED) + 3];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_LED);
@ -80,7 +83,7 @@ void _ledMQTTCallback(unsigned int type, const char * topic, const char * payloa
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttSubtopic((char *) topic);
String t = mqttMagnitude((char *) topic);
if (!t.startsWith(MQTT_TOPIC_LED)) return;
// Get led ID
@ -127,28 +130,28 @@ void ledUpdate(bool value) {
void ledSetup() {
#ifdef LED1_PIN
#if LED1_PIN != GPIO_NONE
_leds.push_back((led_t) { LED1_PIN, LED1_PIN_INVERSE, LED1_MODE, LED1_RELAY });
#endif
#ifdef LED2_PIN
#if LED2_PIN != GPIO_NONE
_leds.push_back((led_t) { LED2_PIN, LED2_PIN_INVERSE, LED2_MODE, LED2_RELAY });
#endif
#ifdef LED3_PIN
#if LED3_PIN != GPIO_NONE
_leds.push_back((led_t) { LED3_PIN, LED3_PIN_INVERSE, LED3_MODE, LED3_RELAY });
#endif
#ifdef LED4_PIN
#if LED4_PIN != GPIO_NONE
_leds.push_back((led_t) { LED4_PIN, LED4_PIN_INVERSE, LED4_MODE, LED4_RELAY });
#endif
#ifdef LED5_PIN
#if LED5_PIN != GPIO_NONE
_leds.push_back((led_t) { LED5_PIN, LED5_PIN_INVERSE, LED5_MODE, LED5_RELAY });
#endif
#ifdef LED6_PIN
#if LED6_PIN != GPIO_NONE
_leds.push_back((led_t) { LED6_PIN, LED6_PIN_INVERSE, LED6_MODE, LED6_RELAY });
#endif
#ifdef LED7_PIN
#if LED7_PIN != GPIO_NONE
_leds.push_back((led_t) { LED7_PIN, LED7_PIN_INVERSE, LED7_MODE, LED7_RELAY });
#endif
#ifdef LED8_PIN
#if LED8_PIN != GPIO_NONE
_leds.push_back((led_t) { LED8_PIN, LED8_PIN_INVERSE, LED8_MODE, LED8_RELAY });
#endif
@ -166,23 +169,52 @@ void ledSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_ledWebSocketOnSend);
wsOnAfterParseRegister(_ledConfigure);
wsOnReceiveRegister(_ledWebSocketOnReceive);
#endif
DEBUG_MSG_P(PSTR("[LED] Number of leds: %d\n"), _leds.size());
// Register loop
espurnaRegisterLoop(ledLoop);
}
void ledLoop() {
uint8_t wifi_state = wifiState();
for (unsigned char i=0; i<_leds.size(); i++) {
if (_ledMode(i) == LED_MODE_WIFI) {
if (wifiConnected()) {
if (WiFi.getMode() == WIFI_AP) {
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
} else if (wifi_state & WIFI_STATE_STA) {
_ledBlink(i, 4900, 100);
} else if (wifi_state & WIFI_STATE_AP) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 500, 500);
}
}
if (_ledMode(i) == LED_MODE_FINDME_WIFI) {
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(_leds[i].relay-1)) {
_ledBlink(i, 4900, 100);
} else {
_ledBlink(i, 100, 4900);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(_leds[i].relay-1)) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 4900, 100);
_ledBlink(i, 100, 900);
}
} else {
_ledBlink(i, 500, 500);
@ -190,21 +222,21 @@ void ledLoop() {
}
if (_ledMode(i) == LED_MODE_MIXED) {
if (_ledMode(i) == LED_MODE_RELAY_WIFI) {
if (wifiConnected()) {
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(_leds[i].relay-1)) {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 4900, 100);
}
_ledBlink(i, 100, 4900);
} else {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 100, 900);
} else {
_ledBlink(i, 100, 4900);
}
_ledBlink(i, 4900, 100);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(_leds[i].relay-1)) {
_ledBlink(i, 100, 900);
} else {
_ledBlink(i, 900, 100);
}
} else {
_ledBlink(i, 500, 500);
@ -234,6 +266,17 @@ void ledLoop() {
_ledStatus(i, status);
}
if (_ledMode(i) == LED_MODE_RELAY) {
bool status = false;
for (unsigned char k=0; k<relayCount(); k++) {
if (relayStatus(k)) {
status = true;
break;
}
}
_ledStatus(i, status);
}
if (_ledMode(i) == LED_MODE_ON) {
_ledStatus(i, true);
}


+ 2
- 2
code/espurna/libs/EmbedisWrap.h View File

@ -12,11 +12,11 @@ class EmbedisWrap : public Embedis {
EmbedisWrap(Stream& stream, size_t buflen = 128, size_t argvlen = 8): Embedis(stream, buflen, argvlen) {}
unsigned char getCommandsCount() {
unsigned char getCommandCount() {
return commands.size();
}
String getCommandName(unsigned char i) {
String getCommandName(unsigned int i) {
if (i < commands.size()) return commands[i].name;
return String();
}


+ 0
- 376
code/espurna/libs/SSDPDevice.cpp View File

@ -1,376 +0,0 @@
#if SSDP_SUPPORT
#include "SSDPDevice.h"
#include "lwip/igmp.h"
SSDPDeviceClass::SSDPDeviceClass() :
m_server(0),
m_port(80),
m_ttl(SSDP_MULTICAST_TTL)
{
m_uuid[0] = '\0';
m_modelNumber[0] = '\0';
sprintf(m_deviceType, "urn:schemas-upnp-org:device:Basic:1");
m_friendlyName[0] = '\0';
m_presentationURL[0] = '\0';
m_serialNumber[0] = '\0';
m_modelName[0] = '\0';
m_modelURL[0] = '\0';
m_manufacturer[0] = '\0';
m_manufacturerURL[0] = '\0';
sprintf(m_schemaURL, "ssdp/schema.xml");
uint32_t chipId = ESP.getChipId();
sprintf(m_uuid, "38323636-4558-4dda-9188-cda0e6%02x%02x%02x",
(uint16_t)((chipId >> 16) & 0xff),
(uint16_t)((chipId >> 8) & 0xff),
(uint16_t)chipId & 0xff);
for (int i = 0; i < SSDP_QUEUE_SIZE; i++) {
m_queue[i].time = 0;
}
}
void SSDPDeviceClass::update() {
postNotifyUpdate();
}
bool SSDPDeviceClass::readLine(String &value) {
char buffer[65];
int bufferPos = 0;
while (1) {
int c = m_server->read();
if (c < 0) {
buffer[bufferPos] = '\0';
break;
}
if (c == '\r' && m_server->peek() == '\n') {
m_server->read();
buffer[bufferPos] = '\0';
break;
}
if (bufferPos < 64) {
buffer[bufferPos++] = c;
}
}
value = String(buffer);
return bufferPos > 0;
}
bool SSDPDeviceClass::readKeyValue(String &key, String &value) {
char buffer[65];
int bufferPos = 0;
while (1) {
int c = m_server->read();
if (c < 0) {
if (bufferPos == 0) return false;
buffer[bufferPos] = '\0';
break;
}
if (c == ':') {
buffer[bufferPos] = '\0';
while (m_server->peek() == ' ') m_server->read();
break;
}
else if (c == '\r' && m_server->peek() == '\n') {
m_server->read();
if (bufferPos == 0) return false;
buffer[bufferPos] = '\0';
key = String();
value = String(buffer);
return true;
}
if (bufferPos < 64) {
buffer[bufferPos++] = c;
}
}
key = String(buffer);
readLine(value);
return true;
}
void SSDPDeviceClass::postNotifyALive() {
unsigned long time = millis();
post(NOTIFY_ALIVE_INIT, ROOT_FOR_ALL, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 10);
post(NOTIFY_ALIVE_INIT, ROOT_BY_UUID, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 55);
post(NOTIFY_ALIVE_INIT, ROOT_BY_TYPE, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 80);
post(NOTIFY_ALIVE_INIT, ROOT_FOR_ALL, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 210);
post(NOTIFY_ALIVE_INIT, ROOT_BY_UUID, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 255);
post(NOTIFY_ALIVE_INIT, ROOT_BY_TYPE, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 280);
post(NOTIFY_ALIVE, ROOT_FOR_ALL, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 610);
post(NOTIFY_ALIVE, ROOT_BY_UUID, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 655);
post(NOTIFY_ALIVE, ROOT_BY_TYPE, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 680);
}
void SSDPDeviceClass::postNotifyUpdate() {
unsigned long time = millis();
post(NOTIFY_UPDATE, ROOT_FOR_ALL, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 10);
post(NOTIFY_UPDATE, ROOT_BY_UUID, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 55);
post(NOTIFY_UPDATE, ROOT_BY_TYPE, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 80);
}
void SSDPDeviceClass::postResponse(long mx) {
unsigned long time = millis();
unsigned long delay = random(0, mx) * 900L; // 1000 ms - 100 ms
IPAddress address = m_server->remoteIP();
uint16_t port = m_server->remotePort();
post(RESPONSE, ROOT_FOR_ALL, address, port, time + delay / 3);
post(RESPONSE, ROOT_BY_UUID, address, port, time + delay / 3 * 2);
post(RESPONSE, ROOT_BY_TYPE, address, port, time + delay);
}
void SSDPDeviceClass::postResponse(ssdp_udn_t udn, long mx) {
post(RESPONSE, udn, m_server->remoteIP(), m_server->remotePort(), millis() + random(0, mx) * 900L); // 1000 ms - 100 ms
}
void SSDPDeviceClass::post(ssdp_message_t type, ssdp_udn_t udn, IPAddress address, uint16_t port, unsigned long time) {
for (int i = 0; i < SSDP_QUEUE_SIZE; i++) {
if (m_queue[i].time == 0) {
m_queue[i].type = type;
m_queue[i].udn = udn;
m_queue[i].address = address;
m_queue[i].port = port;
m_queue[i].time = time;
break;
}
}
}
void SSDPDeviceClass::send(ssdp_send_parameters_t *parameters) {
char buffer[1460];
unsigned int ip = WiFi.localIP();
const char *typeTemplate;
const char *uri, *usn1, *usn2, *usn3;
switch (parameters->type) {
case NOTIFY_ALIVE_INIT:
case NOTIFY_ALIVE:
typeTemplate = SSDP_NOTIFY_ALIVE_TEMPLATE;
break;
case NOTIFY_UPDATE:
typeTemplate = SSDP_NOTIFY_UPDATE_TEMPLATE;
break;
default: // RESPONSE
typeTemplate = SSDP_RESPONSE_TEMPLATE;
break;
}
String uuid = "uuid:" + String(m_uuid);
switch (parameters->udn) {
case ROOT_FOR_ALL:
uri = "upnp:rootdevice";
usn1 = uuid.c_str();
usn2 = "::";
usn3 = "upnp:rootdevice";
break;
case ROOT_BY_UUID:
uri = uuid.c_str();
usn1 = uuid.c_str();
usn2 = "";
usn3 = "";
break;
case ROOT_BY_TYPE:
uri = m_deviceType;
usn1 = uuid.c_str();
usn2 = "::";
usn3 = m_deviceType;
break;
}
int len = snprintf_P(buffer, sizeof(buffer),
SSDP_PACKET_TEMPLATE, typeTemplate,
SSDP_INTERVAL, m_modelName, m_modelNumber, usn1, usn2, usn3, parameters->type == RESPONSE ? "ST" : "NT", uri,
IP2STR(&ip), m_port, m_schemaURL
);
if (parameters->address == SSDP_MULTICAST_ADDR) {
m_server->beginPacketMulticast(parameters->address, parameters->port, m_ttl);
}
else {
m_server->beginPacket(parameters->address, parameters->port);
}
m_server->write(buffer, len);
m_server->endPacket();
parameters->time = parameters->type == NOTIFY_ALIVE ? parameters->time + SSDP_INTERVAL * 900L : 0; // 1000 ms - 100 ms
}
String SSDPDeviceClass::schema() {
char buffer[1024];
uint32_t ip = WiFi.localIP();
snprintf(buffer, sizeof(buffer), SSDP_SCHEMA_TEMPLATE,
IP2STR(&ip), m_port, m_schemaURL,
m_deviceType,
m_friendlyName,
m_presentationURL,
m_serialNumber,
m_modelName,
m_modelNumber,
m_modelURL,
m_manufacturer,
m_manufacturerURL,
m_uuid
);
return String(buffer);
}
void SSDPDeviceClass::handleClient() {
IPAddress current = WiFi.localIP();
if (m_last != current) {
m_last = current;
for (int i = 0; i < SSDP_QUEUE_SIZE; i++) {
m_queue[i].time = 0;
}
if (current != INADDR_NONE) {
if (!m_server) m_server = new WiFiUDP();
m_server->beginMulticast(current, SSDP_MULTICAST_ADDR, SSDP_PORT);
postNotifyALive();
}
else if (m_server) {
m_server->stop();
}
}
if (m_server && m_server->parsePacket()) {
String value;
if (readLine(value) && value.equalsIgnoreCase("M-SEARCH * HTTP/1.1")) {
String key, st;
bool host = false, man = false;
long mx = 0;
while (readKeyValue(key, value)) {
if (key.equalsIgnoreCase("HOST") && value.equals("239.255.255.250:1900")) {
host = true;
}
else if (key.equalsIgnoreCase("MAN") && value.equals("\"ssdp:discover\"")) {
man = true;
}
else if (key.equalsIgnoreCase("ST")) {
st = value;
}
else if (key.equalsIgnoreCase("MX")) {
mx = value.toInt();
}
}
if (host && man && mx > 0) {
if (st.equals("ssdp:all")) {
postResponse(mx);
}
else if (st.equals("upnp:rootdevice")) {
postResponse(ROOT_FOR_ALL, mx);
}
else if (st.equals("uuid:" + String(m_uuid))) {
postResponse(ROOT_BY_UUID, mx);
}
else if (st.equals(m_deviceType)) {
postResponse(ROOT_BY_TYPE, mx);
}
}
}
m_server->flush();
}
else {
unsigned long time = millis();
for (int i = 0; i < SSDP_QUEUE_SIZE; i++) {
if (m_queue[i].time > 0 && m_queue[i].time < time) {
send(&m_queue[i]);
}
}
}
}
void SSDPDeviceClass::setSchemaURL(const char *url) {
strlcpy(m_schemaURL, url, sizeof(m_schemaURL));
}
void SSDPDeviceClass::setHTTPPort(uint16_t port) {
m_port = port;
}
void SSDPDeviceClass::setDeviceType(const char *deviceType) {
strlcpy(m_deviceType, deviceType, sizeof(m_deviceType));
}
void SSDPDeviceClass::setName(const char *name) {
strlcpy(m_friendlyName, name, sizeof(m_friendlyName));
}
void SSDPDeviceClass::setURL(const char *url) {
strlcpy(m_presentationURL, url, sizeof(m_presentationURL));
}
void SSDPDeviceClass::setSerialNumber(const char *serialNumber) {
strlcpy(m_serialNumber, serialNumber, sizeof(m_serialNumber));
}
void SSDPDeviceClass::setSerialNumber(const uint32_t serialNumber) {
snprintf(m_serialNumber, sizeof(uint32_t) * 2 + 1, "%08X", serialNumber);
}
void SSDPDeviceClass::setModelName(const char *name) {
strlcpy(m_modelName, name, sizeof(m_modelName));
}
void SSDPDeviceClass::setModelNumber(const char *num) {
strlcpy(m_modelNumber, num, sizeof(m_modelNumber));
}
void SSDPDeviceClass::setModelURL(const char *url) {
strlcpy(m_modelURL, url, sizeof(m_modelURL));
}
void SSDPDeviceClass::setManufacturer(const char *name) {
strlcpy(m_manufacturer, name, sizeof(m_manufacturer));
}
void SSDPDeviceClass::setManufacturerURL(const char *url) {
strlcpy(m_manufacturerURL, url, sizeof(m_manufacturerURL));
}
void SSDPDeviceClass::setTTL(const uint8_t ttl) {
m_ttl = ttl;
}
SSDPDeviceClass SSDPDevice;
#endif

+ 0
- 198
code/espurna/libs/SSDPDevice.h View File

@ -1,198 +0,0 @@
#if SSDP_SUPPORT // SSDP_SUPPORT
#ifndef _SSDPDEVICE_h
#define _SSDPDEVICE_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#define SSDP_INTERVAL 1200
#define SSDP_PORT 1900
//#define SSDP_METHOD_SIZE 10
//#define SSDP_URI_SIZE 2
//#define SSDP_BUFFER_SIZE 64
#define SSDP_MULTICAST_TTL 2
#define SSDP_QUEUE_SIZE 21
static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250);
#define SSDP_UUID_SIZE 37
#define SSDP_SCHEMA_URL_SIZE 64
#define SSDP_DEVICE_TYPE_SIZE 64
#define SSDP_FRIENDLY_NAME_SIZE 64
#define SSDP_SERIAL_NUMBER_SIZE 32
#define SSDP_PRESENTATION_URL_SIZE 128
#define SSDP_MODEL_NAME_SIZE 64
#define SSDP_MODEL_URL_SIZE 128
#define SSDP_MODEL_VERSION_SIZE 32
#define SSDP_MANUFACTURER_SIZE 64
#define SSDP_MANUFACTURER_URL_SIZE 128
static const char* PROGMEM SSDP_RESPONSE_TEMPLATE =
"HTTP/1.1 200 OK\r\n"
"EXT:\r\n";
static const char* PROGMEM SSDP_NOTIFY_ALIVE_TEMPLATE =
"NOTIFY * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1900\r\n"
"NTS: ssdp:alive\r\n";
static const char* PROGMEM SSDP_NOTIFY_UPDATE_TEMPLATE =
"NOTIFY * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1900\r\n"
"NTS: ssdp:update\r\n";
static const char* PROGMEM SSDP_PACKET_TEMPLATE =
"%s" // _ssdp_response_template / _ssdp_notify_template
"CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL
"SERVER: UPNP/1.1 %s/%s\r\n" // m_modelName, m_modelNumber
"USN: %s%s%s\r\n" // m_uuid
"%s: %s\r\n" // "NT" or "ST", m_deviceType
"LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), m_port, m_schemaURL
"\r\n";
static const char* PROGMEM SSDP_SCHEMA_TEMPLATE =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/xml\r\n"
"Connection: close\r\n"
"Access-Control-Allow-Origin: *\r\n"
"\r\n"
"<?xml version=\"1.0\"?>"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
"<specVersion>"
"<major>1</major>"
"<minor>0</minor>"
"</specVersion>"
"<URLBase>http://%u.%u.%u.%u:%u/%s</URLBase>" // WiFi.localIP(), _port
"<device>"
"<deviceType>%s</deviceType>"
"<friendlyName>%s</friendlyName>"
"<presentationURL>%s</presentationURL>"
"<serialNumber>%s</serialNumber>"
"<modelName>%s</modelName>"
"<modelNumber>%s</modelNumber>"
"<modelURL>%s</modelURL>"
"<manufacturer>%s</manufacturer>"
"<manufacturerURL>%s</manufacturerURL>"
"<UDN>uuid:%s</UDN>"
"</device>"
// "<iconList>"
// "<icon>"
// "<mimetype>image/png</mimetype>"
// "<height>48</height>"
// "<width>48</width>"
// "<depth>24</depth>"
// "<url>icon48.png</url>"
// "</icon>"
// "<icon>"
// "<mimetype>image/png</mimetype>"
// "<height>120</height>"
// "<width>120</width>"
// "<depth>24</depth>"
// "<url>icon120.png</url>"
// "</icon>"
// "</iconList>"
"</root>\r\n"
"\r\n";
typedef enum {
NOTIFY_ALIVE_INIT,
NOTIFY_ALIVE,
NOTIFY_UPDATE,
RESPONSE
} ssdp_message_t;
typedef enum {
ROOT_FOR_ALL,
ROOT_BY_UUID,
ROOT_BY_TYPE
} ssdp_udn_t;
typedef struct {
unsigned long time;
ssdp_message_t type;
ssdp_udn_t udn;
uint32_t address;
uint16_t port;
} ssdp_send_parameters_t;
class SSDPDeviceClass {
private:
WiFiUDP *m_server;
IPAddress m_last;
char m_schemaURL[SSDP_SCHEMA_URL_SIZE];
char m_uuid[SSDP_UUID_SIZE];
char m_deviceType[SSDP_DEVICE_TYPE_SIZE];
char m_friendlyName[SSDP_FRIENDLY_NAME_SIZE];
char m_serialNumber[SSDP_SERIAL_NUMBER_SIZE];
char m_presentationURL[SSDP_PRESENTATION_URL_SIZE];
char m_manufacturer[SSDP_MANUFACTURER_SIZE];
char m_manufacturerURL[SSDP_MANUFACTURER_URL_SIZE];
char m_modelName[SSDP_MODEL_NAME_SIZE];
char m_modelURL[SSDP_MODEL_URL_SIZE];
char m_modelNumber[SSDP_MODEL_VERSION_SIZE];
uint16_t m_port;
uint8_t m_ttl;
ssdp_send_parameters_t m_queue[SSDP_QUEUE_SIZE];
protected:
bool readLine(String &value);
bool readKeyValue(String &key, String &value);
void postNotifyALive();
void postNotifyUpdate();
void postResponse(long mx);
void postResponse(ssdp_udn_t udn, long mx);
void post(ssdp_message_t type, ssdp_udn_t udn, IPAddress address, uint16_t port, unsigned long time);
void send(ssdp_send_parameters_t *parameters);
public:
SSDPDeviceClass();
void update();
String schema();
void handleClient();
void setDeviceType(const String& deviceType) { setDeviceType(deviceType.c_str()); }
void setDeviceType(const char *deviceType);
void setName(const String& name) { setName(name.c_str()); }
void setName(const char *name);
void setURL(const String& url) { setURL(url.c_str()); }
void setURL(const char *url);
void setSchemaURL(const String& url) { setSchemaURL(url.c_str()); }
void setSchemaURL(const char *url);
void setSerialNumber(const String& serialNumber) { setSerialNumber(serialNumber.c_str()); }
void setSerialNumber(const char *serialNumber);
void setSerialNumber(const uint32_t serialNumber);
void setModelName(const String& name) { setModelName(name.c_str()); }
void setModelName(const char *name);
void setModelNumber(const String& num) { setModelNumber(num.c_str()); }
void setModelNumber(const char *num);
void setModelURL(const String& url) { setModelURL(url.c_str()); }
void setModelURL(const char *url);
void setManufacturer(const String& name) { setManufacturer(name.c_str()); }
void setManufacturer(const char *name);
void setManufacturerURL(const String& url) { setManufacturerURL(url.c_str()); }
void setManufacturerURL(const char *url);
void setHTTPPort(uint16_t port);
void setTTL(uint8_t ttl);
};
extern SSDPDeviceClass SSDPDevice;
#endif
#endif // SSDP_SUPPORT

+ 57
- 30
code/espurna/libs/StreamInjector.h View File

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

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

@ -5,8 +5,6 @@ WebSocketIncommingBuffer
Code by Hermann Kraus (https://bitbucket.org/hermr2d2/)
and slightly modified.
https://bitbucket.org/xoseperez/espurna/pull-requests/30/safer-buffer-handling-for-websocket-data
*/
#pragma once
@ -82,8 +80,8 @@ class WebSocketIncommingBuffer {
private:
AwsMessageHandler _cb;
bool _cb_on_fragments;
bool _terminate_string;
bool _cb_on_fragments;
std::vector<uint8_t> *_buffer;
};

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

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

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

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

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

@ -209,7 +209,6 @@ _pwm_phases_prep(struct pwm_phase* pwm)
{
uint8_t n, phases;
uint16_t off_mask = 0;
for (n = 0; n < pwm_channels + 2; n++) {
pwm[n].ticks = 0;
pwm[n].on_mask = 0;

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

@ -9,7 +9,7 @@
/*NOTE!! : DO NOT CHANGE THIS FILE*/
/*SUPPORT UP TO 8 PWM CHANNEL*/
#define PWM_CHANNEL_NUM_MAX 8
//#define PWM_CHANNEL_NUM_MAX 8
struct pwm_param {
uint32 period;


+ 439
- 282
code/espurna/light.ino
File diff suppressed because it is too large
View File


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

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


+ 81
- 18
code/espurna/mdns.ino View File

@ -2,19 +2,21 @@
MDNS MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if MDNS_SUPPORT
// -----------------------------------------------------------------------------
// mDNS Server
// -----------------------------------------------------------------------------
#include <ESP8266mDNS.h>
#if MDNS_SERVER_SUPPORT
WiFiEventHandler _mdns_wifi_onSTA;
WiFiEventHandler _mdns_wifi_onAP;
#include <ESP8266mDNS.h>
#if MQTT_SUPPORT
void mdnsFindMQTT() {
void _mdnsFindMQTT() {
int count = MDNS.queryService("mqtt", "tcp");
DEBUG_MSG_P(PSTR("[MQTT] MQTT brokers found: %d\n"), count);
for (int i=0; i<count; i++) {
@ -22,17 +24,20 @@ void mdnsFindMQTT() {
mqttSetBrokerIfNone(MDNS.IP(i), MDNS.port(i));
}
}
#endif
void _mdnsStart() {
if (MDNS.begin(WiFi.getMode() == WIFI_AP ? APP_NAME : (char *) WiFi.hostname().c_str())) {
void _mdnsServerStart() {
if (MDNS.begin((char *) getSetting("hostname").c_str())) {
DEBUG_MSG_P(PSTR("[MDNS] OK\n"));
} else {
DEBUG_MSG_P(PSTR("[MDNS] FAIL\n"));
}
}
void mdnsSetup() {
// -----------------------------------------------------------------------------
void mdnsServerSetup() {
#if WEB_SUPPORT
MDNS.addService("http", "tcp", getSetting("webPort", WEB_PORT).toInt());
@ -45,25 +50,83 @@ void mdnsSetup() {
// Public ESPurna related txt for OTA discovery
MDNS.addServiceTxt("arduino", "tcp", "app_name", APP_NAME);
MDNS.addServiceTxt("arduino", "tcp", "app_version", APP_VERSION);
MDNS.addServiceTxt("arduino", "tcp", "target_board", DEVICE_NAME);
MDNS.addServiceTxt("arduino", "tcp", "mac", WiFi.macAddress());
MDNS.addServiceTxt("arduino", "tcp", "target_board", getBoardName());
{
char buffer[6];
char buffer[6] = {0};
itoa(ESP.getFlashChipRealSize() / 1024, buffer, 10);
MDNS.addServiceTxt("arduino", "tcp", "mem_size", (const char *) buffer);
}
{
char buffer[6];
char buffer[6] = {0};
itoa(ESP.getFlashChipSize() / 1024, buffer, 10);
MDNS.addServiceTxt("arduino", "tcp", "sdk_size", (const char *) buffer);
}
{
char buffer[6] = {0};
itoa(ESP.getFreeSketchSpace(), buffer, 10);
MDNS.addServiceTxt("arduino", "tcp", "free_space", (const char *) buffer);
}
wifiRegister([](justwifi_messages_t code, char * parameter) {
if (code == MESSAGE_CONNECTED) {
_mdnsServerStart();
#if MQTT_SUPPORT
_mdnsFindMQTT();
#endif // MQTT_SUPPORT
}
if (code == MESSAGE_ACCESSPOINT_CREATED) {
_mdnsServerStart();
}
_mdns_wifi_onSTA = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) {
_mdnsStart();
});
_mdns_wifi_onAP = WiFi.onSoftAPModeStationConnected([](WiFiEventSoftAPModeStationConnected ipInfo) {
_mdnsStart();
});
}
#endif // MDNS_SUPPORT
#endif // MDNS_SERVER_SUPPORT
// -----------------------------------------------------------------------------
// mDNS Client
// -----------------------------------------------------------------------------
#if MDNS_CLIENT_SUPPORT
#include <WiFiUdp.h>
#include <mDNSResolver.h>
using namespace mDNSResolver;
WiFiUDP _mdns_udp;
Resolver _mdns_resolver(_mdns_udp);
String mdnsResolve(char * name) {
if (strlen(name) == 0) return String();
if (WiFi.status() != WL_CONNECTED) return String();
_mdns_resolver.setLocalIP(WiFi.localIP());
IPAddress ip = _mdns_resolver.search(name);
if (ip == INADDR_NONE) return String(name);
DEBUG_MSG_P(PSTR("[MDNS] '%s' resolved to '%s'\n"), name, ip.toString().c_str());
return ip.toString();
}
String mdnsResolve(String name) {
return mdnsResolve((char *) name.c_str());
}
void mdnsClientSetup() {
// Register loop
espurnaRegisterLoop(mdnsClientLoop);
}
void mdnsClientLoop() {
_mdns_resolver.loop();
}
#endif // MDNS_CLIENT_SUPPORT

+ 323
- 2
code/espurna/migrate.ino View File

@ -2,7 +2,7 @@
MIGRATE MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -683,9 +683,330 @@ void migrate() {
setSetting("chLogic", 4, 0);
setSetting("relays", 1);
#elif defined(ARILUX_AL_LC02)
setSetting("board", 52);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 12);
setSetting("chGPIO", 1, 5);
setSetting("chGPIO", 2, 13);
setSetting("chGPIO", 3, 15);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("relays", 1);
#elif defined(KMC_70011)
setSetting("board", 53);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 14);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 12);
setSetting("cf1GPIO", 5);
setSetting("cfGPIO", 4);
#elif defined(GIZWITS_WITTY_CLOUD)
setSetting("board", 54);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 4);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 15);
setSetting("chGPIO", 1, 12);
setSetting("chGPIO", 2, 13);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("relays", 1);
#elif defined(EUROMATE_WIFI_STECKER_SCHUKO)
setSetting("board", 55);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 0);
setSetting("ledGPIO", 1, 12);
setSetting("ledLogic", 1, 0);
setSetting("btnGPIO", 0, 14);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(TONBUX_POWERSTRIP02)
setSetting("board", 56);
setSetting("relayGPIO", 0, 4);
setSetting("relayGPIO", 1, 13);
setSetting("relayGPIO", 2, 12);
setSetting("relayGPIO", 3, 14);
setSetting("relayGPIO", 4, 16);
setSetting("relayType", 0, RELAY_TYPE_INVERSE);
setSetting("relayType", 1, RELAY_TYPE_INVERSE);
setSetting("relayType", 2, RELAY_TYPE_INVERSE);
setSetting("relayType", 3, RELAY_TYPE_INVERSE);
setSetting("relayType", 4, RELAY_TYPE_NORMAL); // Not a relay. USB ports on/off
setSetting("ledGPIO", 0, 0); // 1 blue led
setSetting("ledLogic", 0, 1);
setSetting("ledGPIO", 1, 3); // 3 red leds
setSetting("ledLogic", 1, 1);
setSetting("btnGPIO", 0, 5);
setSetting("btnRelay", 0, 1);
#elif defined(LINGAN_SWA1)
setSetting("board", 57);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(HEYGO_HY02)
setSetting("board", 58);
setSetting("ledGPIO", 0, 0);
setSetting("ledLogic", 0, 1);
setSetting("ledGPIO", 1, 15);
setSetting("ledLogic", 1, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 3);
setSetting("cf1GPIO", 14);
setSetting("cfGPIO", 5);
#elif defined(MAXCIO_WUS002S)
setSetting("board", 59);
setSetting("ledGPIO", 0, 3);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 2);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 13);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 12);
setSetting("cf1GPIO", 5);
setSetting("cfGPIO", 4);
#elif defined(YIDIAN_XSSSA05)
setSetting("board", 60);
setSetting("ledGPIO", 0, 0);
setSetting("ledLogic", 0, 0);
setSetting("ledGPIO", 1, 5);
setSetting("ledLogic", 1, 0);
setSetting("ledGPIO", 2, 2);
setSetting("ledLogic", 2, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(TONBUX_XSSSA06)
setSetting("board", 61);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(GREEN_ESP8266RELAY)
setSetting("board", 62);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 5);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(IKE_ESPIKE)
setSetting("board", 63);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("btnGPIO", 1, 12);
setSetting("btnRelay", 1, 1);
setSetting("btnGPIO", 2, 13);
setSetting("btnRelay", 2, 2);
setSetting("relayGPIO", 0, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayGPIO", 1, 5);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayGPIO", 2, 16);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
#elif defined(ARNIEX_SWIFITCH)
setSetting("board", 64);
setSetting("ledGPIO", 0, 12);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 4);
setSetting("btnRelay", 0, 1);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_INVERSE);
#elif defined(GENERIC_ESP01S_RELAY_V40)
setSetting("board", 65);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 0);
setSetting("relayGPIO", 0, 0);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(GENERIC_ESP01S_RGBLED_V10)
setSetting("board", 66);
setSetting("ledGPIO", 0, 2);
#elif defined(HELTEC_TOUCHRELAY)
setSetting("board", 67);
setSetting("btnGPIO", 0, 14);
setSetting("btnRelay", 0, 1);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(GENERIC_ESP01S_DHT11_V10)
setSetting("board", 68);
#elif defined(GENERIC_ESP01S_DS18B20_V10)
setSetting("board", 69);
#elif defined(ZHILDE_EU44_W)
setSetting("board", 70);
setSetting("btnGPIO", 0, 3);
setSetting("ledGPIO", 0, 1);
setSetting("ledLogic", 0, 1);
setSetting("relayGPIO", 0, 5);
setSetting("relayGPIO", 1, 4);
setSetting("relayGPIO", 2, 12);
setSetting("relayGPIO", 3, 13);
setSetting("relayGPIO", 4, 14);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
setSetting("relayType", 3, RELAY_TYPE_NORMAL);
setSetting("relayType", 4, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SONOFF_POW_R2)
setSetting("board", 71);
setSetting("ledGPIO", 0, 15);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 5);
setSetting("cf1GPIO", 13);
setSetting("cfGPIO", 14);
#elif defined(LUANI_HVIO)
setSetting("board", 72);
setSetting("ledGPIO", 0, 15);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 12);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayGPIO", 1, 5);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(ALLNET_4DUINO_IOT_WLAN_RELAIS)
setSetting("board", 73);
setSetting("relayGPIO", 0, 14);
setSetting("relayResetGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_LATCHED);
#elif defined(TONBUX_MOSQUITO_KILLER)
setSetting("board", 74);
setSetting("ledGPIO", 0, 15);
setSetting("ledLogic", 0, 1);
setSetting("ledGPIO", 1, 14);
setSetting("ledLogic", 1, 1);
setSetting("ledGPIO", 2, 12);
setSetting("ledLogic", 2, 0);
setSetting("ledGPIO", 3, 16);
setSetting("ledLogic", 3, 0);
setSetting("btnGPIO", 0, 2);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(NEO_COOLCAM_POWER_PLUG_WIFI)
setSetting("board", 75);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(PILOTAK_ESP_DIN_V1)
setSetting("board", 76);
setSetting("ledGPIO", 0, 16);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 4);
setSetting("relayGPIO", 1, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(ESTINK_WIFI_POWER_STRIP)
setSetting("board", 76);
setSetting("btnGPIO", 0, 16);
setSetting("btnRelay", 0, 3);
setSetting("ledGPIO", 0, 0);
setSetting("ledGPIO", 1, 12);
setSetting("ledGPIO", 2, 3);
setSetting("ledGPIO", 3, 5);
setSetting("ledLogic", 0, 1);
setSetting("ledLogic", 1, 1);
setSetting("ledLogic", 2, 1);
setSetting("ledLogic", 3, 1);
setSetting("ledMode", 0, LED_MODE_FINDME);
setSetting("ledMode", 1, LED_MODE_FOLLOW);
setSetting("ledMode", 2, LED_MODE_FOLLOW);
setSetting("ledMode", 3, LED_MODE_FOLLOW);
setSetting("ledRelay", 1, 1);
setSetting("ledRelay", 2, 2);
setSetting("ledRelay", 3, 3);
setSetting("relayGPIO", 0, 14);
setSetting("relayGPIO", 1, 13);
setSetting("relayGPIO", 2, 4);
setSetting("relayGPIO", 3, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
setSetting("relayType", 3, RELAY_TYPE_NORMAL);
#elif defined(GENERIC_FASTLED)
setSetting("board", 51);
setSetting("board", 77);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_FASTLED);
setSetting("dataGPIO", 0, 5);


+ 508
- 275
code/espurna/mqtt.ino View File

@ -2,12 +2,13 @@
MQTT MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if MQTT_SUPPORT
#include <EEPROM_Rotate.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ArduinoJson.h>
@ -37,14 +38,16 @@ bool _mqtt_use_json = false;
unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
unsigned char _mqtt_qos = MQTT_QOS;
bool _mqtt_retain = MQTT_RETAIN;
unsigned char _mqtt_keepalive = MQTT_KEEPALIVE;
unsigned long _mqtt_keepalive = MQTT_KEEPALIVE;
String _mqtt_topic;
String _mqtt_topic_json;
String _mqtt_setter;
String _mqtt_getter;
bool _mqtt_forward;
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
@ -52,195 +55,321 @@ unsigned long _mqtt_connected_at = 0;
std::vector<mqtt_callback_f> _mqtt_callbacks;
typedef struct {
unsigned char parent = 255;
char * topic;
char * message;
char * message = NULL;
} mqtt_message_t;
std::vector<mqtt_message_t> _mqtt_queue;
Ticker _mqtt_flush_ticker;
// -----------------------------------------------------------------------------
// Public API
// Private
// -----------------------------------------------------------------------------
bool mqttConnected() {
return _mqtt.connected();
}
void _mqttConnect() {
void mqttDisconnect() {
if (_mqtt.connected()) {
DEBUG_MSG_P(PSTR("[MQTT] Disconnecting\n"));
_mqtt.disconnect();
}
}
// Do not connect if disabled
if (!_mqtt_enabled) return;
bool mqttForward() {
return _mqtt_forward;
}
// Do not connect if already connected
if (_mqtt.connected()) return;
String mqttSubtopic(char * topic) {
String response;
String t = String(topic);
if (t.startsWith(_mqtt_topic) && t.endsWith(_mqtt_setter)) {
response = t.substring(_mqtt_topic.length(), t.length() - _mqtt_setter.length());
}
return response;
}
// Check reconnect interval
static unsigned long last = 0;
if (millis() - last < _mqtt_reconnect_delay) return;
last = millis();
void mqttSendRaw(const char * topic, const char * message) {
if (_mqtt.connected()) {
#if MQTT_USE_ASYNC
unsigned int packetId = _mqtt.publish(topic, _mqtt_qos, _mqtt_retain, message);
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s (PID %d)\n"), topic, message, packetId);
#else
_mqtt.publish(topic, message, _mqtt_retain);
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message);
#endif
// Increase the reconnect delay
_mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP;
if (_mqtt_reconnect_delay > MQTT_RECONNECT_DELAY_MAX) {
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX;
}
}
String getTopic(const char * topic, bool set) {
String output = _mqtt_topic + String(topic);
if (set) output += _mqtt_setter;
return output;
}
String h = getSetting("mqttServer", MQTT_SERVER);
#if MDNS_CLIENT_SUPPORT
h = mdnsResolve(h);
#endif
char * host = strdup(h.c_str());
String getTopic(const char * topic, unsigned int index, bool set) {
char buffer[strlen(topic)+5];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), topic, index);
return getTopic(buffer, set);
}
unsigned int port = getSetting("mqttPort", MQTT_PORT).toInt();
void _mqttFlush() {
if (_mqtt_user) free(_mqtt_user);
if (_mqtt_pass) free(_mqtt_pass);
if (_mqtt_will) free(_mqtt_will);
if (_mqtt_clientid) free(_mqtt_clientid);
if (_mqtt_queue.size() == 0) return;
_mqtt_user = strdup(getSetting("mqttUser", MQTT_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());
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
for (unsigned char i=0; i<_mqtt_queue.size(); i++) {
mqtt_message_t element = _mqtt_queue[i];
root[element.topic] = element.message;
}
#if NTP_SUPPORT
if (ntpConnected()) root[MQTT_TOPIC_TIME] = ntpDateTime();
#endif
root[MQTT_TOPIC_HOSTNAME] = getSetting("hostname");
root[MQTT_TOPIC_IP] = getIP();
DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d\n"), host, port);
String output;
root.printTo(output);
String path = _mqtt_topic + String(MQTT_TOPIC_JSON);
mqttSendRaw(path.c_str(), output.c_str());
#if MQTT_USE_ASYNC
for (unsigned char i = 0; i < _mqtt_queue.size(); i++) {
mqtt_message_t element = _mqtt_queue[i];
free(element.topic);
free(element.message);
}
_mqtt_queue.clear();
_mqtt.setServer(host, port);
_mqtt.setClientId(_mqtt_clientid);
_mqtt.setKeepAlive(_mqtt_keepalive);
_mqtt.setCleanSession(false);
_mqtt.setWill(_mqtt_will, _mqtt_qos, _mqtt_retain, "0");
if ((strlen(_mqtt_user) > 0) && (strlen(_mqtt_pass) > 0)) {
DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user);
_mqtt.setCredentials(_mqtt_user, _mqtt_pass);
}
#if ASYNC_TCP_SSL_ENABLED
bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
_mqtt.setSecure(secure);
if (secure) {
DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
unsigned char fp[20] = {0};
if (sslFingerPrintArray(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
_mqtt.addServerFingerprint(fp);
} else {
DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n"));
}
}
#endif // ASYNC_TCP_SSL_ENABLED
DEBUG_MSG_P(PSTR("[MQTT] Client ID: %s\n"), _mqtt_clientid);
DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), _mqtt_retain ? 1 : 0);
DEBUG_MSG_P(PSTR("[MQTT] Keepalive time: %ds\n"), _mqtt_keepalive);
DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will);
_mqtt.connect();
#else // not MQTT_USE_ASYNC
bool response = true;
#if ASYNC_TCP_SSL_ENABLED
bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
if (secure) {
DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
if (_mqtt_client_secure.connect(host, port)) {
char fp[60] = {0};
if (sslFingerPrintChar(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
if (_mqtt_client_secure.verify(fp, host)) {
_mqtt.setClient(_mqtt_client_secure);
} else {
DEBUG_MSG_P(PSTR("[MQTT] Invalid fingerprint\n"));
response = false;
}
_mqtt_client_secure.stop();
yield();
} else {
DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n"));
response = false;
}
} else {
DEBUG_MSG_P(PSTR("[MQTT] Client connection failed\n"));
response = false;
}
} else {
_mqtt.setClient(_mqtt_client);
}
#else // not ASYNC_TCP_SSL_ENABLED
_mqtt.setClient(_mqtt_client);
#endif // ASYNC_TCP_SSL_ENABLED
if (response) {
_mqtt.setServer(host, port);
if ((strlen(_mqtt_user) > 0) && (strlen(_mqtt_pass) > 0)) {
DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user);
response = _mqtt.connect(_mqtt_clientid, _mqtt_user, _mqtt_pass, _mqtt_will, _mqtt_qos, _mqtt_retain, "0");
} else {
response = _mqtt.connect(_mqtt_clientid, _mqtt_will, _mqtt_qos, _mqtt_retain, "0");
}
DEBUG_MSG_P(PSTR("[MQTT] Client ID: %s\n"), _mqtt_clientid);
DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), _mqtt_retain ? 1 : 0);
DEBUG_MSG_P(PSTR("[MQTT] Keepalive time: %ds\n"), _mqtt_keepalive);
DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will);
}
if (response) {
_mqttOnConnect();
} else {
DEBUG_MSG_P(PSTR("[MQTT] Connection failed\n"));
}
#endif // MQTT_USE_ASYNC
free(host);
}
void mqttSend(const char * topic, const char * message, bool force) {
bool useJson = force ? false : _mqtt_use_json;
if (useJson) {
void _mqttConfigure() {
if (_mqtt_queue.size() >= MQTT_QUEUE_MAX_SIZE) _mqttFlush();
// Get base topic
_mqtt_topic = getSetting("mqttTopic", MQTT_TOPIC);
if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1);
mqtt_message_t element;
element.topic = strdup(topic);
element.message = strdup(message);
_mqtt_queue.push_back(element);
_mqtt_flush_ticker.once_ms(MQTT_USE_JSON_DELAY, _mqttFlush);
// Placeholders
_mqtt_topic.replace("{hostname}", getSetting("hostname"));
_mqtt_topic.replace("{magnitude}", "#");
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);
_mqtt_getter = getSetting("mqttGetter", MQTT_GETTER);
_mqtt_forward = !_mqtt_getter.equals(_mqtt_setter);
// MQTT options
_mqtt_qos = getSetting("mqttQoS", MQTT_QOS).toInt();
_mqtt_retain = getSetting("mqttRetain", MQTT_RETAIN).toInt() == 1;
_mqtt_keepalive = getSetting("mqttKeep", MQTT_KEEPALIVE).toInt();
if (getSetting("mqttClientID").length() == 0) delSetting("mqttClientID");
// Enable
if (getSetting("mqttServer", MQTT_SERVER).length() == 0) {
mqttEnabled(false);
} else {
String path = _mqtt_topic + String(topic) + _mqtt_getter;
mqttSendRaw(path.c_str(), message);
_mqtt_enabled = getSetting("mqttEnabled", MQTT_ENABLED).toInt() == 1;
}
}
void mqttSend(const char * topic, const char * message) {
mqttSend(topic, message, false);
}
_mqtt_use_json = (getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1);
mqttQueueTopic(MQTT_TOPIC_JSON);
void mqttSend(const char * topic, unsigned int index, const char * message, bool force) {
char buffer[strlen(topic)+5];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), topic, index);
mqttSend(buffer, message, force);
}
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
void mqttSend(const char * topic, unsigned int index, const char * message) {
mqttSend(topic, index, message, false);
}
void mqttSubscribeRaw(const char * topic) {
if (_mqtt.connected() && (strlen(topic) > 0)) {
#if MQTT_USE_ASYNC
unsigned int packetId = _mqtt.subscribe(topic, _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s (PID %d)\n"), topic, packetId);
#else
_mqtt.subscribe(topic, _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s\n"), topic);
#endif
void _mqttBackwards() {
String mqttTopic = getSetting("mqttTopic", MQTT_TOPIC);
if (mqttTopic.indexOf("{identifier}") > 0) {
mqttTopic.replace("{identifier}", "{hostname}");
setSetting("mqttTopic", mqttTopic);
}
}
void mqttSubscribe(const char * topic) {
String path = _mqtt_topic + String(topic) + _mqtt_setter;
mqttSubscribeRaw(path.c_str());
}
unsigned long _mqttNextMessageId() {
static unsigned long id = 0;
// just reboot, get last count from EEPROM
if (id == 0) {
// read id from EEPROM and shift it
id = EEPROMr.read(EEPROM_MESSAGE_ID);
if (id == 0xFF) {
// There was nothing in EEPROM,
// next message is first message
id = 0;
} else {
id = (id << 8) + EEPROMr.read(EEPROM_MESSAGE_ID + 1);
id = (id << 8) + EEPROMr.read(EEPROM_MESSAGE_ID + 2);
id = (id << 8) + EEPROMr.read(EEPROM_MESSAGE_ID + 3);
// Calculate next block and start from there
id = MQTT_MESSAGE_ID_SHIFT * (1 + (id / MQTT_MESSAGE_ID_SHIFT));
}
void mqttUnsubscribeRaw(const char * topic) {
if (_mqtt.connected() && (strlen(topic) > 0)) {
#if MQTT_USE_ASYNC
unsigned int packetId = _mqtt.unsubscribe(topic);
DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing to %s (PID %d)\n"), topic, packetId);
#else
_mqtt.unsubscribe(topic);
DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing to %s\n"), topic);
#endif
}
}
void mqttRegister(mqtt_callback_f callback) {
_mqtt_callbacks.push_back(callback);
// Save to EEPROM every MQTT_MESSAGE_ID_SHIFT
if (id % MQTT_MESSAGE_ID_SHIFT == 0) {
EEPROMr.write(EEPROM_MESSAGE_ID + 0, (id >> 24) & 0xFF);
EEPROMr.write(EEPROM_MESSAGE_ID + 1, (id >> 16) & 0xFF);
EEPROMr.write(EEPROM_MESSAGE_ID + 2, (id >> 8) & 0xFF);
EEPROMr.write(EEPROM_MESSAGE_ID + 3, (id >> 0) & 0xFF);
EEPROMr.commit();
}
id++;
return id;
}
// -----------------------------------------------------------------------------
// Callbacks
// WEB
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _mqttWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "mqtt", 3) == 0);
}
void _mqttWebSocketOnSend(JsonObject& root) {
root["mqttVisible"] = 1;
root["mqttStatus"] = mqttConnected();
root["mqttEnabled"] = mqttEnabled();
root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER);
root["mqttPort"] = getSetting("mqttPort", MQTT_PORT);
root["mqttUser"] = getSetting("mqttUser");
root["mqttPassword"] = getSetting("mqttPassword");
root["mqttUser"] = getSetting("mqttUser", MQTT_USER);
root["mqttClientID"] = getSetting("mqttClientID");
root["mqttPassword"] = getSetting("mqttPassword", MQTT_PASS);
root["mqttKeep"] = _mqtt_keepalive;
root["mqttRetain"] = _mqtt_retain;
root["mqttQoS"] = _mqtt_qos;
#if ASYNC_TCP_SSL_ENABLED
root["mqttsslVisible"] = 1;
root["mqttUseSSL"] = getSetting("mqttUseSSL", 0).toInt() == 1;
root["mqttFP"] = getSetting("mqttFP");
root["mqttUseSSL"] = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
root["mqttFP"] = getSetting("mqttFP", MQTT_SSL_FINGERPRINT);
#endif
root["mqttTopic"] = getSetting("mqttTopic", MQTT_TOPIC);
root["mqttUseJson"] = getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1;
}
#endif
// -----------------------------------------------------------------------------
// SETTINGS
// -----------------------------------------------------------------------------
#if TERMINAL_SUPPORT
void _mqttInitCommands() {
settingsRegisterCommand(F("MQTT.RESET"), [](Embedis* e) {
_mqttConfigure();
mqttDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
#endif // TERMINAL_SUPPORT
// -----------------------------------------------------------------------------
// MQTT Callbacks
// -----------------------------------------------------------------------------
void _mqttCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
// Subscribe to internal action topics
mqttSubscribe(MQTT_TOPIC_ACTION);
// Flag system to send heartbeat
systemSendHeartbeat();
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttSubtopic((char *) topic);
String t = mqttMagnitude((char *) topic);
// Actions
if (t.equals(MQTT_TOPIC_ACTION)) {
@ -305,216 +434,299 @@ void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
}
#if MQTT_USE_ASYNC
bool mqttFormatFP(const char * fingerprint, unsigned char * bytearray) {
// check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
if (strlen(fingerprint) != 59) return false;
// -----------------------------------------------------------------------------
// Public API
// -----------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[MQTT] Fingerprint %s\n"), fingerprint);
/**
Returns the magnitude part of a topic
// walk the fingerprint
for (unsigned int i=0; i<20; i++) {
bytearray[i] = strtol(fingerprint + 3*i, NULL, 16);
@param topic the full MQTT topic
@return String object with the magnitude part.
*/
String mqttMagnitude(char * topic) {
String pattern = _mqtt_topic + _mqtt_setter;
int position = pattern.indexOf("#");
if (position == -1) return String();
String start = pattern.substring(0, position);
String end = pattern.substring(position + 1);
String magnitude = String(topic);
if (magnitude.startsWith(start) && magnitude.endsWith(end)) {
magnitude.replace(start, "");
magnitude.replace(end, "");
} else {
magnitude = String();
}
return true;
return magnitude;
}
#else
/**
Returns a full MQTT topic from the magnitude
bool mqttFormatFP(const char * fingerprint, char * destination) {
@param magnitude the magnitude part of the topic.
@param is_set whether to build a command topic (true)
or a state topic (false).
@return String full MQTT topic.
*/
String mqttTopic(const char * magnitude, bool is_set) {
String output = _mqtt_topic;
output.replace("#", magnitude);
output += is_set ? _mqtt_setter : _mqtt_getter;
return output;
}
// check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
if (strlen(fingerprint) != 59) return false;
/**
Returns a full MQTT topic from the magnitude
DEBUG_MSG_P(PSTR("[MQTT] Fingerprint %s\n"), fingerprint);
@param magnitude the magnitude part of the topic.
@param index index of the magnitude when more than one such magnitudes.
@param is_set whether to build a command topic (true)
or a state topic (false).
@return String full MQTT topic.
*/
String mqttTopic(const char * magnitude, unsigned int index, bool is_set) {
char buffer[strlen(magnitude)+5];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), magnitude, index);
return mqttTopic(buffer, is_set);
}
// copy it
strncpy(destination, fingerprint, 59);
// -----------------------------------------------------------------------------
// walk the fingerprint replacing ':' for ' '
for (unsigned char i = 0; i<59; i++) {
if (destination[i] == ':') destination[i] = ' ';
void mqttSendRaw(const char * topic, const char * message, bool retain) {
if (_mqtt.connected()) {
#if MQTT_USE_ASYNC
unsigned int packetId = _mqtt.publish(topic, _mqtt_qos, retain, message);
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s (PID %d)\n"), topic, message, packetId);
#else
_mqtt.publish(topic, message, retain);
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message);
#endif
}
}
return true;
void mqttSendRaw(const char * topic, const char * message) {
mqttSendRaw (topic, message, _mqtt_retain);
}
#endif
void mqttSend(const char * topic, const char * message, bool force, bool retain) {
void mqttEnabled(bool status) {
_mqtt_enabled = status;
setSetting("mqttEnabled", status ? 1 : 0);
}
bool useJson = force ? false : _mqtt_use_json;
bool mqttEnabled() {
return _mqtt_enabled;
}
// Equeue message
if (useJson) {
void mqttConnect() {
// Set default queue topic
mqttQueueTopic(MQTT_TOPIC_JSON);
// Do not connect if disabled
if (!_mqtt_enabled) return;
// Enqueue new message
mqttEnqueue(topic, message);
// Do not connect if already connected
if (_mqtt.connected()) return;
// Reset flush timer
_mqtt_flush_ticker.once_ms(MQTT_USE_JSON_DELAY, mqttFlush);
// Check reconnect interval
static unsigned long last = 0;
if (millis() - last < _mqtt_reconnect_delay) return;
last = millis();
// Send it right away
} else {
mqttSendRaw(mqttTopic(topic, false).c_str(), message, retain);
// Increase the reconnect delay
_mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP;
if (_mqtt_reconnect_delay > MQTT_RECONNECT_DELAY_MAX) {
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX;
}
char * host = strdup(getSetting("mqttServer", MQTT_SERVER).c_str());
if (strlen(host) == 0) return;
unsigned int port = getSetting("mqttPort", MQTT_PORT).toInt();
}
if (_mqtt_user) free(_mqtt_user);
if (_mqtt_pass) free(_mqtt_pass);
if (_mqtt_will) free(_mqtt_will);
void mqttSend(const char * topic, const char * message, bool force) {
mqttSend(topic, message, force, _mqtt_retain);
}
_mqtt_user = strdup(getSetting("mqttUser", MQTT_USER).c_str());
_mqtt_pass = strdup(getSetting("mqttPassword", MQTT_PASS).c_str());
_mqtt_will = strdup((_mqtt_topic + MQTT_TOPIC_STATUS).c_str());
void mqttSend(const char * topic, const char * message) {
mqttSend(topic, message, false);
}
DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d\n"), host, port);
void mqttSend(const char * topic, unsigned int index, const char * message, bool force, bool retain) {
char buffer[strlen(topic)+5];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), topic, index);
mqttSend(buffer, message, force, retain);
}
#if MQTT_USE_ASYNC
void mqttSend(const char * topic, unsigned int index, const char * message, bool force) {
mqttSend(topic, index, message, force, _mqtt_retain);
}
_mqtt.setServer(host, port);
_mqtt.setKeepAlive(_mqtt_keepalive).setCleanSession(false);
_mqtt.setWill(_mqtt_will, _mqtt_qos, _mqtt_retain, "0");
if ((strlen(_mqtt_user) > 0) && (strlen(_mqtt_pass) > 0)) {
DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user);
_mqtt.setCredentials(_mqtt_user, _mqtt_pass);
}
void mqttSend(const char * topic, unsigned int index, const char * message) {
mqttSend(topic, index, message, false);
}
#if ASYNC_TCP_SSL_ENABLED
// -----------------------------------------------------------------------------
bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
_mqtt.setSecure(secure);
if (secure) {
DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
unsigned char fp[20] = {0};
if (mqttFormatFP(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
_mqtt.addServerFingerprint(fp);
} else {
DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n"));
}
unsigned char _mqttBuildTree(JsonObject& root, char parent) {
unsigned char count = 0;
// Add enqueued messages
for (unsigned char i=0; i<_mqtt_queue.size(); i++) {
mqtt_message_t element = _mqtt_queue[i];
if (element.parent == parent) {
++count;
JsonObject& elements = root.createNestedObject(element.topic);
unsigned char num = _mqttBuildTree(elements, i);
if (0 == num) {
root.set(element.topic, element.message);
}
}
}
#endif // ASYNC_TCP_SSL_ENABLED
return count;
DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will);
DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), _mqtt_retain ? 1 : 0);
}
_mqtt.connect();
void mqttFlush() {
#else // not MQTT_USE_ASYNC
if (!_mqtt.connected()) return;
if (_mqtt_queue.size() == 0) return;
bool response = true;
// Build tree recursively
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
_mqttBuildTree(root, 255);
#if ASYNC_TCP_SSL_ENABLED
// Add extra propeties
#if NTP_SUPPORT && MQTT_ENQUEUE_DATETIME
if (ntpSynced()) root[MQTT_TOPIC_TIME] = ntpDateTime();
#endif
#if MQTT_ENQUEUE_MAC
root[MQTT_TOPIC_MAC] = WiFi.macAddress();
#endif
#if MQTT_ENQUEUE_HOSTNAME
root[MQTT_TOPIC_HOSTNAME] = getSetting("hostname");
#endif
#if MQTT_ENQUEUE_IP
root[MQTT_TOPIC_IP] = getIP();
#endif
#if MQTT_ENQUEUE_MESSAGE_ID
root[MQTT_TOPIC_MESSAGE_ID] = _mqttNextMessageId();
#endif
bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
if (secure) {
DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
if (_mqtt_client_secure.connect(host, port)) {
char fp[60] = {0};
if (mqttFormatFP(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
if (_mqtt_client_secure.verify(fp, host)) {
_mqtt.setClient(_mqtt_client_secure);
} else {
DEBUG_MSG_P(PSTR("[MQTT] Invalid fingerprint\n"));
response = false;
}
_mqtt_client_secure.stop();
yield();
} else {
DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n"));
response = false;
}
} else {
DEBUG_MSG_P(PSTR("[MQTT] Client connection failed\n"));
response = false;
}
// Send
String output;
root.printTo(output);
jsonBuffer.clear();
} else {
_mqtt.setClient(_mqtt_client);
}
mqttSendRaw(_mqtt_topic_json.c_str(), output.c_str(), false);
#else // not ASYNC_TCP_SSL_ENABLED
// Clear queue
for (unsigned char i = 0; i < _mqtt_queue.size(); i++) {
mqtt_message_t element = _mqtt_queue[i];
free(element.topic);
if (element.message) {
free(element.message);
}
}
_mqtt_queue.clear();
_mqtt.setClient(_mqtt_client);
}
#endif // ASYNC_TCP_SSL_ENABLED
void mqttQueueTopic(const char * topic) {
String t = mqttTopic(topic, false);
if (!t.equals(_mqtt_topic_json)) {
mqttFlush();
_mqtt_topic_json = t;
}
}
if (response) {
int8_t mqttEnqueue(const char * topic, const char * message, unsigned char parent) {
_mqtt.setServer(host, port);
// Queue is not meant to send message "offline"
// We must prevent the queue does not get full while offline
if (!_mqtt.connected()) return -1;
if ((strlen(_mqtt_user) > 0) && (strlen(_mqtt_pass) > 0)) {
DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user);
response = _mqtt.connect(getIdentifier().c_str(), _mqtt_user, _mqtt_pass, _mqtt_will, _mqtt_qos, _mqtt_retain, "0");
} else {
response = _mqtt.connect(getIdentifier().c_str(), _mqtt_will, _mqtt_qos, _mqtt_retain, "0");
}
// Force flusing the queue if the MQTT_QUEUE_MAX_SIZE has been reached
if (_mqtt_queue.size() >= MQTT_QUEUE_MAX_SIZE) mqttFlush();
DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will);
DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), _mqtt_retain ? 1 : 0);
int8_t index = _mqtt_queue.size();
}
// Enqueue new message
mqtt_message_t element;
element.parent = parent;
element.topic = strdup(topic);
if (NULL != message) {
element.message = strdup(message);
}
_mqtt_queue.push_back(element);
if (response) {
_mqttOnConnect();
} else {
DEBUG_MSG_P(PSTR("[MQTT] Connection failed\n"));
}
return index;
#endif // MQTT_USE_ASYNC
}
free(host);
int8_t mqttEnqueue(const char * topic, const char * message) {
return mqttEnqueue(topic, message, 255);
}
// -----------------------------------------------------------------------------
void mqttSubscribeRaw(const char * topic) {
if (_mqtt.connected() && (strlen(topic) > 0)) {
#if MQTT_USE_ASYNC
unsigned int packetId = _mqtt.subscribe(topic, _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s (PID %d)\n"), topic, packetId);
#else
_mqtt.subscribe(topic, _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s\n"), topic);
#endif
}
}
void mqttConfigure() {
void mqttSubscribe(const char * topic) {
mqttSubscribeRaw(mqttTopic(topic, true).c_str());
}
// Replace identifier
_mqtt_topic = getSetting("mqttTopic", MQTT_TOPIC);
_mqtt_topic.replace("{identifier}", getSetting("hostname"));
if (!_mqtt_topic.endsWith("/")) _mqtt_topic = _mqtt_topic + "/";
void mqttUnsubscribeRaw(const char * topic) {
if (_mqtt.connected() && (strlen(topic) > 0)) {
#if MQTT_USE_ASYNC
unsigned int packetId = _mqtt.unsubscribe(topic);
DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing to %s (PID %d)\n"), topic, packetId);
#else
_mqtt.unsubscribe(topic);
DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing to %s\n"), topic);
#endif
}
}
// Getters and setters
_mqtt_setter = getSetting("mqttSetter", MQTT_USE_SETTER);
_mqtt_getter = getSetting("mqttGetter", MQTT_USE_GETTER);
_mqtt_forward = !_mqtt_getter.equals(_mqtt_setter);
void mqttUnsubscribe(const char * topic) {
mqttUnsubscribeRaw(mqttTopic(topic, true).c_str());
}
// MQTT options
_mqtt_qos = getSetting("mqttQoS", MQTT_QOS).toInt();
_mqtt_retain = getSetting("mqttRetain", MQTT_RETAIN).toInt() == 1;
_mqtt_keepalive = getSetting("mqttKeep", MQTT_KEEPALIVE).toInt();
// -----------------------------------------------------------------------------
// Enable
if (getSetting("mqttServer", MQTT_SERVER).length() == 0) {
mqttEnabled(false);
} else {
_mqtt_enabled = getSetting("mqttEnabled", MQTT_ENABLED).toInt() == 1;
void mqttEnabled(bool status) {
_mqtt_enabled = status;
setSetting("mqttEnabled", status ? 1 : 0);
}
bool mqttEnabled() {
return _mqtt_enabled;
}
bool mqttConnected() {
return _mqtt.connected();
}
void mqttDisconnect() {
if (_mqtt.connected()) {
DEBUG_MSG_P(PSTR("[MQTT] Disconnecting\n"));
_mqtt.disconnect();
}
_mqtt_use_json = (getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1);
}
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
bool mqttForward() {
return _mqtt_forward;
}
void mqttRegister(mqtt_callback_f callback) {
_mqtt_callbacks.push_back(callback);
}
void mqttSetBroker(IPAddress ip, unsigned int port) {
@ -527,10 +739,24 @@ void mqttSetBrokerIfNone(IPAddress ip, unsigned int port) {
if (!hasSetting("mqttServer")) mqttSetBroker(ip, port);
}
void mqttReset() {
_mqttConfigure();
mqttDisconnect();
}
// -----------------------------------------------------------------------------
// Initialization
// -----------------------------------------------------------------------------
void mqttSetup() {
DEBUG_MSG_P(PSTR("[MQTT] MQTT_USE_ASYNC = %d\n"), MQTT_USE_ASYNC);
DEBUG_MSG_P(PSTR("[MQTT] MQTT_AUTOCONNECT = %d\n"), MQTT_AUTOCONNECT);
_mqttBackwards();
DEBUG_MSG_P(PSTR("[MQTT] Async %s, SSL %s, Autoconnect %s\n"),
MQTT_USE_ASYNC ? "ENABLED" : "DISABLED",
ASYNC_TCP_SSL_ENABLED ? "ENABLED" : "DISABLED",
MQTT_AUTOCONNECT ? "ENABLED" : "DISABLED"
);
#if MQTT_USE_ASYNC
@ -572,21 +798,28 @@ void mqttSetup() {
#else // not MQTT_USE_ASYNC
DEBUG_MSG_P(PSTR("[MQTT] Using SYNC MQTT library\n"));
_mqtt.setCallback([](char* topic, byte* payload, unsigned int length) {
_mqttOnMessage(topic, (char *) payload, length);
});
#endif // MQTT_USE_ASYNC
mqttConfigure();
_mqttConfigure();
mqttRegister(_mqttCallback);
#if WEB_SUPPORT
wsOnSendRegister(_mqttWebSocketOnSend);
wsOnAfterParseRegister(_mqttConfigure);
wsOnReceiveRegister(_mqttWebSocketOnReceive);
#endif
#if TERMINAL_SUPPORT
_mqttInitCommands();
#endif
// Register loop
espurnaRegisterLoop(mqttLoop);
}
void mqttLoop() {
@ -595,7 +828,7 @@ void mqttLoop() {
#if MQTT_USE_ASYNC
mqttConnect();
_mqttConnect();
#else // not MQTT_USE_ASYNC
@ -610,7 +843,7 @@ void mqttLoop() {
_mqtt_connected = false;
}
mqttConnect();
_mqttConnect();
}


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

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


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

@ -2,7 +2,7 @@
NOFUSS MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -18,15 +18,27 @@ bool _nofussEnabled = false;
// NOFUSS
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _nofussWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "nofuss", 6) == 0);
}
void _nofussWebSocketOnSend(JsonObject& root) {
root["nofussVisible"] = 1;
root["nofussEnabled"] = getSetting("nofussEnabled", NOFUSS_ENABLED).toInt() == 1;
root["nofussServer"] = getSetting("nofussServer", NOFUSS_SERVER);
}
#endif
void _nofussConfigure() {
String nofussServer = getSetting("nofussServer", NOFUSS_SERVER);
#if MDNS_CLIENT_SUPPORT
nofussServer = mdnsResolve(nofussServer);
#endif
if (nofussServer.length() == 0) {
setSetting("nofussEnabled", 0);
_nofussEnabled = false;
@ -58,6 +70,19 @@ void _nofussConfigure() {
}
#if TERMINAL_SUPPORT
void _nofussInitCommands() {
settingsRegisterCommand(F("NOFUSS"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
nofussRun();
});
}
#endif // TERMINAL_SUPPORT
// -----------------------------------------------------------------------------
void nofussRun() {
@ -118,7 +143,7 @@ void nofussSetup() {
#if WEB_SUPPORT
wsSend_P(PSTR("{\"action\": \"reload\"}"));
#endif
delay(100);
nice_delay(100);
}
if (code == NOFUSS_END) {
@ -130,8 +155,16 @@ void nofussSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_nofussWebSocketOnSend);
wsOnAfterParseRegister(_nofussConfigure);
wsOnReceiveRegister(_nofussWebSocketOnReceive);
#endif
#if TERMINAL_SUPPORT
_nofussInitCommands();
#endif
// Register loop
espurnaRegisterLoop(nofussLoop);
}
void nofussLoop() {


+ 123
- 37
code/espurna/ntp.ino View File

@ -2,7 +2,7 @@
NTP MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -13,64 +13,150 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#include <WiFiClient.h>
#include <Ticker.h>
WiFiEventHandler _ntp_wifi_onSTA;
Ticker _ntp_delay;
unsigned long _ntp_start = 0;
bool _ntp_update = false;
bool _ntp_configure = false;
// -----------------------------------------------------------------------------
// NTP
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _ntpWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "ntp", 3) == 0);
}
void _ntpWebSocketOnSend(JsonObject& root) {
root["time"] = ntpDateTime();
root["ntpVisible"] = 1;
root["ntpStatus"] = ntpConnected();
root["ntpServer1"] = getSetting("ntpServer1", NTP_SERVER);
root["ntpServer2"] = getSetting("ntpServer2");
root["ntpServer3"] = getSetting("ntpServer3");
root["ntpStatus"] = (timeStatus() == timeSet);
root["ntpServer"] = getSetting("ntpServer", NTP_SERVER);
root["ntpOffset"] = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
root["ntpDST"] = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1;
root["ntpRegion"] = getSetting("ntpRegion", NTP_DST_REGION).toInt();
if (ntpSynced()) root["now"] = now();
}
#endif
void _ntpStart() {
_ntp_start = 0;
NTP.begin(getSetting("ntpServer", NTP_SERVER));
NTP.setInterval(NTP_SYNC_INTERVAL, NTP_UPDATE_INTERVAL);
NTP.setNTPTimeout(NTP_TIMEOUT);
_ntpConfigure();
}
void _ntpConfigure() {
_ntp_configure = false;
int offset = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
int sign = offset > 0 ? 1 : -1;
offset = abs(offset);
int tz_hours = sign * (offset / 60);
int tz_minutes = sign * (offset % 60);
if (NTP.getTimeZone() != tz_hours || NTP.getTimeZoneMinutes() != tz_minutes) {
NTP.setTimeZone(tz_hours, tz_minutes);
_ntp_update = true;
}
bool daylight = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1;
if (NTP.getDayLight() != daylight) {
NTP.setDayLight(daylight);
_ntp_update = true;
}
String server = getSetting("ntpServer", NTP_SERVER);
if (!NTP.getNtpServerName().equals(server)) {
NTP.setNtpServerName(server);
}
uint8_t dst_region = getSetting("ntpRegion", NTP_DST_REGION).toInt();
NTP.setDSTZone(dst_region);
}
void _ntpUpdate() {
_ntp_update = false;
#if WEB_SUPPORT
wsSend(_ntpWebSocketOnSend);
#endif
DEBUG_MSG_P(PSTR("[NTP] Time: %s\n"), (char *) ntpDateTime().c_str());
if (ntpSynced()) {
time_t t = now();
DEBUG_MSG_P(PSTR("[NTP] UTC Time : %s\n"), (char *) ntpDateTime(ntpLocal2UTC(t)).c_str());
DEBUG_MSG_P(PSTR("[NTP] Local Time: %s\n"), (char *) ntpDateTime(t).c_str());
}
}
void _ntpConfigure() {
NTP.begin(
getSetting("ntpServer1", NTP_SERVER),
getSetting("ntpOffset", NTP_TIME_OFFSET).toInt(),
getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1
);
if (getSetting("ntpServer2")) NTP.setNtpServerName(getSetting("ntpServer2"), 1);
if (getSetting("ntpServer3")) NTP.setNtpServerName(getSetting("ntpServer3"), 2);
NTP.setInterval(NTP_UPDATE_INTERVAL);
void _ntpLoop() {
if (0 < _ntp_start && _ntp_start < millis()) _ntpStart();
if (_ntp_configure) _ntpConfigure();
if (_ntp_update) _ntpUpdate();
now();
#if BROKER_SUPPORT
static unsigned char last_minute = 60;
if (ntpSynced() && (minute() != last_minute)) {
last_minute = minute();
brokerPublish(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
}
#endif
}
void _ntpBackwards() {
moveSetting("ntpServer1", "ntpServer");
delSetting("ntpServer2");
delSetting("ntpServer3");
int offset = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
if (-30 < offset && offset < 30) {
offset *= 60;
setSetting("ntpOffset", offset);
}
}
// -----------------------------------------------------------------------------
bool ntpConnected() {
return (timeStatus() == timeSet);
bool ntpSynced() {
return (year() > 2017);
}
String ntpDateTime() {
if (!ntpConnected()) return String("Not set");
String value = NTP.getTimeDateString();
int hour = value.substring(0, 2).toInt();
int minute = value.substring(3, 5).toInt();
int second = value.substring(6, 8).toInt();
int day = value.substring(9, 11).toInt();
int month = value.substring(12, 14).toInt();
int year = value.substring(15, 19).toInt();
String ntpDateTime(time_t t) {
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%04d-%02d-%02d %02d:%02d:%02d"), year, month, day, hour, minute, second);
snprintf_P(buffer, sizeof(buffer),
PSTR("%04d-%02d-%02d %02d:%02d:%02d"),
year(t), month(t), day(t), hour(t), minute(t), second(t)
);
return String(buffer);
}
String ntpDateTime() {
if (ntpSynced()) return ntpDateTime(now());
return String();
}
time_t ntpLocal2UTC(time_t local) {
int offset = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
if (NTP.isSummerTime()) offset += 60;
return local - offset * 60;
}
// -----------------------------------------------------------------------------
void ntpSetup() {
_ntpBackwards();
NTP.onNTPSyncEvent([](NTPSyncEvent_t error) {
if (error) {
#if WEB_SUPPORT
@ -82,23 +168,23 @@ void ntpSetup() {
DEBUG_MSG_P(PSTR("[NTP] Error: Invalid NTP server address\n"));
}
} else {
_ntp_delay.once_ms(100, _ntpUpdate);
_ntp_update = true;
}
});
_ntp_wifi_onSTA = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) {
_ntpConfigure();
wifiRegister([](justwifi_messages_t code, char * parameter) {
if (code == MESSAGE_CONNECTED) _ntp_start = millis() + NTP_START_DELAY;
});
#if WEB_SUPPORT
wsOnSendRegister(_ntpWebSocketOnSend);
wsOnAfterParseRegister(_ntpConfigure);
wsOnReceiveRegister(_ntpWebSocketOnReceive);
wsOnAfterParseRegister([]() { _ntp_configure = true; });
#endif
}
// Register loop
espurnaRegisterLoop(_ntpLoop);
void ntpLoop() {
now();
}
#endif // NTP_SUPPORT

+ 202
- 9
code/espurna/ota.ino View File

@ -2,40 +2,236 @@
OTA MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "ArduinoOTA.h"
// -----------------------------------------------------------------------------
// OTA
// Arduino OTA
// -----------------------------------------------------------------------------
void _otaConfigure() {
ArduinoOTA.setPort(OTA_PORT);
ArduinoOTA.setHostname(getSetting("hostname").c_str());
ArduinoOTA.setPassword(getSetting("adminPass", ADMIN_PASS).c_str());
#if USE_PASSWORD
ArduinoOTA.setPassword(getSetting("adminPass", ADMIN_PASS).c_str());
#endif
}
void _otaLoop() {
ArduinoOTA.handle();
}
// -----------------------------------------------------------------------------
// Terminal OTA
// -----------------------------------------------------------------------------
#if TERMINAL_SUPPORT
#include <ESPAsyncTCP.h>
AsyncClient * _ota_client;
char * _ota_host;
char * _ota_url;
unsigned int _ota_port = 80;
unsigned long _ota_size = 0;
const char OTA_REQUEST_TEMPLATE[] PROGMEM =
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n"
"User-Agent: ESPurna\r\n"
"Connection: close\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"Content-Length: 0\r\n\r\n\r\n";
void _otaFrom(const char * host, unsigned int port, const char * url) {
if (_ota_host) free(_ota_host);
if (_ota_url) free(_ota_url);
_ota_host = strdup(host);
_ota_url = strdup(url);
_ota_port = port;
_ota_size = 0;
if (_ota_client == NULL) {
_ota_client = new AsyncClient();
}
_ota_client->onDisconnect([](void *s, AsyncClient *c) {
DEBUG_MSG_P(PSTR("\n"));
if (Update.end(true)){
DEBUG_MSG_P(PSTR("[OTA] Success: %u bytes\n"), _ota_size);
deferredReset(100, CUSTOM_RESET_OTA);
} else {
#ifdef DEBUG_PORT
Update.printError(DEBUG_PORT);
#endif
eepromRotate(true);
}
DEBUG_MSG_P(PSTR("[OTA] Disconnected\n"));
_ota_client->free();
delete _ota_client;
_ota_client = NULL;
free(_ota_host);
_ota_host = NULL;
free(_ota_url);
_ota_url = NULL;
}, 0);
_ota_client->onTimeout([](void *s, AsyncClient *c, uint32_t time) {
_ota_client->close(true);
}, 0);
_ota_client->onData([](void * arg, AsyncClient * c, void * data, size_t len) {
char * p = (char *) data;
if (_ota_size == 0) {
Update.runAsync(true);
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
#ifdef DEBUG_PORT
Update.printError(DEBUG_PORT);
#endif
}
p = strstr((char *)data, "\r\n\r\n") + 4;
len = len - (p - (char *) data);
}
if (!Update.hasError()) {
if (Update.write((uint8_t *) p, len) != len) {
#ifdef DEBUG_PORT
Update.printError(DEBUG_PORT);
#endif
}
}
_ota_size += len;
DEBUG_MSG_P(PSTR("[OTA] Progress: %u bytes\r"), _ota_size);
}, NULL);
_ota_client->onConnect([](void * arg, AsyncClient * client) {
#if ASYNC_TCP_SSL_ENABLED
if (443 == _ota_port) {
uint8_t fp[20] = {0};
sslFingerPrintArray(getSetting("otafp", OTA_GITHUB_FP).c_str(), fp);
SSL * ssl = _ota_client->getSSL();
if (ssl_match_fingerprint(ssl, fp) != SSL_OK) {
DEBUG_MSG_P(PSTR("[OTA] Warning: certificate doesn't match\n"));
}
}
#endif
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
eepromRotate(false);
DEBUG_MSG_P(PSTR("[OTA] Downloading %s\n"), _ota_url);
char buffer[strlen_P(OTA_REQUEST_TEMPLATE) + strlen(_ota_url) + strlen(_ota_host)];
snprintf_P(buffer, sizeof(buffer), OTA_REQUEST_TEMPLATE, _ota_url, _ota_host);
client->write(buffer);
}, NULL);
#if ASYNC_TCP_SSL_ENABLED
bool connected = _ota_client->connect(host, port, 443 == port);
#else
bool connected = _ota_client->connect(host, port);
#endif
if (!connected) {
DEBUG_MSG_P(PSTR("[OTA] Connection failed\n"));
_ota_client->close(true);
}
}
void _otaFrom(String url) {
// Port from protocol
unsigned int port = 80;
if (url.startsWith("https://")) port = 443;
url = url.substring(url.indexOf("/") + 2);
// Get host
String host = url.substring(0, url.indexOf("/"));
// Explicit port
int p = host.indexOf(":");
if (p > 0) {
port = host.substring(p + 1).toInt();
host = host.substring(0, p);
}
// Get URL
String uri = url.substring(url.indexOf("/"));
_otaFrom(host.c_str(), port, uri.c_str());
}
void _otaInitCommands() {
settingsRegisterCommand(F("OTA"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
} else {
DEBUG_MSG_P(PSTR("+OK\n"));
String url = String(e->argv[1]);
_otaFrom(url);
}
});
}
#endif // TERMINAL_SUPPORT
// -----------------------------------------------------------------------------
void otaSetup() {
_otaConfigure();
#if WEB_SUPPORT
wsOnAfterParseRegister(_otaConfigure);
#endif
#if TERMINAL_SUPPORT
_otaInitCommands();
#endif
// Register loop
espurnaRegisterLoop(_otaLoop);
// -------------------------------------------------------------------------
ArduinoOTA.onStart([]() {
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
eepromRotate(false);
DEBUG_MSG_P(PSTR("[OTA] Start\n"));
#if WEB_SUPPORT
wsSend_P(PSTR("{\"message\": 2}"));
#endif
});
ArduinoOTA.onEnd([]() {
DEBUG_MSG_P(PSTR("\n[OTA] End\n"));
DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[OTA] Done, restarting...\n"));
#if WEB_SUPPORT
wsSend_P(PSTR("{\"action\": \"reload\"}"));
#endif
@ -43,7 +239,7 @@ void otaSetup() {
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
DEBUG_MSG_P(PSTR("[OTA] Progress: %u%%%%\r"), (progress / (total / 100)));
DEBUG_MSG_P(PSTR("[OTA] Progress: %u%%\r"), (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
@ -55,12 +251,9 @@ void otaSetup() {
else if (error == OTA_RECEIVE_ERROR) DEBUG_MSG_P(PSTR("Receive Failed\n"));
else if (error == OTA_END_ERROR) DEBUG_MSG_P(PSTR("End Failed\n"));
#endif
eepromRotate(true);
});
ArduinoOTA.begin();
}
void otaLoop() {
ArduinoOTA.handle();
}

+ 319
- 121
code/espurna/relay.ino View File

@ -2,11 +2,11 @@
RELAY MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <EEPROM.h>
#include <EEPROM_Rotate.h>
#include <Ticker.h>
#include <ArduinoJson.h>
#include <vector>
@ -17,12 +17,12 @@ typedef struct {
// Configuration variables
unsigned char pin; // GPIO pin for the relay
unsigned char type; // RELAY_TYPE_NORMAL, RELAY_TYPE_INVERSE or RELAY_TYPE_LATCHED
unsigned char type; // RELAY_TYPE_NORMAL, RELAY_TYPE_INVERSE, RELAY_TYPE_LATCHED or RELAY_TYPE_LATCHED_INVERSE
unsigned char reset_pin; // GPIO to reset the relay if RELAY_TYPE_LATCHED
unsigned char pulse; // RELAY_PULSE_NONE, RELAY_PULSE_OFF or RELAY_PULSE_ON
unsigned long pulse_ms; // Pulse length in millis
unsigned long delay_on; // Delay to turn relay ON
unsigned long delay_off; // Delay to turn relay OFF
unsigned char pulse; // RELAY_PULSE_NONE, RELAY_PULSE_OFF or RELAY_PULSE_ON
unsigned long pulse_ms; // Pulse length in millis
// Status variables
@ -77,9 +77,40 @@ void _relayProviderStatus(unsigned char id, bool status) {
#endif
#if RELAY_PROVIDER == RELAY_PROVIDER_STM
Serial.flush();
Serial.write(0xA0);
Serial.write(id + 1);
Serial.write(status);
Serial.write(0xA1 + status + id);
Serial.flush();
#endif
#if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
lightState(status);
// 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() + 1) {
if (id == 0) {
lightState(status);
} else {
lightState(id-1, status);
}
} else {
lightState(status);
}
lightUpdate(true, true);
#endif
#if RELAY_PROVIDER == RELAY_PROVIDER_RELAY
@ -87,28 +118,99 @@ void _relayProviderStatus(unsigned char id, bool status) {
digitalWrite(_relays[id].pin, status);
} else if (_relays[id].type == RELAY_TYPE_INVERSE) {
digitalWrite(_relays[id].pin, !status);
} else if (_relays[id].type == RELAY_TYPE_LATCHED) {
digitalWrite(_relays[id].pin, LOW);
digitalWrite(_relays[id].reset_pin, LOW);
} else if (_relays[id].type == RELAY_TYPE_LATCHED || _relays[id].type == RELAY_TYPE_LATCHED_INVERSE) {
bool pulse = RELAY_TYPE_LATCHED ? HIGH : LOW;
digitalWrite(_relays[id].pin, !pulse);
digitalWrite(_relays[id].reset_pin, !pulse);
if (status) {
digitalWrite(_relays[id].pin, HIGH);
digitalWrite(_relays[id].pin, pulse);
} else {
digitalWrite(_relays[id].reset_pin, HIGH);
digitalWrite(_relays[id].reset_pin, pulse);
}
delay(RELAY_LATCHING_PULSE);
digitalWrite(_relays[id].pin, LOW);
digitalWrite(_relays[id].reset_pin, LOW);
nice_delay(RELAY_LATCHING_PULSE);
digitalWrite(_relays[id].pin, !pulse);
digitalWrite(_relays[id].reset_pin, !pulse);
}
#endif
}
/**
* Walks the relay vector processing only those relays
* that have to change to the requested mode
* @bool mode Requested mode
*/
void _relayProcess(bool mode) {
unsigned long current_time = millis();
for (unsigned char id = 0; id < _relays.size(); id++) {
bool target = _relays[id].target_status;
// 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
if (target != mode) continue;
// Only process if the change_time has arrived
if (current_time < _relays[id].change_time) continue;
DEBUG_MSG_P(PSTR("[RELAY] #%d set to %s\n"), id, target ? "ON" : "OFF");
// Call the provider to perform the action
_relayProviderStatus(id, target);
// Send to Broker
#if BROKER_SUPPORT
brokerPublish(MQTT_TOPIC_RELAY, id, target ? "1" : "0");
#endif
// Send MQTT
#if MQTT_SUPPORT
relayMQTT(id);
#endif
if (!_relayRecursive) {
relayPulse(id);
_relaySaveTicker.once_ms(RELAY_SAVE_DELAY, relaySave);
#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
ledUpdate(true);
_relays[id].report = false;
_relays[id].group_report = false;
}
}
// -----------------------------------------------------------------------------
// RELAY
// -----------------------------------------------------------------------------
void relayPulse(unsigned char id) {
_relays[id].pulseTicker.detach();
byte mode = _relays[id].pulse;
if (mode == RELAY_PULSE_NONE) return;
unsigned long ms = _relays[id].pulse_ms;
@ -117,10 +219,12 @@ void relayPulse(unsigned char id) {
bool status = relayStatus(id);
bool pulseStatus = (mode == RELAY_PULSE_ON);
if (pulseStatus == status) {
_relays[id].pulseTicker.detach();
} else {
if (pulseStatus != status) {
DEBUG_MSG_P(PSTR("[RELAY] Scheduling relay #%d back in %lums (pulse)\n"), id, ms);
_relays[id].pulseTicker.once_ms(ms, relayToggle, id);
// Reconfigure after dynamic pulse
_relays[id].pulse = getSetting("relayPulse", id, RELAY_PULSE_MODE).toInt();
_relays[id].pulse_ms = 1000 * getSetting("relayTime", id, RELAY_PULSE_MODE).toFloat();
}
}
@ -146,6 +250,9 @@ bool relayStatus(unsigned char id, bool status, bool report, bool group_report)
rfbStatus(id, status);
#endif
// Update the pulse counter if the relay is already in the non-normal state (#454)
relayPulse(id);
} else {
unsigned int current_time = millis();
@ -253,9 +360,9 @@ void relaySave() {
if (relayStatus(i)) mask += bit;
bit += bit;
}
EEPROM.write(EEPROM_RELAY_STATUS, mask);
EEPROMr.write(EEPROM_RELAY_STATUS, mask);
DEBUG_MSG_P(PSTR("[RELAY] Saving mask: %d\n"), mask);
EEPROM.commit();
EEPROMr.commit();
}
void relayToggle(unsigned char id, bool report, bool group_report) {
@ -333,7 +440,7 @@ void _relayBoot() {
bool trigger_save = false;
// Get last statuses from EEPROM
unsigned char mask = EEPROM.read(EEPROM_RELAY_STATUS);
unsigned char mask = EEPROMr.read(EEPROM_RELAY_STATUS);
DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %d\n"), mask);
// Walk the relays
@ -345,7 +452,7 @@ void _relayBoot() {
case RELAY_BOOT_SAME:
status = ((mask & bit) == bit);
break;
case RELAY_BOOT_TOOGLE:
case RELAY_BOOT_TOGGLE:
status = ((mask & bit) != bit);
mask ^= bit;
trigger_save = true;
@ -360,26 +467,45 @@ void _relayBoot() {
}
_relays[i].current_status = !status;
_relays[i].target_status = status;
_relays[i].change_time = millis();
#if RELAY_PROVIDER == RELAY_PROVIDER_STM
_relays[i].change_time = millis() + 3000 + 1000 * i;
#else
_relays[i].change_time = millis();
#endif
bit <<= 1;
}
// Save if there is any relay in the RELAY_BOOT_TOOGLE mode
// Save if there is any relay in the RELAY_BOOT_TOGGLE mode
if (trigger_save) {
EEPROM.write(EEPROM_RELAY_STATUS, mask);
EEPROM.commit();
EEPROMr.write(EEPROM_RELAY_STATUS, mask);
EEPROMr.commit();
}
_relayRecursive = false;
}
void _relayConfigure() {
for (unsigned int i=0; i<_relays.size(); i++) {
pinMode(_relays[i].pin, OUTPUT);
if (_relays[i].type == RELAY_TYPE_LATCHED || _relays[i].type == RELAY_TYPE_LATCHED_INVERSE) {
pinMode(_relays[i].reset_pin, OUTPUT);
}
_relays[i].pulse = getSetting("relayPulse", i, RELAY_PULSE_MODE).toInt();
_relays[i].pulse_ms = 1000 * getSetting("relayTime", i, RELAY_PULSE_MODE).toFloat();
}
}
//------------------------------------------------------------------------------
// WEBSOCKETS
//------------------------------------------------------------------------------
#if WEB_SUPPORT
bool _relayWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "relay", 5) == 0);
}
void _relayWebSocketUpdate(JsonObject& root) {
JsonArray& relay = root.createNestedArray("relayStatus");
for (unsigned char i=0; i<relayCount(); i++) {
@ -407,6 +533,7 @@ void _relayWebSocketOnStart(JsonObject& root) {
#if MQTT_SUPPORT
line["group"] = getSetting("mqttGroup", i, "");
line["group_inv"] = getSetting("mqttGroupInv", i, 0).toInt();
line["on_disc"] = getSetting("relayOnDisc", i, 0).toInt();
#endif
}
@ -419,7 +546,7 @@ void _relayWebSocketOnStart(JsonObject& root) {
}
void _relayWebSocketOnAction(const char * action, JsonObject& data) {
void _relayWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "relay") != 0) return;
@ -454,19 +581,11 @@ void _relayWebSocketOnAction(const char * action, JsonObject& data) {
}
void _relayConfigure() {
for (unsigned int i=0; i<_relays.size(); i++) {
pinMode(_relays[i].pin, OUTPUT);
if (_relays[i].type == RELAY_TYPE_LATCHED) pinMode(_relays[i].reset_pin, OUTPUT);
_relays[i].pulse = getSetting("relayPulse", i, RELAY_PULSE_MODE).toInt();
_relays[i].pulse_ms = 1000 * getSetting("relayTime", i, RELAY_PULSE_MODE).toFloat();
}
}
void relaySetupWS() {
wsOnSendRegister(_relayWebSocketOnStart);
wsOnActionRegister(_relayWebSocketOnAction);
wsOnAfterParseRegister(_relayConfigure);
wsOnReceiveRegister(_relayWebSocketOnReceive);
}
#endif // WEB_SUPPORT
@ -482,12 +601,12 @@ void relaySetupAPI() {
// API entry points (protected with apikey)
for (unsigned int relayID=0; relayID<relayCount(); relayID++) {
char key[15];
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_RELAY, relayID);
char key[20];
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_RELAY, relayID);
apiRegister(key,
[relayID](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), relayStatus(relayID) ? 1 : 0);
snprintf_P(buffer, len, PSTR("%d"), _relays[relayID].target_status ? 1 : 0);
},
[relayID](const char * payload) {
@ -509,6 +628,30 @@ void relaySetupAPI() {
}
);
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_PULSE, relayID);
apiRegister(key,
[relayID](char * buffer, size_t len) {
dtostrf((double) _relays[relayID].pulse_ms / 1000, 1-len, 3, buffer);
},
[relayID](const char * payload) {
unsigned long pulse = 1000 * String(payload).toFloat();
if (0 == pulse) return;
if (RELAY_PULSE_NONE != _relays[relayID].pulse) {
DEBUG_MSG_P(PSTR("[RELAY] Overriding relay #%d pulse settings\n"), relayID);
}
_relays[relayID].pulse_ms = pulse;
_relays[relayID].pulse = relayStatus(relayID) ? RELAY_PULSE_ON : RELAY_PULSE_OFF;
relayToggle(relayID, true, false);
return;
}
);
}
}
@ -541,7 +684,6 @@ void relayMQTT(unsigned char id) {
mqttSendRaw(t.c_str(), status ? "1" : "0");
}
}
}
void relayMQTT() {
@ -551,13 +693,20 @@ void relayMQTT() {
}
void relayStatusWrap(unsigned char id, unsigned char value, bool is_group_topic) {
// Action to perform
if (value == 0) {
relayStatus(id, false, mqttForward(), !is_group_topic);
} else if (value == 1) {
relayStatus(id, true, mqttForward(), !is_group_topic);
} else if (value == 2) {
relayToggle(id, true, true);
switch (value) {
case 0:
relayStatus(id, false, mqttForward(), !is_group_topic);
break;
case 1:
relayStatus(id, true, mqttForward(), !is_group_topic);
break;
case 2:
relayToggle(id, true, true);
break;
default:
_relays[id].report = true;
relayMQTT(id);
break;
}
}
@ -571,9 +720,14 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
#endif
// Subscribe to own /set topic
char buffer[strlen(MQTT_TOPIC_RELAY) + 3];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RELAY);
mqttSubscribe(buffer);
char relay_topic[strlen(MQTT_TOPIC_RELAY) + 3];
snprintf_P(relay_topic, sizeof(relay_topic), PSTR("%s/+"), MQTT_TOPIC_RELAY);
mqttSubscribe(relay_topic);
// Subscribe to pulse topic
char pulse_topic[strlen(MQTT_TOPIC_PULSE) + 3];
snprintf_P(pulse_topic, sizeof(pulse_topic), PSTR("%s/+"), MQTT_TOPIC_PULSE);
mqttSubscribe(pulse_topic);
// Subscribe to group topics
for (unsigned int i=0; i < _relays.size(); i++) {
@ -585,26 +739,53 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
if (type == MQTT_MESSAGE_EVENT) {
// Check relay topic
String t = mqttSubtopic((char *) topic);
if (t.startsWith(MQTT_TOPIC_RELAY)) {
String t = mqttMagnitude((char *) topic);
// Get value
unsigned char value = relayParsePayload(payload);
if (value == 0xFF) return;
// magnitude is relay/#/pulse
if (t.startsWith(MQTT_TOPIC_PULSE)) {
unsigned int id = t.substring(strlen(MQTT_TOPIC_PULSE)+1).toInt();
if (id >= relayCount()) {
DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), id);
return;
}
unsigned long pulse = 1000 * String(payload).toFloat();
if (0 == pulse) return;
if (RELAY_PULSE_NONE != _relays[id].pulse) {
DEBUG_MSG_P(PSTR("[RELAY] Overriding relay #%d pulse settings\n"), id);
}
_relays[id].pulse_ms = pulse;
_relays[id].pulse = relayStatus(id) ? RELAY_PULSE_ON : RELAY_PULSE_OFF;
relayToggle(id, true, false);
return;
}
// magnitude is relay/#
if (t.startsWith(MQTT_TOPIC_RELAY)) {
// Get relay ID
unsigned int id = t.substring(strlen(MQTT_TOPIC_RELAY)+1).toInt();
if (id >= relayCount()) {
DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), id);
} else {
relayStatusWrap(id, value, false);
return;
}
return;
// Get value
unsigned char value = relayParsePayload(payload);
if (value == 0xFF) return;
relayStatusWrap(id, value, false);
return;
}
// Check group topics
for (unsigned int i=0; i < _relays.size(); i++) {
@ -629,6 +810,20 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
}
if (type == MQTT_DISCONNECT_EVENT) {
for (unsigned int i=0; i < _relays.size(); i++){
int reaction = getSetting("relayOnDisc", i, 0).toInt();
if (1 == reaction) { // switch relay OFF
DEBUG_MSG_P(PSTR("[RELAY] Reset relay (%d) due to MQTT disconnection\n"), i);
relayStatusWrap(i, false, false);
} else if(2 == reaction) { // switch relay ON
DEBUG_MSG_P(PSTR("[RELAY] Set relay (%d) due to MQTT disconnection\n"), i);
relayStatusWrap(i, true, false);
}
}
}
}
void relaySetupMQTT() {
@ -642,50 +837,99 @@ void relaySetupMQTT() {
//------------------------------------------------------------------------------
#if INFLUXDB_SUPPORT
void relayInfluxDB(unsigned char id) {
if (id >= _relays.size()) return;
idbSend(MQTT_TOPIC_RELAY, id, relayStatus(id) ? "1" : "0");
}
#endif
//------------------------------------------------------------------------------
// Settings
//------------------------------------------------------------------------------
#if TERMINAL_SUPPORT
void _relayInitCommands() {
settingsRegisterCommand(F("RELAY"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
int id = String(e->argv[1]).toInt();
if (id >= relayCount()) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong relayID (%d)\n"), id);
return;
}
if (e->argc > 2) {
int value = String(e->argv[2]).toInt();
if (value == 2) {
relayToggle(id);
} else {
relayStatus(id, value == 1);
}
}
DEBUG_MSG_P(PSTR("Status: %s\n"), _relays[id].target_status ? "true" : "false");
if (_relays[id].pulse != RELAY_PULSE_NONE) {
DEBUG_MSG_P(PSTR("Pulse: %s\n"), (_relays[id].pulse == RELAY_PULSE_ON) ? "ON" : "OFF");
DEBUG_MSG_P(PSTR("Pulse time: %d\n"), _relays[id].pulse_ms);
}
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
#endif // TERMINAL_SUPPORT
//------------------------------------------------------------------------------
// Setup
//------------------------------------------------------------------------------
void _relayLoop() {
_relayProcess(false);
_relayProcess(true);
}
void relaySetup() {
// Dummy relays for AI Light, Magic Home LED Controller, H801,
// Sonoff Dual and Sonoff RF Bridge
#ifdef DUMMY_RELAY_COUNT
#if DUMMY_RELAY_COUNT > 0
unsigned int _delay_on[8] = {RELAY1_DELAY_ON, RELAY2_DELAY_ON, RELAY3_DELAY_ON, RELAY4_DELAY_ON, RELAY5_DELAY_ON, RELAY6_DELAY_ON, RELAY7_DELAY_ON, RELAY8_DELAY_ON};
unsigned int _delay_off[8] = {RELAY1_DELAY_OFF, RELAY2_DELAY_OFF, RELAY3_DELAY_OFF, RELAY4_DELAY_OFF, RELAY5_DELAY_OFF, RELAY6_DELAY_OFF, RELAY7_DELAY_OFF, RELAY8_DELAY_OFF};
for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL});
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL,0,_delay_on[i], _delay_off[i]});
}
#else
#ifdef RELAY1_PIN
#if RELAY1_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY1_PIN, RELAY1_TYPE, RELAY1_RESET_PIN, RELAY1_DELAY_ON, RELAY1_DELAY_OFF });
#endif
#ifdef RELAY2_PIN
#if RELAY2_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY2_PIN, RELAY2_TYPE, RELAY2_RESET_PIN, RELAY2_DELAY_ON, RELAY2_DELAY_OFF });
#endif
#ifdef RELAY3_PIN
#if RELAY3_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY3_PIN, RELAY3_TYPE, RELAY3_RESET_PIN, RELAY3_DELAY_ON, RELAY3_DELAY_OFF });
#endif
#ifdef RELAY4_PIN
#if RELAY4_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY4_PIN, RELAY4_TYPE, RELAY4_RESET_PIN, RELAY4_DELAY_ON, RELAY4_DELAY_OFF });
#endif
#ifdef RELAY5_PIN
#if RELAY5_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY5_PIN, RELAY5_TYPE, RELAY5_RESET_PIN, RELAY5_DELAY_ON, RELAY5_DELAY_OFF });
#endif
#ifdef RELAY6_PIN
#if RELAY6_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY6_PIN, RELAY6_TYPE, RELAY6_RESET_PIN, RELAY6_DELAY_ON, RELAY6_DELAY_OFF });
#endif
#ifdef RELAY7_PIN
#if RELAY7_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY7_PIN, RELAY7_TYPE, RELAY7_RESET_PIN, RELAY7_DELAY_ON, RELAY7_DELAY_OFF });
#endif
#ifdef RELAY8_PIN
#if RELAY8_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY8_PIN, RELAY8_TYPE, RELAY8_RESET_PIN, RELAY8_DELAY_ON, RELAY8_DELAY_OFF });
#endif
@ -694,67 +938,21 @@ void relaySetup() {
_relayBackwards();
_relayConfigure();
_relayBoot();
relayLoop();
_relayLoop();
espurnaRegisterLoop(_relayLoop);
#if WEB_SUPPORT
relaySetupAPI();
relaySetupWS();
#endif
#if MQTT_SUPPORT
relaySetupMQTT();
#endif
#if TERMINAL_SUPPORT
_relayInitCommands();
#endif
DEBUG_MSG_P(PSTR("[RELAY] Number of relays: %d\n"), _relays.size());
}
void relayLoop(void) {
unsigned char id;
for (id = 0; id < _relays.size(); id++) {
unsigned int current_time = millis();
bool status = _relays[id].target_status;
if ((_relays[id].current_status != status)
&& (current_time >= _relays[id].change_time)) {
DEBUG_MSG_P(PSTR("[RELAY] #%d set to %s\n"), id, status ? "ON" : "OFF");
// Call the provider to perform the action
_relayProviderStatus(id, status);
// Send MQTT
#if MQTT_SUPPORT
relayMQTT(id);
#endif
if (!_relayRecursive) {
relayPulse(id);
_relaySaveTicker.once_ms(RELAY_SAVE_DELAY, relaySave);
#if WEB_SUPPORT
wsSend(_relayWebSocketUpdate);
#endif
}
#if DOMOTICZ_SUPPORT
domoticzSendRelay(id);
#endif
#if INFLUXDB_SUPPORT
relayInfluxDB(id);
#endif
// Flag relay-based LEDs to update status
ledUpdate(true);
_relays[id].report = false;
_relays[id].group_report = false;
}
}
}

+ 148
- 62
code/espurna/rf.ino View File

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

+ 310
- 109
code/espurna/rfbridge.ino View File

@ -2,28 +2,40 @@
ITEAD RF BRIDGE MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#ifdef ITEAD_SONOFF_RFBRIDGE
#include <vector>
#include <queue>
#include <Ticker.h>
#if RFB_DIRECT
#include <RCSwitch.h>
#endif
// -----------------------------------------------------------------------------
// DEFINITIONS
// -----------------------------------------------------------------------------
#define RF_MESSAGE_SIZE 9
#define RF_CODE_START 0xAA
#define RF_CODE_ACK 0xA0
#define RF_CODE_LEARN 0xA1
#define RF_CODE_LEARN_KO 0xA2
#define RF_CODE_LEARN_OK 0xA3
#define RF_CODE_RFIN 0xA4
#define RF_CODE_RFOUT 0xA5
#define RF_CODE_STOP 0x55
#define RF_MESSAGE_SIZE 9
#define RF_MAX_MESSAGE_SIZE (112+4)
#define RF_CODE_START 0xAA
#define RF_CODE_ACK 0xA0
#define RF_CODE_LEARN 0xA1
#define RF_CODE_LEARN_KO 0xA2
#define RF_CODE_LEARN_OK 0xA3
#define RF_CODE_RFIN 0xA4
#define RF_CODE_RFOUT 0xA5
#define RF_CODE_SNIFFING_ON 0xA6
#define RF_CODE_SNIFFING_OFF 0xA7
#define RF_CODE_RFOUT_NEW 0xA8
#define RF_CODE_LEARN_NEW 0xA9
#define RF_CODE_LEARN_KO_NEW 0xAA
#define RF_CODE_LEARN_OK_NEW 0xAB
#define RF_CODE_RFOUT_BUCKET 0xB0
#define RF_CODE_STOP 0x55
// -----------------------------------------------------------------------------
// GLOBALS TO THE MODULE
@ -39,16 +51,51 @@ typedef struct {
byte code[RF_MESSAGE_SIZE];
byte times;
} rfb_message_t;
std::vector<rfb_message_t> _rfb_message_queue;
Ticker _rfbTicker;
static std::queue<rfb_message_t> _rfb_message_queue;
Ticker _rfb_ticker;
bool _rfb_ticker_active = false;
#if RFB_DIRECT
RCSwitch * _rfModem;
bool _learning = false;
#endif
// -----------------------------------------------------------------------------
// PRIVATES
// -----------------------------------------------------------------------------
/*
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)
*/
static bool _rfbToChar(byte * in, char * out, int n = RF_MESSAGE_SIZE) {
for (unsigned char p = 0; p<n; p++) {
sprintf_P(&out[p*2], PSTR("%02X"), in[p]);
}
return true;
}
void _rfbWebSocketOnSend(JsonObject& root) {
root["rfbVisible"] = 1;
root["rfbCount"] = relayCount();
#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++) {
@ -60,31 +107,37 @@ void _rfbWebSocketOnSend(JsonObject& root) {
}
}
void _rfbWebSocketOnAction(const char * action, JsonObject& data) {
void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "rfblearn") == 0) rfbLearn(data["id"], data["status"]);
if (strcmp(action, "rfbforget") == 0) rfbForget(data["id"], data["status"]);
if (strcmp(action, "rfbsend") == 0) rfbStore(data["id"], data["status"], data["data"].as<const char*>());
}
void _rfbAck() {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_ACK);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
#if not RFB_DIRECT
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_ACK);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
#endif
}
void _rfbLearn() {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_LEARN);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
#if RFB_DIRECT
DEBUG_MSG_P(PSTR("[RFBRIDGE] Entering LEARN mode\n"));
_learning = true;
#else
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_LEARN);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
#endif
#if WEB_SUPPORT
char buffer[100];
@ -94,26 +147,49 @@ void _rfbLearn() {
}
void _rfbSend(byte * message) {
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_RFOUT);
for (unsigned char j=0; j<RF_MESSAGE_SIZE; j++) {
void _rfbSendRaw(const byte *message, const unsigned char n = RF_MESSAGE_SIZE) {
for (unsigned char j=0; j<n; j++) {
Serial.write(message[j]);
}
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
}
void _rfbSend(byte * message) {
#if RFB_DIRECT
unsigned int protocol = message[1];
unsigned int timing =
(message[2] << 8) |
(message[3] << 0) ;
unsigned int bitlength = message[4];
unsigned long rf_code =
(message[5] << 24) |
(message[6] << 16) |
(message[7] << 8) |
(message[8] << 0) ;
_rfModem->setProtocol(protocol);
if (timing > 0) {
_rfModem->setPulseLength(timing);
}
_rfModem->send(rf_code, bitlength);
_rfModem->resetAvailable();
#else
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_RFOUT);
_rfbSendRaw(message);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
#endif
}
void _rfbSend() {
// Check if there is something in the queue
if (_rfb_message_queue.size() == 0) return;
if (_rfb_message_queue.empty()) return;
// Pop the first element
rfb_message_t message = _rfb_message_queue.front();
_rfb_message_queue.erase(_rfb_message_queue.begin());
_rfb_message_queue.pop();
// Send the message
_rfbSend(message.code);
@ -121,31 +197,51 @@ void _rfbSend() {
// If it should be further sent, push it to the stack again
if (message.times > 1) {
message.times = message.times - 1;
_rfb_message_queue.push_back(message);
_rfb_message_queue.push(message);
}
// if there are still messages in the queue...
if (_rfb_message_queue.size() > 0) {
_rfbTicker.once_ms(RF_SEND_DELAY, _rfbSend);
if (_rfb_message_queue.empty()) {
_rfb_ticker.detach();
_rfb_ticker_active = false;
}
}
void _rfbSend(byte * code, int times) {
void _rfbSend(byte * code, unsigned char times) {
#if RFB_DIRECT
times = 1;
#endif
char buffer[RF_MESSAGE_SIZE];
_rfbToChar(code, buffer);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending MESSAGE '%s' %d time(s)\n"), buffer, times);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Enqueuing MESSAGE '%s' %d time(s)\n"), buffer, times);
rfb_message_t message;
memcpy(message.code, code, RF_MESSAGE_SIZE);
message.times = times;
_rfb_message_queue.push_back(message);
_rfbSend();
_rfb_message_queue.push(message);
// Enable the ticker if not running
if (!_rfb_ticker_active) {
_rfb_ticker_active = true;
_rfb_ticker.attach_ms(RF_SEND_DELAY, _rfbSend);
}
}
bool _rfbMatch(char * code, unsigned char& relayID, unsigned char& value) {
#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);
_rfbSendRaw(code, length);
}
#endif // RF_RAW_SUPPORT
bool _rfbMatch(char* code, unsigned char& relayID, unsigned char& value, char* buffer = NULL) {
if (strlen(code) != 18) return false;
@ -161,6 +257,7 @@ bool _rfbMatch(char * code, unsigned char& relayID, unsigned char& value) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Match ON code for relay %d\n"), i);
value = 1;
found = true;
if (buffer) strcpy(buffer, code_on.c_str());
}
String code_off = rfbRetrieve(i, false);
@ -168,6 +265,7 @@ bool _rfbMatch(char * code, unsigned char& relayID, unsigned char& value) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Match OFF code for relay %d\n"), i);
if (found) value = 2;
found = true;
if (buffer) strcpy(buffer, code_off.c_str());
}
if (found) {
@ -199,12 +297,25 @@ void _rfbDecode() {
#endif
}
unsigned char id;
unsigned char status;
bool matched = false;
if (action == RF_CODE_LEARN_OK || action == RF_CODE_RFIN) {
_rfbAck();
_rfbToChar(&_uartbuf[1], buffer);
/* Look for the code, possibly replacing the code with the exact learned one on match
* we want to do this on learn too to be sure that the learned code is the same if it
* is equivalent
*/
DEBUG_MSG_P(PSTR("[RFBRIDGE] Received message '%s'\n"), buffer);
matched = _rfbMatch(buffer, id, status, buffer);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Matched message '%s'\n"), buffer);
#if MQTT_SUPPORT
_rfbToChar(&_uartbuf[1], buffer);
mqttSend(MQTT_TOPIC_RFIN, buffer);
#endif
_rfbAck();
}
if (action == RF_CODE_LEARN_OK) {
@ -222,13 +333,8 @@ void _rfbDecode() {
}
if (action == RF_CODE_RFIN) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Forward message '%s'\n"), buffer);
// Look for the code
unsigned char id;
unsigned char status = 0;
if (_rfbMatch(buffer, id, status)) {
if (matched) {
_rfbin = true;
if (status == 2) {
relayToggle(id);
@ -242,30 +348,77 @@ void _rfbDecode() {
}
void _rfbReceive() {
static bool receiving = false;
while (Serial.available()) {
yield();
byte c = Serial.read();
//DEBUG_MSG_P(PSTR("[RFBRIDGE] Received 0x%02X\n"), c);
if (receiving) {
if (c == RF_CODE_STOP) {
#if RFB_DIRECT
static long learn_start = 0;
if (!_learning && learn_start) {
learn_start = 0;
}
if (_learning) {
if (!learn_start) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] arming learn timeout\n"));
learn_start = millis();
}
if (learn_start > 0 && millis() - learn_start > RF_LEARN_TIMEOUT) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] learn timeout triggered\n"));
memset(_uartbuf, 0, sizeof(_uartbuf));
_uartbuf[0] = RF_CODE_LEARN_KO;
_rfbDecode();
receiving = false;
} else {
_uartbuf[_uartpos++] = c;
_learning = false;
}
} else if (c == RF_CODE_START) {
_uartpos = 0;
receiving = true;
}
}
if (_rfModem->available()) {
static unsigned long last = 0;
if (millis() - last > RF_DEBOUNCE) {
last = millis();
unsigned long rf_code = _rfModem->getReceivedValue();
if ( rf_code > 0) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Received code: %08X\n"), rf_code);
unsigned int timing = _rfModem->getReceivedDelay();
memset(_uartbuf, 0, sizeof(_uartbuf));
unsigned char *msgbuf = _uartbuf + 1;
_uartbuf[0] = _learning ? RF_CODE_LEARN_OK: RF_CODE_RFIN;
msgbuf[0] = 0xC0;
msgbuf[1] = _rfModem->getReceivedProtocol();
msgbuf[2] = timing >> 8;
msgbuf[3] = timing >> 0;
msgbuf[4] = _rfModem->getReceivedBitlength();
msgbuf[5] = rf_code >> 24;
msgbuf[6] = rf_code >> 16;
msgbuf[7] = rf_code >> 8;
msgbuf[8] = rf_code >> 0;
_rfbDecode();
_learning = false;
}
}
_rfModem->resetAvailable();
}
#else
static bool receiving = false;
while (Serial.available()) {
yield();
byte c = Serial.read();
//DEBUG_MSG_P(PSTR("[RFBRIDGE] Received 0x%02X\n"), c);
if (receiving) {
if (c == RF_CODE_STOP && (_uartpos == 1 || _uartpos == RF_MESSAGE_SIZE + 1)) {
_rfbDecode();
receiving = false;
} else if (_uartpos <= RF_MESSAGE_SIZE) {
_uartbuf[_uartpos++] = c;
} else {
// wrong message, should have received a RF_CODE_STOP
receiving = false;
}
} else if (c == RF_CODE_START) {
_uartpos = 0;
receiving = true;
}
}
#endif
}
bool _rfbCompare(const char * code1, const char * code2) {
@ -276,29 +429,6 @@ bool _rfbSameOnOff(unsigned char id) {
return _rfbCompare(rfbRetrieve(id, true).c_str(), rfbRetrieve(id, false).c_str());
}
/*
From an hexa char array ("A220EE...") to a byte array (half the size)
*/
bool _rfbToArray(const char * in, byte * out) {
if (strlen(in) != RF_MESSAGE_SIZE * 2) return false;
char tmp[3] = {0};
for (unsigned char p = 0; p<RF_MESSAGE_SIZE; p++) {
memcpy(tmp, &in[p*2], 2);
out[p] = strtol(tmp, NULL, 16);
}
return true;
}
/*
From a byte array to an hexa char array ("A220EE...", double the size)
*/
bool _rfbToChar(byte * in, char * out) {
for (unsigned char p = 0; p<RF_MESSAGE_SIZE; p++) {
sprintf_P(&out[p*2], PSTR("%02X"), in[p]);
}
return true;
}
#if MQTT_SUPPORT
void _rfbMqttCallback(unsigned int type, const char * topic, const char * payload) {
@ -307,12 +437,15 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RFLEARN);
mqttSubscribe(buffer);
mqttSubscribe(MQTT_TOPIC_RFOUT);
#if RF_RAW_SUPPORT
mqttSubscribe(MQTT_TOPIC_RFRAW);
#endif
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttSubtopic((char *) topic);
String t = mqttMagnitude((char *) topic);
// Check if should go into learn mode
if (t.startsWith(MQTT_TOPIC_RFLEARN)) {
@ -324,14 +457,21 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
}
_learnStatus = (char)payload[0] != '0';
_rfbLearn();
return;
}
if (t.equals(MQTT_TOPIC_RFOUT)) {
bool isRFOut = t.equals(MQTT_TOPIC_RFOUT);
#if RF_RAW_SUPPORT
bool isRFRaw = !isRFOut && t.equals(MQTT_TOPIC_RFRAW);
#else
bool isRFRaw = false;
#endif
if (isRFOut || isRFRaw) {
// The payload may be a code in HEX format ([0-9A-Z]{18}) or
// the code comma the number of times to transmit it.
byte message[RF_MESSAGE_SIZE];
char * tok = strtok((char *) payload, ",");
// Check if a switch is linked to that message
@ -346,11 +486,28 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
return;
}
if (_rfbToArray(tok, message)) {
tok = strtok(NULL, ",");
byte times = (tok != NULL) ? atoi(tok) : 1;
_rfbSend(message, times);
}
#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
}
@ -377,15 +534,47 @@ String rfbRetrieve(unsigned char id, bool status) {
}
void rfbStatus(unsigned char id, bool status) {
String value = rfbRetrieve(id, status);
if (value.length() > 0) {
bool same = _rfbSameOnOff(id);
byte message[RF_MESSAGE_SIZE];
_rfbToArray(value.c_str(), message);
unsigned char times = RF_SEND_TIMES;
if (same) times = _rfbin ? 0 : 1;
_rfbSend(message, times);
#if RF_RAW_SUPPORT
byte message[RF_MAX_MESSAGE_SIZE];
int len = _rfbToArray(value.c_str(), message, 0);
if (len == RF_MESSAGE_SIZE && // probably a standard msg
(message[0] != RF_CODE_START || // raw would start with 0xAA
message[1] != RF_CODE_RFOUT_BUCKET || // followed by 0xB0,
message[2] + 4 != len || // needs a valid length,
message[len-1] != RF_CODE_STOP)) { // and finish with 0x55
if (!_rfbin) {
unsigned char times = same ? 1 : RF_SEND_TIMES;
_rfbSend(message, times);
}
} else {
_rfbSendRawOnce(message, len); // send a raw message
}
#else // RF_RAW_SUPPORT
if (!_rfbin) {
byte message[RF_MESSAGE_SIZE];
_rfbToArray(value.c_str(), message);
unsigned char times = same ? 1 : RF_SEND_TIMES;
_rfbSend(message, times);
}
#endif // RF_RAW_SUPPORT
}
_rfbin = false;
}
void rfbLearn(unsigned char id, bool status) {
@ -424,6 +613,18 @@ void rfbSetup() {
wsOnActionRegister(_rfbWebSocketOnAction);
#endif
#if RFB_DIRECT
_rfModem = new RCSwitch();
_rfModem->enableReceive(RFB_RX_PIN);
_rfModem->enableTransmit(RFB_TX_PIN);
_rfModem->setRepeatTransmit(6);
DEBUG_MSG_P(PSTR("[RFBRIDGE] RF receiver on GPIO %u\n"), RFB_RX_PIN);
DEBUG_MSG_P(PSTR("[RFBRIDGE] RF transmitter on GPIO %u\n"), RFB_TX_PIN);
#endif
// Register loop
espurnaRegisterLoop(rfbLoop);
}
void rfbLoop() {


+ 227
- 0
code/espurna/scheduler.ino View File

@ -0,0 +1,227 @@
/*
SCHEDULER MODULE
Copyright (C) 2017 by faina09
Adapted by Xose Pérez <xose dot perez at gmail dot com>
*/
#if SCHEDULER_SUPPORT
#include <TimeLib.h>
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _schWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "sch", 3) == 0);
}
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, "");
}
}
}
#endif // WEB_SUPPORT
// -----------------------------------------------------------------------------
void _schConfigure() {
bool delete_flag = false;
for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
int sch_switch = getSetting("schSwitch", i, 0xFF).toInt();
if (sch_switch == 0xFF) delete_flag = true;
if (delete_flag) {
delSetting("schEnabled", i);
delSetting("schSwitch", i);
delSetting("schAction", i);
delSetting("schHour", i);
delSetting("schMinute", i);
delSetting("schWDs", i);
delSetting("schType", i);
delSetting("schUTC", i);
} else {
#if DEBUG_SUPPORT
bool sch_enabled = getSetting("schEnabled", i, 1).toInt() == 1;
int sch_action = getSetting("schAction", i, 0).toInt();
int sch_hour = getSetting("schHour", i, 0).toInt();
int sch_minute = getSetting("schMinute", i, 0).toInt();
bool sch_utc = getSetting("schUTC", i, 0).toInt() == 1;
String sch_weekdays = getSetting("schWDs", i, "");
unsigned char sch_type = getSetting("schType", i, SCHEDULER_TYPE_SWITCH).toInt();
DEBUG_MSG_P(
PSTR("[SCH] Schedule #%d: %s #%d to %d at %02d:%02d %s on %s%s\n"),
i, SCHEDULER_TYPE_SWITCH == sch_type ? "switch" : "channel", sch_switch,
sch_action, sch_hour, sch_minute, sch_utc ? "UTC" : "local time",
(char *) sch_weekdays.c_str(),
sch_enabled ? "" : " (disabled)"
);
#endif // DEBUG_SUPPORT
}
}
}
bool _schIsThisWeekday(time_t t, String weekdays){
// Convert from Sunday to Monday as day 1
int w = weekday(t) - 1;
if (0 == w) w = 7;
char pch;
char * p = (char *) weekdays.c_str();
unsigned char position = 0;
while ((pch = p[position++])) {
if ((pch - '0') == w) return true;
}
return false;
}
int _schMinutesLeft(time_t t, unsigned char schedule_hour, unsigned char schedule_minute){
unsigned char now_hour = hour(t);
unsigned char now_minute = minute(t);
return (schedule_hour - now_hour) * 60 + schedule_minute - now_minute;
}
void _schCheck() {
time_t local_time = now();
time_t utc_time = ntpLocal2UTC(local_time);
// Check schedules
for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
int sch_switch = getSetting("schSwitch", i, 0xFF).toInt();
if (sch_switch == 0xFF) break;
// Skip disabled schedules
if (getSetting("schEnabled", i, 1).toInt() == 0) continue;
// Get the datetime used for the calculation
bool sch_utc = getSetting("schUTC", i, 0).toInt() == 1;
time_t t = sch_utc ? utc_time : local_time;
String sch_weekdays = getSetting("schWDs", i, "");
if (_schIsThisWeekday(t, sch_weekdays)) {
int sch_hour = getSetting("schHour", i, 0).toInt();
int sch_minute = getSetting("schMinute", i, 0).toInt();
int minutes_to_trigger = _schMinutesLeft(t, sch_hour, sch_minute);
if (minutes_to_trigger == 0) {
unsigned char sch_type = getSetting("schType", i, SCHEDULER_TYPE_SWITCH).toInt();
if (SCHEDULER_TYPE_SWITCH == sch_type) {
int sch_action = getSetting("schAction", i, 0).toInt();
DEBUG_MSG_P(PSTR("[SCH] Switching switch %d to %d\n"), sch_switch, sch_action);
if (sch_action == 2) {
relayToggle(sch_switch);
} else {
relayStatus(sch_switch, sch_action);
}
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (SCHEDULER_TYPE_DIM == sch_type) {
int sch_brightness = getSetting("schAction", i, -1).toInt();
DEBUG_MSG_P(PSTR("[SCH] Set channel %d value to %d\n"), sch_switch, sch_brightness);
lightChannel(sch_switch, sch_brightness);
lightUpdate(true, true);
}
#endif
DEBUG_MSG_P(PSTR("[SCH] Schedule #%d TRIGGERED!!\n"), i);
// Show minutes to trigger every 15 minutes
// or every minute if less than 15 minutes to scheduled time.
// This only works for schedules on this same day.
// For instance, if your scheduler is set for 00:01 you will only
// get one notification before the trigger (at 00:00)
} else if (minutes_to_trigger > 0) {
#if DEBUG_SUPPORT
if ((minutes_to_trigger % 15 == 0) || (minutes_to_trigger < 15)) {
DEBUG_MSG_P(
PSTR("[SCH] %d minutes to trigger schedule #%d\n"),
minutes_to_trigger, i
);
}
#endif
}
}
}
}
void _schLoop() {
// Check time has been sync'ed
if (!ntpSynced()) return;
// Check schedules every minute at hh:mm:00
static unsigned long last_minute = 60;
unsigned char current_minute = minute();
if (current_minute != last_minute) {
last_minute = current_minute;
_schCheck();
}
}
// -----------------------------------------------------------------------------
void schSetup() {
_schConfigure();
// Update websocket clients
#if WEB_SUPPORT
wsOnSendRegister(_schWebSocketOnSend);
wsOnReceiveRegister(_schWebSocketOnReceive);
wsOnAfterParseRegister(_schConfigure);
#endif
// Register loop
espurnaRegisterLoop(_schLoop);
}
#endif // SCHEDULER_SUPPORT

+ 537
- 104
code/espurna/sensor.ino View File

@ -2,7 +2,7 @@
SENSOR MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -28,57 +28,87 @@ typedef struct {
std::vector<BaseSensor *> _sensors;
std::vector<sensor_magnitude_t> _magnitudes;
bool _sensors_ready = false;
unsigned char _counts[MAGNITUDE_MAX];
bool _sensor_realtime = API_REAL_TIME_VALUES;
unsigned long _sensor_read_interval = 1000 * SENSOR_READ_INTERVAL;
unsigned char _sensor_report_every = SENSOR_REPORT_EVERY;
unsigned char _sensor_power_units = SENSOR_POWER_UNITS;
unsigned char _sensor_energy_units = SENSOR_ENERGY_UNITS;
unsigned char _sensor_temperature_units = SENSOR_TEMPERATURE_UNITS;
double _sensor_temperature_correction = SENSOR_TEMPERATURE_CORRECTION;
double _sensor_humidity_correction = SENSOR_HUMIDITY_CORRECTION;
String _sensor_energy_reset_ts = String();
// -----------------------------------------------------------------------------
// Private
// -----------------------------------------------------------------------------
String _magnitudeTopic(unsigned char type) {
char buffer[16] = {0};
if (type < MAGNITUDE_MAX) strncpy_P(buffer, magnitude_topics[type], sizeof(buffer));
return String(buffer);
}
unsigned char _magnitudeDecimals(unsigned char type) {
// Hardcoded decimals (these should be linked to the unit, instead of the magnitude)
if (type == MAGNITUDE_ENERGY ||
type == MAGNITUDE_ENERGY_DELTA) {
if (_sensor_energy_units == ENERGY_KWH) return 3;
}
if (type == MAGNITUDE_POWER_ACTIVE ||
type == MAGNITUDE_POWER_APPARENT ||
type == MAGNITUDE_POWER_REACTIVE) {
if (_sensor_power_units == POWER_KILOWATTS) return 3;
}
if (type < MAGNITUDE_MAX) return pgm_read_byte(magnitude_decimals + type);
return 0;
}
String _magnitudeUnits(unsigned char type) {
char buffer[8] = {0};
if (type < MAGNITUDE_MAX) {
if ((type == MAGNITUDE_TEMPERATURE) && (_sensor_temperature_units == TMP_FAHRENHEIT)) {
strncpy_P(buffer, magnitude_fahrenheit, sizeof(buffer));
} else {
strncpy_P(buffer, magnitude_units[type], sizeof(buffer));
}
}
return String(buffer);
}
double _magnitudeProcess(unsigned char type, double value) {
// Hardcoded conversions (these should be linked to the unit, instead of the magnitude)
if (type == MAGNITUDE_TEMPERATURE) {
if (_sensor_temperature_units == TMP_FAHRENHEIT) value = value * 1.8 + 32;
value = value + _sensor_temperature_correction;
}
if (type == MAGNITUDE_HUMIDITY) {
value = constrain(value + _sensor_humidity_correction, 0, 100);
}
if (type == MAGNITUDE_ENERGY ||
type == MAGNITUDE_ENERGY_DELTA) {
if (_sensor_energy_units == ENERGY_KWH) value = value / 3600000;
}
if (type == MAGNITUDE_POWER_ACTIVE ||
type == MAGNITUDE_POWER_APPARENT ||
type == MAGNITUDE_POWER_REACTIVE) {
if (_sensor_power_units == POWER_KILOWATTS) value = value / 1000;
}
return roundTo(value, _magnitudeDecimals(type));
}
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _sensorWebSocketOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, "pwr", 3) == 0) return true;
if (strncmp(key, "sns", 3) == 0) return true;
if (strncmp(key, "tmp", 3) == 0) return true;
if (strncmp(key, "hum", 3) == 0) return true;
if (strncmp(key, "energy", 6) == 0) return true;
return false;
}
void _sensorWebSocketSendData(JsonObject& root) {
char buffer[10];
bool hasTemperature = false;
bool hasHumidity = false;
JsonArray& list = root.createNestedArray("magnitudes");
for (unsigned char i=0; i<_magnitudes.size(); i++) {
@ -91,15 +121,23 @@ void _sensorWebSocketSendData(JsonObject& root) {
element["index"] = int(magnitude.global);
element["type"] = int(magnitude.type);
element["value"] = String(buffer);
element["units"] = _magnitudeUnits(magnitude.type);
element["description"] = magnitude.sensor->slot(magnitude.local);
element["units"] = magnitudeUnits(magnitude.type);
element["error"] = magnitude.sensor->error();
if (magnitude.type == MAGNITUDE_ENERGY) {
if (_sensor_energy_reset_ts.length() == 0) _sensorReset();
element["description"] = magnitude.sensor->slot(magnitude.local) + _sensor_energy_reset_ts;
} else {
element["description"] = magnitude.sensor->slot(magnitude.local);
}
if (magnitude.type == MAGNITUDE_TEMPERATURE) hasTemperature = true;
if (magnitude.type == MAGNITUDE_HUMIDITY) hasHumidity = true;
}
if (hasTemperature) root["temperatureVisible"] = 1;
if (hasHumidity) root["humidityVisible"] = 1;
}
@ -112,6 +150,7 @@ void _sensorWebSocketStart(JsonObject& root) {
#if EMON_ANALOG_SUPPORT
if (sensor->getID() == SENSOR_EMON_ANALOG_ID) {
root["emonVisible"] = 1;
root["pwrVisible"] = 1;
root["pwrVoltage"] = ((EmonAnalogSensor *) sensor)->getVoltage();
}
#endif
@ -119,6 +158,32 @@ void _sensorWebSocketStart(JsonObject& root) {
#if HLW8012_SUPPORT
if (sensor->getID() == SENSOR_HLW8012_ID) {
root["hlwVisible"] = 1;
root["pwrVisible"] = 1;
}
#endif
#if CSE7766_SUPPORT
if (sensor->getID() == SENSOR_CSE7766_ID) {
root["cseVisible"] = 1;
root["pwrVisible"] = 1;
}
#endif
#if V9261F_SUPPORT
if (sensor->getID() == SENSOR_V9261F_ID) {
root["pwrVisible"] = 1;
}
#endif
#if ECH1560_SUPPORT
if (sensor->getID() == SENSOR_ECH1560_ID) {
root["pwrVisible"] = 1;
}
#endif
#if PZEM004T_SUPPORT
if (sensor->getID() == SENSOR_PZEM004T_ID) {
root["pwrVisible"] = 1;
}
#endif
@ -127,8 +192,11 @@ void _sensorWebSocketStart(JsonObject& root) {
if (_magnitudes.size() > 0) {
root["sensorsVisible"] = 1;
//root["apiRealTime"] = _sensor_realtime;
root["pwrUnits"] = _sensor_power_units;
root["energyUnits"] = _sensor_energy_units;
root["tmpUnits"] = _sensor_temperature_units;
root["tmpCorrection"] = _sensor_temperature_correction;
root["humCorrection"] = _sensor_humidity_correction;
root["snsRead"] = _sensor_read_interval / 1000;
root["snsReport"] = _sensor_report_every;
}
@ -158,7 +226,7 @@ void _sensorAPISetup() {
sensor_magnitude_t magnitude = _magnitudes[magnitude_id];
String topic = _magnitudeTopic(magnitude.type);
String topic = magnitudeTopic(magnitude.type);
if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) topic = topic + "/" + String(magnitude.global);
apiRegister(topic.c_str(), [magnitude_id](char * buffer, size_t len) {
@ -173,6 +241,26 @@ void _sensorAPISetup() {
}
#endif
#if TERMINAL_SUPPORT
void _sensorInitCommands() {
settingsRegisterCommand(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"),
i,
magnitudeTopic(magnitude.type).c_str(),
magnitude.sensor->slot(magnitude.local).c_str(),
magnitudeTopic(magnitude.type).c_str(),
magnitude.global
);
}
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
#endif
void _sensorTick() {
for (unsigned char i=0; i<_sensors.size(); i++) {
_sensors[i]->tick();
@ -197,11 +285,40 @@ void _sensorPost() {
}
}
void _sensorReset() {
#if NTP_SUPPORT
if (ntpSynced()) {
_sensor_energy_reset_ts = String(" (since ") + ntpDateTime() + String(")");
}
#endif
}
// -----------------------------------------------------------------------------
// Sensor initialization
// -----------------------------------------------------------------------------
void _sensorInit() {
void _sensorLoad() {
/*
This is temporal, in the future sensors will be initialized based on
soft configuration (data stored in EEPROM config) so you will be able
to define and configure new sensors on the fly
At the time being, only enabled sensors (those with *_SUPPORT to 1) are being
loaded and initialized here. If you want to add new sensors of the same type
just duplicate the block and change the arguments for the set* methods.
Check the DHT block below for an example
*/
#if AM2320_SUPPORT
{
AM2320Sensor * sensor = new AM2320Sensor();
sensor->setAddress(AM2320_ADDRESS);
_sensors.push_back(sensor);
}
#endif
#if ANALOG_SUPPORT
{
@ -227,6 +344,14 @@ void _sensorInit() {
}
#endif
#if CSE7766_SUPPORT
{
CSE7766Sensor * sensor = new CSE7766Sensor();
sensor->setRX(CSE7766_PIN);
_sensors.push_back(sensor);
}
#endif
#if DALLAS_SUPPORT
{
DallasSensor * sensor = new DallasSensor();
@ -244,6 +369,19 @@ void _sensorInit() {
}
#endif
/*
// Example on how to add a second DHT sensor
// DHT2_PIN and DHT2_TYPE should be defined in sensors.h file
#if DHT_SUPPORT
{
DHTSensor * sensor = new DHTSensor();
sensor->setGPIO(DHT2_PIN);
sensor->setType(DHT2_TYPE);
_sensors.push_back(sensor);
}
#endif
*/
#if DIGITAL_SUPPORT
{
DigitalSensor * sensor = new DigitalSensor();
@ -312,6 +450,35 @@ void _sensorInit() {
}
#endif
#if GEIGER_SUPPORT
{
GeigerSensor * sensor = new GeigerSensor(); // Create instance of thr Geiger module.
sensor->setGPIO(GEIGER_PIN); // Interrupt pin of the attached geiger counter board.
sensor->setMode(GEIGER_PIN_MODE); // This pin is an input.
sensor->setDebounceTime(GEIGER_DEBOUNCE); // Debounce time 25ms, because https://github.com/Trickx/espurna/wiki/Geiger-counter
sensor->setInterruptMode(GEIGER_INTERRUPT_MODE); // Interrupt triggering: edge detection rising.
sensor->setCPM2SievertFactor(GEIGER_CPM2SIEVERT); // Conversion factor from counts per minute to µSv/h
_sensors.push_back(sensor);
}
#endif
#if GUVAS12SD_SUPPORT
{
GUVAS12SDSensor * sensor = new GUVAS12SDSensor();
sensor->setGPIO(GUVAS12SD_PIN);
_sensors.push_back(sensor);
}
#endif
#if HCSR04_SUPPORT
{
HCSR04Sensor * sensor = new HCSR04Sensor();
sensor->setTrigger(HCSR04_TRIGGER);
sensor->setEcho(HCSR04_ECHO);
_sensors.push_back(sensor);
}
#endif
#if HLW8012_SUPPORT
{
HLW8012Sensor * sensor = new HLW8012Sensor();
@ -332,11 +499,34 @@ void _sensorInit() {
}
#endif
#if SENSEAIR_SUPPORT
{
SenseAirSensor * sensor = new SenseAirSensor();
sensor->setRX(SENSEAIR_RX_PIN);
sensor->setTX(SENSEAIR_TX_PIN);
_sensors.push_back(sensor);
}
#endif
#if PMSX003_SUPPORT
{
PMSX003Sensor * sensor = new PMSX003Sensor();
sensor->setRX(PMS_RX_PIN);
sensor->setTX(PMS_TX_PIN);
sensor->setType(PMS_TYPE);
_sensors.push_back(sensor);
}
#endif
#if PZEM004T_SUPPORT
{
PZEM004TSensor * sensor = new PZEM004TSensor();
#if PZEM004T_USE_SOFT
sensor->setRX(PZEM004T_RX_PIN);
sensor->setTX(PZEM004T_TX_PIN);
#else
sensor->setSerial(& PZEM004T_HW_PORT);
#endif
_sensors.push_back(sensor);
}
#endif
@ -357,6 +547,14 @@ void _sensorInit() {
}
#endif
#if TMP3X_SUPPORT
{
TMP3XSensor * sensor = new TMP3XSensor();
sensor->setType(TMP3X_TYPE);
_sensors.push_back(sensor);
}
#endif
#if V9261F_SUPPORT
{
V9261FSensor * sensor = new V9261FSensor();
@ -368,21 +566,144 @@ void _sensorInit() {
}
void _sensorCallback(unsigned char i, unsigned char type, const char * payload) {
DEBUG_MSG_P(PSTR("[SENSOR] Sensor #%u callback, type %u, payload: '%s'\n"), i, type, payload);
}
void _sensorInit() {
_sensors_ready = true;
for (unsigned char i=0; i<_sensors.size(); i++) {
// Do not process an already initialized sensor
if (_sensors[i]->ready()) continue;
DEBUG_MSG_P(PSTR("[SENSOR] Initializing %s\n"), _sensors[i]->description().c_str());
// Force sensor to reload config
_sensors[i]->begin();
if (!_sensors[i]->ready()) {
if (_sensors[i]->error() != 0) DEBUG_MSG_P(PSTR("[SENSOR] -> ERROR %d\n"), _sensors[i]->error());
_sensors_ready = false;
continue;
}
// Initialize magnitudes
for (unsigned char k=0; k<_sensors[i]->count(); k++) {
unsigned char type = _sensors[i]->type(k);
sensor_magnitude_t new_magnitude;
new_magnitude.sensor = _sensors[i];
new_magnitude.local = k;
new_magnitude.type = type;
new_magnitude.global = _counts[type];
new_magnitude.current = 0;
new_magnitude.filtered = 0;
new_magnitude.reported = 0;
new_magnitude.min_change = 0;
if (type == MAGNITUDE_DIGITAL) {
new_magnitude.filter = new MaxFilter();
} else if (type == MAGNITUDE_EVENTS || type == MAGNITUDE_GEIGER_CPM|| type == MAGNITUDE_GEIGER_SIEVERT) { // For geiger counting moving average filter is the most appropriate if needed at all.
new_magnitude.filter = new MovingAverageFilter();
} else {
new_magnitude.filter = new MedianFilter();
}
new_magnitude.filter->resize(_sensor_report_every);
_magnitudes.push_back(new_magnitude);
DEBUG_MSG_P(PSTR("[SENSOR] -> %s:%d\n"), magnitudeTopic(type).c_str(), _counts[type]);
_counts[type] = _counts[type] + 1;
}
// Hook callback
_sensors[i]->onEvent([i](unsigned char type, const char * payload) {
_sensorCallback(i, type, payload);
});
// Custom initializations
#if EMON_ANALOG_SUPPORT
if (_sensors[i]->getID() == SENSOR_EMON_ANALOG_ID) {
EmonAnalogSensor * sensor = (EmonAnalogSensor *) _sensors[i];
sensor->setCurrentRatio(0, getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat());
sensor->setVoltage(getSetting("pwrVoltage", EMON_MAINS_VOLTAGE).toInt());
}
#endif // EMON_ANALOG_SUPPORT
#if HLW8012_SUPPORT
if (_sensors[i]->getID() == SENSOR_HLW8012_ID) {
HLW8012Sensor * sensor = (HLW8012Sensor *) _sensors[i];
double value;
value = getSetting("pwrRatioC", 0).toFloat();
if (value > 0) sensor->setCurrentRatio(value);
value = getSetting("pwrRatioV", 0).toFloat();
if (value > 0) sensor->setVoltageRatio(value);
value = getSetting("pwrRatioP", 0).toFloat();
if (value > 0) sensor->setPowerRatio(value);
}
#endif // HLW8012_SUPPORT
#if CSE7766_SUPPORT
if (_sensors[i]->getID() == SENSOR_CSE7766_ID) {
CSE7766Sensor * sensor = (CSE7766Sensor *) _sensors[i];
double value;
value = getSetting("pwrRatioC", 0).toFloat();
if (value > 0) sensor->setCurrentRatio(value);
value = getSetting("pwrRatioV", 0).toFloat();
if (value > 0) sensor->setVoltageRatio(value);
value = getSetting("pwrRatioP", 0).toFloat();
if (value > 0) sensor->setPowerRatio(value);
}
#endif // CSE7766_SUPPORT
}
}
void _sensorConfigure() {
double value;
// General sensor settings
_sensor_read_interval = 1000 * constrain(getSetting("snsRead", SENSOR_READ_INTERVAL).toInt(), SENSOR_READ_MIN_INTERVAL, SENSOR_READ_MAX_INTERVAL);
_sensor_report_every = constrain(getSetting("snsReport", SENSOR_REPORT_EVERY).toInt(), SENSOR_REPORT_MIN_EVERY, SENSOR_REPORT_MAX_EVERY);
_sensor_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
_sensor_power_units = getSetting("pwrUnits", SENSOR_POWER_UNITS).toInt();
_sensor_energy_units = getSetting("energyUnits", SENSOR_ENERGY_UNITS).toInt();
_sensor_temperature_units = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt();
_sensor_temperature_correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat();
_sensor_humidity_correction = getSetting("humCorrection", SENSOR_HUMIDITY_CORRECTION).toFloat();
// Specific sensor settings
for (unsigned char i=0; i<_sensors.size(); i++) {
#if EMON_ANALOG_SUPPORT
if (_sensors[i]->getID() == SENSOR_EMON_ANALOG_ID) {
double value;
EmonAnalogSensor * sensor = (EmonAnalogSensor *) _sensors[i];
if (value = getSetting("pwrExpectedP", 0).toInt() == 0) {
value = getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat();
if (value > 0) sensor->setCurrentRatio(0, value);
} else {
if (value = getSetting("pwrExpectedP", 0).toInt()) {
sensor->expectedPower(0, value);
setSetting("pwrRatioC", sensor->getCurrentRatio(0));
}
@ -392,42 +713,63 @@ void _sensorConfigure() {
delSetting("pwrRatioC");
}
sensor->setVoltage(getSetting("pwrVoltage", EMON_MAINS_VOLTAGE).toInt());
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
}
sensor->setVoltage(getSetting("pwrVoltage", EMON_MAINS_VOLTAGE).toInt());
}
#endif // EMON_ANALOG_SUPPORT
// Force sensor to reload config
_sensors[i]->begin();
#if EMON_ADC121_SUPPORT
if (_sensors[i]->getID() == SENSOR_EMON_ADC121_ID) {
EmonADC121Sensor * sensor = (EmonADC121Sensor *) _sensors[i];
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
}
}
#endif
#if EMON_ADS1X15_SUPPORT
if (_sensors[i]->getID() == SENSOR_EMON_ADS1X15_ID) {
EmonADS1X15Sensor * sensor = (EmonADS1X15Sensor *) _sensors[i];
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
}
}
#endif
#if HLW8012_SUPPORT
if (_sensors[i]->getID() == SENSOR_HLW8012_ID) {
double value;
HLW8012Sensor * sensor = (HLW8012Sensor *) _sensors[i];
if (value = getSetting("pwrExpectedC", 0).toFloat()) {
sensor->expectedCurrent(value);
setSetting("pwrRatioC", sensor->getCurrentRatio());
} else {
value = getSetting("pwrRatioC", 0).toFloat();
if (value > 0) sensor->setCurrentRatio(value);
}
if (value = getSetting("pwrExpectedV", 0).toInt()) {
sensor->expectedVoltage(value);
setSetting("pwrRatioV", sensor->getVoltageRatio());
} else {
value = getSetting("pwrRatioV", 0).toFloat();
if (value > 0) sensor->setVoltageRatio(value);
}
if (value = getSetting("pwrExpectedP", 0).toInt()) {
sensor->expectedPower(value);
setSetting("pwrRatioP", sensor->getPowerRatio());
} else {
value = getSetting("pwrRatioP", 0).toFloat();
if (value > 0) sensor->setPowerRatio(value);
}
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
}
if (getSetting("pwrResetCalibration", 0).toInt() == 1) {
@ -438,70 +780,62 @@ void _sensorConfigure() {
}
}
#endif // HLW8012_SUPPORT
}
// General sensor settings
_sensor_read_interval = 1000 * constrain(getSetting("snsRead", SENSOR_READ_INTERVAL).toInt(), SENSOR_READ_MIN_INTERVAL, SENSOR_READ_MAX_INTERVAL);
_sensor_report_every = constrain(getSetting("snsReport", SENSOR_REPORT_EVERY).toInt(), SENSOR_REPORT_MIN_EVERY, SENSOR_REPORT_MAX_EVERY);
_sensor_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
_sensor_temperature_units = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt();
_sensor_temperature_correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat();
// Update filter sizes
for (unsigned char i=0; i<_magnitudes.size(); i++) {
_magnitudes[i].filter->resize(_sensor_report_every);
}
#endif // HLW8012_SUPPORT
// Save settings
delSetting("pwrExpectedP");
delSetting("pwrExpectedC");
delSetting("pwrExpectedV");
delSetting("pwrResetCalibration");
//saveSettings();
#if CSE7766_SUPPORT
}
if (_sensors[i]->getID() == SENSOR_CSE7766_ID) {
void _magnitudesInit() {
double value;
CSE7766Sensor * sensor = (CSE7766Sensor *) _sensors[i];
for (unsigned char i=0; i<_sensors.size(); i++) {
if (value = getSetting("pwrExpectedC", 0).toFloat()) {
sensor->expectedCurrent(value);
setSetting("pwrRatioC", sensor->getCurrentRatio());
}
BaseSensor * sensor = _sensors[i];
if (value = getSetting("pwrExpectedV", 0).toInt()) {
sensor->expectedVoltage(value);
setSetting("pwrRatioV", sensor->getVoltageRatio());
}
DEBUG_MSG_P(PSTR("[SENSOR] %s\n"), sensor->description().c_str());
if (sensor->error() != 0) DEBUG_MSG_P(PSTR("[SENSOR] -> ERROR %d\n"), sensor->error());
if (value = getSetting("pwrExpectedP", 0).toInt()) {
sensor->expectedPower(value);
setSetting("pwrRatioP", sensor->getPowerRatio());
}
for (unsigned char k=0; k<sensor->count(); k++) {
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
}
unsigned char type = sensor->type(k);
if (getSetting("pwrResetCalibration", 0).toInt() == 1) {
sensor->resetRatios();
delSetting("pwrRatioC");
delSetting("pwrRatioV");
delSetting("pwrRatioP");
}
sensor_magnitude_t new_magnitude;
new_magnitude.sensor = sensor;
new_magnitude.local = k;
new_magnitude.type = type;
new_magnitude.global = _counts[type];
new_magnitude.current = 0;
new_magnitude.filtered = 0;
new_magnitude.reported = 0;
new_magnitude.min_change = 0;
if (type == MAGNITUDE_DIGITAL) {
new_magnitude.filter = new MaxFilter();
} else if (type == MAGNITUDE_EVENTS) {
new_magnitude.filter = new MovingAverageFilter();
} else {
new_magnitude.filter = new MedianFilter();
}
new_magnitude.filter->resize(_sensor_report_every);
_magnitudes.push_back(new_magnitude);
DEBUG_MSG_P(PSTR("[SENSOR] -> %s:%d\n"), _magnitudeTopic(type).c_str(), _counts[type]);
_counts[type] = _counts[type] + 1;
#endif // CSE7766_SUPPORT
}
}
// Update filter sizes
for (unsigned char i=0; i<_magnitudes.size(); i++) {
_magnitudes[i].filter->resize(_sensor_report_every);
}
// Save settings
delSetting("pwrExpectedP");
delSetting("pwrExpectedC");
delSetting("pwrExpectedV");
delSetting("pwrResetCalibration");
delSetting("pwrResetE");
saveSettings();
}
// -----------------------------------------------------------------------------
@ -538,23 +872,65 @@ unsigned char magnitudeIndex(unsigned char index) {
return 0;
}
String magnitudeTopic(unsigned char type) {
char buffer[16] = {0};
if (type < MAGNITUDE_MAX) strncpy_P(buffer, magnitude_topics[type], sizeof(buffer));
return String(buffer);
}
String magnitudeTopicIndex(unsigned char index) {
char topic[32] = {0};
if (index < _magnitudes.size()) {
sensor_magnitude_t magnitude = _magnitudes[index];
if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) {
snprintf(topic, sizeof(topic), "%s/%u", magnitudeTopic(magnitude.type).c_str(), magnitude.global);
} else {
snprintf(topic, sizeof(topic), "%s", magnitudeTopic(magnitude.type).c_str());
}
}
return String(topic);
}
String magnitudeUnits(unsigned char type) {
char buffer[8] = {0};
if (type < MAGNITUDE_MAX) {
if ((type == MAGNITUDE_TEMPERATURE) && (_sensor_temperature_units == TMP_FAHRENHEIT)) {
strncpy_P(buffer, magnitude_fahrenheit, sizeof(buffer));
} else if (
(type == MAGNITUDE_ENERGY || type == MAGNITUDE_ENERGY_DELTA) &&
(_sensor_energy_units == ENERGY_KWH)) {
strncpy_P(buffer, magnitude_kwh, sizeof(buffer));
} else if (
(type == MAGNITUDE_POWER_ACTIVE || type == MAGNITUDE_POWER_APPARENT || type == MAGNITUDE_POWER_REACTIVE) &&
(_sensor_power_units == POWER_KILOWATTS)) {
strncpy_P(buffer, magnitude_kw, sizeof(buffer));
} else {
strncpy_P(buffer, magnitude_units[type], sizeof(buffer));
}
}
return String(buffer);
}
// -----------------------------------------------------------------------------
void sensorSetup() {
// Backwards compatibility
moveSetting("powerUnits", "pwrUnits");
// Load sensors
_sensorLoad();
_sensorInit();
// Configure stored values
_sensorConfigure();
// Load magnitudes
_magnitudesInit();
#if WEB_SUPPORT
// Websockets
wsOnSendRegister(_sensorWebSocketStart);
wsOnReceiveRegister(_sensorWebSocketOnReceive);
wsOnSendRegister(_sensorWebSocketSendData);
wsOnAfterParseRegister(_sensorConfigure);
@ -563,12 +939,25 @@ void sensorSetup() {
#endif
#if TERMINAL_SUPPORT
_sensorInitCommands();
#endif
// Register loop
espurnaRegisterLoop(sensorLoop);
}
void sensorLoop() {
static unsigned long last_update = 0;
static unsigned long report_count = 0;
// Check if we still have uninitialized sensors
static unsigned long last_init = 0;
if (!_sensors_ready) {
if (millis() - last_init > SENSOR_INIT_INTERVAL) {
last_init = millis();
_sensorInit();
}
}
if (_magnitudes.size() == 0) return;
@ -576,6 +965,8 @@ void sensorLoop() {
_sensorTick();
// Check if we should read new data
static unsigned long last_update = 0;
static unsigned long report_count = 0;
if (millis() - last_update > _sensor_read_interval) {
last_update = millis();
@ -588,6 +979,11 @@ void sensorLoop() {
// Pre-read hook
_sensorPre();
// Get the first relay state
#if SENSOR_POWER_CHECK_STATUS
bool relay_off = (relayCount() > 0) && (relayStatus(0) == 0);
#endif
// Get readings
for (unsigned char i=0; i<_magnitudes.size(); i++) {
@ -595,26 +991,43 @@ void sensorLoop() {
if (magnitude.sensor->status()) {
unsigned char decimals = _magnitudeDecimals(magnitude.type);
current = magnitude.sensor->value(magnitude.local);
// Completely remove spurious values if relay is OFF
#if SENSOR_POWER_CHECK_STATUS
if (relay_off) {
if (magnitude.type == MAGNITUDE_POWER_ACTIVE ||
magnitude.type == MAGNITUDE_POWER_REACTIVE ||
magnitude.type == MAGNITUDE_POWER_APPARENT ||
magnitude.type == MAGNITUDE_CURRENT ||
magnitude.type == MAGNITUDE_ENERGY_DELTA
) {
current = 0;
}
}
#endif
magnitude.filter->add(current);
// Special case
if (magnitude.type == MAGNITUDE_EVENTS) current = magnitude.filter->result();
if (magnitude.type == MAGNITUDE_EVENTS) {
current = magnitude.filter->result();
}
current = _magnitudeProcess(magnitude.type, current);
_magnitudes[i].current = current;
unsigned char decimals = _magnitudeDecimals(magnitude.type);
// Debug
#if SENSOR_DEBUG
{
dtostrf(current, 1-sizeof(buffer), decimals, buffer);
DEBUG_MSG_P(PSTR("[SENSOR] %s - %s: %s%s\n"),
magnitude.sensor->slot(magnitude.local).c_str(),
_magnitudeTopic(magnitude.type).c_str(),
magnitudeTopic(magnitude.type).c_str(),
buffer,
_magnitudeUnits(magnitude.type).c_str()
magnitudeUnits(magnitude.type).c_str()
);
}
#endif // SENSOR_DEBUG
@ -633,22 +1046,38 @@ void sensorLoop() {
_magnitudes[i].reported = filtered;
dtostrf(filtered, 1-sizeof(buffer), decimals, buffer);
#if BROKER_SUPPORT
brokerPublish(magnitudeTopic(magnitude.type).c_str(), magnitude.local, buffer);
#endif
#if MQTT_SUPPORT
if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) {
mqttSend(_magnitudeTopic(magnitude.type).c_str(), magnitude.global, buffer);
} else {
mqttSend(_magnitudeTopic(magnitude.type).c_str(), buffer);
}
mqttSend(magnitudeTopicIndex(i).c_str(), buffer);
#if SENSOR_PUBLISH_ADDRESSES
char topic[32];
snprintf(topic, sizeof(topic), "%s/%s", SENSOR_ADDRESS_TOPIC, magnitudeTopic(magnitude.type).c_str());
if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) {
mqttSend(topic, magnitude.global, magnitude.sensor->address(magnitude.local).c_str());
} else {
mqttSend(topic, magnitude.sensor->address(magnitude.local).c_str());
}
#endif // SENSOR_PUBLISH_ADDRESSES
#endif // MQTT_SUPPORT
#if INFLUXDB_SUPPORT
if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) {
idbSend(_magnitudeTopic(magnitude.type).c_str(), magnitude.global, buffer);
idbSend(magnitudeTopic(magnitude.type).c_str(), magnitude.global, buffer);
} else {
idbSend(_magnitudeTopic(magnitude.type).c_str(), buffer);
idbSend(magnitudeTopic(magnitude.type).c_str(), buffer);
}
#endif // INFLUXDB_SUPPORT
#if THINGSPEAK_SUPPORT
tspkEnqueueMeasurement(i, buffer);
#endif
#if DOMOTICZ_SUPPORT
{
char key[15];
@ -685,6 +1114,10 @@ void sensorLoop() {
wsSend(_sensorWebSocketSendData);
#endif
#if THINGSPEAK_SUPPORT
if (report_count == 0) tspkFlush();
#endif
}
}


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

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

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

@ -1,12 +1,16 @@
// -----------------------------------------------------------------------------
// Analog Sensor (maps to an analogRead)
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && ANALOG_SUPPORT
#pragma once
// Set ADC to TOUT pin
#undef ADC_MODE_VALUE
#define ADC_MODE_VALUE ADC_TOUT
#include "Arduino.h"
#include "BaseSensor.h"
@ -30,26 +34,33 @@ class AnalogSensor : public BaseSensor {
// Initialization method, must be idempotent
void begin() {
pinMode(0, INPUT);
_ready = true;
}
// Descriptive name of the sensor
String description() {
return String("ANALOG @ GPIO0");
return String("ANALOG @ TOUT");
}
// Descriptive name of the slot # index
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) {
_error = SENSOR_ERROR_OK;
if (index == 0) return MAGNITUDE_ANALOG;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return analogRead(0);
_error = SENSOR_ERROR_OUT_OF_RANGE;
return 0;
}


+ 30
- 47
code/espurna/sensors/BH1750Sensor.h View File

@ -1,19 +1,17 @@
// -----------------------------------------------------------------------------
// BH1750 Liminosity sensor over I2C
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && BH1750_SUPPORT
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
#if I2C_USE_BRZO
#include <brzo_i2c.h>
#else
#include <Wire.h>
#endif
#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
#define BH1750_CONTINUOUS_HIGH_RES_MODE_2 0x11 // Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
@ -60,16 +58,16 @@ class BH1750Sensor : public I2CSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
// I2C auto-discover
unsigned char addresses[] = {0x23, 0x5C};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Configure
_configure();
delay(10);
// Run configuration on next update
_run_configure = true;
_ready = true;
_dirty = false;
}
@ -82,44 +80,31 @@ class BH1750Sensor : public I2CSensor {
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return MAGNITUDE_LUX;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
_lux = _read();
}
// Current value for slot # index
double value(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return _read();
_error = SENSOR_ERROR_OUT_OF_RANGE;
if (index == 0) return _lux;
return 0;
}
protected:
void _configure() {
#if I2C_USE_BRZO
uint8_t buffer[1] = {_mode};
brzo_i2c_start_transaction(_address, I2C_SCL_FREQUENCY);
brzo_i2c_write(buffer, 1, false);
brzo_i2c_end_transaction();
#else
Wire.beginTransmission(_address);
Wire.write(_mode);
Wire.endTransmission();
#endif
}
double _read() {
double level;
uint8_t buffer[2];
// For one-shot modes reconfigure sensor & wait for conversion
if (_mode & 0x20) {
if (_run_configure) {
_configure();
// Configure mode
i2c_write_uint8(_address, _mode);
// According to datasheet
// conversion time is ~16ms for low resolution
@ -129,27 +114,25 @@ class BH1750Sensor : public I2CSensor {
unsigned long start = millis();
while (millis() - start < wait) delay(1);
// Keep on running configure each time if one-shot mode
_run_configure = _mode & 0x20;
}
#if I2C_USE_BRZO
brzo_i2c_start_transaction(_address, I2C_SCL_FREQUENCY);
brzo_i2c_read(buffer, 2, false);
brzo_i2c_end_transaction();
#else
Wire.beginTransmission(_address);
Wire.requestFrom(_address, (unsigned char) 2);
buffer[0] = Wire.read();
buffer[1] = Wire.read();
Wire.endTransmission();
#endif
level = buffer[0] * 256 + buffer[1];
double level = (double) i2c_read_uint16(_address);
if (level == 0xFFFF) {
_error = SENSOR_ERROR_CRC;
_run_configure = true;
return 0;
}
return level / 1.2;
}
unsigned char _mode;
bool _run_configure = false;
double _lux = 0;
};
#endif // SENSOR_SUPPORT && SI7021_SUPPORT
#endif // SENSOR_SUPPORT && BH1750_SUPPORT

+ 265
- 75
code/espurna/sensors/BMX280Sensor.h View File

@ -1,19 +1,54 @@
// -----------------------------------------------------------------------------
// BME280/BMP280 Sensor over I2C
// Uses SparkFun BME280 library
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && BMX280_SUPPORT
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
#include <SparkFunBME280.h>
#define BMX280_CHIP_BMP280 0x58
#define BMX280_CHIP_BME280 0x60
#define BMX280_CHIP_BMP280 0x58
#define BMX280_CHIP_BME280 0x60
#define BMX280_REGISTER_DIG_T1 0x88
#define BMX280_REGISTER_DIG_T2 0x8A
#define BMX280_REGISTER_DIG_T3 0x8C
#define BMX280_REGISTER_DIG_P1 0x8E
#define BMX280_REGISTER_DIG_P2 0x90
#define BMX280_REGISTER_DIG_P3 0x92
#define BMX280_REGISTER_DIG_P4 0x94
#define BMX280_REGISTER_DIG_P5 0x96
#define BMX280_REGISTER_DIG_P6 0x98
#define BMX280_REGISTER_DIG_P7 0x9A
#define BMX280_REGISTER_DIG_P8 0x9C
#define BMX280_REGISTER_DIG_P9 0x9E
#define BMX280_REGISTER_DIG_H1 0xA1
#define BMX280_REGISTER_DIG_H2 0xE1
#define BMX280_REGISTER_DIG_H3 0xE3
#define BMX280_REGISTER_DIG_H4 0xE4
#define BMX280_REGISTER_DIG_H5 0xE5
#define BMX280_REGISTER_DIG_H6 0xE7
#define BMX280_REGISTER_CHIPID 0xD0
#define BMX280_REGISTER_VERSION 0xD1
#define BMX280_REGISTER_SOFTRESET 0xE0
#define BMX280_REGISTER_CAL26 0xE1
#define BMX280_REGISTER_CONTROLHUMID 0xF2
#define BMX280_REGISTER_CONTROL 0xF4
#define BMX280_REGISTER_CONFIG 0xF5
#define BMX280_REGISTER_PRESSUREDATA 0xF7
#define BMX280_REGISTER_TEMPDATA 0xFA
#define BMX280_REGISTER_HUMIDDATA 0xFD
class BMX280Sensor : public I2CSensor {
@ -27,11 +62,6 @@ class BMX280Sensor : public I2CSensor {
BMX280Sensor(): I2CSensor() {
_sensor_id = SENSOR_BMX280_ID;
_bme = new BME280();
}
~BMX280Sensor() {
delete _bme;
}
// ---------------------------------------------------------------------
@ -40,18 +70,9 @@ class BMX280Sensor : public I2CSensor {
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_dirty = false;
_chip = 0;
// I2C auto-discover
_address = _begin_i2c(_address, sizeof(BMX280Sensor::addresses), BMX280Sensor::addresses);
if (_address == 0) return;
// Init
_init();
_dirty = !_ready;
}
// Descriptive name of the sensor
@ -63,58 +84,61 @@ class BMX280Sensor : public I2CSensor {
// Type for slot # index
unsigned char type(unsigned char index) {
if (index < _count) {
_error = SENSOR_ERROR_OK;
unsigned char i = 0;
#if BMX280_TEMPERATURE > 0
if (index == i++) return MAGNITUDE_TEMPERATURE;
#endif
#if BMX280_PRESSURE > 0
if (index == i++) return MAGNITUDE_PRESSURE;
#endif
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) {
if (index == i) return MAGNITUDE_HUMIDITY;
}
#endif
}
_error = SENSOR_ERROR_OUT_OF_RANGE;
unsigned char i = 0;
#if BMX280_TEMPERATURE > 0
if (index == i++) return MAGNITUDE_TEMPERATURE;
#endif
#if BMX280_PRESSURE > 0
if (index == i++) return MAGNITUDE_PRESSURE;
#endif
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) {
if (index == i) return MAGNITUDE_HUMIDITY;
}
#endif
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;
#if BMX280_MODE == 1
forceRead();
_forceRead();
#endif
_error = _read();
if (_error != SENSOR_ERROR_OK) {
_run_init = true;
}
}
// Current value for slot # index
double value(unsigned char index) {
if (index < _count) {
_error = SENSOR_ERROR_OK;
unsigned char i = 0;
#if BMX280_TEMPERATURE > 0
if (index == i++) return _bme->readTempC();
#endif
#if BMX280_PRESSURE > 0
if (index == i++) return _bme->readFloatPressure() / 100;
#endif
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) {
if (index == i) return _bme->readFloatHumidity();
}
#endif
}
_error = SENSOR_ERROR_OUT_OF_RANGE;
unsigned char i = 0;
#if BMX280_TEMPERATURE > 0
if (index == i++) return _temperature;
#endif
#if BMX280_PRESSURE > 0
if (index == i++) return _pressure / 100;
#endif
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) {
if (index == i) return _humidity;
}
#endif
return 0;
}
@ -150,7 +174,7 @@ class BMX280Sensor : public I2CSensor {
void getConfig(JsonObject& root) {
root["sensor_id"] = _sensor_id;
root["address"] = getAddress();
root["address"] = _address;
};
void setConfig(JsonObject& root) {
@ -161,29 +185,35 @@ class BMX280Sensor : public I2CSensor {
void _init() {
_bme->settings.commInterface = I2C_MODE;
_bme->settings.I2CAddress = _address;
_bme->settings.runMode = BMX280_MODE;
_bme->settings.tStandby = 0;
_bme->settings.filter = 0;
_bme->settings.tempOverSample = BMX280_TEMPERATURE;
_bme->settings.pressOverSample = BMX280_PRESSURE;
_bme->settings.humidOverSample = BMX280_HUMIDITY;
// Make sure sensor had enough time to turn on. BMX280 requires 2ms to start up
nice_delay(10);
// Fix when not measuring temperature, t_fine should have a sensible value
if (BMX280_TEMPERATURE == 0) _bme->t_fine = 100000; // aprox 20ºC
// No chip ID by default
_chip = 0;
// Make sure sensor had enough time to turn on. BMX280 requires 2ms to start up
delay(10);
// I2C auto-discover
_address = _begin_i2c(_address, sizeof(BMX280Sensor::addresses), BMX280Sensor::addresses);
if (_address == 0) return;
// Check sensor correctly initialized
_chip = _bme->begin();
_chip = i2c_read_uint8(_address, BMX280_REGISTER_CHIPID);
if ((_chip != BMX280_CHIP_BME280) && (_chip != BMX280_CHIP_BMP280)) {
_chip = 0;
i2cReleaseLock(_address);
_previous_address = 0;
_error = SENSOR_ERROR_UNKNOWN_ID;
// Setting _address to 0 forces auto-discover
// This might be necessary at this stage if there is a
// different sensor in the hardcoded address
_address = 0;
return;
}
_count = 0;
#if BMX280_TEMPERATURE > 0
++_count;
#endif
@ -194,11 +224,53 @@ class BMX280Sensor : public I2CSensor {
if (_chip == BMX280_CHIP_BME280) ++_count;
#endif
_measurement_delay = measurementTime();
_readCoefficients();
unsigned char data = 0;
i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data);
data = (BMX280_STANDBY << 0x5) & 0xE0;
data |= (BMX280_FILTER << 0x02) & 0x1C;
i2c_write_uint8(_address, BMX280_REGISTER_CONFIG, data);
data = (BMX280_HUMIDITY) & 0x07;
i2c_write_uint8(_address, BMX280_REGISTER_CONTROLHUMID, data);
data = (BMX280_TEMPERATURE << 5) & 0xE0;
data |= (BMX280_PRESSURE << 2) & 0x1C;
data |= (BMX280_MODE) & 0x03;
i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data);
_measurement_delay = _measurementTime();
_run_init = false;
_ready = true;
}
void _readCoefficients() {
_bmx280_calib.dig_T1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_T1);
_bmx280_calib.dig_T2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T2);
_bmx280_calib.dig_T3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T3);
_bmx280_calib.dig_P1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_P1);
_bmx280_calib.dig_P2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P2);
_bmx280_calib.dig_P3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P3);
_bmx280_calib.dig_P4 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P4);
_bmx280_calib.dig_P5 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P5);
_bmx280_calib.dig_P6 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P6);
_bmx280_calib.dig_P7 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P7);
_bmx280_calib.dig_P8 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P8);
_bmx280_calib.dig_P9 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P9);
_bmx280_calib.dig_H1 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H1);
_bmx280_calib.dig_H2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_H2);
_bmx280_calib.dig_H3 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H3);
_bmx280_calib.dig_H4 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4+1) & 0xF);
_bmx280_calib.dig_H5 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5+1) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5) >> 4);
_bmx280_calib.dig_H6 = (int8_t) i2c_read_uint8(_address, BMX280_REGISTER_DIG_H6);
}
unsigned long measurementTime() {
unsigned long _measurementTime() {
// Measurement Time (as per BMX280 datasheet section 9.1)
// T_max(ms) = 1.25
@ -224,23 +296,141 @@ class BMX280Sensor : public I2CSensor {
}
void forceRead() {
void _forceRead() {
// We set the sensor in "forced mode" to force a reading.
// After the reading the sensor will go back to sleep mode.
uint8_t value = _bme->readRegister(BME280_CTRL_MEAS_REG);
uint8_t value = i2c_read_uint8(_address, BMX280_REGISTER_CONTROL);
value = (value & 0xFC) + 0x01;
_bme->writeRegister(BME280_CTRL_MEAS_REG, value);
i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, value);
nice_delay(_measurement_delay);
}
unsigned char _read() {
#if BMX280_TEMPERATURE > 0
int32_t adc_T = i2c_read_uint16(_address, BMX280_REGISTER_TEMPDATA);
if (0xFFFF == adc_T) return SENSOR_ERROR_I2C;
adc_T <<= 8;
adc_T |= i2c_read_uint8(_address, BMX280_REGISTER_TEMPDATA+2);
adc_T >>= 4;
int32_t var1t = ((((adc_T>>3) -
((int32_t)_bmx280_calib.dig_T1 <<1))) *
((int32_t)_bmx280_calib.dig_T2)) >> 11;
int32_t var2t = (((((adc_T>>4) -
((int32_t)_bmx280_calib.dig_T1)) *
((adc_T>>4) - ((int32_t)_bmx280_calib.dig_T1))) >> 12) *
((int32_t)_bmx280_calib.dig_T3)) >> 14;
int32_t t_fine = var1t + var2t;
double T = (t_fine * 5 + 128) >> 8;
_temperature = T / 100;
#else
int32_t t_fine = 102374; // ~20ºC
#endif
// -----------------------------------------------------------------
#if BMX280_PRESSURE > 0
int64_t var1, var2, p;
int32_t adc_P = i2c_read_uint16(_address, BMX280_REGISTER_PRESSUREDATA);
if (0xFFFF == adc_P) return SENSOR_ERROR_I2C;
adc_P <<= 8;
adc_P |= i2c_read_uint8(_address, BMX280_REGISTER_PRESSUREDATA+2);
adc_P >>= 4;
var1 = ((int64_t)t_fine) - 128000;
var2 = var1 * var1 * (int64_t)_bmx280_calib.dig_P6;
var2 = var2 + ((var1*(int64_t)_bmx280_calib.dig_P5)<<17);
var2 = var2 + (((int64_t)_bmx280_calib.dig_P4)<<35);
var1 = ((var1 * var1 * (int64_t)_bmx280_calib.dig_P3)>>8) +
((var1 * (int64_t)_bmx280_calib.dig_P2)<<12);
var1 = (((((int64_t)1)<<47)+var1))*((int64_t)_bmx280_calib.dig_P1)>>33;
if (var1 == 0) return SENSOR_ERROR_I2C; // avoid exception caused by division by zero
p = 1048576 - adc_P;
p = (((p<<31) - var2)*3125) / var1;
var1 = (((int64_t)_bmx280_calib.dig_P9) * (p>>13) * (p>>13)) >> 25;
var2 = (((int64_t)_bmx280_calib.dig_P8) * p) >> 19;
p = ((p + var1 + var2) >> 8) + (((int64_t)_bmx280_calib.dig_P7)<<4);
_pressure = (double) p / 256;
#endif
// -----------------------------------------------------------------
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) {
int32_t adc_H = i2c_read_uint16(_address, BMX280_REGISTER_HUMIDDATA);
if (0xFFFF == adc_H) return SENSOR_ERROR_I2C;
int32_t v_x1_u32r;
v_x1_u32r = (t_fine - ((int32_t)76800));
v_x1_u32r = (((((adc_H << 14) - (((int32_t)_bmx280_calib.dig_H4) << 20) -
(((int32_t)_bmx280_calib.dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) *
(((((((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H6)) >> 10) *
(((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H3)) >> 11) + ((int32_t)32768))) >> 10) +
((int32_t)2097152)) * ((int32_t)_bmx280_calib.dig_H2) + 8192) >> 14));
v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) *
((int32_t)_bmx280_calib.dig_H1)) >> 4));
v_x1_u32r = (v_x1_u32r < 0) ? 0 : v_x1_u32r;
v_x1_u32r = (v_x1_u32r > 419430400) ? 419430400 : v_x1_u32r;
double h = (v_x1_u32r >> 12);
_humidity = h / 1024.0;
}
#endif
delay(_measurement_delay);
return SENSOR_ERROR_OK;
}
// ---------------------------------------------------------------------
BME280 * _bme = NULL;
unsigned char _chip;
unsigned long _measurement_delay;
bool _run_init = false;
double _temperature = 0;
double _pressure = 0;
double _humidity = 0;
typedef struct {
uint16_t dig_T1;
int16_t dig_T2;
int16_t dig_T3;
uint16_t dig_P1;
int16_t dig_P2;
int16_t dig_P3;
int16_t dig_P4;
int16_t dig_P5;
int16_t dig_P6;
int16_t dig_P7;
int16_t dig_P8;
int16_t dig_P9;
uint8_t dig_H1;
int16_t dig_H2;
uint8_t dig_H3;
int16_t dig_H4;
int16_t dig_H5;
int8_t dig_H6;
} bmx280_calib_t;
bmx280_calib_t _bmx280_calib;
};


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Abstract sensor class (other sensor classes extend this class)
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
@ -10,8 +10,6 @@
#include <Arduino.h>
#include <ArduinoJson.h>
#define GPIO_NONE 0x99
#define SENSOR_ERROR_OK 0 // No error
#define SENSOR_ERROR_OUT_OF_RANGE 1 // Result out of sensor range
#define SENSOR_ERROR_WARM_UP 2 // Sensor is warming-up
@ -20,6 +18,10 @@
#define SENSOR_ERROR_CRC 5 // Sensor data corrupted
#define SENSOR_ERROR_I2C 6 // Wrong or locked I2C address
#define SENSOR_ERROR_GPIO_USED 7 // The GPIO is already in use
#define SENSOR_ERROR_CALIBRATION 8 // Calibration error or Not calibrated
#define SENSOR_ERROR_OTHER 99 // Any other error
typedef std::function<void(unsigned char, const char *)> TSensorCallback;
class BaseSensor {
@ -46,6 +48,12 @@ class BaseSensor {
// Descriptive name of the sensor
virtual String description() {}
// Address of the sensor (it could be the GPIO or I2C address)
virtual String address(unsigned char index) {}
// Descriptive name of the slot # index
virtual String slot(unsigned char index) {};
// Type for slot # index
virtual unsigned char type(unsigned char index) {}
@ -61,14 +69,14 @@ class BaseSensor {
// Load the configuration manifest
static void manifest(JsonArray& root) {};
// Descriptive name of the slot # index
String slot(unsigned char index) { return description(); }
// Sensor ID
unsigned char getID() { return _sensor_id; };
// Return sensor status (true for ready)
bool status() { return _error == 0; }
// Return status (true if no errors)
bool status() { return 0 == _error; }
// Return ready status (true for ready)
bool ready() { return _ready; }
// Return sensor last internal error
int error() { return _error; }
@ -76,12 +84,17 @@ class BaseSensor {
// Number of available slots
unsigned char count() { return _count; }
// Hook for event callback
void onEvent(TSensorCallback fn) { _callback = fn; };
protected:
TSensorCallback _callback = NULL;
unsigned char _sensor_id = 0x00;
int _error = 0;
bool _dirty = true;
unsigned char _count = 0;
bool _ready = false;
};


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

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

+ 26
- 14
code/espurna/sensors/DHTSensor.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// DHTXX Sensor
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && DHT_SUPPORT
@ -33,7 +33,7 @@ class DHTSensor : public BaseSensor {
}
~DHTSensor() {
if (_previous != 0xFF) gpioReleaseLock(_previous);
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
}
// ---------------------------------------------------------------------
@ -66,8 +66,8 @@ class DHTSensor : public BaseSensor {
_count = 0;
// Manage GPIO lock
if (_previous != 0xFF) gpioReleaseLock(_previous);
_previous = 0xFF;
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
_previous = GPIO_NONE;
if (!gpioGetLock(_gpio)) {
_error = SENSOR_ERROR_GPIO_USED;
return;
@ -75,11 +75,13 @@ class DHTSensor : public BaseSensor {
_previous = _gpio;
_count = 2;
_ready = true;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
_read();
}
@ -90,21 +92,27 @@ class DHTSensor : public BaseSensor {
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return _temperature;
if (index == 1) return _humidity;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return 0;
}
@ -132,12 +140,16 @@ class DHTSensor : public BaseSensor {
if (++_errors > DHT_MAX_ERRORS) {
_errors = 0;
digitalWrite(_gpio, HIGH);
delay(250);
nice_delay(250);
}
pinMode(_gpio, OUTPUT);
noInterrupts();
digitalWrite(_gpio, LOW);
delayMicroseconds(_type == DHT_CHIP_DHT11 ? 20000 : 500);
if (_type == DHT_CHIP_DHT11) {
nice_delay(20);
} else {
delayMicroseconds(500);
}
digitalWrite(_gpio, HIGH);
delayMicroseconds(40);
pinMode(_gpio, INPUT_PULLUP);
@ -218,15 +230,15 @@ class DHTSensor : public BaseSensor {
return uSec;
}
unsigned char _gpio;
unsigned char _previous = 0xFF;
unsigned char _gpio = GPIO_NONE;
unsigned char _previous = GPIO_NONE;
unsigned char _type = DHT_CHIP_DHT22;
unsigned long _last_ok = 0;
unsigned char _errors = 0;
double _temperature;
unsigned int _humidity;
double _temperature = 0;
unsigned int _humidity = 0;
};


+ 42
- 39
code/espurna/sensors/DallasSensor.h View File

@ -1,7 +1,7 @@
// -----------------------------------------------------------------------------
// Dallas OneWire Sensor
// Uses OneWire library
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && DALLAS_SUPPORT
@ -18,15 +18,13 @@
#define DS_CHIP_DS18B20 0x28
#define DS_CHIP_DS1825 0x3B
#define DS_DATA_SIZE 9
#define DS_PARASITE 1
#define DS_DISCONNECTED -127
#define DS_CMD_START_CONVERSION 0x44
#define DS_CMD_READ_SCRATCHPAD 0xBE
#define DS_ERROR_FAILED_RESET -2
#define DS_ERROR_FAILED_READ -3
class DallasSensor : public BaseSensor {
public:
@ -41,7 +39,7 @@ class DallasSensor : public BaseSensor {
~DallasSensor() {
if (_wire) delete _wire;
if (_previous != 0xFF) gpioReleaseLock(_previous);
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
}
// ---------------------------------------------------------------------
@ -66,11 +64,10 @@ class DallasSensor : public BaseSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
// Manage GPIO lock
if (_previous != 0xFF) gpioReleaseLock(_previous);
_previous = 0xFF;
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
_previous = GPIO_NONE;
if (!gpioGetLock(_gpio)) {
_error = SENSOR_ERROR_GPIO_USED;
return;
@ -95,6 +92,8 @@ class DallasSensor : public BaseSensor {
} else {
_previous = _gpio;
}
_ready = true;
_dirty = false;
}
@ -121,38 +120,26 @@ class DallasSensor : public BaseSensor {
// Read scratchpad
if (_wire->reset() == 0) {
_error = DS_ERROR_FAILED_RESET;
// Force a CRC check error
_devices[index].data[0] = _devices[index].data[0] + 1;
return;
}
_wire->select(_devices[index].address);
_wire->write(DS_CMD_READ_SCRATCHPAD);
uint8_t data[9];
for (unsigned char i = 0; i < 9; i++) {
uint8_t data[DS_DATA_SIZE];
for (unsigned char i = 0; i < DS_DATA_SIZE; i++) {
data[i] = _wire->read();
}
#if false
Serial.printf("[DS18B20] Data = ");
for (unsigned char i = 0; i < 9; i++) {
Serial.printf("%02X ", data[i]);
}
Serial.printf(" CRC = %02X\n", OneWire::crc8(data, 8));
#endif
if (_wire->reset() != 1) {
_error = DS_ERROR_FAILED_READ;
return;
}
if (OneWire::crc8(data, 8) != data[8]) {
_error = SENSOR_ERROR_CRC;
// Force a CRC check error
_devices[index].data[0] = _devices[index].data[0] + 1;
return;
}
memcpy(_devices[index].data, data, 9);
memcpy(_devices[index].data, data, DS_DATA_SIZE);
}
@ -170,9 +157,21 @@ class DallasSensor : public BaseSensor {
return String(buffer);
}
// Address of the device
String address(unsigned char index) {
char buffer[20] = {0};
if (index < _count) {
uint8_t * address = _devices[index].address;
snprintf(buffer, sizeof(buffer), "%02X%02X%02X%02X%02X%02X%02X%02X",
address[0], address[1], address[2], address[3],
address[4], address[5], address[6], address[7]
);
}
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index < _count) {
char buffer[40];
uint8_t * address = _devices[index].address;
@ -184,28 +183,32 @@ class DallasSensor : public BaseSensor {
);
return String(buffer);
}
_error = SENSOR_ERROR_OUT_OF_RANGE;
return String();
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index < _count) return MAGNITUDE_TEMPERATURE;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
}
// Current value for slot # index
double value(unsigned char index) {
if (index >= _count) {
_error = SENSOR_ERROR_OUT_OF_RANGE;
return 0;
}
if (index >= _count) return 0;
uint8_t * data = _devices[index].data;
if (OneWire::crc8(data, DS_DATA_SIZE-1) != data[DS_DATA_SIZE-1]) {
_error = SENSOR_ERROR_CRC;
return 0;
}
// Registers
// byte 0: temperature LSB
// byte 1: temperature MSB
@ -239,7 +242,7 @@ class DallasSensor : public BaseSensor {
_error = SENSOR_ERROR_CRC;
return 0;
}
_error = SENSOR_ERROR_OK;
return value;
}
@ -294,12 +297,12 @@ class DallasSensor : public BaseSensor {
typedef struct {
uint8_t address[8];
uint8_t data[9];
uint8_t data[DS_DATA_SIZE];
} ds_device_t;
std::vector<ds_device_t> _devices;
unsigned char _gpio;
unsigned char _previous = 0xFF;
unsigned char _gpio = GPIO_NONE;
unsigned char _previous = GPIO_NONE;
OneWire * _wire = NULL;
};


+ 12
- 5
code/espurna/sensors/DigitalSensor.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Digital Sensor (maps to a digitalRead)
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && DIGITAL_SUPPORT
@ -58,6 +58,7 @@ class DigitalSensor : public BaseSensor {
// Initialization method, must be idempotent
void begin() {
pinMode(_gpio, _mode);
_ready = true;
}
// Descriptive name of the sensor
@ -67,19 +68,25 @@ class DigitalSensor : public BaseSensor {
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return MAGNITUDE_DIGITAL;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return (digitalRead(_gpio) == _default) ? 0 : 1;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return 0;
}


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

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// ECH1560 based power monitor
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && ECH1560_SUPPORT
@ -18,7 +18,7 @@ class ECH1560Sensor : public BaseSensor {
// Public
// ---------------------------------------------------------------------
ECH1560Sensor(): BaseSensor() {
ECH1560Sensor(): BaseSensor(), _data() {
_count = 3;
_sensor_id = SENSOR_ECH1560_ID;
}
@ -67,38 +67,53 @@ class ECH1560Sensor : public BaseSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
pinMode(_clk, INPUT);
pinMode(_miso, INPUT);
_enableInterrupts(true);
_dirty = false;
_ready = true;
}
// Loop-like method, call it in your main loop
void tick() {
if (_dosync) _sync();
}
// Descriptive name of the sensor
String description() {
char buffer[25];
snprintf(buffer, sizeof(buffer), "ECH1560 @ GPIO(%i,%i)", _clk, _miso);
char buffer[35];
snprintf(buffer, sizeof(buffer), "ECH1560 (CLK,SDO) @ GPIO(%u,%u)", _clk, _miso);
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", _clk, _miso);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_APPARENT;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return _current;
if (index == 1) return _voltage;
if (index == 2) return _apparent;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return 0;
}
@ -111,7 +126,7 @@ class ECH1560Sensor : public BaseSensor {
_clk_count = 0;
// register how long the ClkHigh is high to evaluate if we are at the part wher clk goes high for 1-2 ms
// register how long the ClkHigh is high to evaluate if we are at the part where clk goes high for 1-2 ms
while (digitalRead(_clk) == HIGH) {
_clk_count += 1;
delayMicroseconds(30); //can only use delayMicroseconds in an interrupt.
@ -173,17 +188,17 @@ class ECH1560Sensor : public BaseSensor {
while (_bits_count < 40); // skip the uninteresting 5 first bytes
_bits_count = 0;
while (_bits_count < 24) { // loop through the next 3 Bytes (6-8) and save byte 6 and 7 in Ba and Bb
while (_bits_count < 24) { // loop through the next 3 Bytes (6-8) and save byte 6 and 7 in byte1 and byte2
if (_nextbit) {
if (_bits_count < 9) { // first Byte/8 bits in Ba
if (_bits_count < 9) { // first Byte/8 bits in byte1
byte1 = byte1 << 1;
if (digitalRead(_miso) == HIGH) byte1 |= 1;
_nextbit = false;
} else if (_bits_count < 17) { // bit 9-16 is byte 7, stor in Bb
} else if (_bits_count < 17) { // bit 9-16 is byte 7, store in byte2
byte2 = byte2 << 1;
if (digitalRead(_miso) == HIGH) byte2 |= 1;
@ -195,9 +210,9 @@ class ECH1560Sensor : public BaseSensor {
}
if (byte2 != 3) { // if bit Bb is not 3, we have reached the important part, U is allready in Ba and Bb and next 8 Bytes will give us the Power.
if (byte2 != 3) { // if bit byte2 is not 3, we have reached the important part, U is allready in byte1 and byte2 and next 8 Bytes will give us the Power.
// voltage = 2 * (Ba + Bb / 255)
// voltage = 2 * (byte1 + byte2 / 255)
_voltage = 2.0 * ((float) byte1 + (float) byte2 / 255.0);
// power:
@ -209,7 +224,7 @@ class ECH1560Sensor : public BaseSensor {
byte2 = 0;
byte3 = 0;
while (_bits_count < 24) { //store byte 6, 7 and 8 in Ba and Bb & Bc.
while (_bits_count < 24) { //store byte 6, 7 and 8 in byte1 and byte2 & byte3.
if (_nextbit) {
@ -241,7 +256,7 @@ class ECH1560Sensor : public BaseSensor {
byte3 = 255 - byte3;
}
// power = (Ba*255+Bb+Bc/255)/2
// power = (byte1*255+byte2+byte3/255)/2
_apparent = ( (float) byte1 * 255 + (float) byte2 + (float) byte3 / 255.0) / 2;
_current = _apparent / _voltage;
@ -249,9 +264,13 @@ class ECH1560Sensor : public BaseSensor {
}
// If Bb is not 3 or something else than 0, something is wrong!
if (byte2 == 0) _dosync = false;
// If byte2 is not 3 or something else than 0, something is wrong!
if (byte2 == 0) {
_dosync = false;
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("Nothing connected, or out of sync!\n"));
#endif
}
}
// ---------------------------------------------------------------------


+ 52
- 47
code/espurna/sensors/EmonADC121Sensor.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// ADS121-based Energy Monitor Sensor over I2C
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EMON_ADC121_SUPPORT
@ -8,13 +8,7 @@
#pragma once
#include "Arduino.h"
#include "EmonAnalogSensor.h"
#if I2C_USE_BRZO
#include <brzo_i2c.h>
#else
#include <Wire.h>
#endif
#include "EmonSensor.h"
// ADC121 Registers
#define ADC121_REG_RESULT 0x00
@ -29,7 +23,7 @@
#define ADC121_RESOLUTION 12
#define ADC121_CHANNELS 1
class EmonADC121Sensor : public EmonAnalogSensor {
class EmonADC121Sensor : public EmonSensor {
public:
@ -37,7 +31,7 @@ class EmonADC121Sensor : public EmonAnalogSensor {
// Public
// ---------------------------------------------------------------------
EmonADC121Sensor(): EmonAnalogSensor() {
EmonADC121Sensor(): EmonSensor() {
_channels = ADC121_CHANNELS;
_sensor_id = SENSOR_EMON_ADC121_ID;
init();
@ -59,19 +53,7 @@ class EmonADC121Sensor : public EmonAnalogSensor {
if (_address == 0) return;
// Init sensor
#if I2C_USE_BRZO
uint8_t buffer[2];
buffer[0] = ADC121_REG_CONFIG;
buffer[1] = 0x00;
brzo_i2c_start_transaction(_address, I2C_SCL_FREQUENCY);
brzo_i2c_write(buffer, 2, false);
brzo_i2c_end_transaction();
#else
Wire.beginTransmission(_address);
Wire.write(ADC121_REG_CONFIG);
Wire.write(0x00);
Wire.endTransmission();
#endif
_init();
// Just one channel
_count = _magnitudes;
@ -102,8 +84,49 @@ class EmonADC121Sensor : public EmonAnalogSensor {
return;
}
EmonAnalogSensor:pre();
_current[0] = read(0);
#if EMON_REPORT_ENERGY
static unsigned long last = 0;
if (last > 0) {
_energy[0] += (_current[0] * _voltage * (millis() - last) / 1000);
}
last = millis();
#endif
_error = SENSOR_ERROR_OK;
}
// Type for slot # index
unsigned char type(unsigned char index) {
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (index == i++) return MAGNITUDE_CURRENT;
#endif
#if EMON_REPORT_POWER
if (index == i++) return MAGNITUDE_POWER_APPARENT;
#endif
#if EMON_REPORT_ENERGY
if (index == i) return MAGNITUDE_ENERGY;
#endif
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
unsigned char channel = index / _magnitudes;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (index == i++) return _current[channel];
#endif
#if EMON_REPORT_POWER
if (index == i++) return _current[channel] * _voltage;
#endif
#if EMON_REPORT_ENERGY
if (index == i) return _energy[channel];
#endif
return 0;
}
protected:
@ -112,32 +135,14 @@ class EmonADC121Sensor : public EmonAnalogSensor {
// Protected
// ---------------------------------------------------------------------
unsigned int readADC(unsigned char channel) {
void _init() {
i2c_write_uint8(_address, ADC121_REG_CONFIG, 0);
}
unsigned int readADC(unsigned char channel) {
(void) channel;
unsigned int value;
#if I2C_USE_BRZO
uint8_t buffer[2];
buffer[0] = ADC121_REG_RESULT;
brzo_i2c_start_transaction(_address, I2C_SCL_FREQUENCY);
brzo_i2c_write(buffer, 1, false);
brzo_i2c_read(buffer, 2, false);
brzo_i2c_end_transaction();
value = (buffer[0] & 0x0F) << 8;
value |= buffer[1];
#else
Wire.beginTransmission(_address);
Wire.write(ADC121_REG_RESULT);
Wire.endTransmission();
Wire.requestFrom(_address, (unsigned char) 2);
value = (Wire.read() & 0x0F) << 8;
value = value + Wire.read();
#endif
unsigned int value = i2c_read_uint16(_address, ADC121_REG_RESULT) & 0x0FFF;
return value;
}
};


+ 36
- 91
code/espurna/sensors/EmonADS1X15Sensor.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// ADS1X15-based Energy Monitor Sensor over I2C
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EMON_ADS1X15_SUPPORT
@ -10,12 +10,6 @@
#include "Arduino.h"
#include "EmonSensor.h"
#if I2C_USE_BRZO
#include <brzo_i2c.h>
#else
#include <Wire.h>
#endif
#define ADS1X15_CHANNELS (4)
#define ADS1X15_CHIP_ADS1015 (0)
@ -153,7 +147,6 @@ class EmonADS1X15Sensor : public EmonSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
// Discover
unsigned char addresses[] = {0x48, 0x49, 0x4A, 0x4B};
@ -203,23 +196,27 @@ class EmonADS1X15Sensor : public EmonSensor {
return String(buffer);
}
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[10];
unsigned char channel = getChannel(index % _ports);
snprintf(buffer, sizeof(buffer), "0x%02X:%u", _address, channel);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index < _count) {
_error = SENSOR_ERROR_OK;
unsigned char magnitude = index / _ports;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (magnitude == i++) return MAGNITUDE_CURRENT;
#endif
#if EMON_REPORT_POWER
if (magnitude == i++) return MAGNITUDE_POWER_APPARENT;
#endif
#if EMON_REPORT_ENERGY
if (magnitude == i) return MAGNITUDE_ENERGY;
#endif
}
_error = SENSOR_ERROR_OUT_OF_RANGE;
unsigned char magnitude = index / _ports;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (magnitude == i++) return MAGNITUDE_CURRENT;
#endif
#if EMON_REPORT_POWER
if (magnitude == i++) return MAGNITUDE_POWER_APPARENT;
#endif
#if EMON_REPORT_ENERGY
if (magnitude == i) return MAGNITUDE_ENERGY;
#endif
return MAGNITUDE_NONE;
}
@ -233,33 +230,24 @@ class EmonADS1X15Sensor : public EmonSensor {
#endif
}
last = millis();
_error = SENSOR_ERROR_OK;
}
// Current value for slot # index
double value(unsigned char index) {
if (index < _count) {
_error = SENSOR_ERROR_OK;
unsigned char port = index % _ports;
unsigned char magnitude = index / _ports;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (magnitude == i++) return _current[port];
#endif
#if EMON_REPORT_POWER
if (magnitude == i++) return _current[port] * _voltage;
#endif
#if EMON_REPORT_ENERGY
if (magnitude == i) return _energy[port];
#endif
}
_error = SENSOR_ERROR_OUT_OF_RANGE;
unsigned char port = index % _ports;
unsigned char magnitude = index / _ports;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (magnitude == i++) return _current[port];
#endif
#if EMON_REPORT_POWER
if (magnitude == i++) return _current[port] * _voltage;
#endif
#if EMON_REPORT_ENERGY
if (magnitude == i) return _energy[port];
#endif
return 0;
}
protected:
@ -313,26 +301,8 @@ class EmonADS1X15Sensor : public EmonSensor {
}
config |= ((channel + 4) << 12); // Set single-ended input channel (0x4000 - 0x7000)
#if SENSOR_DEBUG
//Serial.printf("[EMON] ADS1X115 Config Registry: %04X\n", config);
#endif
// Write config register to the ADC
#if I2C_USE_BRZO
uint8_t buffer[3];
buffer[0] = ADS1X15_REG_POINTER_CONFIG;
buffer[1] = config >> 8;
buffer[2] = config & 0xFF;
brzo_i2c_start_transaction(_address, I2C_SCL_FREQUENCY);
brzo_i2c_write(buffer, 3, false);
brzo_i2c_end_transaction();
#else
Wire.beginTransmission(_address);
Wire.write((uint8_t) ADS1X15_REG_POINTER_CONFIG);
Wire.write((uint8_t) (config >> 8));
Wire.write((uint8_t) (config & 0xFF));
Wire.endTransmission();
#endif
i2c_write_uint16(_address, ADS1X15_REG_POINTER_CONFIG, config);
}
@ -344,7 +314,7 @@ class EmonADS1X15Sensor : public EmonSensor {
setConfigRegistry(channel, true, false);
setConfigRegistry(channel, false, false);
setConfigRegistry(channel, false, true);
delay(10);
nice_delay(10);
readADC(channel);
previous = channel;
}
@ -355,36 +325,11 @@ class EmonADS1X15Sensor : public EmonSensor {
}
unsigned int readADC(unsigned char channel) {
(void) channel;
unsigned int value = 0;
#if I2C_USE_BRZO
uint8_t buffer[3];
buffer[0] = ADS1X15_REG_POINTER_CONVERT;
brzo_i2c_start_transaction(_address, I2C_SCL_FREQUENCY);
brzo_i2c_write(buffer, 1, false);
brzo_i2c_read(buffer, 2, false);
brzo_i2c_end_transaction();
value |= buffer[0] << 8;
value |= buffer[1];
#else
Wire.beginTransmission(_address);
Wire.write(ADS1X15_REG_POINTER_CONVERT);
Wire.endTransmission();
Wire.requestFrom(_address, (unsigned char) 2);
value |= Wire.read() << 8;
value |= Wire.read();
#endif
unsigned int value = i2c_read_uint16(_address, ADS1X15_REG_POINTER_CONVERT);
if (_type = ADS1X15_CHIP_ADS1015) value >>= ADS1015_BIT_SHIFT;
delayMicroseconds(500);
return value;
}
unsigned char _type = ADS1X15_CHIP_ADS1115;


+ 12
- 9
code/espurna/sensors/EmonAnalogSensor.h View File

@ -1,12 +1,16 @@
// -----------------------------------------------------------------------------
// Energy Monitor Sensor using builtin ADC
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EMON_ANALOG_SUPPORT
#pragma once
// Set ADC to TOUT pin
#undef ADC_MODE_VALUE
#define ADC_MODE_VALUE ADC_TOUT
#include "Arduino.h"
#include "EmonSensor.h"
@ -59,9 +63,13 @@ class EmonAnalogSensor : public EmonSensor {
return String("EMON @ ANALOG @ GPIO0");
}
// 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) {
_error = SENSOR_ERROR_OK;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (index == i++) return MAGNITUDE_CURRENT;
@ -72,7 +80,6 @@ class EmonAnalogSensor : public EmonSensor {
#if EMON_REPORT_ENERGY
if (index == i) return MAGNITUDE_ENERGY;
#endif
_error = SENSOR_ERROR_OUT_OF_RANGE;
return MAGNITUDE_NONE;
}
@ -89,14 +96,13 @@ class EmonAnalogSensor : public EmonSensor {
last = millis();
#endif
_error = SENSOR_ERROR_OK;
}
// Current value for slot # index
double value(unsigned char index) {
_error = SENSOR_ERROR_OK;
unsigned char channel = index / _magnitudes;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (index == i++) return _current[channel];
@ -107,10 +113,7 @@ class EmonAnalogSensor : public EmonSensor {
#if EMON_REPORT_ENERGY
if (index == i) return _energy[channel];
#endif
_error = SENSOR_ERROR_OUT_OF_RANGE;
return 0;
}
protected:


+ 32
- 10
code/espurna/sensors/EmonSensor.h View File

@ -1,14 +1,20 @@
// -----------------------------------------------------------------------------
// Abstract Energy Monitor Sensor (other EMON sensors extend this class)
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
extern "C" {
#include "libs/fs_math.h"
}
class EmonSensor : public I2CSensor {
@ -39,9 +45,16 @@ class EmonSensor : public I2CSensor {
if (actual == 0) return;
if (expected == actual) return;
_current_ratio[channel] = _current_ratio[channel] * ((double) expected / (double) actual);
calculateFactors(channel);
_dirty = true;
}
void resetEnergy() {
for (unsigned char i=0; i<_channels; i++) {
_energy[i] = 0;
}
}
// ---------------------------------------------------------------------
void setVoltage(double voltage) {
@ -60,6 +73,7 @@ class EmonSensor : public I2CSensor {
if (channel >= _channels) return;
if (_current_ratio[channel] == current_ratio) return;
_current_ratio[channel] = current_ratio;
calculateFactors(channel);
_dirty = true;
}
@ -95,8 +109,7 @@ class EmonSensor : public I2CSensor {
for (unsigned char i=0; i<_channels; i++) {
_energy[i] = _current[i] = 0;
_pivot[i] = _adc_counts >> 1;
_current_factor[i] = _current_ratio[i] * _reference / _adc_counts;
_multiplier[i] = calculateMultiplier(_current_factor[i]);
calculateFactors(i);
}
#if SENSOR_DEBUG
@ -109,6 +122,10 @@ class EmonSensor : public I2CSensor {
}
#endif
_ready = true;
_dirty = false;
}
protected:
@ -131,31 +148,36 @@ class EmonSensor : public I2CSensor {
virtual unsigned int readADC(unsigned char channel) {}
unsigned int calculateMultiplier(double current_factor) {
void calculateFactors(unsigned char channel) {
_current_factor[channel] = _current_ratio[channel] * _reference / _adc_counts;
unsigned int s = 1;
unsigned int i = 1;
unsigned int m = s * i;
unsigned int multiplier;
while (m * current_factor < 1) {
while (m * _current_factor[channel] < 1) {
multiplier = m;
i = (i == 1) ? 2 : (i == 2) ? 5 : 1;
if (i == 1) s *= 10;
m = s * i;
}
return multiplier;
_multiplier[channel] = multiplier;
}
double read(unsigned char channel) {
int sample;
int max = 0;
int min = _adc_counts;
double filtered;
double sum = 0;
unsigned long time_span = millis();
for (unsigned long i=0; i<_samples; i++) {
int sample;
double filtered;
// Read analog value
sample = readADC(channel);
if (sample > max) max = sample;
@ -177,7 +199,7 @@ class EmonSensor : public I2CSensor {
}
// Calculate current
double rms = _samples > 0 ? sqrt(sum / _samples) : 0;
double rms = _samples > 0 ? fs_sqrt(sum / _samples) : 0;
double current = _current_factor[channel] * rms;
current = (double) (int(current * _multiplier[channel]) - 1) / _multiplier[channel];
if (current < 0) current = 0;
@ -191,7 +213,7 @@ class EmonSensor : public I2CSensor {
DEBUG_MSG("[EMON] Min value: %d\n", min);
DEBUG_MSG("[EMON] Midpoint value: %d\n", int(_pivot[channel]));
DEBUG_MSG("[EMON] RMS value: %d\n", int(rms));
DEBUG_MSG("[EMON] Current (mA): %d\n", int(current));
DEBUG_MSG("[EMON] Current (mA): %d\n", int(1000 * current));
#endif
// Check timing


+ 12
- 5
code/espurna/sensors/EventSensor.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Event Counter Sensor
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EVENTS_SUPPORT
@ -72,6 +72,7 @@ class EventSensor : public BaseSensor {
void begin() {
pinMode(_gpio, _mode);
_enableInterrupts(true);
_ready = true;
}
// Descriptive name of the sensor
@ -81,23 +82,29 @@ class EventSensor : public BaseSensor {
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return MAGNITUDE_EVENTS;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) {
double value = _events;
_events = 0;
return value;
};
_error = SENSOR_ERROR_OUT_OF_RANGE;
return 0;
}


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

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

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

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

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

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

+ 26
- 12
code/espurna/sensors/HLW8012Sensor.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// Event Counter Sensor
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && HLW8012_SUPPORT
@ -48,6 +48,10 @@ class HLW8012Sensor : public BaseSensor {
_hlw8012->resetMultipliers();
}
void resetEnergy() {
_hlw8012->resetEnergy();
}
// ---------------------------------------------------------------------
void setSEL(unsigned char sel) {
@ -153,18 +157,31 @@ class HLW8012Sensor : public BaseSensor {
});
#endif
_ready = true;
}
// Descriptive name of the sensor
String description() {
char buffer[25];
snprintf(buffer, sizeof(buffer), "HLW8012 @ GPIO(%i,%i,%i)", _sel, _cf, _cf1);
snprintf(buffer, sizeof(buffer), "HLW8012 @ GPIO(%u,%u,%u)", _sel, _cf, _cf1);
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[10];
snprintf(buffer, sizeof(buffer), "%u:%u:%u", _sel, _cf, _cf1);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
@ -172,13 +189,11 @@ class HLW8012Sensor : public BaseSensor {
if (index == 4) return MAGNITUDE_POWER_APPARENT;
if (index == 5) return MAGNITUDE_POWER_FACTOR;
if (index == 6) return MAGNITUDE_ENERGY;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return _hlw8012->getCurrent();
if (index == 1) return _hlw8012->getVoltage();
if (index == 2) return _hlw8012->getActivePower();
@ -186,7 +201,6 @@ class HLW8012Sensor : public BaseSensor {
if (index == 4) return _hlw8012->getApparentPower();
if (index == 5) return 100 * _hlw8012->getPowerFactor();
if (index == 6) return _hlw8012->getEnergy();
_error = SENSOR_ERROR_OUT_OF_RANGE;
return 0;
}
@ -243,10 +257,10 @@ class HLW8012Sensor : public BaseSensor {
// ---------------------------------------------------------------------
unsigned char _sel;
unsigned char _cf;
unsigned char _cf1;
bool _sel_current;
unsigned char _sel = GPIO_NONE;
unsigned char _cf = GPIO_NONE;
unsigned char _cf1 = GPIO_NONE;
bool _sel_current = true;
HLW8012 * _hlw8012 = NULL;
@ -295,7 +309,7 @@ void HLW8012Sensor::_attach(HLW8012Sensor * instance, unsigned char gpio, unsign
_hlw8012_sensor_instance[index] = instance;
attachInterrupt(gpio, _hlw8012_sensor_isr_list[index], mode);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt attached to %s\n"), gpio, instance->description().c_str());
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt attached to %s\n"), gpio, instance->description().c_str());
#endif
}
@ -305,7 +319,7 @@ void HLW8012Sensor::_detach(unsigned char gpio) {
if (_hlw8012_sensor_instance[index]) {
detachInterrupt(gpio);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt detached from %s\n"), gpio, _hlw8012_sensor_instance[index]->description().c_str());
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt detached from %s\n"), gpio, _hlw8012_sensor_instance[index]->description().c_str());
#endif
_hlw8012_sensor_instance[index] = NULL;
}


+ 49
- 15
code/espurna/sensors/I2CSensor.h View File

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


+ 21
- 8
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 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && MHZ19_SUPPORT
@ -72,28 +72,41 @@ class MHZ19Sensor : public BaseSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
if (_serial) delete _serial;
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 256);
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32);
_serial->enableIntTx(false);
_serial->begin(9600);
calibrateAuto(false);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "MHZ19 @ SwSerial(%i,%i)", _pin_rx, _pin_tx);
snprintf(buffer, sizeof(buffer), "MHZ19 @ 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) {
_error = SENSOR_ERROR_OK;
if (index == 0) return MAGNITUDE_CO2;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return MAGNITUDE_NONE;
}
@ -103,9 +116,7 @@ class MHZ19Sensor : public BaseSensor {
// Current value for slot # index
double value(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return _co2;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return 0;
}
@ -185,6 +196,8 @@ class MHZ19Sensor : public BaseSensor {
_error = SENSOR_ERROR_OUT_OF_RANGE;
}
} else {
_error = SENSOR_ERROR_CRC;
}
}


+ 219
- 50
code/espurna/sensors/PMSX003Sensor.h View File

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

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

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

+ 15
- 45
code/espurna/sensors/SHT3XI2CSensor.h View File

@ -1,19 +1,18 @@
// -----------------------------------------------------------------------------
// SHT3X Sensor over I2C (Wemos)
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && SHT3X_I2C_SUPPORT
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
#if I2C_USE_BRZO
#include <brzo_i2c.h>
#else
#include <Wire.h>
#endif
class SHT3XI2CSensor : public I2CSensor {
@ -36,13 +35,15 @@ class SHT3XI2CSensor : public I2CSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
// I2C auto-discover
unsigned char addresses[] = {0x45};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
@ -54,49 +55,20 @@ class SHT3XI2CSensor : public I2CSensor {
// Type for slot # index
unsigned char type(unsigned char index) {
if (index < _count) {
_error = SENSOR_ERROR_OK;
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
}
_error = SENSOR_ERROR_OUT_OF_RANGE;
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
unsigned char buffer[6];
_error = SENSOR_ERROR_OK;
#if I2C_USE_BRZO
buffer[0] = 0x2C;
buffer[1] = 0x06;
brzo_i2c_start_transaction(_address, I2C_SCL_FREQUENCY);
brzo_i2c_write(buffer, 2, false);
#else
Wire.beginTransmission(_address);
Wire.write(0x2C);
Wire.write(0x06);
if (Wire.endTransmission() != 0) {
_error = SENSOR_ERROR_TIMEOUT;
return;
}
#endif
delay(500);
#if I2C_USE_BRZO
brzo_i2c_read(buffer, 6, false);
brzo_i2c_end_transaction();
#else
Wire.requestFrom(_address, (unsigned char) 6);
for (int i=0; i<6; i++) buffer[i] = Wire.read();
delay(50);
if (Wire.available() != 0) {
_error = SENSOR_ERROR_CRC;
return;
}
#endif
unsigned char buffer[6];
i2c_write_uint8(_address, 0x2C, 0x06);
nice_delay(500);
i2c_read_buffer(_address, buffer, 6);
// cTemp msb, cTemp lsb, cTemp crc, humidity msb, humidity lsb, humidity crc
_temperature = ((((buffer[0] * 256.0) + buffer[1]) * 175) / 65535.0) - 45;
@ -106,10 +78,8 @@ class SHT3XI2CSensor : public I2CSensor {
// Current value for slot # index
double value(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return _temperature;
if (index == 1) return _humidity;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return 0;
}


+ 88
- 96
code/espurna/sensors/SI7021Sensor.h View File

@ -1,19 +1,19 @@
// -----------------------------------------------------------------------------
// SI7021 / HTU21D Sensor over I2C
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && SI7021_SUPPORT
#pragma once
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h"
#include "I2CSensor.h"
#if I2C_USE_BRZO
#include <brzo_i2c.h>
#else
#include <Wire.h>
#endif
#define SI7021_SCL_FREQUENCY 200
@ -25,6 +25,9 @@
#define SI7021_CMD_TMP_NOHOLD 0xF3
#define SI7021_CMD_HUM_NOHOLD 0xF5
PROGMEM const char si7021_chip_si7021_name[] = "SI7021";
PROGMEM const char si7021_chip_htu21d_name[] = "HTU21D";
class SI7021Sensor : public I2CSensor {
public:
@ -43,76 +46,61 @@ class SI7021Sensor : public I2CSensor {
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_dirty = false;
// I2C auto-discover
unsigned char addresses[] = {0x40};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Check device
#if I2C_USE_BRZO
uint8_t buffer[2] = {0xFC, 0xC9};
brzo_i2c_start_transaction(_address, SI7021_SCL_FREQUENCY);
brzo_i2c_write(buffer, 2, false);
brzo_i2c_read(buffer, 1, false);
brzo_i2c_end_transaction();
_chip = buffer[0];
#else
Wire.beginTransmission(_address);
Wire.write(0xFC);
Wire.write(0xC9);
Wire.endTransmission();
Wire.requestFrom(_address, (unsigned char) 1);
_chip = Wire.read();
#endif
if ((_chip != SI7021_CHIP_SI7021) & (_chip != SI7021_CHIP_HTU21D)) {
i2cReleaseLock(_address);
_error = SENSOR_ERROR_UNKNOWN_ID;
} else {
_count = 2;
}
_init();
_dirty = !_ready;
}
// Descriptive name of the sensor
String description() {
char name[10];
strncpy_P(name,
_chip == SI7021_CHIP_SI7021 ?
si7021_chip_si7021_name :
si7021_chip_htu21d_name,
sizeof(name)
);
char buffer[25];
snprintf(buffer, sizeof(buffer), "%s @ I2C (0x%02X)", chipAsString().c_str(), _address);
snprintf(buffer, sizeof(buffer), "%s @ I2C (0x%02X)", name, _address);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Type for slot # index
unsigned char type(unsigned char index) {
if (index < _count) {
_error = SENSOR_ERROR_OK;
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
}
_error = SENSOR_ERROR_OUT_OF_RANGE;
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_UNKNOWN_ID;
if (_chip == 0) return;
_error = SENSOR_ERROR_OK;
double value;
value = _read(SI7021_CMD_TMP_NOHOLD);
if (_error != SENSOR_ERROR_OK) return;
_temperature = (175.72 * value / 65536) - 46.85;
value = _read(SI7021_CMD_HUM_NOHOLD);
if (_error != SENSOR_ERROR_OK) return;
value = (125.0 * value / 65536) - 6;
_humidity = constrain(value, 0, 100);
}
// Current value for slot # index
double value(unsigned char index) {
if (index < _count) {
_error = SENSOR_ERROR_OK;
double value;
if (index == 0) {
value = read(SI7021_CMD_TMP_NOHOLD);
value = (175.72 * value / 65536) - 46.85;
}
if (index == 1) {
value = read(SI7021_CMD_HUM_NOHOLD);
value = (125.0 * value / 65536) - 6;
value = constrain(value, 0, 100);
}
return value;
}
_error = SENSOR_ERROR_OUT_OF_RANGE;
if (index == 0) return _temperature;
if (index == 1) return _humidity;
return 0;
}
@ -122,59 +110,63 @@ class SI7021Sensor : public I2CSensor {
// Protected
// ---------------------------------------------------------------------
unsigned int read(uint8_t command) {
void _init() {
// I2C auto-discover
unsigned char addresses[] = {0x40};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Check device
i2c_write_uint8(_address, 0xFC, 0xC9);
_chip = i2c_read_uint8(_address);
unsigned char bytes = (command == 0xE0) ? 2 : 3;
if ((_chip != SI7021_CHIP_SI7021) & (_chip != SI7021_CHIP_HTU21D)) {
_count = 0;
i2cReleaseLock(_address);
_previous_address = 0;
_error = SENSOR_ERROR_UNKNOWN_ID;
#if I2C_USE_BRZO
uint8_t buffer[2] = {command, 0x00};
brzo_i2c_start_transaction(_address, SI7021_SCL_FREQUENCY);
brzo_i2c_write(buffer, 1, false);
#else
Wire.beginTransmission(_address);
Wire.write(command);
Wire.endTransmission();
#endif
// Setting _address to 0 forces auto-discover
// This might be necessary at this stage if there is a
// different sensor in the hardcoded address
_address = 0;
} else {
_count = 2;
}
_ready = true;
}
unsigned int _read(uint8_t command) {
// Request measurement
i2c_write_uint8(_address, command);
// When not using clock stretching (*_NOHOLD commands) delay here
// is needed to wait for the measurement.
// According to datasheet the max. conversion time is ~22ms
unsigned long start = millis();
while (millis() - start < 50) delay(1);
#if I2C_USE_BRZO
brzo_i2c_read(buffer, 2, false);
brzo_i2c_end_transaction();
unsigned int msb = buffer[0];
unsigned int lsb = buffer[1];
#else
Wire.requestFrom(_address, bytes);
if (Wire.available() != bytes) {
_error = SENSOR_ERROR_CRC;
return 0;
}
unsigned int msb = Wire.read();
unsigned int lsb = Wire.read();
#endif
nice_delay(50);
// Clear the last to bits of LSB to 00.
// According to datasheet LSB of RH is always xxxxxx10
lsb &= 0xFC;
unsigned int value = (msb << 8) | lsb;
unsigned int value = i2c_read_uint16(_address) & 0xFFFC;
// We should be checking there are no pending bytes in the buffer
// and raise a CRC error if there are
_error = SENSOR_ERROR_OK;
return value;
}
return value;
String chipAsString() {
if (_chip == SI7021_CHIP_SI7021) return String("SI7021");
if (_chip == SI7021_CHIP_HTU21D) return String("HTU21D");
return String("Unknown");
}
unsigned char _chip;
double _temperature = 0;
double _humidity = 0;
};


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

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

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

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

+ 22
- 10
code/espurna/sensors/V9261FSensor.h View File

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// V9261F based power monitor
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && V9261F_SUPPORT
@ -9,6 +9,9 @@
#include "Arduino.h"
#include "BaseSensor.h"
extern "C" {
#include "libs/fs_math.h"
}
#include <SoftwareSerial.h>
@ -20,7 +23,7 @@ class V9261FSensor : public BaseSensor {
// Public
// ---------------------------------------------------------------------
V9261FSensor(): BaseSensor() {
V9261FSensor(): BaseSensor(), _data() {
_count = 6;
_sensor_id = SENSOR_V9261F_ID;
}
@ -61,22 +64,35 @@ class V9261FSensor : public BaseSensor {
void begin() {
if (!_dirty) return;
_dirty = false;
if (_serial) delete _serial;
_serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 256);
_serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32);
_serial->enableIntTx(false);
_serial->begin(V9261F_BAUDRATE);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "V9261F @ SwSerial(%i,NULL)", _pin_rx);
snprintf(buffer, sizeof(buffer), "V9261F @ SwSerial(%u,NULL)", _pin_rx);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_pin_rx);
}
// Loop-like method, call it in your main loop
void tick() {
_read();
@ -84,27 +100,23 @@ class V9261FSensor : public BaseSensor {
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;
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_REACTIVE;
if (index == 4) return MAGNITUDE_POWER_APPARENT;
if (index == 5) return MAGNITUDE_POWER_FACTOR;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
_error = SENSOR_ERROR_OK;
if (index == 0) return _current;
if (index == 1) return _voltage;
if (index == 2) return _active;
if (index == 3) return _reactive;
if (index == 4) return _apparent;
if (index == 5) return _apparent > 0 ? 100 * _active / _apparent : 100;
_error = SENSOR_ERROR_OUT_OF_RANGE;
return 0;
}
@ -194,7 +206,7 @@ class V9261FSensor : public BaseSensor {
if (_voltage < 0) _voltage = 0;
if (_current < 0) _current = 0;
_apparent = sqrt(_reactive * _reactive + _active * _active);
_apparent = fs_sqrt(_reactive * _reactive + _active * _active);
}


+ 321
- 250
code/espurna/settings.ino View File

@ -2,87 +2,71 @@
SETTINGS MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <EEPROM.h>
#include "spi_flash.h"
#include <EEPROM_Rotate.h>
#include <vector>
#include "libs/EmbedisWrap.h"
#include <StreamString.h>
#if TELNET_SUPPORT
#include "libs/StreamInjector.h"
#ifdef DEBUG_PORT
StreamInjector _serial = StreamInjector(DEBUG_PORT);
#else
StreamInjector _serial = StreamInjector(Serial);
#endif
EmbedisWrap embedis(_serial);
#else
#ifdef DEBUG_PORT
EmbedisWrap embedis(DEBUG_PORT);
#else
EmbedisWrap embedis(_serial);
#endif
#endif
#include <Stream.h>
#include "libs/StreamInjector.h"
StreamInjector _serial = StreamInjector(TERMINAL_BUFFER_SIZE);
EmbedisWrap embedis(_serial, TERMINAL_BUFFER_SIZE);
#if TERMINAL_SUPPORT
#if SERIAL_RX_ENABLED
char _serial_rx_buffer[TERMINAL_BUFFER_SIZE];
static unsigned char _serial_rx_pointer = 0;
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
bool _settings_save = false;
// -----------------------------------------------------------------------------
// Settings
// Reverse engineering EEPROM storage format
// -----------------------------------------------------------------------------
#if TELNET_SUPPORT
void settingsInject(void *data, size_t len) {
_serial.inject((char *) data, len);
}
#endif
size_t settingsMaxSize() {
size_t size = EEPROM_SIZE;
if (size > SPI_FLASH_SEC_SIZE) size = SPI_FLASH_SEC_SIZE;
size = (size + 3) & (~3);
return size;
}
unsigned long settingsSize() {
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
while (size_t len = EEPROMr.read(pos)) {
pos = pos - len - 2;
}
return SPI_FLASH_SEC_SIZE - pos;
}
unsigned int settingsKeyCount() {
// -----------------------------------------------------------------------------
unsigned int _settingsKeyCount() {
unsigned count = 0;
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
while (size_t len = EEPROMr.read(pos)) {
pos = pos - len - 2;
len = EEPROM.read(pos);
len = EEPROMr.read(pos);
pos = pos - len - 2;
count ++;
}
return count;
}
String settingsKeyName(unsigned int index) {
String _settingsKeyName(unsigned int index) {
String s;
unsigned count = 0;
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
while (size_t len = EEPROMr.read(pos)) {
pos = pos - len - 2;
if (count == index) {
s.reserve(len);
for (unsigned char i = 0 ; i < len; i++) {
s += (char) EEPROM.read(pos + i + 1);
s += (char) EEPROMr.read(pos + i + 1);
}
break;
}
count++;
len = EEPROM.read(pos);
len = EEPROMr.read(pos);
pos = pos - len - 2;
}
@ -90,287 +74,230 @@ String settingsKeyName(unsigned int index) {
}
bool settingsRestore(JsonObject& data) {
std::vector<String> _settingsKeys() {
const char* app = data["app"];
if (strcmp(app, APP_NAME) != 0) return false;
// Get sorted list of keys
std::vector<String> keys;
for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF);
}
//unsigned int size = settingsKeyCount();
unsigned int size = _settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
for (auto element : data) {
if (strcmp(element.key, "app") == 0) continue;
if (strcmp(element.key, "version") == 0) continue;
setSetting(element.key, element.value.as<char*>());
}
//String key = settingsKeyName(i);
String key = _settingsKeyName(i);
bool inserted = false;
for (unsigned char j=0; j<keys.size(); j++) {
saveSettings();
// Check if we have to insert it before the current element
if (keys[j].compareTo(key) > 0) {
keys.insert(keys.begin() + j, key);
inserted = true;
break;
}
DEBUG_MSG_P(PSTR("[SETTINGS] Settings restored successfully\n"));
return true;
}
}
// If we could not insert it, just push it at the end
if (!inserted) keys.push_back(key);
void settingsFactoryReset() {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF);
}
EEPROM.commit();
return keys;
}
void settingsHelp() {
unsigned char len = embedis.getCommandsCount();
DEBUG_MSG_P(PSTR("\nAvailable commands:\n\n"));
for (unsigned char i=0; i<len; i++) {
DEBUG_MSG_P(PSTR("* %s\n"), embedis.getCommandName(i).c_str());
if (embedis.getCommandName(i).equals("WRITE")) {
DEBUG_MSG_P(PSTR("\n"));
// -----------------------------------------------------------------------------
// Commands
// -----------------------------------------------------------------------------
void _settingsHelpCommand() {
// Get sorted list of commands
std::vector<String> commands;
unsigned char size = embedis.getCommandCount();
for (unsigned int i=0; i<size; i++) {
String command = embedis.getCommandName(i);
bool inserted = false;
for (unsigned char j=0; j<commands.size(); j++) {
// Check if we have to insert it before the current element
if (commands[j].compareTo(command) > 0) {
commands.insert(commands.begin() + j, command);
inserted = true;
break;
}
}
// If we could not insert it, just push it at the end
if (!inserted) commands.push_back(command);
}
DEBUG_MSG_P(PSTR("\n"));
}
void settingsSetup() {
// Output the list
DEBUG_MSG_P(PSTR("Available commands:\n"));
for (unsigned char i=0; i<commands.size(); i++) {
DEBUG_MSG_P(PSTR("> %s\n"), (commands[i]).c_str());
}
EEPROM.begin(SPI_FLASH_SEC_SIZE);
}
#if TELNET_SUPPORT
_serial.callback([](uint8_t ch) {
telnetWrite(ch);
});
#endif
void _settingsKeysCommand() {
Embedis::dictionary( F("EEPROM"),
SPI_FLASH_SEC_SIZE,
[](size_t pos) -> char { return EEPROM.read(pos); },
[](size_t pos, char value) { EEPROM.write(pos, value); },
#if SETTINGS_AUTOSAVE
[]() { _settings_save = true; }
#else
[]() {}
#endif
);
// Get sorted list of keys
std::vector<String> keys = _settingsKeys();
Embedis::hardware( F("WIFI"), [](Embedis* e) {
StreamString s;
WiFi.printDiag(s);
DEBUG_MSG(s.c_str());
}, 0);
// Write key-values
DEBUG_MSG_P(PSTR("Current settings:\n"));
for (unsigned int i=0; i<keys.size(); i++) {
String value = getSetting(keys[i]);
DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), (keys[i]).c_str(), value.c_str());
}
// -------------------------------------------------------------------------
unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize();
DEBUG_MSG_P(PSTR("Number of keys: %d\n"), keys.size());
DEBUG_MSG_P(PSTR("Current EEPROM sector: %u\n"), EEPROMr.current());
DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
Embedis::command( F("BRIGHTNESS"), [](Embedis* e) {
if (e->argc > 1) {
lightBrightness(String(e->argv[1]).toInt());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Brightness: %d\n"), lightBrightness());
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
Embedis::command( F("CHANNEL"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
}
int id = String(e->argv[1]).toInt();
if (e->argc > 2) {
int value = String(e->argv[2]).toInt();
lightChannel(id, value);
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Channel #%d: %d\n"), id, lightChannel(id));
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
void _settingsFactoryResetCommand() {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROMr.write(i, 0xFF);
}
EEPROMr.commit();
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
Embedis::command( 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"));
});
#endif
void _settingsInitCommands() {
#if DEBUG_SUPPORT
Embedis::command( F("CRASH"), [](Embedis* e) {
settingsRegisterCommand(F("CRASH"), [](Embedis* e) {
debugDumpCrashInfo();
debugClearCrashInfo();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
Embedis::command( F("DUMP"), [](Embedis* e) {
unsigned int size = settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
String key = settingsKeyName(i);
String value = getSetting(key);
DEBUG_MSG_P(PSTR("+%s => %s\n"), key.c_str(), value.c_str());
}
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("DUMP.RAW"), [](Embedis* e) {
bool ascii = false;
if (e->argc == 2) ascii = String(e->argv[1]).toInt() == 1;
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
if (i % 16 == 0) DEBUG_MSG_P(PSTR("\n[%04X] "), i);
byte c = EEPROM.read(i);
if (ascii && 32 <= c && c <= 126) {
DEBUG_MSG_P(PSTR(" %c "), c);
} else {
DEBUG_MSG_P(PSTR("%02X "), c);
}
}
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
Embedis::command( F("EEPROM"), [](Embedis* e) {
unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize();
DEBUG_MSG_P(PSTR("Number of keys: %d\n"), settingsKeyCount());
DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
settingsRegisterCommand(F("COMMANDS"), [](Embedis* e) {
_settingsHelpCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("ERASE.CONFIG"), [](Embedis* e) {
settingsRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
resetReason(CUSTOM_RESET_TERMINAL);
ESP.eraseConfig();
*((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494
});
Embedis::command( F("FACTORY.RESET"), [](Embedis* e) {
settingsFactoryReset();
#if I2C_SUPPORT
settingsRegisterCommand(F("I2C.SCAN"), [](Embedis* e) {
i2cScan();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("I2C.CLEAR"), [](Embedis* e) {
i2cClearBus();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("FACTORY.RESET"), [](Embedis* e) {
_settingsFactoryResetCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("GPIO"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
int pin = String(e->argv[1]).toInt();
//if (!gpioValid(pin)) {
// DEBUG_MSG_P(PSTR("-ERROR: Invalid GPIO\n"));
// return;
//}
if (e->argc > 2) {
bool state = String(e->argv[2]).toInt() == 1;
digitalWrite(pin, state);
}
DEBUG_MSG_P(PSTR("GPIO %d is %s\n"), pin, digitalRead(pin) == HIGH ? "HIGH" : "LOW");
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("HEAP"), [](Embedis* e) {
settingsRegisterCommand(F("HEAP"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Free HEAP: %d bytes\n"), getFreeHeap());
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("HELP"), [](Embedis* e) {
settingsHelp();
settingsRegisterCommand(F("HELP"), [](Embedis* e) {
_settingsHelpCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("INFO"), [](Embedis* e) {
welcome();
wifiStatus();
settingsRegisterCommand(F("INFO"), [](Embedis* e) {
info();
wifiDebug();
//StreamString s;
//WiFi.printDiag(s);
//DEBUG_MSG(s.c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
Embedis::command( 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"));
});
#endif
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
Embedis::command( F("MIRED"), [](Embedis* e) {
if (e->argc > 1) {
String color = String("M") + String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
#if NOFUSS_SUPPORT
Embedis::command( F("NOFUSS"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
nofussRun();
});
#endif
settingsRegisterCommand(F("KEYS"), [](Embedis* e) {
_settingsKeysCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("RELAY"), [](Embedis* e) {
settingsRegisterCommand(F("GET"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
int id = String(e->argv[1]).toInt();
if (e->argc > 2) {
int value = String(e->argv[2]).toInt();
if (value == 2) {
relayToggle(id);
} else {
relayStatus(id, value == 1);
for (unsigned char i = 1; i < e->argc; i++) {
String key = String(e->argv[i]);
String value;
if (!Embedis::get(key, value)) {
DEBUG_MSG_P(PSTR("> %s =>\n"), key.c_str());
continue;
}
DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), key.c_str(), value.c_str());
}
DEBUG_MSG_P(PSTR("Status: %s\n"), relayStatus(id) ? "true" : "false");
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("RESET"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
#if MQTT_SUPPORT
Embedis::command( F("RESET.MQTT"), [](Embedis* e) {
mqttConfigure();
mqttDisconnect();
#if WEB_SUPPORT
settingsRegisterCommand(F("RELOAD"), [](Embedis* e) {
wsReload();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("RESET"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
Embedis::command( F("RESET.WIFI"), [](Embedis* e) {
wifiConfigure();
wifiDisconnect();
settingsRegisterCommand(F("RESET.SAFE"), [](Embedis* e) {
EEPROMr.write(EEPROM_CRASH_COUNTER, SYSTEM_CHECK_MAX);
DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
Embedis::command( F("UPTIME"), [](Embedis* e) {
settingsRegisterCommand(F("UPTIME"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime());
DEBUG_MSG_P(PSTR("+OK\n"));
});
DEBUG_MSG_P(PSTR("[SETTINGS] EEPROM size: %d bytes\n"), SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("[SETTINGS] Settings size: %d bytes\n"), settingsSize());
}
void settingsDump() {
unsigned int size = settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
String key = settingsKeyName(i);
String value = getSetting(key);
DEBUG_MSG_P(PSTR("%s => %s\n"), key.c_str(), value.c_str());
}
}
void settingsLoop() {
if (_settings_save) {
//DEBUG_MSG_P(PSTR("[SETTINGS] Saving\n"));
EEPROM.commit();
_settings_save = false;
}
#if TERMINAL_SUPPORT
embedis.process();
#endif
}
void saveSettings() {
#if not SETTINGS_AUTOSAVE
_settings_save = true;
#endif
//settingsDump();
}
// -----------------------------------------------------------------------------
// Key-value API
// -----------------------------------------------------------------------------
void moveSetting(const char * from, const char * to) {
@ -416,3 +343,147 @@ bool hasSetting(const String& key) {
bool hasSetting(const String& key, unsigned int index) {
return getSetting(key, index, "").length() != 0;
}
void saveSettings() {
#if not SETTINGS_AUTOSAVE
_settings_save = true;
#endif
}
void resetSettings() {
_settingsFactoryResetCommand();
}
// -----------------------------------------------------------------------------
// Settings
// -----------------------------------------------------------------------------
void settingsInject(void *data, size_t len) {
_serial.inject((char *) data, len);
}
Stream & settingsSerial() {
return (Stream &) _serial;
}
size_t settingsMaxSize() {
size_t size = EEPROM_SIZE;
if (size > SPI_FLASH_SEC_SIZE) size = SPI_FLASH_SEC_SIZE;
size = (size + 3) & (~3);
return size;
}
bool settingsRestoreJson(JsonObject& data) {
const char* app = data["app"];
if (strcmp(app, APP_NAME) != 0) return false;
for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROMr.write(i, 0xFF);
}
for (auto element : data) {
if (strcmp(element.key, "app") == 0) continue;
if (strcmp(element.key, "version") == 0) continue;
setSetting(element.key, element.value.as<char*>());
}
saveSettings();
DEBUG_MSG_P(PSTR("[SETTINGS] Settings restored successfully\n"));
return true;
}
void settingsGetJson(JsonObject& root) {
// Get sorted list of keys
std::vector<String> keys = _settingsKeys();
// Add the key-values to the json object
for (unsigned int i=0; i<keys.size(); i++) {
String value = getSetting(keys[i]);
root[keys[i]] = value;
}
}
void settingsRegisterCommand(const String& name, void (*call)(Embedis*)) {
Embedis::command(name, call);
};
// -----------------------------------------------------------------------------
// Initialization
// -----------------------------------------------------------------------------
void settingsSetup() {
EEPROMr.begin(SPI_FLASH_SEC_SIZE);
_serial.callback([](uint8_t ch) {
#if TELNET_SUPPORT
telnetWrite(ch);
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.write(ch);
#endif
});
Embedis::dictionary( F("EEPROM"),
SPI_FLASH_SEC_SIZE,
[](size_t pos) -> char { return EEPROMr.read(pos); },
[](size_t pos, char value) { EEPROMr.write(pos, value); },
#if SETTINGS_AUTOSAVE
[]() { _settings_save = true; }
#else
[]() {}
#endif
);
_settingsInitCommands();
#if TERMINAL_SUPPORT
#if SERIAL_RX_ENABLED
SERIAL_RX_PORT.begin(SERIAL_RX_BAUDRATE);
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
// Register loop
espurnaRegisterLoop(settingsLoop);
}
void settingsLoop() {
if (_settings_save) {
EEPROMr.commit();
_settings_save = false;
}
#if TERMINAL_SUPPORT
#if DEBUG_SERIAL_SUPPORT
while (DEBUG_PORT.available()) {
_serial.inject(DEBUG_PORT.read());
}
#endif
embedis.process();
#if SERIAL_RX_ENABLED
while (SERIAL_RX_PORT.available() > 0) {
char rc = Serial.read();
_serial_rx_buffer[_serial_rx_pointer++] = rc;
if ((_serial_rx_pointer == TERMINAL_BUFFER_SIZE) || (rc == 10)) {
settingsInject(_serial_rx_buffer, (size_t) _serial_rx_pointer);
_serial_rx_pointer = 0;
}
}
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
}

+ 64
- 21
code/espurna/ssdp.ino View File

@ -2,7 +2,7 @@
SSDP MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
Uses SSDP library by PawelDino (https://github.com/PawelDino)
https://github.com/esp8266/Arduino/issues/2283#issuecomment-299635604
@ -10,31 +10,74 @@ https://github.com/esp8266/Arduino/issues/2283#issuecomment-299635604
#if SSDP_SUPPORT
#include <libs/SSDPDevice.h>
#include <ESP8266SSDP.h>
const char _ssdp_template[] PROGMEM =
"<?xml version=\"1.0\"?>"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
"<specVersion>"
"<major>1</major>"
"<minor>0</minor>"
"</specVersion>"
"<URLBase>http://%s:%u/</URLBase>"
"<device>"
"<deviceType>%s</deviceType>"
"<friendlyName>%s</friendlyName>"
"<presentationURL>/</presentationURL>"
"<serialNumber>%u</serialNumber>"
"<modelName>%s</modelName>"
"<modelNumber>%s</modelNumber>"
"<modelURL>%s</modelURL>"
"<manufacturer>%s</manufacturer>"
"<manufacturerURL>%s</manufacturerURL>"
"<UDN>uuid:38323636-4558-4dda-9188-cda0e6%06x</UDN>"
"</device>"
"</root>\r\n"
"\r\n";
void ssdpSetup() {
SSDPDevice.setName(getSetting("hostname"));
SSDPDevice.setDeviceType("urn:schemas-upnp-org:device:BinaryLight:1");
SSDPDevice.setSchemaURL("description.xml");
SSDPDevice.setSerialNumber(ESP.getChipId());
SSDPDevice.setURL("/");
SSDPDevice.setModelName(DEVICE);
SSDPDevice.setModelNumber("");
SSDPDevice.setManufacturer(MANUFACTURER);
#if WEB_SUPPORT
webServer()->on("/description.xml", HTTP_GET, [](AsyncWebServerRequest *request) {
DEBUG_MSG_P(PSTR("[SSDP] Schema request\n"));
String schema = SSDPDevice.schema();
request->send(200, "application/xml", schema.c_str());
});
#endif
webServer()->on("/description.xml", HTTP_GET, [](AsyncWebServerRequest *request) {
}
DEBUG_MSG_P(PSTR("[SSDP] Schema request\n"));
IPAddress ip = WiFi.localIP();
uint32_t chipId = ESP.getChipId();
char response[strlen_P(_ssdp_template) + 100];
snprintf_P(response, sizeof(response), _ssdp_template,
WiFi.localIP().toString().c_str(), // ip
webPort(), // port
SSDP_DEVICE_TYPE, // device type
getSetting("hostname").c_str(), // friendlyName
chipId, // serialNumber
APP_NAME, // modelName
APP_VERSION, // modelNumber
APP_WEBSITE, // modelURL
DEVICE_NAME, // manufacturer
"", // manufacturerURL
chipId // UUID
);
request->send(200, "text/xml", response);
});
SSDP.setSchemaURL("description.xml");
SSDP.setHTTPPort(webPort());
SSDP.setDeviceType(SSDP_DEVICE_TYPE); //https://github.com/esp8266/Arduino/issues/2283
SSDP.setName(getSetting("hostname"));
SSDP.setSerialNumber(String(ESP.getChipId()));
SSDP.setModelName(APP_NAME);
SSDP.setModelNumber(APP_VERSION);
SSDP.setModelURL(APP_WEBSITE);
SSDP.setManufacturer(DEVICE_NAME);
SSDP.setManufacturerURL("");
SSDP.setURL("/");
SSDP.begin();
DEBUG_MSG_P(PSTR("[SSDP] Started\n"));
void ssdpLoop() {
SSDPDevice.handleClient();
}
#endif // SSDP_SUPPORT

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

Loading…
Cancel
Save