Browse Source

Merge remote-tracking branch 'xoseperez/dev' into lightfox-dual

rules-rpn
Andrey F. Kupreychik 5 years ago
parent
commit
bcb36c1859
77 changed files with 26684 additions and 22868 deletions
  1. +48
    -0
      .github/ISSUE_TEMPLATE/bug_report.md
  2. +26
    -0
      .github/ISSUE_TEMPLATE/feature_request.md
  3. +42
    -0
      .github/ISSUE_TEMPLATE/questions---troubleshooting.md
  4. +16
    -0
      .github/contribute.md
  5. +1
    -1
      .travis.yml
  6. +16
    -9
      README.md
  7. +25
    -7
      code/build.sh
  8. +7
    -6
      code/debug.sh
  9. +40
    -3
      code/espurna/alexa.ino
  10. +4
    -5
      code/espurna/api.ino
  11. +14
    -0
      code/espurna/config/arduino.h
  12. +86
    -7
      code/espurna/config/general.h
  13. +385
    -5
      code/espurna/config/hardware.h
  14. +31
    -9
      code/espurna/config/progmem.h
  15. +5
    -0
      code/espurna/config/prototypes.h
  16. +234
    -41
      code/espurna/config/sensors.h
  17. +49
    -40
      code/espurna/config/types.h
  18. +1
    -1
      code/espurna/config/version.h
  19. +157
    -0
      code/espurna/crash.ino
  20. BIN
      code/espurna/data/index.all.html.gz
  21. BIN
      code/espurna/data/index.light.html.gz
  22. BIN
      code/espurna/data/index.lightfox.html.gz
  23. BIN
      code/espurna/data/index.rfbridge.html.gz
  24. BIN
      code/espurna/data/index.rfm69.html.gz
  25. BIN
      code/espurna/data/index.sensor.html.gz
  26. BIN
      code/espurna/data/index.small.html.gz
  27. +2
    -138
      code/espurna/debug.ino
  28. +43
    -0
      code/espurna/eeprom.ino
  29. +1
    -4
      code/espurna/espurna.ino
  30. +11
    -24
      code/espurna/ir.ino
  31. +88
    -33
      code/espurna/light.ino
  32. +22
    -0
      code/espurna/migrate.ino
  33. +23
    -11
      code/espurna/mqtt.ino
  34. +5
    -1
      code/espurna/nofuss.ino
  35. +5
    -1
      code/espurna/ntp.ino
  36. +27
    -0
      code/espurna/ota.ino
  37. +129
    -76
      code/espurna/relay.ino
  38. +0
    -192
      code/espurna/rf.ino
  39. +155
    -62
      code/espurna/rfbridge.ino
  40. +189
    -19
      code/espurna/sensor.ino
  41. +42
    -1
      code/espurna/sensors/AnalogSensor.h
  42. +253
    -0
      code/espurna/sensors/BMP180Sensor.h
  43. +212
    -0
      code/espurna/sensors/EZOPHSensor.h
  44. +1
    -1
      code/espurna/sensors/GUVAS12SDSensor.h
  45. +21
    -18
      code/espurna/sensors/HLW8012Sensor.h
  46. +150
    -0
      code/espurna/sensors/MAX6675.h
  47. +6
    -6
      code/espurna/sensors/PMSX003Sensor.h
  48. +188
    -24
      code/espurna/sensors/PZEM004TSensor.h
  49. +231
    -0
      code/espurna/sensors/PulseMeterSensor.h
  50. +96
    -0
      code/espurna/sensors/VEML6075Sensor.h
  51. +119
    -0
      code/espurna/sensors/VL53L1XSensor.h
  52. +10
    -19
      code/espurna/settings.ino
  53. +3134
    -3118
      code/espurna/static/index.all.html.gz.h
  54. +3011
    -3000
      code/espurna/static/index.light.html.gz.h
  55. +2570
    -2558
      code/espurna/static/index.lightfox.html.gz.h
  56. +2613
    -2602
      code/espurna/static/index.rfbridge.html.gz.h
  57. +4092
    -4081
      code/espurna/static/index.rfm69.html.gz.h
  58. +2680
    -2664
      code/espurna/static/index.sensor.html.gz.h
  59. +2570
    -2558
      code/espurna/static/index.small.html.gz.h
  60. +9
    -1
      code/espurna/system.ino
  61. +40
    -30
      code/espurna/telnet.ino
  62. +11
    -4
      code/espurna/thinkspeak.ino
  63. +113
    -45
      code/espurna/utils.ino
  64. +18
    -0
      code/espurna/web.ino
  65. +5
    -6
      code/gulpfile.js
  66. +10
    -7
      code/html/custom.js
  67. +67
    -17
      code/html/index.html
  68. +6
    -6
      code/ota.py
  69. +2205
    -1381
      code/package-lock.json
  70. +2
    -5
      code/package.json
  71. +293
    -14
      code/platformio.ini
  72. BIN
      images/devices/lombex-lux-nova-flash-2.jpg
  73. BIN
      images/devices/lombex-lux-nova-flash.jpg
  74. BIN
      images/devices/lombex-lux-nova.jpg
  75. BIN
      images/devices/schuko-wifi-plug-v2.jpg
  76. BIN
      images/devices/tonbux-xs-ssa01.jpg
  77. +19
    -7
      pre-commit

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

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

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

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

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

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

+ 16
- 0
.github/contribute.md View File

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

+ 1
- 1
.travis.yml View File

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


+ 16
- 9
README.md View File

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


+ 25
- 7
code/build.sh View File

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


+ 7
- 6
code/debug.sh View File

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


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

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


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

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


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

@ -36,6 +36,7 @@
//#define ITEAD_SONOFF_T1_2CH //#define ITEAD_SONOFF_T1_2CH
//#define ITEAD_SONOFF_T1_3CH //#define ITEAD_SONOFF_T1_3CH
//#define ITEAD_SONOFF_S31 //#define ITEAD_SONOFF_S31
//#define ORVIBO_B25
//#define YJZK_SWITCH_2CH //#define YJZK_SWITCH_2CH
//#define ELECTRODRAGON_WIFI_IOT //#define ELECTRODRAGON_WIFI_IOT
//#define WORKCHOICE_ECOPLUG //#define WORKCHOICE_ECOPLUG
@ -66,12 +67,14 @@
//#define WEMOS_D1_TARPUNA_SHIELD //#define WEMOS_D1_TARPUNA_SHIELD
//#define GIZWITS_WITTY_CLOUD //#define GIZWITS_WITTY_CLOUD
//#define EUROMATE_WIFI_STECKER_SCHUKO //#define EUROMATE_WIFI_STECKER_SCHUKO
//#define EUROMATE_WIFI_STECKER_SCHUKO_V2
//#define TONBUX_POWERSTRIP02 //#define TONBUX_POWERSTRIP02
//#define LINGAN_SWA1 //#define LINGAN_SWA1
//#define HEYGO_HY02 //#define HEYGO_HY02
//#define MAXCIO_WUS002S //#define MAXCIO_WUS002S
//#define YIDIAN_XSSSA05 //#define YIDIAN_XSSSA05
//#define TONBUX_XSSSA06 //#define TONBUX_XSSSA06
//#define TONBUX_XSSSA01
//#define GREEN_ESP8266RELAY //#define GREEN_ESP8266RELAY
//#define IKE_ESPIKE //#define IKE_ESPIKE
//#define ARNIEX_SWIFITCH //#define ARNIEX_SWIFITCH
@ -100,6 +103,13 @@
//#define PHYX_ESP12_RGB //#define PHYX_ESP12_RGB
//#define IWOOLE_LED_TABLE_LAMP //#define IWOOLE_LED_TABLE_LAMP
//#define EXS_WIFI_RELAY_V50 //#define EXS_WIFI_RELAY_V50
//#define TECKIN_SP22_V14
//#define LOMBEX_LUX_NOVA2_TUNABLE_WHITE
//#define LOMBEX_LUX_NOVA2_WHITE_COLOR
//#define MAGICHOME_ZJ_WFMN_A_11
//#define MAGICHOME_ZJ_WFMN_B_11
//#define GBLIFE_RGBW_SOCKET
//#define SMARTLIFE_MINI_SMART_SOCKET
//#define FOXEL_LIGHTFOX_DUAL //#define FOXEL_LIGHTFOX_DUAL
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
@ -147,6 +157,7 @@
//#define AM2320_SUPPORT 1 //#define AM2320_SUPPORT 1
//#define ANALOG_SUPPORT 1 //#define ANALOG_SUPPORT 1
//#define BH1750_SUPPORT 1 //#define BH1750_SUPPORT 1
//#define BMP180_SUPPORT 1
//#define BMX280_SUPPORT 1 //#define BMX280_SUPPORT 1
//#define CSE7766_SUPPORT 1 //#define CSE7766_SUPPORT 1
//#define DALLAS_SUPPORT 1 //#define DALLAS_SUPPORT 1
@ -165,6 +176,7 @@
//#define MICS5525_SUPPORT 1 //#define MICS5525_SUPPORT 1
//#define NTC_SUPPORT 1 //#define NTC_SUPPORT 1
//#define PMSX003_SUPPORT 1 //#define PMSX003_SUPPORT 1
//#define PULSEMETER_SUPPORT 1
//#define PZEM004T_SUPPORT 1 //#define PZEM004T_SUPPORT 1
//#define SDS011_SUPPORT 1 //#define SDS011_SUPPORT 1
//#define SENSEAIR_SUPPORT 1 //#define SENSEAIR_SUPPORT 1
@ -173,3 +185,5 @@
//#define SONAR_SUPPORT 1 //#define SONAR_SUPPORT 1
//#define TMP3X_SUPPORT 1 //#define TMP3X_SUPPORT 1
//#define V9261F_SUPPORT 1 //#define V9261F_SUPPORT 1
//#define VL53L1X_SUPPORT 1
//#define EZOPH_SUPPORT 1

+ 86
- 7
code/espurna/config/general.h View File

@ -110,8 +110,8 @@
#define TELNET_STA 0 // By default, disallow connections via STA interface #define TELNET_STA 0 // By default, disallow connections via STA interface
#endif #endif
#ifndef TELNET_PASSWORD
#define TELNET_PASSWORD 1 // Request password to start telnet session by default
#ifndef TELNET_AUTHENTICATION
#define TELNET_AUTHENTICATION 1 // Request password to start telnet session by default
#endif #endif
#define TELNET_PORT 23 // Port to listen to telnet clients #define TELNET_PORT 23 // Port to listen to telnet clients
@ -185,22 +185,74 @@
#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value #define UPTIME_OVERFLOW 4294967295 // Uptime overflow value
// Topics that will be reported in heartbeat
// Values that will be reported in heartbeat
#ifndef HEARTBEAT_REPORT_STATUS
#define HEARTBEAT_REPORT_STATUS 1 #define HEARTBEAT_REPORT_STATUS 1
#endif
#ifndef HEARTBEAT_REPORT_SSID
#define HEARTBEAT_REPORT_SSID 1
#endif
#ifndef HEARTBEAT_REPORT_IP
#define HEARTBEAT_REPORT_IP 1 #define HEARTBEAT_REPORT_IP 1
#endif
#ifndef HEARTBEAT_REPORT_MAC
#define HEARTBEAT_REPORT_MAC 1 #define HEARTBEAT_REPORT_MAC 1
#endif
#ifndef HEARTBEAT_REPORT_RSSI
#define HEARTBEAT_REPORT_RSSI 1 #define HEARTBEAT_REPORT_RSSI 1
#endif
#ifndef HEARTBEAT_REPORT_UPTIME
#define HEARTBEAT_REPORT_UPTIME 1 #define HEARTBEAT_REPORT_UPTIME 1
#endif
#ifndef HEARTBEAT_REPORT_DATETIME
#define HEARTBEAT_REPORT_DATETIME 1 #define HEARTBEAT_REPORT_DATETIME 1
#endif
#ifndef HEARTBEAT_REPORT_FREEHEAP
#define HEARTBEAT_REPORT_FREEHEAP 1 #define HEARTBEAT_REPORT_FREEHEAP 1
#endif
#ifndef HEARTBEAT_REPORT_VCC
#define HEARTBEAT_REPORT_VCC 1 #define HEARTBEAT_REPORT_VCC 1
#endif
#ifndef HEARTBEAT_REPORT_RELAY
#define HEARTBEAT_REPORT_RELAY 1 #define HEARTBEAT_REPORT_RELAY 1
#endif
#ifndef HEARTBEAT_REPORT_LIGHT
#define HEARTBEAT_REPORT_LIGHT 1 #define HEARTBEAT_REPORT_LIGHT 1
#endif
#ifndef HEARTBEAT_REPORT_HOSTNAME
#define HEARTBEAT_REPORT_HOSTNAME 1 #define HEARTBEAT_REPORT_HOSTNAME 1
#endif
#ifndef HEARTBEAT_REPORT_APP
#define HEARTBEAT_REPORT_APP 1 #define HEARTBEAT_REPORT_APP 1
#endif
#ifndef HEARTBEAT_REPORT_VERSION
#define HEARTBEAT_REPORT_VERSION 1 #define HEARTBEAT_REPORT_VERSION 1
#endif
#ifndef HEARTBEAT_REPORT_BOARD
#define HEARTBEAT_REPORT_BOARD 1 #define HEARTBEAT_REPORT_BOARD 1
#endif
#ifndef HEARTBEAT_REPORT_LOADAVG
#define HEARTBEAT_REPORT_LOADAVG 1
#endif
#ifndef HEARTBEAT_REPORT_INTERVAL
#define HEARTBEAT_REPORT_INTERVAL 0 #define HEARTBEAT_REPORT_INTERVAL 0
#endif
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Load average // Load average
@ -210,10 +262,6 @@
#define LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms) #define LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms)
#endif #endif
#ifndef LOADAVG_REPORT
#define LOADAVG_REPORT 1 // Should we report Load average over MQTT?
#endif
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// BUTTON // BUTTON
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -236,6 +284,9 @@
#ifndef BUTTON_LNGLNGCLICK_DELAY #ifndef BUTTON_LNGLNGCLICK_DELAY
#define BUTTON_LNGLNGCLICK_DELAY 10000 // Time in ms holding the button down to get a long-long click #define BUTTON_LNGLNGCLICK_DELAY 10000 // Time in ms holding the button down to get a long-long click
#endif
#ifndef BUTTON_MQTT_SEND_ALL_EVENTS
#define BUTTON_MQTT_SEND_ALL_EVENTS 0 // 0 - to send only events the are bound to actions #define BUTTON_MQTT_SEND_ALL_EVENTS 0 // 0 - to send only events the are bound to actions
// 1 - to send all button events to MQTT // 1 - to send all button events to MQTT
#endif #endif
@ -434,7 +485,9 @@
// or in the Internet. Since the WebUI is just one compressed file with HTML, CSS and JS // or in the Internet. Since the WebUI is just one compressed file with HTML, CSS and JS
// there are no special requirements. Any static web server will do (NGinx, Apache, Lighttpd,...). // there are no special requirements. Any static web server will do (NGinx, Apache, Lighttpd,...).
// The only requirement is that the resource must be available under this domain. // The only requirement is that the resource must be available under this domain.
#ifndef WEB_REMOTE_DOMAIN
#define WEB_REMOTE_DOMAIN "http://tinkerman.cat" #define WEB_REMOTE_DOMAIN "http://tinkerman.cat"
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// WEBSOCKETS // WEBSOCKETS
@ -531,6 +584,10 @@
#define OTA_PORT 8266 // OTA port #define OTA_PORT 8266 // OTA port
#endif #endif
#ifndef OTA_MQTT_SUPPORT
#define OTA_MQTT_SUPPORT 0 // No support by default
#endif
#define OTA_GITHUB_FP "D7:9F:07:61:10:B3:92:93:E3:49:AC:89:84:5B:03:80:C1:9E:2F:8B" #define OTA_GITHUB_FP "D7:9F:07:61:10:B3:92:93:E3:49:AC:89:84:5B:03:80:C1:9E:2F:8B"
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -731,6 +788,7 @@
#define MQTT_TOPIC_LED "led" #define MQTT_TOPIC_LED "led"
#define MQTT_TOPIC_BUTTON "button" #define MQTT_TOPIC_BUTTON "button"
#define MQTT_TOPIC_IP "ip" #define MQTT_TOPIC_IP "ip"
#define MQTT_TOPIC_SSID "ssid"
#define MQTT_TOPIC_VERSION "version" #define MQTT_TOPIC_VERSION "version"
#define MQTT_TOPIC_UPTIME "uptime" #define MQTT_TOPIC_UPTIME "uptime"
#define MQTT_TOPIC_DATETIME "datetime" #define MQTT_TOPIC_DATETIME "datetime"
@ -756,6 +814,7 @@
#define MQTT_TOPIC_SPEED "speed" #define MQTT_TOPIC_SPEED "speed"
#define MQTT_TOPIC_IRIN "irin" #define MQTT_TOPIC_IRIN "irin"
#define MQTT_TOPIC_IROUT "irout" #define MQTT_TOPIC_IROUT "irout"
#define MQTT_TOPIC_OTA "ota"
// Light module // Light module
#define MQTT_TOPIC_CHANNEL "channel" #define MQTT_TOPIC_CHANNEL "channel"
@ -766,6 +825,7 @@
#define MQTT_TOPIC_BRIGHTNESS "brightness" #define MQTT_TOPIC_BRIGHTNESS "brightness"
#define MQTT_TOPIC_MIRED "mired" #define MQTT_TOPIC_MIRED "mired"
#define MQTT_TOPIC_KELVIN "kelvin" #define MQTT_TOPIC_KELVIN "kelvin"
#define MQTT_TOPIC_TRANSITION "transition"
#define MQTT_STATUS_ONLINE "1" // Value for the device ON message #define MQTT_STATUS_ONLINE "1" // Value for the device ON message
#define MQTT_STATUS_OFFLINE "0" // Value for the device OFF message (will) #define MQTT_STATUS_OFFLINE "0" // Value for the device OFF message (will)
@ -925,8 +985,13 @@
#define HOMEASSISTANT_SUPPORT MQTT_SUPPORT // Build with home assistant support (if MQTT, 1.64Kb) #define HOMEASSISTANT_SUPPORT MQTT_SUPPORT // Build with home assistant support (if MQTT, 1.64Kb)
#endif #endif
#ifndef HOMEASSISTANT_ENABLED
#define HOMEASSISTANT_ENABLED 0 // Integration not enabled by default #define HOMEASSISTANT_ENABLED 0 // Integration not enabled by default
#endif
#ifndef HOMEASSISTANT_PREFIX
#define HOMEASSISTANT_PREFIX "homeassistant" // Default MQTT prefix #define HOMEASSISTANT_PREFIX "homeassistant" // Default MQTT prefix
#endif
#ifndef HOMEASSISTANT_PAYLOAD_ON #ifndef HOMEASSISTANT_PAYLOAD_ON
#define HOMEASSISTANT_PAYLOAD_ON "1" // Payload for ON and available messages #define HOMEASSISTANT_PAYLOAD_ON "1" // Payload for ON and available messages
@ -993,6 +1058,11 @@
#define THINGSPEAK_APIKEY "" // Default API KEY #define THINGSPEAK_APIKEY "" // Default API KEY
#endif #endif
#ifndef THINGSPEAK_CLEAR_CACHE
#define THINGSPEAK_CLEAR_CACHE 1 // Clear cache after sending values
// Not clearing it will result in latest values for each field being sent every time
#endif
#define THINGSPEAK_USE_ASYNC 1 // Use AsyncClient instead of WiFiClientSecure #define THINGSPEAK_USE_ASYNC 1 // Use AsyncClient instead of WiFiClientSecure
// THINGSPEAK OVER SSL // THINGSPEAK OVER SSL
@ -1071,6 +1141,10 @@
#define NTP_DST_REGION 0 // 0 for Europe, 1 for USA (defined in NtpClientLib) #define NTP_DST_REGION 0 // 0 for Europe, 1 for USA (defined in NtpClientLib)
#endif #endif
#ifndef NTP_WAIT_FOR_SYNC
#define NTP_WAIT_FOR_SYNC 1 // Do not report any datetime until NTP sync'ed
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// ALEXA // ALEXA
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -1335,8 +1409,13 @@
#define RF_PIN 14 #define RF_PIN 14
#endif #endif
#ifndef RF_DEBOUNCE
#define RF_DEBOUNCE 500 #define RF_DEBOUNCE 500
#endif
#ifndef RF_LEARN_TIMEOUT
#define RF_LEARN_TIMEOUT 60000 #define RF_LEARN_TIMEOUT 60000
#endif
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Custom RFM69 to MQTT bridge // Custom RFM69 to MQTT bridge


+ 385
- 5
code/espurna/config/hardware.h View File

@ -112,7 +112,7 @@
#define MANUFACTURER "WEMOS" #define MANUFACTURER "WEMOS"
#define DEVICE "D1_MINI_RELAYSHIELD" #define DEVICE "D1_MINI_RELAYSHIELD"
// Buttons
// Buttons
// No buttons on the D1 MINI alone, but defining it without adding a button doen't create problems // No buttons on the D1 MINI alone, but defining it without adding a button doen't create problems
#define BUTTON1_PIN 0 // Connect a pushbutton between D3 and GND, #define BUTTON1_PIN 0 // Connect a pushbutton between D3 and GND,
// it's the same as using a Wemos one button shield // it's the same as using a Wemos one button shield
@ -123,7 +123,7 @@
#define RELAY1_PIN 5 #define RELAY1_PIN 5
#define RELAY1_TYPE RELAY_TYPE_NORMAL #define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
// Light RGBW
#define LED1_PIN 2 #define LED1_PIN 2
#define LED1_PIN_INVERSE 1 #define LED1_PIN_INVERSE 1
@ -930,6 +930,31 @@
#define LED1_PIN 13 #define LED1_PIN 13
#define LED1_PIN_INVERSE 1 #define LED1_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// ORVIBO
// -----------------------------------------------------------------------------
#elif defined(ORVIBO_B25)
// Info
#define MANUFACTURER "ORVIBO"
#define DEVICE "B25"
// Buttons
#define BUTTON1_PIN 14
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 5
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 12 // 4 blue led
#define LED1_PIN_INVERSE 1
#define LED2_PIN 4 // 12 red led
#define LED2_PIN_INVERSE 1
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// YJZK // YJZK
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -1188,6 +1213,67 @@
#define IR_RX_PIN 4 #define IR_RX_PIN 4
#define IR_BUTTON_SET 1 #define IR_BUTTON_SET 1
#elif defined(MAGICHOME_ZJ_WFMN_A_11)
// Info
#define MANUFACTURER "MAGICHOME"
#define DEVICE "ZJ_WFMN_A_11"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
// LEDs
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
#define LED2_PIN 15
#define LED2_PIN_INVERSE 1
// Light
#define LIGHT_CHANNELS 4
#define LIGHT_CH1_PIN 12 // RED
#define LIGHT_CH2_PIN 5 // GREEN
#define LIGHT_CH3_PIN 13 // BLUE
#define LIGHT_CH4_PIN 14 // WHITE
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
// IR
#define IR_SUPPORT 1
#define IR_RX_PIN 4
#define IR_BUTTON_SET 1
#elif defined(MAGICHOME_ZJ_WFMN_B_11)
// Info
#define MANUFACTURER "MAGICHOME"
#define DEVICE "ZJ_WFMN_B_11"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
// LEDs
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
#define LED2_PIN 15
#define LED2_PIN_INVERSE 1
// Light
#define LIGHT_CHANNELS 4
#define LIGHT_CH1_PIN 14 // RED
#define LIGHT_CH2_PIN 5 // GREEN
#define LIGHT_CH3_PIN 12 // BLUE
#define LIGHT_CH4_PIN 13 // WHITE
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
// RF
#define RF_SUPPORT 1
#define RF_PIN 4
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// HUACANXING H801 & H802 // HUACANXING H801 & H802
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -1731,7 +1817,7 @@
#define HLW8012_VOLTAGE_R_UP ( 2 * 1000000 ) // Upstream voltage resistor #define HLW8012_VOLTAGE_R_UP ( 2 * 1000000 ) // Upstream voltage resistor
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Euromate (?) Wifi Stecker Shuko
// Euromate (?) Wifi Stecker Schuko
// https://www.obi.de/hausfunksteuerung/wifi-stecker-schuko/p/2291706 // https://www.obi.de/hausfunksteuerung/wifi-stecker-schuko/p/2291706
// Thanks to @Geitde // Thanks to @Geitde
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -1761,6 +1847,36 @@
#define LED1_PIN 4 #define LED1_PIN 4
#define LED1_PIN_INVERSE 0 #define LED1_PIN_INVERSE 0
// -----------------------------------------------------------------------------
// Euromate (?) Wifi Stecker Schuko Version 2
// This configuration is for the second generation of devices sold by OBI.
// https://www.obi.de/hausfunksteuerung/wifi-stecker-schuko-weiss/p/4077806
// -----------------------------------------------------------------------------
#elif defined(EUROMATE_WIFI_STECKER_SCHUKO_V2)
// Info
#define MANUFACTURER "EUROMATE"
#define DEVICE "WIFI_STECKER_SCHUKO_V2"
// Buttons
#define BUTTON1_PIN 5
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 4
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// Green
#define LED1_PIN 12
#define LED1_MODE LED_MODE_WIFI
#define LED1_PIN_INVERSE 0
// Red
#define LED2_PIN 13
#define LED2_MODE LED_MODE_RELAY
#define LED2_PIN_INVERSE 0
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Generic 8CH // Generic 8CH
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -1920,6 +2036,29 @@
#define HLW8012_CURRENT_R 0.002 // Current resistor #define HLW8012_CURRENT_R 0.002 // Current resistor
#define HLW8012_VOLTAGE_R_UP ( 2 * 1000000 ) // Upstream voltage resistor #define HLW8012_VOLTAGE_R_UP ( 2 * 1000000 ) // Upstream voltage resistor
// -----------------------------------------------------------------------------
// Maxcio W-DE004
// -----------------------------------------------------------------------------
#elif defined(MAXCIO_WDE004)
// Info
#define MANUFACTURER "MAXCIO"
#define DEVICE "WDE004"
// Buttons
#define BUTTON1_PIN 1
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// YiDian XS-SSA05 // YiDian XS-SSA05
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -1962,6 +2101,29 @@
#define HLW8012_POWER_RATIO 3414290 #define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING #define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// TONBUX XS-SSA01
// -----------------------------------------------------------------------------
#elif defined(TONBUX_XSSSA01)
// Info
#define MANUFACTURER "TONBUX"
#define DEVICE "XSSSA01"
// Buttons
#define BUTTON1_PIN 4
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 13
#define LED1_PIN_INVERSE 0
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// TONBUX XS-SSA06 // TONBUX XS-SSA06
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -2530,10 +2692,93 @@
#define HLW8012_POWER_RATIO 3414290 #define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING #define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// Same as the above but new board version marked V2.3
// -----------------------------------------------------------------------------
#elif defined(BLITZWOLF_BWSHP2_V23)
// Info
#define MANUFACTURER "BLITZWOLF"
#define DEVICE "BWSHP2V2.3"
// Buttons
#define BUTTON1_PIN 3
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 1
#define LED1_PIN_INVERSE 1
#define LED2_PIN 13
#define LED2_PIN_INVERSE 1
#define LED2_MODE LED_MODE_FINDME
#define LED2_RELAY 1
// HJL01 / BL0937
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 1
#endif
#define HLW8012_SEL_PIN 12
#define HLW8012_CF1_PIN 5
#define HLW8012_CF_PIN 4
#define HLW8012_SEL_CURRENT LOW
#define HLW8012_CURRENT_RATIO 25740
#define HLW8012_VOLTAGE_RATIO 313400
#define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// Teckin SP22 v1.4 - v1.6
// -----------------------------------------------------------------------------
#elif defined(TECKIN_SP22_V14)
// Info
#define MANUFACTURER "TECKIN"
#define DEVICE "SP22_V14"
// Buttons
#define BUTTON1_PIN 1
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 3
#define LED1_PIN_INVERSE 1
#define LED2_PIN 13
#define LED2_PIN_INVERSE 1
#define LED2_MODE LED_MODE_FINDME
#define LED2_RELAY 1
// HJL01 / BL0937
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 1
#endif
#define HLW8012_SEL_PIN 12
#define HLW8012_CF1_PIN 5
#define HLW8012_CF_PIN 4
#define HLW8012_SEL_CURRENT LOW
#define HLW8012_CURRENT_RATIO 20730
#define HLW8012_VOLTAGE_RATIO 264935
#define HLW8012_POWER_RATIO 2533110
#define HLW8012_INTERRUPT_ON FALLING
// ---------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------
// Homecube 16A is similar but some pins differ and it also has RGB LEDs // Homecube 16A is similar but some pins differ and it also has RGB LEDs
// https://www.amazon.de/gp/product/B07D7RVF56/ref=oh_aui_detailpage_o00_s01?ie=UTF8&psc=1 // https://www.amazon.de/gp/product/B07D7RVF56/ref=oh_aui_detailpage_o00_s01?ie=UTF8&psc=1
// ---------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------
#elif defined(HOMECUBE_16A) #elif defined(HOMECUBE_16A)
// Info // Info
@ -2828,6 +3073,53 @@
#define LIGHT_CH3_INVERSE 0 #define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0 #define LIGHT_CH4_INVERSE 0
// -----------------------------------------------------------------------------
// Lombex Lux Nova 2 Tunable White
// https://www.amazon.com/Lombex-Compatible-Equivalent-Dimmable-2700K-6500K/dp/B07B8K72PR
// -----------------------------------------------------------------------------
#elif defined(LOMBEX_LUX_NOVA2_TUNABLE_WHITE)
// Info
#define MANUFACTURER "LOMBEX"
#define DEVICE "LUX_NOVA2_TUNABLE_WHITE"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX
#define DUMMY_RELAY_COUNT 1
// Light
#define LIGHT_CHANNELS 5
#define MY92XX_MODEL MY92XX_MODEL_MY9291
#define MY92XX_CHIPS 1
#define MY92XX_DI_PIN 4
#define MY92XX_DCKI_PIN 5
#define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT
// No RGB on this bulb. Warm white on channel 0, cool white on channel 3
#define MY92XX_MAPPING 255, 255, 255, 3, 0
// -----------------------------------------------------------------------------
// Lombex Lux Nova 2 White and Color
// https://www.amazon.com/Lombex-Compatible-Equivalent-Dimmable-2700K-6500K/dp/B07B8K72PR
// -----------------------------------------------------------------------------
#elif defined(LOMBEX_LUX_NOVA2_WHITE_COLOR)
// Info
#define MANUFACTURER "LOMBEX"
#define DEVICE "LUX_NOVA2_WHITE_COLOR"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX
#define DUMMY_RELAY_COUNT 1
// Light
#define LIGHT_CHANNELS 4
#define MY92XX_MODEL MY92XX_MODEL_MY9291
#define MY92XX_CHIPS 1
#define MY92XX_DI_PIN 4
#define MY92XX_DCKI_PIN 5
#define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT
// RGB on channels 0/1/2, either cool or warm white on channel 3
// The bulb *should* have cool leds, but could also have warm leds as a common defect
#define MY92XX_MAPPING 0, 1, 2, 3
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Bestek Smart Plug with 2 USB ports // Bestek Smart Plug with 2 USB ports
// https://www.bestekcorp.com/bestek-smart-plug-works-with-amazon-alexa-google-assistant-and-ifttt-with-2-usb // https://www.bestekcorp.com/bestek-smart-plug-works-with-amazon-alexa-google-assistant-and-ifttt-with-2-usb
@ -2852,6 +3144,91 @@
#define LED1_PIN 4 #define LED1_PIN 4
#define LED1_PIN_INVERSE 1 #define LED1_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// GBLIFE RGBW SOCKET
// -----------------------------------------------------------------------------
#elif defined(GBLIFE_RGBW_SOCKET)
// Info
#define MANUFACTURER "GBLIFE"
#define DEVICE "RGBW_SOCKET"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 15
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// Light RGBW
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
#define LIGHT_CHANNELS 4
#define LIGHT_CH1_PIN 5 // RED
#define LIGHT_CH2_PIN 14 // GREEN
#define LIGHT_CH3_PIN 12 // BLUE
#define LIGHT_CH4_PIN 4 // WHITE
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
// ----------------------------------------------------------------------------------------
// Smart life Mini Smart Socket is similar Homecube 16A but some GPIOs differ
// https://www.ebay.de/itm/Smart-Steckdose-WIFI-WLAN-Amazon-Alexa-Fernbedienung-Home-Socket-Zeitschaltuh-DE/123352026749?hash=item1cb85a8e7d:g:IasAAOSwk6dbj390
// ----------------------------------------------------------------------------------------
#elif defined(SMARTLIFE_MINI_SMART_SOCKET)
// Info
#define MANUFACTURER "SMARTLIFE"
#define DEVICE "MINI_SMART_SOCKET"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 15
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
//Red LED: 0
//Green LED: 4
//Blue LED: 2
// Light
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
#define LIGHT_CHANNELS 3
#define LIGHT_CH1_PIN 0 // RED
#define LIGHT_CH2_PIN 4 // GREEN
#define LIGHT_CH3_PIN 2 // BLUE
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
// HJL01 / BL0937
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 1
#endif
#define HLW8012_SEL_PIN 12
#define HLW8012_CF1_PIN 14
#define HLW8012_CF_PIN 5
#define HLW8012_SEL_CURRENT LOW
#define HLW8012_CURRENT_RATIO 25740
#define HLW8012_VOLTAGE_RATIO 313400
#define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Foxel's LightFox dual // Foxel's LightFox dual
// https://github.com/foxel/esp-dual-rf-switch // https://github.com/foxel/esp-dual-rf-switch
@ -2909,6 +3286,7 @@
// If we dont got it, you don't want it! // If we dont got it, you don't want it!
#define AM2320_SUPPORT 1 #define AM2320_SUPPORT 1
#define BH1750_SUPPORT 1 #define BH1750_SUPPORT 1
#define BMP180_SUPPORT 1
#define BMX280_SUPPORT 1 #define BMX280_SUPPORT 1
#define SHT3X_I2C_SUPPORT 1 #define SHT3X_I2C_SUPPORT 1
#define EMON_ADC121_SUPPORT 1 #define EMON_ADC121_SUPPORT 1
@ -2916,8 +3294,8 @@
#define SHT3X_I2C_SUPPORT 1 #define SHT3X_I2C_SUPPORT 1
#define SI7021_SUPPORT 1 #define SI7021_SUPPORT 1
#define PMSX003_SUPPORT 1 #define PMSX003_SUPPORT 1
#define SENSEAIR_SUPPORT1
#define SENSEAIR_SUPPORT 1
#define VL53L1X_SUPPORT 1
// A bit of lights - pin 5 // A bit of lights - pin 5
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
@ -3053,6 +3431,8 @@
#define EMON_ANALOG_SUPPORT 1 #define EMON_ANALOG_SUPPORT 1
#endif #endif
#define PULSEMETER_SUPPORT 1
// Test non-default modules // Test non-default modules
#define LLMNR_SUPPORT 1 #define LLMNR_SUPPORT 1
#define NETBIOS_SUPPORT 1 #define NETBIOS_SUPPORT 1


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

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

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

@ -69,6 +69,8 @@ extern "C" {
#include <EEPROM_Rotate.h> #include <EEPROM_Rotate.h>
EEPROM_Rotate EEPROMr; EEPROM_Rotate EEPROMr;
void eepromSectorsDebug();
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// GPIO // GPIO
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -165,7 +167,9 @@ void nice_delay(unsigned long ms);
#define AsyncWebSocket void #define AsyncWebSocket void
#define AwsEventType void * #define AwsEventType void *
#endif #endif
typedef std::function<bool(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)> web_body_callback_f;
typedef std::function<bool(AsyncWebServerRequest *request)> web_request_callback_f; typedef std::function<bool(AsyncWebServerRequest *request)> web_request_callback_f;
void webBodyRegister(web_body_callback_f callback);
void webRequestRegister(web_request_callback_f callback); void webRequestRegister(web_request_callback_f callback);
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -193,3 +197,4 @@ void webRequestRegister(web_request_callback_f callback);
#include "JustWifi.h" #include "JustWifi.h"
typedef std::function<void(justwifi_messages_t code, char * parameter)> wifi_callback_f; typedef std::function<void(justwifi_messages_t code, char * parameter)> wifi_callback_f;
void wifiRegister(wifi_callback_f callback); void wifiRegister(wifi_callback_f callback);
bool wifiConnected();

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

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

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

@ -250,38 +250,44 @@
// These should remain over time, do not modify them, only add new ones at the end // These should remain over time, do not modify them, only add new ones at the end
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
#define SENSOR_DHTXX_ID 0x01
#define SENSOR_DALLAS_ID 0x02
#define SENSOR_EMON_ANALOG_ID 0x03
#define SENSOR_EMON_ADC121_ID 0x04
#define SENSOR_EMON_ADS1X15_ID 0x05
#define SENSOR_HLW8012_ID 0x06
#define SENSOR_V9261F_ID 0x07
#define SENSOR_ECH1560_ID 0x08
#define SENSOR_ANALOG_ID 0x09
#define SENSOR_DIGITAL_ID 0x10
#define SENSOR_EVENTS_ID 0x11
#define SENSOR_PMSX003_ID 0x12
#define SENSOR_BMX280_ID 0x13
#define SENSOR_MHZ19_ID 0x14
#define SENSOR_SI7021_ID 0x15
#define SENSOR_SHT3X_I2C_ID 0x16
#define SENSOR_BH1750_ID 0x17
#define SENSOR_PZEM004T_ID 0x18
#define SENSOR_AM2320_ID 0x19
#define SENSOR_GUVAS12SD_ID 0x20
#define SENSOR_CSE7766_ID 0x21
#define SENSOR_TMP3X_ID 0x22
#define SENSOR_SONAR_ID 0x23
#define SENSOR_SENSEAIR_ID 0x24
#define SENSOR_GEIGER_ID 0x25
#define SENSOR_NTC_ID 0x26
#define SENSOR_SDS011_ID 0x27
#define SENSOR_MICS2710_ID 0x28
#define SENSOR_MICS5525_ID 0x29
#define SENSOR_DHTXX_ID 1
#define SENSOR_DALLAS_ID 2
#define SENSOR_EMON_ANALOG_ID 3
#define SENSOR_EMON_ADC121_ID 4
#define SENSOR_EMON_ADS1X15_ID 5
#define SENSOR_HLW8012_ID 6
#define SENSOR_V9261F_ID 7
#define SENSOR_ECH1560_ID 8
#define SENSOR_ANALOG_ID 9
#define SENSOR_DIGITAL_ID 10
#define SENSOR_EVENTS_ID 11
#define SENSOR_PMSX003_ID 12
#define SENSOR_BMX280_ID 13
#define SENSOR_MHZ19_ID 14
#define SENSOR_SI7021_ID 15
#define SENSOR_SHT3X_I2C_ID 16
#define SENSOR_BH1750_ID 17
#define SENSOR_PZEM004T_ID 18
#define SENSOR_AM2320_ID 19
#define SENSOR_GUVAS12SD_ID 20
#define SENSOR_CSE7766_ID 21
#define SENSOR_TMP3X_ID 22
#define SENSOR_SONAR_ID 23
#define SENSOR_SENSEAIR_ID 24
#define SENSOR_GEIGER_ID 25
#define SENSOR_NTC_ID 26
#define SENSOR_SDS011_ID 27
#define SENSOR_MICS2710_ID 28
#define SENSOR_MICS5525_ID 29
#define SENSOR_PULSEMETER_ID 30
#define SENSOR_VEML6075_ID 31
#define SENSOR_VL53L1X_ID 32
#define SENSOR_EZOPH_ID 33
#define SENSOR_BMP180_ID 34
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Magnitudes // Magnitudes
// These should remain over time, do not modify their values, only add new ones at the end
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
#define MAGNITUDE_NONE 0 #define MAGNITUDE_NONE 0
@ -304,14 +310,17 @@
#define MAGNITUDE_PM10 17 #define MAGNITUDE_PM10 17
#define MAGNITUDE_CO2 18 #define MAGNITUDE_CO2 18
#define MAGNITUDE_LUX 19 #define MAGNITUDE_LUX 19
#define MAGNITUDE_UV 20
#define MAGNITUDE_DISTANCE 21
#define MAGNITUDE_HCHO 22
#define MAGNITUDE_GEIGER_CPM 23
#define MAGNITUDE_GEIGER_SIEVERT 24
#define MAGNITUDE_COUNT 25
#define MAGNITUDE_NO2 26
#define MAGNITUDE_CO 27
#define MAGNITUDE_RESISTANCE 28
#define MAGNITUDE_MAX 29
#define MAGNITUDE_UVA 20
#define MAGNITUDE_UVB 21
#define MAGNITUDE_UVI 22
#define MAGNITUDE_DISTANCE 23
#define MAGNITUDE_HCHO 24
#define MAGNITUDE_GEIGER_CPM 25
#define MAGNITUDE_GEIGER_SIEVERT 26
#define MAGNITUDE_COUNT 27
#define MAGNITUDE_NO2 28
#define MAGNITUDE_CO 29
#define MAGNITUDE_RESISTANCE 30
#define MAGNITUDE_PH 31
#define MAGNITUDE_MAX 32

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

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

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

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

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


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


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


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


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


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


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


+ 2
- 138
code/espurna/debug.ino View File

@ -8,14 +8,6 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#if DEBUG_SUPPORT #if DEBUG_SUPPORT
#include <stdio.h>
#include <stdarg.h>
#include <EEPROM_Rotate.h>
extern "C" {
#include "user_interface.h"
}
#if DEBUG_UDP_SUPPORT #if DEBUG_UDP_SUPPORT
#include <WiFiUdp.h> #include <WiFiUdp.h>
WiFiUDP _udp_debug; WiFiUDP _udp_debug;
@ -155,6 +147,8 @@ void debugWebSetup() {
#endif // DEBUG_WEB_SUPPORT #endif // DEBUG_WEB_SUPPORT
// -----------------------------------------------------------------------------
void debugSetup() { void debugSetup() {
#if DEBUG_SERIAL_SUPPORT #if DEBUG_SERIAL_SUPPORT
@ -166,134 +160,4 @@ void debugSetup() {
} }
// -----------------------------------------------------------------------------
// Save crash info
// Taken from krzychb EspSaveCrash
// https://github.com/krzychb/EspSaveCrash
// -----------------------------------------------------------------------------
#define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data
/**
* Structure of the single crash data set
*
* 1. Crash time
* 2. Restart reason
* 3. Exception cause
* 4. epc1
* 5. epc2
* 6. epc3
* 7. excvaddr
* 8. depc
* 9. adress of stack start
* 10. adress of stack end
* 11. stack trace bytes
* ...
*/
#define SAVE_CRASH_CRASH_TIME 0x00 // 4 bytes
#define SAVE_CRASH_RESTART_REASON 0x04 // 1 byte
#define SAVE_CRASH_EXCEPTION_CAUSE 0x05 // 1 byte
#define SAVE_CRASH_EPC1 0x06 // 4 bytes
#define SAVE_CRASH_EPC2 0x0A // 4 bytes
#define SAVE_CRASH_EPC3 0x0E // 4 bytes
#define SAVE_CRASH_EXCVADDR 0x12 // 4 bytes
#define SAVE_CRASH_DEPC 0x16 // 4 bytes
#define SAVE_CRASH_STACK_START 0x1A // 4 bytes
#define SAVE_CRASH_STACK_END 0x1E // 4 bytes
#define SAVE_CRASH_STACK_TRACE 0x22 // variable
/**
* Save crash information in EEPROM
* This function is called automatically if ESP8266 suffers an exception
* It should be kept quick / consise to be able to execute before hardware wdt may kick in
*/
extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack_start, uint32_t stack_end ) {
// This method assumes EEPROM has already been initialized
// which is the first thing ESPurna does
// write crash time to EEPROM
uint32_t crash_time = millis();
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
// write reset info to EEPROM
EEPROMr.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON, rst_info->reason);
EEPROMr.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE, rst_info->exccause);
// write epc1, epc2, epc3, excvaddr and depc to EEPROM
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, rst_info->epc1);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, rst_info->epc2);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, rst_info->epc3);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, rst_info->excvaddr);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, rst_info->depc);
// write stack start and end address to EEPROM
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
// write stack trace to EEPROM
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
for (uint32_t i = stack_start; i < stack_end; i++) {
byte* byteValue = (byte*) i;
EEPROMr.write(current_address++, *byteValue);
}
EEPROMr.commit();
}
/**
* Clears crash info
*/
void debugClearCrashInfo() {
uint32_t crash_time = 0xFFFFFFFF;
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROMr.commit();
}
/**
* Print out crash information that has been previusly saved in EEPROM
*/
void debugDumpCrashInfo() {
uint32_t crash_time;
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
if ((crash_time == 0) || (crash_time == 0xFFFFFFFF)) {
DEBUG_MSG_P(PSTR("[DEBUG] No crash info\n"));
return;
}
DEBUG_MSG_P(PSTR("[DEBUG] Latest crash was at %lu ms after boot\n"), crash_time);
DEBUG_MSG_P(PSTR("[DEBUG] Reason of restart: %u\n"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON));
DEBUG_MSG_P(PSTR("[DEBUG] Exception cause: %u\n"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE));
uint32_t epc1, epc2, epc3, excvaddr, depc;
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, epc1);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, epc2);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, epc3);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, excvaddr);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, depc);
DEBUG_MSG_P(PSTR("[DEBUG] epc1=0x%08x epc2=0x%08x epc3=0x%08x\n"), epc1, epc2, epc3);
DEBUG_MSG_P(PSTR("[DEBUG] excvaddr=0x%08x depc=0x%08x\n"), excvaddr, depc);
uint32_t stack_start, stack_end;
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
DEBUG_MSG_P(PSTR("[DEBUG] >>>stack>>>\n[DEBUG] "));
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
int16_t stack_len = stack_end - stack_start;
uint32_t stack_trace;
for (int16_t i = 0; i < stack_len; i += 0x10) {
DEBUG_MSG_P(PSTR("%08x: "), stack_start + i);
for (byte j = 0; j < 4; j++) {
EEPROMr.get(current_address, stack_trace);
DEBUG_MSG_P(PSTR("%08x "), stack_trace);
current_address += 4;
}
DEBUG_MSG_P(PSTR("\n[DEBUG] "));
}
DEBUG_MSG_P(PSTR("<<<stack<<<\n"));
}
#endif // DEBUG_SUPPORT #endif // DEBUG_SUPPORT

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

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

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

@ -148,7 +148,7 @@ void setup() {
#if I2C_SUPPORT #if I2C_SUPPORT
i2cSetup(); i2cSetup();
#endif #endif
#ifdef ITEAD_SONOFF_RFBRIDGE
#if defined(ITEAD_SONOFF_RFBRIDGE) || RF_SUPPORT
rfbSetup(); rfbSetup();
#endif #endif
#if ALEXA_SUPPORT #if ALEXA_SUPPORT
@ -166,9 +166,6 @@ void setup() {
#if RFM69_SUPPORT #if RFM69_SUPPORT
rfm69Setup(); rfm69Setup();
#endif #endif
#if RF_SUPPORT
rfSetup();
#endif
#if IR_SUPPORT #if IR_SUPPORT
irSetup(); irSetup();
#endif #endif


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

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


+ 88
- 33
code/espurna/light.ino View File

@ -34,7 +34,7 @@ typedef struct {
bool state; bool state;
unsigned char inputValue; // value that has been inputted unsigned char inputValue; // value that has been inputted
unsigned char value; // normalized value including brightness unsigned char value; // normalized value including brightness
unsigned char shadow; // represented value
unsigned char target; // target value
double current; // transition value double current; // transition value
} channel_t; } channel_t;
std::vector<channel_t> _light_channel; std::vector<channel_t> _light_channel;
@ -314,25 +314,29 @@ void _fromMireds(unsigned long mireds) {
// Output Values // Output Values
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void _toRGB(char * rgb, size_t len) {
void _toRGB(char * rgb, size_t len, bool target) {
unsigned long value = 0; unsigned long value = 0;
value += _light_channel[0].inputValue;
value += target ? _light_channel[0].target : _light_channel[0].inputValue;
value <<= 8; value <<= 8;
value += _light_channel[1].inputValue;
value += target ? _light_channel[1].target : _light_channel[1].inputValue;
value <<= 8; value <<= 8;
value += _light_channel[2].inputValue;
value += target ? _light_channel[2].target : _light_channel[2].inputValue;
snprintf_P(rgb, len, PSTR("#%06X"), value); snprintf_P(rgb, len, PSTR("#%06X"), value);
} }
void _toHSV(char * hsv, size_t len) {
void _toRGB(char * rgb, size_t len) {
_toRGB(rgb, len, false);
}
void _toHSV(char * hsv, size_t len, bool target) {
double h, s, v; double h, s, v;
double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS; double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS;
double r = (double) (_light_channel[0].inputValue * brightness) / 255.0;
double g = (double) (_light_channel[1].inputValue * brightness) / 255.0;
double b = (double) (_light_channel[2].inputValue * brightness) / 255.0;
double r = (double) ((target ? _light_channel[0].target : _light_channel[0].inputValue) * brightness) / 255.0;
double g = (double) ((target ? _light_channel[1].target : _light_channel[1].inputValue) * brightness) / 255.0;
double b = (double) ((target ? _light_channel[2].target : _light_channel[2].inputValue) * brightness) / 255.0;
double min = std::min(r, std::min(g, b)); double min = std::min(r, std::min(g, b));
double max = std::max(r, std::max(g, b)); double max = std::max(r, std::max(g, b));
@ -363,27 +367,41 @@ void _toHSV(char * hsv, size_t len) {
snprintf_P(hsv, len, PSTR("%d,%d,%d"), round(h), round(s), round(v)); snprintf_P(hsv, len, PSTR("%d,%d,%d"), round(h), round(s), round(v));
} }
void _toLong(char * color, size_t len) {
void _toHSV(char * hsv, size_t len) {
_toHSV(hsv, len, false);
}
void _toLong(char * color, size_t len, bool target) {
if (!_light_has_color) return; if (!_light_has_color) return;
snprintf_P(color, len, PSTR("%d,%d,%d"), snprintf_P(color, len, PSTR("%d,%d,%d"),
(int) _light_channel[0].inputValue,
(int) _light_channel[1].inputValue,
(int) _light_channel[2].inputValue
(int) (target ? _light_channel[0].target : _light_channel[0].inputValue),
(int) (target ? _light_channel[1].target : _light_channel[1].inputValue),
(int) (target ? _light_channel[2].target : _light_channel[2].inputValue)
); );
} }
void _toCSV(char * buffer, size_t len, bool applyBrightness) {
void _toLong(char * color, size_t len) {
_toLong(color, len, false);
}
void _toCSV(char * buffer, size_t len, bool applyBrightness, bool target) {
char num[10]; char num[10];
float b = applyBrightness ? (float) _light_brightness / LIGHT_MAX_BRIGHTNESS : 1; float b = applyBrightness ? (float) _light_brightness / LIGHT_MAX_BRIGHTNESS : 1;
for (unsigned char i=0; i<_light_channel.size(); i++) { for (unsigned char i=0; i<_light_channel.size(); i++) {
itoa(_light_channel[i].inputValue * b, num, 10);
itoa((target ? _light_channel[i].target : _light_channel[i].inputValue) * b, num, 10);
if (i>0) strncat(buffer, ",", len--); if (i>0) strncat(buffer, ",", len--);
strncat(buffer, num, len); strncat(buffer, num, len);
len = len - strlen(num); len = len - strlen(num);
} }
} }
void _toCSV(char * buffer, size_t len, bool applyBrightness) {
_toCSV(buffer, len, applyBrightness, false);
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// PROVIDER // PROVIDER
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -399,37 +417,32 @@ unsigned int _toPWM(unsigned long value, bool gamma, bool reverse) {
// Returns a PWM value for the given channel ID // Returns a PWM value for the given channel ID
unsigned int _toPWM(unsigned char id) { unsigned int _toPWM(unsigned char id) {
bool useGamma = _light_use_gamma && _light_has_color && (id < 3); bool useGamma = _light_use_gamma && _light_has_color && (id < 3);
return _toPWM(_light_channel[id].shadow, useGamma, _light_channel[id].reverse);
return _toPWM(_light_channel[id].current, useGamma, _light_channel[id].reverse);
} }
void _shadow() {
void _transition() {
// Update transition ticker // Update transition ticker
_light_steps_left--; _light_steps_left--;
if (_light_steps_left == 0) _light_transition_ticker.detach(); if (_light_steps_left == 0) _light_transition_ticker.detach();
// Transitions // Transitions
unsigned char target;
for (unsigned int i=0; i < _light_channel.size(); i++) { for (unsigned int i=0; i < _light_channel.size(); i++) {
target = _light_state && _light_channel[i].state ? _light_channel[i].value : 0;
if (_light_steps_left == 0) { if (_light_steps_left == 0) {
_light_channel[i].current = target;
_light_channel[i].current = _light_channel[i].target;
} else { } else {
double difference = (double) (target - _light_channel[i].current) / (_light_steps_left + 1);
double difference = (double) (_light_channel[i].target - _light_channel[i].current) / (_light_steps_left + 1);
_light_channel[i].current = _light_channel[i].current + difference; _light_channel[i].current = _light_channel[i].current + difference;
} }
_light_channel[i].shadow = _light_channel[i].current;
} }
} }
void _lightProviderUpdate() { void _lightProviderUpdate() {
_shadow();
_transition();
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX #if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX
@ -491,6 +504,7 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl
mqttSubscribe(MQTT_TOPIC_KELVIN); mqttSubscribe(MQTT_TOPIC_KELVIN);
mqttSubscribe(MQTT_TOPIC_COLOR_RGB); mqttSubscribe(MQTT_TOPIC_COLOR_RGB);
mqttSubscribe(MQTT_TOPIC_COLOR_HSV); mqttSubscribe(MQTT_TOPIC_COLOR_HSV);
mqttSubscribe(MQTT_TOPIC_TRANSITION);
} }
// Group color // Group color
@ -548,6 +562,12 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl
return; return;
} }
// Transitions
if (t.equals(MQTT_TOPIC_TRANSITION)) {
lightTransitionTime(atol(payload));
return;
}
// Channel // Channel
if (t.startsWith(MQTT_TOPIC_CHANNEL)) { if (t.startsWith(MQTT_TOPIC_CHANNEL)) {
unsigned int channelID = t.substring(strlen(MQTT_TOPIC_CHANNEL)+1).toInt(); unsigned int channelID = t.substring(strlen(MQTT_TOPIC_CHANNEL)+1).toInt();
@ -572,13 +592,13 @@ void lightMQTT() {
// Color // Color
if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) { if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) {
_toRGB(buffer, sizeof(buffer));
_toRGB(buffer, sizeof(buffer), true);
} else { } else {
_toLong(buffer, sizeof(buffer));
_toLong(buffer, sizeof(buffer), true);
} }
mqttSend(MQTT_TOPIC_COLOR_RGB, buffer); mqttSend(MQTT_TOPIC_COLOR_RGB, buffer);
_toHSV(buffer, sizeof(buffer));
_toHSV(buffer, sizeof(buffer), true);
mqttSend(MQTT_TOPIC_COLOR_HSV, buffer); mqttSend(MQTT_TOPIC_COLOR_HSV, buffer);
// Mireds // Mireds
@ -589,7 +609,7 @@ void lightMQTT() {
// Channels // Channels
for (unsigned int i=0; i < _light_channel.size(); i++) { for (unsigned int i=0; i < _light_channel.size(); i++) {
itoa(_light_channel[i].inputValue, buffer, 10);
itoa(_light_channel[i].target, buffer, 10);
mqttSend(MQTT_TOPIC_CHANNEL, i, buffer); mqttSend(MQTT_TOPIC_CHANNEL, i, buffer);
} }
@ -642,6 +662,12 @@ void lightUpdate(bool save, bool forward, bool group_forward) {
_generateBrightness(); _generateBrightness();
// Update channels
for (unsigned int i=0; i < _light_channel.size(); i++) {
_light_channel[i].target = _light_state && _light_channel[i].state ? _light_channel[i].value : 0;
//DEBUG_MSG_P("[LIGHT] Channel #%u target value: %u\n", i, _light_channel[i].target);
}
// Configure color transition // Configure color transition
_light_steps_left = _light_use_transitions ? _light_transition_time / LIGHT_TRANSITION_STEP : 1; _light_steps_left = _light_use_transitions ? _light_transition_time / LIGHT_TRANSITION_STEP : 1;
_light_transition_ticker.attach_ms(LIGHT_TRANSITION_STEP, _lightProviderUpdate); _light_transition_ticker.attach_ms(LIGHT_TRANSITION_STEP, _lightProviderUpdate);
@ -755,6 +781,26 @@ void lightBrightnessStep(int steps) {
lightBrightness(_light_brightness + steps * LIGHT_STEP); lightBrightness(_light_brightness + steps * LIGHT_STEP);
} }
unsigned long lightTransitionTime() {
if (_light_use_transitions) {
return _light_transition_time;
} else {
return 0;
}
}
void lightTransitionTime(unsigned long m) {
if (0 == m) {
_light_use_transitions = false;
} else {
_light_use_transitions = true;
_light_transition_time = m;
}
setSetting("useTransitions", _light_use_transitions);
setSetting("lightTime", _light_transition_time);
saveSettings();
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// SETUP // SETUP
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -844,9 +890,9 @@ void _lightAPISetup() {
apiRegister(MQTT_TOPIC_COLOR_RGB, apiRegister(MQTT_TOPIC_COLOR_RGB,
[](char * buffer, size_t len) { [](char * buffer, size_t len) {
if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) { if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) {
_toRGB(buffer, len);
_toRGB(buffer, len, true);
} else { } else {
_toLong(buffer, len);
_toLong(buffer, len, true);
} }
}, },
[](const char * payload) { [](const char * payload) {
@ -857,7 +903,7 @@ void _lightAPISetup() {
apiRegister(MQTT_TOPIC_COLOR_HSV, apiRegister(MQTT_TOPIC_COLOR_HSV,
[](char * buffer, size_t len) { [](char * buffer, size_t len) {
_toHSV(buffer, len);
_toHSV(buffer, len, true);
}, },
[](const char * payload) { [](const char * payload) {
lightColor(payload, false); lightColor(payload, false);
@ -889,7 +935,7 @@ void _lightAPISetup() {
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_CHANNEL, id); snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_CHANNEL, id);
apiRegister(key, apiRegister(key,
[id](char * buffer, size_t len) { [id](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), lightChannel(id));
snprintf_P(buffer, len, PSTR("%d"), _light_channel[id].target);
}, },
[id](const char * payload) { [id](const char * payload) {
lightChannel(id, atoi(payload)); lightChannel(id, atoi(payload));
@ -899,6 +945,15 @@ void _lightAPISetup() {
} }
apiRegister(MQTT_TOPIC_TRANSITION,
[](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), lightTransitionTime());
},
[](const char * payload) {
lightTransitionTime(atol(payload));
}
);
apiRegister(MQTT_TOPIC_BRIGHTNESS, apiRegister(MQTT_TOPIC_BRIGHTNESS,
[](char * buffer, size_t len) { [](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), _light_brightness); snprintf_P(buffer, len, PSTR("%d"), _light_brightness);


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

@ -1223,6 +1223,28 @@ void migrate() {
setSetting("ledGPIO", 1, 15); setSetting("ledGPIO", 1, 15);
setSetting("ledLogic", 1, 0); setSetting("ledLogic", 1, 0);
#elif defined(TONBUX_XSSSA01)
setSetting("board", 92);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(EUROMATE_WIFI_STECKER_SCHUKO_V2)
setSetting("board", 93);
setSetting("ledGPIO", 0, 13); // Red LED
setSetting("ledLogic", 0, 1);
setSetting("ledGPIO", 1, 12); // Green LED
setSetting("ledLogic", 1, 1);
setSetting("btnGPIO", 0, 5);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(FOXEL_LIGHTFOX_DUAL) #elif defined(FOXEL_LIGHTFOX_DUAL)
setSetting("board", 92); setSetting("board", 92);


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

@ -51,6 +51,7 @@ char *_mqtt_clientid;
#if MQTT_SKIP_RETAINED #if MQTT_SKIP_RETAINED
unsigned long _mqtt_connected_at = 0; unsigned long _mqtt_connected_at = 0;
#endif #endif
unsigned long _mqtt_disconnected_at = 0;
std::vector<mqtt_callback_f> _mqtt_callbacks; std::vector<mqtt_callback_f> _mqtt_callbacks;
@ -75,9 +76,7 @@ void _mqttConnect() {
if (_mqtt.connected()) return; if (_mqtt.connected()) return;
// Check reconnect interval // Check reconnect interval
static unsigned long last = 0;
if (millis() - last < _mqtt_reconnect_delay) return;
last = millis();
if (millis() - _mqtt_disconnected_at < _mqtt_reconnect_delay) return;
// Increase the reconnect delay // Increase the reconnect delay
_mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP; _mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP;
@ -98,10 +97,14 @@ void _mqttConnect() {
if (_mqtt_will) free(_mqtt_will); if (_mqtt_will) free(_mqtt_will);
if (_mqtt_clientid) free(_mqtt_clientid); if (_mqtt_clientid) free(_mqtt_clientid);
_mqtt_user = strdup(getSetting("mqttUser", MQTT_USER).c_str());
String user = getSetting("mqttUser", MQTT_USER);
_mqttPlaceholders(&user);
_mqtt_user = strdup(user.c_str());
_mqtt_pass = strdup(getSetting("mqttPassword", MQTT_PASS).c_str()); _mqtt_pass = strdup(getSetting("mqttPassword", MQTT_PASS).c_str());
_mqtt_will = strdup(mqttTopic(MQTT_TOPIC_STATUS, false).c_str()); _mqtt_will = strdup(mqttTopic(MQTT_TOPIC_STATUS, false).c_str());
_mqtt_clientid = strdup(getSetting("mqttClientID", getIdentifier()).c_str());
String clientid = getSetting("mqttClientID", getIdentifier());
_mqttPlaceholders(&clientid);
_mqtt_clientid = strdup(clientid.c_str());
DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d\n"), host, port); DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d\n"), host, port);
@ -211,6 +214,17 @@ void _mqttConnect() {
} }
void _mqttPlaceholders(String *text) {
text->replace("{hostname}", getSetting("hostname"));
text->replace("{magnitude}", "#");
String mac = WiFi.macAddress();
mac.replace(":", "");
text->replace("{mac}", mac);
}
void _mqttConfigure() { void _mqttConfigure() {
// Get base topic // Get base topic
@ -218,12 +232,8 @@ void _mqttConfigure() {
if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1); if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1);
// Placeholders // Placeholders
_mqtt_topic.replace("{hostname}", getSetting("hostname"));
_mqtt_topic.replace("{magnitude}", "#");
_mqttPlaceholders(&_mqtt_topic);
if (_mqtt_topic.indexOf("#") == -1) _mqtt_topic = _mqtt_topic + "/#"; if (_mqtt_topic.indexOf("#") == -1) _mqtt_topic = _mqtt_topic + "/#";
String mac = WiFi.macAddress();
mac.replace(":", "");
_mqtt_topic.replace("{mac}", mac);
// Getters and setters // Getters and setters
_mqtt_setter = getSetting("mqttSetter", MQTT_SETTER); _mqtt_setter = getSetting("mqttSetter", MQTT_SETTER);
@ -291,7 +301,7 @@ unsigned long _mqttNextMessageId() {
EEPROMr.write(EEPROM_MESSAGE_ID + 1, (id >> 16) & 0xFF); EEPROMr.write(EEPROM_MESSAGE_ID + 1, (id >> 16) & 0xFF);
EEPROMr.write(EEPROM_MESSAGE_ID + 2, (id >> 8) & 0xFF); EEPROMr.write(EEPROM_MESSAGE_ID + 2, (id >> 8) & 0xFF);
EEPROMr.write(EEPROM_MESSAGE_ID + 3, (id >> 0) & 0xFF); EEPROMr.write(EEPROM_MESSAGE_ID + 3, (id >> 0) & 0xFF);
saveSettings();
eepromCommit();
} }
id++; id++;
@ -403,6 +413,8 @@ void _mqttOnConnect() {
void _mqttOnDisconnect() { void _mqttOnDisconnect() {
_mqtt_disconnected_at = millis();
DEBUG_MSG_P(PSTR("[MQTT] Disconnected!\n")); DEBUG_MSG_P(PSTR("[MQTT] Disconnected!\n"));
// Send disconnect event to subscribers // Send disconnect event to subscribers


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

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


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

@ -128,7 +128,11 @@ void _ntpBackwards() {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
bool ntpSynced() { bool ntpSynced() {
return (year() > 2017);
#if NTP_WAIT_FOR_SYNC
return (NTP.getLastNTPSync() > 0);
#else
return true;
#endif
} }
String ntpDateTime(time_t t) { String ntpDateTime(time_t t) {


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

@ -158,6 +158,10 @@ void _otaFrom(const char * host, unsigned int port, const char * url) {
} }
void _otaFrom(String url) { void _otaFrom(String url) {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n"));
return;
}
// Port from protocol // Port from protocol
unsigned int port = 80; unsigned int port = 80;
@ -197,6 +201,25 @@ void _otaInitCommands() {
#endif // TERMINAL_SUPPORT #endif // TERMINAL_SUPPORT
#if OTA_MQTT_SUPPORT
void _otaMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribe(MQTT_TOPIC_OTA);
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttMagnitude((char *) topic);
if (t.equals(MQTT_TOPIC_OTA)) {
DEBUG_MSG_P(PSTR("[OTA] Initiating from URL: %s\n"), payload);
_otaFrom(payload);
}
}
}
#endif // OTA_MQTT_SUPPORT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void otaSetup() { void otaSetup() {
@ -207,6 +230,10 @@ void otaSetup() {
_otaInitCommands(); _otaInitCommands();
#endif #endif
#if OTA_MQTT_SUPPORT
mqttRegister(_otaMQTTCallback);
#endif
// Main callbacks // Main callbacks
espurnaRegisterLoop(_otaLoop); espurnaRegisterLoop(_otaLoop);
espurnaRegisterReload(_otaConfigure); espurnaRegisterReload(_otaConfigure);


+ 129
- 76
code/espurna/relay.ino View File

@ -90,32 +90,44 @@ void _relayProviderStatus(unsigned char id, bool status) {
#if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT #if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
// If the number of relays matches the number of light channels
// assume each relay controls one channel.
// If the number of relays is the number of channels plus 1
// assume the first one controls all the channels and
// the rest one channel each.
// Otherwise every relay controls all channels.
// TODO: this won't work with a mixed of dummy and real relays
// but this option is not allowed atm (YANGNI)
if (_relays.size() == lightChannels()) {
lightState(id, status);
lightState(true);
} else if (_relays.size() == (lightChannels() + 1u)) {
if (id == 0) {
lightState(status);
// Real relays
uint8_t physical = _relays.size() - DUMMY_RELAY_COUNT;
// Support for a mixed of dummy and real relays
// Reference: https://github.com/xoseperez/espurna/issues/1305
if (id >= physical) {
// If the number of dummy relays matches the number of light channels
// assume each relay controls one channel.
// If the number of dummy relays is the number of channels plus 1
// assume the first one controls all the channels and
// the rest one channel each.
// Otherwise every dummy relay controls all channels.
if (DUMMY_RELAY_COUNT == lightChannels()) {
lightState(id-physical, status);
lightState(true);
} else if (DUMMY_RELAY_COUNT == (lightChannels() + 1u)) {
if (id == physical) {
lightState(status);
} else {
lightState(id-1-physical, status);
}
} else { } else {
lightState(id-1, status);
lightState(status);
} }
} else {
lightState(status);
}
lightUpdate(true, true);
lightUpdate(true, true);
return;
}
#endif #endif
#if RELAY_PROVIDER == RELAY_PROVIDER_RELAY
#if (RELAY_PROVIDER == RELAY_PROVIDER_RELAY) || (RELAY_PROVIDER == RELAY_PROVIDER_LIGHT)
// If this is a light, all dummy relays have already been processed above
// we reach here if the user has toggled a physical relay
if (_relays[id].type == RELAY_TYPE_NORMAL) { if (_relays[id].type == RELAY_TYPE_NORMAL) {
digitalWrite(_relays[id].pin, status); digitalWrite(_relays[id].pin, status);
} else if (_relays[id].type == RELAY_TYPE_INVERSE) { } else if (_relays[id].type == RELAY_TYPE_INVERSE) {
@ -133,6 +145,7 @@ void _relayProviderStatus(unsigned char id, bool status) {
digitalWrite(_relays[id].pin, !pulse); digitalWrite(_relays[id].pin, !pulse);
if (GPIO_NONE != _relays[id].reset_pin) digitalWrite(_relays[id].reset_pin, !pulse); if (GPIO_NONE != _relays[id].reset_pin) digitalWrite(_relays[id].reset_pin, !pulse);
} }
#endif #endif
} }
@ -420,7 +433,7 @@ void relaySave(bool do_commit) {
// We are actually enqueuing the commit so it will be // We are actually enqueuing the commit so it will be
// executed on the main loop, in case this is called from a callback // executed on the main loop, in case this is called from a callback
saveSettings();
eepromCommit();
} }
@ -550,7 +563,7 @@ void _relayBoot() {
// Save if there is any relay in the RELAY_BOOT_TOGGLE mode // Save if there is any relay in the RELAY_BOOT_TOGGLE mode
if (trigger_save) { if (trigger_save) {
EEPROMr.write(EEPROM_RELAY_STATUS, mask); EEPROMr.write(EEPROM_RELAY_STATUS, mask);
saveSettings();
eepromCommit();
} }
_relayRecursive = false; _relayRecursive = false;
@ -559,6 +572,8 @@ void _relayBoot() {
void _relayConfigure() { void _relayConfigure() {
for (unsigned int i=0; i<_relays.size(); i++) { for (unsigned int i=0; i<_relays.size(); i++) {
if (GPIO_NONE == _relays[i].pin) continue;
pinMode(_relays[i].pin, OUTPUT); pinMode(_relays[i].pin, OUTPUT);
if (GPIO_NONE != _relays[i].reset_pin) { if (GPIO_NONE != _relays[i].reset_pin) {
pinMode(_relays[i].reset_pin, OUTPUT); pinMode(_relays[i].reset_pin, OUTPUT);
@ -589,30 +604,74 @@ void _relayWebSocketUpdate(JsonObject& root) {
} }
} }
void _relayWebSocketSendRelay(unsigned char i) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
JsonArray& config = root.createNestedArray("relayConfig");
JsonObject& line = config.createNestedObject();
line["id"] = i;
if (GPIO_NONE == _relays[i].pin) {
#if (RELAY_PROVIDER == RELAY_PROVIDER_LIGHT)
uint8_t physical = _relays.size() - DUMMY_RELAY_COUNT;
if (i >= physical) {
if (DUMMY_RELAY_COUNT == lightChannels()) {
line["gpio"] = String("CH") + String(i-physical);
} else if (DUMMY_RELAY_COUNT == (lightChannels() + 1u)) {
if (physical == i) {
line["gpio"] = String("Light");
} else {
line["gpio"] = String("CH") + String(i-1-physical);
}
} else {
line["gpio"] = String("Light");
}
} else {
line["gpio"] = String("?");
}
#else
line["gpio"] = String("SW") + String(i);
#endif
} else {
line["gpio"] = String("GPIO") + String(_relays[i].pin);
}
line["type"] = _relays[i].type;
line["reset"] = _relays[i].reset_pin;
line["boot"] = getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt();
line["pulse"] = _relays[i].pulse;
line["pulse_ms"] = _relays[i].pulse_ms / 1000.0;
#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
String output;
root.printTo(output);
jsonBuffer.clear();
wsSend((char *) output.c_str());
}
void _relayWebSocketSendRelays() {
for (unsigned char i=0; i<relayCount(); i++) {
_relayWebSocketSendRelay(i);
}
}
void _relayWebSocketOnStart(JsonObject& root) { void _relayWebSocketOnStart(JsonObject& root) {
if (relayCount() == 0) return; if (relayCount() == 0) return;
// Per-relay configuration
_relayWebSocketSendRelays();
// Statuses // Statuses
_relayWebSocketUpdate(root); _relayWebSocketUpdate(root);
// Configuration
JsonArray& config = root.createNestedArray("relayConfig");
for (unsigned char i=0; i<relayCount(); i++) {
JsonObject& line = config.createNestedObject();
line["gpio"] = _relays[i].pin;
line["type"] = _relays[i].type;
line["reset"] = _relays[i].reset_pin;
line["boot"] = getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt();
line["pulse"] = _relays[i].pulse;
line["pulse_ms"] = _relays[i].pulse_ms / 1000.0;
#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
}
// Options
if (relayCount() > 1) { if (relayCount() > 1) {
root["multirelayVisible"] = 1; root["multirelayVisible"] = 1;
root["relaySync"] = getSetting("relaySync", RELAY_SYNC); root["relaySync"] = getSetting("relaySync", RELAY_SYNC);
@ -1001,44 +1060,38 @@ void _relayLoop() {
void relaySetup() { void relaySetup() {
// Dummy relays for AI Light, Magic Home LED Controller, H801,
// Sonoff Dual and Sonoff RF Bridge
#if DUMMY_RELAY_COUNT > 0
// No delay_on or off for these devices to easily allow having more than
// 8 channels. This behaviour will be recovered with v2.
for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL, 0, 0, 0});
}
#else
#if RELAY1_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY1_PIN, RELAY1_TYPE, RELAY1_RESET_PIN, RELAY1_DELAY_ON, RELAY1_DELAY_OFF });
#endif
#if RELAY2_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY2_PIN, RELAY2_TYPE, RELAY2_RESET_PIN, RELAY2_DELAY_ON, RELAY2_DELAY_OFF });
#endif
#if RELAY3_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY3_PIN, RELAY3_TYPE, RELAY3_RESET_PIN, RELAY3_DELAY_ON, RELAY3_DELAY_OFF });
#endif
#if RELAY4_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY4_PIN, RELAY4_TYPE, RELAY4_RESET_PIN, RELAY4_DELAY_ON, RELAY4_DELAY_OFF });
#endif
#if RELAY5_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY5_PIN, RELAY5_TYPE, RELAY5_RESET_PIN, RELAY5_DELAY_ON, RELAY5_DELAY_OFF });
#endif
#if RELAY6_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY6_PIN, RELAY6_TYPE, RELAY6_RESET_PIN, RELAY6_DELAY_ON, RELAY6_DELAY_OFF });
#endif
#if RELAY7_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY7_PIN, RELAY7_TYPE, RELAY7_RESET_PIN, RELAY7_DELAY_ON, RELAY7_DELAY_OFF });
#endif
#if RELAY8_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY8_PIN, RELAY8_TYPE, RELAY8_RESET_PIN, RELAY8_DELAY_ON, RELAY8_DELAY_OFF });
#endif
// Ad-hoc relays
#if RELAY1_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY1_PIN, RELAY1_TYPE, RELAY1_RESET_PIN, RELAY1_DELAY_ON, RELAY1_DELAY_OFF });
#endif
#if RELAY2_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY2_PIN, RELAY2_TYPE, RELAY2_RESET_PIN, RELAY2_DELAY_ON, RELAY2_DELAY_OFF });
#endif
#if RELAY3_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY3_PIN, RELAY3_TYPE, RELAY3_RESET_PIN, RELAY3_DELAY_ON, RELAY3_DELAY_OFF });
#endif #endif
#if RELAY4_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY4_PIN, RELAY4_TYPE, RELAY4_RESET_PIN, RELAY4_DELAY_ON, RELAY4_DELAY_OFF });
#endif
#if RELAY5_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY5_PIN, RELAY5_TYPE, RELAY5_RESET_PIN, RELAY5_DELAY_ON, RELAY5_DELAY_OFF });
#endif
#if RELAY6_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY6_PIN, RELAY6_TYPE, RELAY6_RESET_PIN, RELAY6_DELAY_ON, RELAY6_DELAY_OFF });
#endif
#if RELAY7_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY7_PIN, RELAY7_TYPE, RELAY7_RESET_PIN, RELAY7_DELAY_ON, RELAY7_DELAY_OFF });
#endif
#if RELAY8_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY8_PIN, RELAY8_TYPE, RELAY8_RESET_PIN, RELAY8_DELAY_ON, RELAY8_DELAY_OFF });
#endif
// Dummy relays for AI Light, Magic Home LED Controller, H801, Sonoff Dual and Sonoff RF Bridge
// No delay_on or off for these devices to easily allow having more than
// 8 channels. This behaviour will be recovered with v2.
for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
_relays.push_back((relay_t) {GPIO_NONE, RELAY_TYPE_NORMAL, 0, 0, 0});
}
_relayBackwards(); _relayBackwards();
_relayConfigure(); _relayConfigure();


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

@ -1,192 +0,0 @@
/*
RF MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if RF_SUPPORT
#include <RCSwitch.h>
RCSwitch * _rfModem;
unsigned long _rf_learn_start = 0;
unsigned char _rf_learn_id = 0;
bool _rf_learn_status = true;
bool _rf_learn_active = false;
// -----------------------------------------------------------------------------
// RF
// -----------------------------------------------------------------------------
unsigned long _rfRetrieve(unsigned char id, bool status) {
String code = getSetting(status ? "rfbON" : "rfbOFF", id, "0");
return strtoul(code.c_str(), 0, 16);
}
void _rfStore(unsigned char id, bool status, unsigned long code) {
DEBUG_MSG_P(PSTR("[RF] Storing %d-%s => %X\n"), id, status ? "ON" : "OFF", code);
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%X"), code);
setSetting(status ? "rfbON" : "rfbOFF", id, buffer);
}
void _rfLearn(unsigned char id, bool status) {
_rf_learn_start = millis();
_rf_learn_id = id;
_rf_learn_status = status;
_rf_learn_active = true;
}
void _rfForget(unsigned char id, bool status) {
delSetting(status ? "rfbON" : "rfbOFF", id);
// Websocket update
#if WEB_SUPPORT
char wsb[100];
snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"\"}]}"), id, status ? 1 : 0);
wsSend(wsb);
#endif
}
bool _rfMatch(unsigned long code, unsigned char& relayID, unsigned char& value) {
bool found = false;
DEBUG_MSG_P(PSTR("[RF] Trying to match code %X\n"), code);
for (unsigned char i=0; i<relayCount(); i++) {
unsigned long code_on = _rfRetrieve(i, true);
unsigned long code_off = _rfRetrieve(i, false);
if (code == code_on) {
DEBUG_MSG_P(PSTR("[RF] Match ON code for relay %d\n"), i);
value = 1;
found = true;
}
if (code == code_off) {
DEBUG_MSG_P(PSTR("[RF] Match OFF code for relay %d\n"), i);
if (found) value = 2;
found = true;
}
if (found) {
relayID = i;
return true;
}
}
return false;
}
// -----------------------------------------------------------------------------
// WEB
// -----------------------------------------------------------------------------
void _rfWebSocketOnSend(JsonObject& root) {
char buffer[20];
root["rfbVisible"] = 1;
root["rfbCount"] = relayCount();
JsonArray& rfb = root.createNestedArray("rfb");
for (byte id=0; id<relayCount(); id++) {
for (byte status=0; status<2; status++) {
JsonObject& node = rfb.createNestedObject();
snprintf_P(buffer, sizeof(buffer), PSTR("%X"), _rfRetrieve(id, status == 1));
node["id"] = id;
node["status"] = status;
node["data"] = String(buffer);
}
}
}
void _rfWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "rfblearn") == 0) _rfLearn(data["id"], data["status"]);
if (strcmp(action, "rfbforget") == 0) _rfForget(data["id"], data["status"]);
if (strcmp(action, "rfbsend") == 0) _rfStore(data["id"], data["status"], data["data"].as<long>());
}
// -----------------------------------------------------------------------------
void rfLoop() {
if (_rfModem->available()) {
static unsigned long last = 0;
if (millis() - last > RF_DEBOUNCE) {
last = millis();
if (_rfModem->getReceivedValue() > 0) {
unsigned long rf_code = _rfModem->getReceivedValue();
DEBUG_MSG_P(PSTR("[RF] Received code: %X\n"), rf_code);
if (_rf_learn_active) {
_rf_learn_active = false;
_rfStore(_rf_learn_id, _rf_learn_status, rf_code);
// Websocket update
#if WEB_SUPPORT
char wsb[100];
snprintf_P(
wsb, sizeof(wsb),
PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%X\"}]}"),
_rf_learn_id, _rf_learn_status ? 1 : 0, rf_code);
wsSend(wsb);
#endif
} else {
unsigned char id;
unsigned char value;
if (_rfMatch(rf_code, id, value)) {
if (2 == value) {
relayToggle(id);
} else {
relayStatus(id, 1 == value);
}
}
}
}
}
_rfModem->resetAvailable();
}
if (_rf_learn_active && (millis() - _rf_learn_start > RF_LEARN_TIMEOUT)) {
_rf_learn_active = false;
}
}
void rfSetup() {
_rfModem = new RCSwitch();
_rfModem->enableReceive(RF_PIN);
DEBUG_MSG_P(PSTR("[RF] RF receiver on GPIO %u\n"), RF_PIN);
#if WEB_SUPPORT
wsOnSendRegister(_rfWebSocketOnSend);
wsOnActionRegister(_rfWebSocketOnAction);
#endif
// Register loop
espurnaRegisterLoop(rfLoop);
}
#endif

+ 155
- 62
code/espurna/rfbridge.ino View File

@ -1,17 +1,17 @@
/* /*
ITEAD RF BRIDGE MODULE
RF MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/ */
#ifdef ITEAD_SONOFF_RFBRIDGE
#if defined(ITEAD_SONOFF_RFBRIDGE) || RF_SUPPORT
#include <queue> #include <queue>
#include <Ticker.h> #include <Ticker.h>
#if RFB_DIRECT
#if RFB_DIRECT || RF_SUPPORT
#include <RCSwitch.h> #include <RCSwitch.h>
#endif #endif
@ -19,6 +19,8 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// DEFINITIONS // DEFINITIONS
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// EFM8 Protocol
#define RF_MESSAGE_SIZE 9 #define RF_MESSAGE_SIZE 9
#define RF_MAX_MESSAGE_SIZE (112+4) #define RF_MAX_MESSAGE_SIZE (112+4)
#define RF_CODE_START 0xAA #define RF_CODE_START 0xAA
@ -37,6 +39,10 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
#define RF_CODE_RFOUT_BUCKET 0xB0 #define RF_CODE_RFOUT_BUCKET 0xB0
#define RF_CODE_STOP 0x55 #define RF_CODE_STOP 0x55
// Settings
#define RF_MAX_KEY_LENGTH (9)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// GLOBALS TO THE MODULE // GLOBALS TO THE MODULE
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -47,6 +53,7 @@ unsigned char _learnId = 0;
bool _learnStatus = true; bool _learnStatus = true;
bool _rfbin = false; bool _rfbin = false;
#if not RF_SUPPORT
typedef struct { typedef struct {
byte code[RF_MESSAGE_SIZE]; byte code[RF_MESSAGE_SIZE];
byte times; byte times;
@ -54,31 +61,21 @@ typedef struct {
static std::queue<rfb_message_t> _rfb_message_queue; static std::queue<rfb_message_t> _rfb_message_queue;
Ticker _rfb_ticker; Ticker _rfb_ticker;
bool _rfb_ticker_active = false; bool _rfb_ticker_active = false;
#endif
#if RFB_DIRECT
#if RFB_DIRECT || RF_SUPPORT
RCSwitch * _rfModem; RCSwitch * _rfModem;
bool _learning = false; bool _learning = false;
#endif #endif
#if WEB_SUPPORT
Ticker _rfb_sendcodes;
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// PRIVATES // 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) From a byte array to an hexa char array ("A220EE...", double the size)
*/ */
@ -89,6 +86,20 @@ static bool _rfbToChar(byte * in, char * out, int n = RF_MESSAGE_SIZE) {
return true; return true;
} }
#if WEB_SUPPORT
void _rfbWebSocketSendCode(unsigned char id, bool status, const char * code) {
char wsb[192]; // (32 * 5): 46 bytes for json , 116 bytes raw code, reserve
snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%s\"}]}"), id, status ? 1 : 0, code);
wsSend(wsb);
}
void _rfbWebSocketSendCodes() {
for (unsigned char id=0; id<relayCount(); id++) {
_rfbWebSocketSendCode(id, true, rfbRetrieve(id, true).c_str());
_rfbWebSocketSendCode(id, false, rfbRetrieve(id, false).c_str());
}
}
void _rfbWebSocketOnSend(JsonObject& root) { void _rfbWebSocketOnSend(JsonObject& root) {
root["rfbVisible"] = 1; root["rfbVisible"] = 1;
@ -96,15 +107,7 @@ void _rfbWebSocketOnSend(JsonObject& root) {
#if RF_RAW_SUPPORT #if RF_RAW_SUPPORT
root["rfbrawVisible"] = 1; root["rfbrawVisible"] = 1;
#endif #endif
JsonArray& rfb = root.createNestedArray("rfb");
for (byte id=0; id<relayCount(); id++) {
for (byte status=0; status<2; status++) {
JsonObject& node = rfb.createNestedObject();
node["id"] = id;
node["status"] = status;
node["data"] = rfbRetrieve(id, status == 1);
}
}
_rfb_sendcodes.once_ms(1000, _rfbWebSocketSendCodes);
} }
void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
@ -113,9 +116,11 @@ void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject&
if (strcmp(action, "rfbsend") == 0) rfbStore(data["id"], data["status"], data["data"].as<const char*>()); if (strcmp(action, "rfbsend") == 0) rfbStore(data["id"], data["status"], data["data"].as<const char*>());
} }
#endif // WEB_SUPPORT
void _rfbAck() { void _rfbAck() {
#if not RFB_DIRECT
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n"));
#if (not RFB_DIRECT) && (not RF_SUPPORT)
DEBUG_MSG_P(PSTR("[RF] Sending ACK\n"));
Serial.println(); Serial.println();
Serial.write(RF_CODE_START); Serial.write(RF_CODE_START);
Serial.write(RF_CODE_ACK); Serial.write(RF_CODE_ACK);
@ -126,11 +131,11 @@ void _rfbAck() {
} }
void _rfbLearn() { void _rfbLearn() {
#if RFB_DIRECT
DEBUG_MSG_P(PSTR("[RFBRIDGE] Entering LEARN mode\n"));
#if RFB_DIRECT || RF_SUPPORT
DEBUG_MSG_P(PSTR("[RF] Entering LEARN mode\n"));
_learning = true; _learning = true;
#else #else
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n"));
DEBUG_MSG_P(PSTR("[RF] Sending LEARN\n"));
Serial.println(); Serial.println();
Serial.write(RF_CODE_START); Serial.write(RF_CODE_START);
Serial.write(RF_CODE_LEARN); Serial.write(RF_CODE_LEARN);
@ -147,6 +152,24 @@ void _rfbLearn() {
} }
#if not RF_SUPPORT
/*
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;
}
void _rfbSendRaw(const byte *message, const unsigned char n = RF_MESSAGE_SIZE) { void _rfbSendRaw(const byte *message, const unsigned char n = RF_MESSAGE_SIZE) {
for (unsigned char j=0; j<n; j++) { for (unsigned char j=0; j<n; j++) {
Serial.write(message[j]); Serial.write(message[j]);
@ -215,7 +238,7 @@ void _rfbSend(byte * code, unsigned char times) {
char buffer[RF_MESSAGE_SIZE]; char buffer[RF_MESSAGE_SIZE];
_rfbToChar(code, buffer); _rfbToChar(code, buffer);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Enqueuing MESSAGE '%s' %d time(s)\n"), buffer, times);
DEBUG_MSG_P(PSTR("[RF] Enqueuing MESSAGE '%s' %d time(s)\n"), buffer, times);
rfb_message_t message; rfb_message_t message;
memcpy(message.code, code, RF_MESSAGE_SIZE); memcpy(message.code, code, RF_MESSAGE_SIZE);
@ -230,12 +253,14 @@ void _rfbSend(byte * code, unsigned char times) {
} }
#endif // not RF_SUPPORT
#if RF_RAW_SUPPORT #if RF_RAW_SUPPORT
void _rfbSendRawOnce(byte *code, unsigned char length) { void _rfbSendRawOnce(byte *code, unsigned char length) {
char buffer[length*2]; char buffer[length*2];
_rfbToChar(code, buffer, length); _rfbToChar(code, buffer, length);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending RAW MESSAGE '%s'\n"), buffer);
DEBUG_MSG_P(PSTR("[RF] Sending RAW MESSAGE '%s'\n"), buffer);
_rfbSendRaw(code, length); _rfbSendRaw(code, length);
} }
@ -248,13 +273,13 @@ bool _rfbMatch(char* code, unsigned char& relayID, unsigned char& value, char* b
bool found = false; bool found = false;
String compareto = String(&code[12]); String compareto = String(&code[12]);
compareto.toUpperCase(); compareto.toUpperCase();
DEBUG_MSG_P(PSTR("[RFBRIDGE] Trying to match code %s\n"), compareto.c_str());
DEBUG_MSG_P(PSTR("[RF] Trying to match code %s\n"), compareto.c_str());
for (unsigned char i=0; i<relayCount(); i++) { for (unsigned char i=0; i<relayCount(); i++) {
String code_on = rfbRetrieve(i, true); String code_on = rfbRetrieve(i, true);
if (code_on.length() && code_on.endsWith(compareto)) { if (code_on.length() && code_on.endsWith(compareto)) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Match ON code for relay %d\n"), i);
DEBUG_MSG_P(PSTR("[RF] Match ON code for relay %d\n"), i);
value = 1; value = 1;
found = true; found = true;
if (buffer) strcpy(buffer, code_on.c_str()); if (buffer) strcpy(buffer, code_on.c_str());
@ -262,7 +287,7 @@ bool _rfbMatch(char* code, unsigned char& relayID, unsigned char& value, char* b
String code_off = rfbRetrieve(i, false); String code_off = rfbRetrieve(i, false);
if (code_off.length() && code_off.endsWith(compareto)) { if (code_off.length() && code_off.endsWith(compareto)) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Match OFF code for relay %d\n"), i);
DEBUG_MSG_P(PSTR("[RF] Match OFF code for relay %d\n"), i);
if (found) value = 2; if (found) value = 2;
found = true; found = true;
if (buffer) strcpy(buffer, code_off.c_str()); if (buffer) strcpy(buffer, code_off.c_str());
@ -287,11 +312,11 @@ void _rfbDecode() {
byte action = _uartbuf[0]; byte action = _uartbuf[0];
char buffer[RF_MESSAGE_SIZE * 2 + 1] = {0}; char buffer[RF_MESSAGE_SIZE * 2 + 1] = {0};
DEBUG_MSG_P(PSTR("[RFBRIDGE] Action 0x%02X\n"), action);
DEBUG_MSG_P(PSTR("[RF] Action 0x%02X\n"), action);
if (action == RF_CODE_LEARN_KO) { if (action == RF_CODE_LEARN_KO) {
_rfbAck(); _rfbAck();
DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn timeout\n"));
DEBUG_MSG_P(PSTR("[RF] Learn timeout\n"));
#if WEB_SUPPORT #if WEB_SUPPORT
wsSend_P(PSTR("{\"action\": \"rfbTimeout\"}")); wsSend_P(PSTR("{\"action\": \"rfbTimeout\"}"));
#endif #endif
@ -302,20 +327,18 @@ void _rfbDecode() {
_rfbAck(); _rfbAck();
_rfbToChar(&_uartbuf[1], buffer); _rfbToChar(&_uartbuf[1], buffer);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Received message '%s'\n"), buffer);
DEBUG_MSG_P(PSTR("[RF] Received message '%s'\n"), buffer);
} }
if (action == RF_CODE_LEARN_OK) { if (action == RF_CODE_LEARN_OK) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn success\n"));
DEBUG_MSG_P(PSTR("[RF] Learn success\n"));
rfbStore(_learnId, _learnStatus, buffer); rfbStore(_learnId, _learnStatus, buffer);
// Websocket update // Websocket update
#if WEB_SUPPORT #if WEB_SUPPORT
char wsb[100];
snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%s\"}]}"), _learnId, _learnStatus ? 1 : 0, buffer);
wsSend(wsb);
_rfbWebSocketSendCode(_learnId, _learnStatus, buffer);
#endif #endif
} }
@ -331,7 +354,7 @@ void _rfbDecode() {
bool matched = _rfbMatch(buffer, id, status, buffer); bool matched = _rfbMatch(buffer, id, status, buffer);
if (matched) { if (matched) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Matched message '%s'\n"), buffer);
DEBUG_MSG_P(PSTR("[RF] Matched message '%s'\n"), buffer);
_rfbin = true; _rfbin = true;
if (status == 2) { if (status == 2) {
relayToggle(id); relayToggle(id);
@ -349,18 +372,18 @@ void _rfbDecode() {
} }
void _rfbReceive() { void _rfbReceive() {
#if RFB_DIRECT
#if RFB_DIRECT || RF_SUPPORT
static long learn_start = 0; static long learn_start = 0;
if (!_learning && learn_start) { if (!_learning && learn_start) {
learn_start = 0; learn_start = 0;
} }
if (_learning) { if (_learning) {
if (!learn_start) { if (!learn_start) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] arming learn timeout\n"));
DEBUG_MSG_P(PSTR("[RF] Arming learn timeout\n"));
learn_start = millis(); learn_start = millis();
} }
if (learn_start > 0 && millis() - learn_start > RF_LEARN_TIMEOUT) { if (learn_start > 0 && millis() - learn_start > RF_LEARN_TIMEOUT) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] learn timeout triggered\n"));
DEBUG_MSG_P(PSTR("[RF] Learn timeout triggered\n"));
memset(_uartbuf, 0, sizeof(_uartbuf)); memset(_uartbuf, 0, sizeof(_uartbuf));
_uartbuf[0] = RF_CODE_LEARN_KO; _uartbuf[0] = RF_CODE_LEARN_KO;
_rfbDecode(); _rfbDecode();
@ -374,7 +397,7 @@ void _rfbReceive() {
last = millis(); last = millis();
unsigned long rf_code = _rfModem->getReceivedValue(); unsigned long rf_code = _rfModem->getReceivedValue();
if ( rf_code > 0) { if ( rf_code > 0) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Received code: %08X\n"), rf_code);
DEBUG_MSG_P(PSTR("[RF] Received code: %08X\n"), rf_code);
unsigned int timing = _rfModem->getReceivedDelay(); unsigned int timing = _rfModem->getReceivedDelay();
memset(_uartbuf, 0, sizeof(_uartbuf)); memset(_uartbuf, 0, sizeof(_uartbuf));
unsigned char *msgbuf = _uartbuf + 1; unsigned char *msgbuf = _uartbuf + 1;
@ -401,7 +424,7 @@ void _rfbReceive() {
yield(); yield();
byte c = Serial.read(); byte c = Serial.read();
//DEBUG_MSG_P(PSTR("[RFBRIDGE] Received 0x%02X\n"), c);
//DEBUG_MSG_P(PSTR("[RF] Received 0x%02X\n"), c);
if (receiving) { if (receiving) {
if (c == RF_CODE_STOP && (_uartpos == 1 || _uartpos == RF_MESSAGE_SIZE + 1)) { if (c == RF_CODE_STOP && (_uartpos == 1 || _uartpos == RF_MESSAGE_SIZE + 1)) {
@ -437,7 +460,9 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
char buffer[strlen(MQTT_TOPIC_RFLEARN) + 3]; char buffer[strlen(MQTT_TOPIC_RFLEARN) + 3];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RFLEARN); snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RFLEARN);
mqttSubscribe(buffer); mqttSubscribe(buffer);
#if not RF_SUPPORT
mqttSubscribe(MQTT_TOPIC_RFOUT); mqttSubscribe(MQTT_TOPIC_RFOUT);
#endif
#if RF_RAW_SUPPORT #if RF_RAW_SUPPORT
mqttSubscribe(MQTT_TOPIC_RFRAW); mqttSubscribe(MQTT_TOPIC_RFRAW);
#endif #endif
@ -453,7 +478,7 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
_learnId = t.substring(strlen(MQTT_TOPIC_RFLEARN)+1).toInt(); _learnId = t.substring(strlen(MQTT_TOPIC_RFLEARN)+1).toInt();
if (_learnId >= relayCount()) { if (_learnId >= relayCount()) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Wrong learnID (%d)\n"), _learnId);
DEBUG_MSG_P(PSTR("[RF] Wrong learnID (%d)\n"), _learnId);
return; return;
} }
_learnStatus = (char)payload[0] != '0'; _learnStatus = (char)payload[0] != '0';
@ -462,13 +487,17 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
} }
bool isRFOut = t.equals(MQTT_TOPIC_RFOUT);
#if not RF_SUPPORT
bool isRFOut = t.equals(MQTT_TOPIC_RFOUT);
#endif
#if RF_RAW_SUPPORT #if RF_RAW_SUPPORT
bool isRFRaw = !isRFOut && t.equals(MQTT_TOPIC_RFRAW); bool isRFRaw = !isRFOut && t.equals(MQTT_TOPIC_RFRAW);
#else
#elif not RF_SUPPORT
bool isRFRaw = false; bool isRFRaw = false;
#endif #endif
#if not RF_SUPPORT
if (isRFOut || isRFRaw) { if (isRFOut || isRFRaw) {
// The payload may be a code in HEX format ([0-9A-Z]{18}) or // The payload may be a code in HEX format ([0-9A-Z]{18}) or
@ -512,28 +541,82 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
} }
#endif // not RF_SUPPORT
} }
} }
#endif #endif
#if TERMINAL_SUPPORT
void _rfbInitCommands() {
settingsRegisterCommand(F("LEARN"), [](Embedis* e) {
if (e->argc < 3) {
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;
}
int status = String(e->argv[2]).toInt();
rfbLearn(id, status == 1);
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("FORGET"), [](Embedis* e) {
if (e->argc < 3) {
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;
}
int status = String(e->argv[2]).toInt();
rfbForget(id, status == 1);
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
#endif // TERMINAL_SUPPORT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// PUBLIC // PUBLIC
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void rfbStore(unsigned char id, bool status, const char * code) { void rfbStore(unsigned char id, bool status, const char * code) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Storing %d-%s => '%s'\n"), id, status ? "ON" : "OFF", code);
char key[8] = {0};
DEBUG_MSG_P(PSTR("[RF] Storing %d-%s => '%s'\n"), id, status ? "ON" : "OFF", code);
char key[RF_MAX_KEY_LENGTH] = {0};
snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id); snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
setSetting(key, code); setSetting(key, code);
} }
String rfbRetrieve(unsigned char id, bool status) { String rfbRetrieve(unsigned char id, bool status) {
char key[8] = {0};
char key[RF_MAX_KEY_LENGTH] = {0};
snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id); snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
return getSetting(key); return getSetting(key);
} }
#if not RF_SUPPORT
void rfbStatus(unsigned char id, bool status) { void rfbStatus(unsigned char id, bool status) {
String value = rfbRetrieve(id, status); String value = rfbRetrieve(id, status);
@ -577,6 +660,7 @@ void rfbStatus(unsigned char id, bool status) {
_rfbin = false; _rfbin = false;
} }
#endif // not RF_SUPPORT
void rfbLearn(unsigned char id, bool status) { void rfbLearn(unsigned char id, bool status) {
_learnId = id; _learnId = id;
@ -586,7 +670,7 @@ void rfbLearn(unsigned char id, bool status) {
void rfbForget(unsigned char id, bool status) { void rfbForget(unsigned char id, bool status) {
char key[8] = {0};
char key[RF_MAX_KEY_LENGTH] = {0};
snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id); snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
delSetting(key); delSetting(key);
@ -614,13 +698,22 @@ void rfbSetup() {
wsOnActionRegister(_rfbWebSocketOnAction); wsOnActionRegister(_rfbWebSocketOnAction);
#endif #endif
#if RFB_DIRECT
#if TERMINAL_SUPPORT
_rfbInitCommands();
#endif
#if RFB_DIRECT || RF_SUPPORT
_rfModem = new RCSwitch(); _rfModem = new RCSwitch();
#if RF_SUPPORT
_rfModem->enableReceive(RF_PIN);
DEBUG_MSG_P(PSTR("[RF] RF receiver on GPIO %u\n"), RF_PIN);
#else
_rfModem->enableReceive(RFB_RX_PIN); _rfModem->enableReceive(RFB_RX_PIN);
_rfModem->enableTransmit(RFB_TX_PIN); _rfModem->enableTransmit(RFB_TX_PIN);
_rfModem->setRepeatTransmit(6); _rfModem->setRepeatTransmit(6);
DEBUG_MSG_P(PSTR("[RFBRIDGE] RF receiver on GPIO %u\n"), RFB_RX_PIN);
DEBUG_MSG_P(PSTR("[RFBRIDGE] RF transmitter on GPIO %u\n"), RFB_TX_PIN);
DEBUG_MSG_P(PSTR("[RF] RF receiver on GPIO %u\n"), RFB_RX_PIN);
DEBUG_MSG_P(PSTR("[RF] RF transmitter on GPIO %u\n"), RFB_TX_PIN);
#endif
#endif #endif
// Register loop // Register loop


+ 189
- 19
code/espurna/sensor.ino View File

@ -42,6 +42,10 @@ unsigned char _sensor_temperature_units = SENSOR_TEMPERATURE_UNITS;
double _sensor_temperature_correction = SENSOR_TEMPERATURE_CORRECTION; double _sensor_temperature_correction = SENSOR_TEMPERATURE_CORRECTION;
double _sensor_humidity_correction = SENSOR_HUMIDITY_CORRECTION; double _sensor_humidity_correction = SENSOR_HUMIDITY_CORRECTION;
#if PZEM004T_SUPPORT
PZEM004TSensor *pzem004t_sensor;
#endif
String _sensor_energy_reset_ts = String(); String _sensor_energy_reset_ts = String();
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -52,6 +56,7 @@ unsigned char _magnitudeDecimals(unsigned char type) {
// Hardcoded decimals (these should be linked to the unit, instead of the magnitude) // Hardcoded decimals (these should be linked to the unit, instead of the magnitude)
if (type == MAGNITUDE_ANALOG) return ANALOG_DECIMALS;
if (type == MAGNITUDE_ENERGY || if (type == MAGNITUDE_ENERGY ||
type == MAGNITUDE_ENERGY_DELTA) { type == MAGNITUDE_ENERGY_DELTA) {
if (_sensor_energy_units == ENERGY_KWH) return 3; if (_sensor_energy_units == ENERGY_KWH) return 3;
@ -196,6 +201,13 @@ void _sensorWebSocketStart(JsonObject& root) {
} }
#endif #endif
#if PULSEMETER_SUPPORT
if (sensor->getID() == SENSOR_PULSEMETER_ID) {
root["pmVisible"] = 1;
root["pwrRatioE"] = ((PulseMeterSensor *) sensor)->getEnergyRatio();
}
#endif
} }
if (_magnitudes.size() > 0) { if (_magnitudes.size() > 0) {
@ -272,6 +284,63 @@ void _sensorInitCommands() {
} }
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
#if PZEM004T_SUPPORT
settingsRegisterCommand(F("PZ.ADDRESS"), [](Embedis* e) {
if (e->argc == 1) {
DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
unsigned char dev_count = pzem004t_sensor->getAddressesCount();
for(unsigned char dev = 0; dev < dev_count; dev++) {
DEBUG_MSG_P(PSTR("Device %d/%s\n"), dev, pzem004t_sensor->getAddress(dev).c_str());
}
DEBUG_MSG_P(PSTR("+OK\n"));
} else if(e->argc == 2) {
IPAddress addr;
if (addr.fromString(String(e->argv[1]))) {
if(pzem004t_sensor->setDeviceAddress(&addr)) {
DEBUG_MSG_P(PSTR("+OK\n"));
}
} else {
DEBUG_MSG_P(PSTR("-ERROR: Invalid address argument\n"));
}
} else {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
}
});
settingsRegisterCommand(F("PZ.RESET"), [](Embedis* e) {
if(e->argc > 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
} else {
unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0;
unsigned char limit = e->argc == 2 ? init +1 : pzem004t_sensor->getAddressesCount();
DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
for(unsigned char dev = init; dev < limit; dev++) {
float offset = pzem004t_sensor->resetEnergy(dev);
setSetting("pzEneTotal", dev, offset);
DEBUG_MSG_P(PSTR("Device %d/%s - Offset: %s\n"), dev, pzem004t_sensor->getAddress(dev).c_str(), String(offset).c_str());
}
DEBUG_MSG_P(PSTR("+OK\n"));
}
});
settingsRegisterCommand(F("PZ.VALUE"), [](Embedis* e) {
if(e->argc > 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
} else {
unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0;
unsigned char limit = e->argc == 2 ? init +1 : pzem004t_sensor->getAddressesCount();
DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
for(unsigned char dev = init; dev < limit; dev++) {
DEBUG_MSG_P(PSTR("Device %d/%s - Current: %s Voltage: %s Power: %s Energy: %s\n"), //
dev,
pzem004t_sensor->getAddress(dev).c_str(),
String(pzem004t_sensor->value(dev * PZ_MAGNITUDE_CURRENT_INDEX)).c_str(),
String(pzem004t_sensor->value(dev * PZ_MAGNITUDE_VOLTAGE_INDEX)).c_str(),
String(pzem004t_sensor->value(dev * PZ_MAGNITUDE_POWER_ACTIVE_INDEX)).c_str(),
String(pzem004t_sensor->value(dev * PZ_MAGNITUDE_ENERGY_INDEX)).c_str());
}
DEBUG_MSG_P(PSTR("+OK\n"));
}
});
#endif
} }
#endif #endif
@ -347,6 +416,9 @@ void _sensorLoad() {
AnalogSensor * sensor = new AnalogSensor(); AnalogSensor * sensor = new AnalogSensor();
sensor->setSamples(ANALOG_SAMPLES); sensor->setSamples(ANALOG_SAMPLES);
sensor->setDelay(ANALOG_DELAY); sensor->setDelay(ANALOG_DELAY);
//CICM For analog scaling
sensor->setFactor(ANALOG_FACTOR);
sensor->setOffset(ANALOG_OFFSET);
_sensors.push_back(sensor); _sensors.push_back(sensor);
} }
#endif #endif
@ -360,6 +432,14 @@ void _sensorLoad() {
} }
#endif #endif
#if BMP180_SUPPORT
{
BMP180Sensor * sensor = new BMP180Sensor();
sensor->setAddress(BMP180_ADDRESS);
_sensors.push_back(sensor);
}
#endif
#if BMX280_SUPPORT #if BMX280_SUPPORT
{ {
BMX280Sensor * sensor = new BMX280Sensor(); BMX280Sensor * sensor = new BMX280Sensor();
@ -559,24 +639,6 @@ void _sensorLoad() {
} }
#endif #endif
#if SENSEAIR_SUPPORT
{
SenseAirSensor * sensor = new SenseAirSensor();
sensor->setRX(SENSEAIR_RX_PIN);
sensor->setTX(SENSEAIR_TX_PIN);
_sensors.push_back(sensor);
}
#endif
#if SDS011_SUPPORT
{
SDS011Sensor * sensor = new SDS011Sensor();
sensor->setRX(SDS011_RX_PIN);
sensor->setTX(SDS011_TX_PIN);
_sensors.push_back(sensor);
}
#endif
#if PMSX003_SUPPORT #if PMSX003_SUPPORT
{ {
PMSX003Sensor * sensor = new PMSX003Sensor(); PMSX003Sensor * sensor = new PMSX003Sensor();
@ -591,15 +653,50 @@ void _sensorLoad() {
} }
#endif #endif
#if PULSEMETER_SUPPORT
{
PulseMeterSensor * sensor = new PulseMeterSensor();
sensor->setGPIO(PULSEMETER_PIN);
sensor->setEnergyRatio(PULSEMETER_ENERGY_RATIO);
sensor->setDebounceTime(PULSEMETER_DEBOUNCE);
_sensors.push_back(sensor);
}
#endif
#if PZEM004T_SUPPORT #if PZEM004T_SUPPORT
{ {
PZEM004TSensor * sensor = new PZEM004TSensor();
PZEM004TSensor * sensor = pzem004t_sensor = new PZEM004TSensor();
#if PZEM004T_USE_SOFT #if PZEM004T_USE_SOFT
sensor->setRX(PZEM004T_RX_PIN); sensor->setRX(PZEM004T_RX_PIN);
sensor->setTX(PZEM004T_TX_PIN); sensor->setTX(PZEM004T_TX_PIN);
#else #else
sensor->setSerial(& PZEM004T_HW_PORT); sensor->setSerial(& PZEM004T_HW_PORT);
#endif #endif
sensor->setAddresses(PZEM004T_ADDRESSES);
// Read saved energy offset
unsigned char dev_count = sensor->getAddressesCount();
for(unsigned char dev = 0; dev < dev_count; dev++) {
float value = getSetting("pzEneTotal", dev, 0).toFloat();
if (value > 0) sensor->resetEnergy(dev, value);
}
_sensors.push_back(sensor);
}
#endif
#if SENSEAIR_SUPPORT
{
SenseAirSensor * sensor = new SenseAirSensor();
sensor->setRX(SENSEAIR_RX_PIN);
sensor->setTX(SENSEAIR_TX_PIN);
_sensors.push_back(sensor);
}
#endif
#if SDS011_SUPPORT
{
SDS011Sensor * sensor = new SDS011Sensor();
sensor->setRX(SDS011_RX_PIN);
sensor->setTX(SDS011_TX_PIN);
_sensors.push_back(sensor); _sensors.push_back(sensor);
} }
#endif #endif
@ -637,6 +734,43 @@ void _sensorLoad() {
} }
#endif #endif
#if MAX6675_SUPPORT
{
MAX6675Sensor * sensor = new MAX6675Sensor();
sensor->setCS(MAX6675_CS_PIN);
sensor->setSO(MAX6675_SO_PIN);
sensor->setSCK(MAX6675_SCK_PIN);
_sensors.push_back(sensor);
}
#endif
#if VEML6075_SUPPORT
{
VEML6075Sensor * sensor = new VEML6075Sensor();
sensor->setIntegrationTime(VEML6075_INTEGRATION_TIME);
sensor->setDynamicMode(VEML6075_DYNAMIC_MODE);
_sensors.push_back(sensor);
}
#endif
#if VL53L1X_SUPPORT
{
VL53L1XSensor * sensor = new VL53L1XSensor();
sensor->setInterMeasurementPeriod(VL53L1X_INTER_MEASUREMENT_PERIOD);
sensor->setDistanceMode(VL53L1X_DISTANCE_MODE);
sensor->setMeasurementTimingBudget(VL53L1X_MEASUREMENT_TIMING_BUDGET);
_sensors.push_back(sensor);
}
#endif
#if EZOPH_SUPPORT
{
EZOPHSensor * sensor = new EZOPHSensor();
sensor->setRX(EZOPH_RX_PIN);
sensor->setTX(EZOPH_TX_PIN);
_sensors.push_back(sensor);
}
#endif
} }
void _sensorCallback(unsigned char i, unsigned char type, double value) { void _sensorCallback(unsigned char i, unsigned char type, double value) {
@ -795,6 +929,13 @@ void _sensorInit() {
#endif // CSE7766_SUPPORT #endif // CSE7766_SUPPORT
#if PULSEMETER_SUPPORT
if (_sensors[i]->getID() == SENSOR_PULSEMETER_ID) {
PulseMeterSensor * sensor = (PulseMeterSensor *) _sensors[i];
sensor->setEnergyRatio(getSetting("pwrRatioE", PULSEMETER_ENERGY_RATIO).toInt());
}
#endif // PULSEMETER_SUPPORT
} }
} }
@ -970,6 +1111,35 @@ void _sensorConfigure() {
#endif // CSE7766_SUPPORT #endif // CSE7766_SUPPORT
#if PULSEMETER_SUPPORT
if (_sensors[i]->getID() == SENSOR_PULSEMETER_ID) {
PulseMeterSensor * sensor = (PulseMeterSensor *) _sensors[i];
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
delSetting("eneTotal");
_sensorResetTS();
}
sensor->setEnergyRatio(getSetting("pwrRatioE", PULSEMETER_ENERGY_RATIO).toInt());
}
#endif // PULSEMETER_SUPPORT
#if PZEM004T_SUPPORT
if (_sensors[i]->getID() == SENSOR_PZEM004T_ID) {
PZEM004TSensor * sensor = (PZEM004TSensor *) _sensors[i];
if (getSetting("pwrResetE", 0).toInt() == 1) {
unsigned char dev_count = sensor->getAddressesCount();
for(unsigned char dev = 0; dev < dev_count; dev++) {
sensor->resetEnergy(dev, 0);
delSetting("pzEneTotal", dev);
}
_sensorResetTS();
}
}
#endif // PZEM004T_SUPPORT
} }
// Update filter sizes // Update filter sizes


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

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


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

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

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

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

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

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


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

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


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

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

+ 6
- 6
code/espurna/sensors/PMSX003Sensor.h View File

@ -90,7 +90,7 @@ class PMSX003 {
int avail = _serial->available(); int avail = _serial->available();
#if SENSOR_DEBUG #if SENSOR_DEBUG
//debugSend("[SENSOR] PMS: Packet available = %d\n", avail);
//DEBUG_MSG("[SENSOR] PMS: Packet available = %d\n", avail);
#endif #endif
if (avail < PMS_PACKET_SIZE(data_count)) { if (avail < PMS_PACKET_SIZE(data_count)) {
break; break;
@ -102,7 +102,7 @@ class PMSX003 {
uint16_t size = read16(sum); uint16_t size = read16(sum);
if (size != PMS_PAYLOAD_SIZE(data_count)) { if (size != PMS_PAYLOAD_SIZE(data_count)) {
#if SENSOR_DEBUG #if SENSOR_DEBUG
debugSend(("[SENSOR] PMS: Payload size: %d != %d.\n"), size, PMS_PAYLOAD_SIZE(data_count));
DEBUG_MSG(("[SENSOR] PMS: Payload size: %d != %d.\n"), size, PMS_PAYLOAD_SIZE(data_count));
#endif #endif
break; break;
} }
@ -110,7 +110,7 @@ class PMSX003 {
for (int i = 0; i < data_count; i++) { for (int i = 0; i < data_count; i++) {
data[i] = read16(sum); data[i] = read16(sum);
#if SENSOR_DEBUG #if SENSOR_DEBUG
//debugSend(("[SENSOR] PMS: data[%d] = %d\n"), i, data[i]);
//DEBUG_MSG(("[SENSOR] PMS: data[%d] = %d\n"), i, data[i]);
#endif #endif
} }
@ -119,7 +119,7 @@ class PMSX003 {
return true; return true;
} else { } else {
#if SENSOR_DEBUG #if SENSOR_DEBUG
debugSend(("[SENSOR] PMS checksum: %04X != %04X\n"), sum, checksum);
DEBUG_MSG(("[SENSOR] PMS checksum: %04X != %04X\n"), sum, checksum);
#endif #endif
} }
break; break;
@ -282,7 +282,7 @@ class PMSX003Sensor : public BaseSensor, PMSX003 {
readCycle = _readCount % 30; readCycle = _readCount % 30;
if (readCycle == 0) { if (readCycle == 0) {
#if SENSOR_DEBUG #if SENSOR_DEBUG
debugSend("[SENSOR] %s: Wake up: %d\n", pms_specs[_type].name, _readCount);
DEBUG_MSG("[SENSOR] %s: Wake up: %d\n", pms_specs[_type].name, _readCount);
#endif #endif
wakeUp(); wakeUp();
return; return;
@ -321,7 +321,7 @@ class PMSX003Sensor : public BaseSensor, PMSX003 {
if (readCycle == 6) { if (readCycle == 6) {
sleep(); sleep();
#if SENSOR_DEBUG #if SENSOR_DEBUG
debugSend("[SENSOR] %s: Enter sleep mode: %d\n", pms_specs[_type].name, _readCount);
DEBUG_MSG("[SENSOR] %s: Enter sleep mode: %d\n", pms_specs[_type].name, _readCount);
#endif #endif
return; return;
} }


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

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


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

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

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

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

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

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

+ 10
- 19
code/espurna/settings.ino View File

@ -22,8 +22,6 @@ EmbedisWrap embedis(_serial, TERMINAL_BUFFER_SIZE);
#endif // SERIAL_RX_ENABLED #endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT #endif // TERMINAL_SUPPORT
bool _settings_save = false;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Reverse engineering EEPROM storage format // Reverse engineering EEPROM storage format
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -175,8 +173,8 @@ void _settingsInitCommands() {
#if DEBUG_SUPPORT #if DEBUG_SUPPORT
settingsRegisterCommand(F("CRASH"), [](Embedis* e) { settingsRegisterCommand(F("CRASH"), [](Embedis* e) {
debugDumpCrashInfo();
debugClearCrashInfo();
crashDump();
crashClear();
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
#endif #endif
@ -189,6 +187,7 @@ void _settingsInitCommands() {
settingsRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) { settingsRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
resetReason(CUSTOM_RESET_TERMINAL); resetReason(CUSTOM_RESET_TERMINAL);
_eepromCommit();
ESP.eraseConfig(); ESP.eraseConfig();
*((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494 *((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494
}); });
@ -275,12 +274,10 @@ void _settingsInitCommands() {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
#if WEB_SUPPORT
settingsRegisterCommand(F("RELOAD"), [](Embedis* e) {
espurnaReload();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("RELOAD"), [](Embedis* e) {
espurnaReload();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("RESET"), [](Embedis* e) { settingsRegisterCommand(F("RESET"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
@ -310,7 +307,7 @@ void _settingsInitCommands() {
#if not SETTINGS_AUTOSAVE #if not SETTINGS_AUTOSAVE
settingsRegisterCommand(F("SAVE"), [](Embedis* e) { settingsRegisterCommand(F("SAVE"), [](Embedis* e) {
_settings_save = true;
eepromCommit();
DEBUG_MSG_P(PSTR("\n+OK\n")); DEBUG_MSG_P(PSTR("\n+OK\n"));
}); });
#endif #endif
@ -367,7 +364,7 @@ bool hasSetting(const String& key, unsigned int index) {
void saveSettings() { void saveSettings() {
#if not SETTINGS_AUTOSAVE #if not SETTINGS_AUTOSAVE
_settings_save = true;
eepromCommit();
#endif #endif
} }
@ -464,7 +461,7 @@ void settingsSetup() {
[](size_t pos) -> char { return EEPROMr.read(pos); }, [](size_t pos) -> char { return EEPROMr.read(pos); },
[](size_t pos, char value) { EEPROMr.write(pos, value); }, [](size_t pos, char value) { EEPROMr.write(pos, value); },
#if SETTINGS_AUTOSAVE #if SETTINGS_AUTOSAVE
[]() { _settings_save = true; }
[]() { eepromCommit(); }
#else #else
[]() {} []() {}
#endif #endif
@ -485,12 +482,6 @@ void settingsSetup() {
void settingsLoop() { void settingsLoop() {
if (_settings_save) {
EEPROMr.commit();
_settings_save = false;
}
#if TERMINAL_SUPPORT #if TERMINAL_SUPPORT
#if DEBUG_SERIAL_SUPPORT #if DEBUG_SERIAL_SUPPORT


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


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


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


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


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


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


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


+ 9
- 1
code/espurna/system.ino View File

@ -42,7 +42,7 @@ void systemCheck(bool stable) {
} }
} }
EEPROMr.write(EEPROM_CRASH_COUNTER, value); EEPROMr.write(EEPROM_CRASH_COUNTER, value);
EEPROMr.commit();
eepromCommit();
} }
bool systemCheck() { bool systemCheck() {
@ -77,6 +77,14 @@ unsigned long systemLoadAverage() {
void systemLoop() { void systemLoop() {
// -------------------------------------------------------------------------
// User requested reset
// -------------------------------------------------------------------------
if (checkNeedsReset()) {
reset();
}
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Check system stability // Check system stability
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------


+ 40
- 30
code/espurna/telnet.ino View File

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


+ 11
- 4
code/espurna/thinkspeak.ino View File

@ -25,6 +25,8 @@ const char THINGSPEAK_REQUEST_TEMPLATE[] PROGMEM =
"%s\r\n"; "%s\r\n";
bool _tspk_enabled = false; bool _tspk_enabled = false;
bool _tspk_clear = false;
char * _tspk_queue[THINGSPEAK_FIELDS] = {NULL}; char * _tspk_queue[THINGSPEAK_FIELDS] = {NULL};
bool _tspk_flush = false; bool _tspk_flush = false;
@ -45,6 +47,7 @@ void _tspkWebSocketOnSend(JsonObject& root) {
root["tspkEnabled"] = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1; root["tspkEnabled"] = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
root["tspkKey"] = getSetting("tspkKey"); root["tspkKey"] = getSetting("tspkKey");
root["tspkClear"] = getSetting("tspkClear", THINGSPEAK_CLEAR_CACHE).toInt() == 1;
JsonArray& relays = root.createNestedArray("tspkRelays"); JsonArray& relays = root.createNestedArray("tspkRelays");
for (byte i=0; i<relayCount(); i++) { for (byte i=0; i<relayCount(); i++) {
@ -71,6 +74,7 @@ void _tspkWebSocketOnSend(JsonObject& root) {
#endif #endif
void _tspkConfigure() { void _tspkConfigure() {
_tspk_clear = getSetting("tspkClear", THINGSPEAK_CLEAR_CACHE).toInt() == 1;
_tspk_enabled = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1; _tspk_enabled = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
if (_tspk_enabled && (getSetting("tspkKey").length() == 0)) { if (_tspk_enabled && (getSetting("tspkKey").length() == 0)) {
_tspk_enabled = false; _tspk_enabled = false;
@ -221,10 +225,12 @@ void _tspkEnqueue(unsigned char index, char * payload) {
} }
void _tspkClearQueue() { void _tspkClearQueue() {
for (unsigned char id=0; id<THINGSPEAK_FIELDS; id++) {
if (_tspk_queue[id] != NULL) {
free(_tspk_queue[id]);
_tspk_queue[id] = NULL;
if (_tspk_clear) {
for (unsigned char id=0; id<THINGSPEAK_FIELDS; id++) {
if (_tspk_queue[id] != NULL) {
free(_tspk_queue[id]);
_tspk_queue[id] = NULL;
}
} }
} }
} }
@ -250,6 +256,7 @@ void _tspkFlush() {
} }
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
bool tspkEnqueueRelay(unsigned char index, unsigned char status) { bool tspkEnqueueRelay(unsigned char index, unsigned char status) {


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

@ -9,6 +9,8 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <Ticker.h> #include <Ticker.h>
Ticker _defer_reset; Ticker _defer_reset;
uint8_t _reset_reason = 0;
String getIdentifier() { String getIdentifier() {
char buffer[20]; char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%s-%06X"), APP_NAME, ESP.getChipId()); snprintf_P(buffer, sizeof(buffer), PSTR("%s-%06X"), APP_NAME, ESP.getChipId());
@ -135,6 +137,59 @@ unsigned long getUptime() {
#if HEARTBEAT_MODE != HEARTBEAT_NONE #if HEARTBEAT_MODE != HEARTBEAT_NONE
// -----------------------------------------------------------------------------
// Heartbeat helper
// -----------------------------------------------------------------------------
namespace Heartbeat {
enum Report : uint32_t {
Status = 1 << 1,
Ssid = 1 << 2,
Ip = 1 << 3,
Mac = 1 << 4,
Rssi = 1 << 5,
Uptime = 1 << 6,
Datetime = 1 << 7,
Freeheap = 1 << 8,
Vcc = 1 << 9,
Relay = 1 << 10,
Light = 1 << 11,
Hostname = 1 << 12,
App = 1 << 13,
Version = 1 << 14,
Board = 1 << 15,
Loadavg = 1 << 16,
Interval = 1 << 17
};
constexpr uint32_t defaultValue() {
return (Status * (HEARTBEAT_REPORT_STATUS)) | \
(Ssid * (HEARTBEAT_REPORT_SSID)) | \
(Ip * (HEARTBEAT_REPORT_IP)) | \
(Mac * (HEARTBEAT_REPORT_MAC)) | \
(Rssi * (HEARTBEAT_REPORT_RSSI)) | \
(Uptime * (HEARTBEAT_REPORT_UPTIME)) | \
(Datetime * (HEARTBEAT_REPORT_DATETIME)) | \
(Freeheap * (HEARTBEAT_REPORT_FREEHEAP)) | \
(Vcc * (HEARTBEAT_REPORT_VCC)) | \
(Relay * (HEARTBEAT_REPORT_RELAY)) | \
(Light * (HEARTBEAT_REPORT_LIGHT)) | \
(Hostname * (HEARTBEAT_REPORT_HOSTNAME)) | \
(App * (HEARTBEAT_REPORT_APP)) | \
(Version * (HEARTBEAT_REPORT_VERSION)) | \
(Board * (HEARTBEAT_REPORT_BOARD)) | \
(Loadavg * (HEARTBEAT_REPORT_LOADAVG)) | \
(Interval * (HEARTBEAT_REPORT_INTERVAL));
}
uint32_t currentValue() {
const String cfg = getSetting("hbReport");
if (!cfg.length()) return defaultValue();
return strtoul(cfg.c_str(), NULL, 10);
}
}
void heartbeat() { void heartbeat() {
unsigned long uptime_seconds = getUptime(); unsigned long uptime_seconds = getUptime();
@ -161,62 +216,70 @@ void heartbeat() {
#endif #endif
} }
const uint32_t hb_cfg = Heartbeat::currentValue();
if (!hb_cfg) return;
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// MQTT // MQTT
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
#if MQTT_SUPPORT #if MQTT_SUPPORT
if (!serial) { if (!serial) {
#if (HEARTBEAT_REPORT_INTERVAL)
mqttSend(MQTT_TOPIC_INTERVAL, HEARTBEAT_INTERVAL / 1000);
#endif
#if (HEARTBEAT_REPORT_APP)
if (hb_cfg & Heartbeat::Interval)
mqttSend(MQTT_TOPIC_INTERVAL, String(HEARTBEAT_INTERVAL / 1000).c_str());
if (hb_cfg & Heartbeat::App)
mqttSend(MQTT_TOPIC_APP, APP_NAME); mqttSend(MQTT_TOPIC_APP, APP_NAME);
#endif
#if (HEARTBEAT_REPORT_VERSION)
if (hb_cfg & Heartbeat::Version)
mqttSend(MQTT_TOPIC_VERSION, APP_VERSION); mqttSend(MQTT_TOPIC_VERSION, APP_VERSION);
#endif
#if (HEARTBEAT_REPORT_BOARD)
if (hb_cfg & Heartbeat::Board)
mqttSend(MQTT_TOPIC_BOARD, getBoardName().c_str()); mqttSend(MQTT_TOPIC_BOARD, getBoardName().c_str());
#endif
#if (HEARTBEAT_REPORT_HOSTNAME)
mqttSend(MQTT_TOPIC_HOSTNAME, getSetting("hostname").c_str());
#endif
#if (HEARTBEAT_REPORT_IP)
if (hb_cfg & Heartbeat::Hostname)
mqttSend(MQTT_TOPIC_HOSTNAME, getSetting("hostname", getIdentifier()).c_str());
if (hb_cfg & Heartbeat::Ssid)
mqttSend(MQTT_TOPIC_SSID, WiFi.SSID().c_str());
if (hb_cfg & Heartbeat::Ip)
mqttSend(MQTT_TOPIC_IP, getIP().c_str()); mqttSend(MQTT_TOPIC_IP, getIP().c_str());
#endif
#if (HEARTBEAT_REPORT_MAC)
if (hb_cfg & Heartbeat::Mac)
mqttSend(MQTT_TOPIC_MAC, WiFi.macAddress().c_str()); mqttSend(MQTT_TOPIC_MAC, WiFi.macAddress().c_str());
#endif
#if (HEARTBEAT_REPORT_RSSI)
if (hb_cfg & Heartbeat::Rssi)
mqttSend(MQTT_TOPIC_RSSI, String(WiFi.RSSI()).c_str()); mqttSend(MQTT_TOPIC_RSSI, String(WiFi.RSSI()).c_str());
#endif
#if (HEARTBEAT_REPORT_UPTIME)
if (hb_cfg & Heartbeat::Uptime)
mqttSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str()); mqttSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str());
#if NTP_SUPPORT
if ((hb_cfg & Heartbeat::Datetime) && (ntpSynced()))
mqttSend(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
#endif #endif
#if (HEARTBEAT_REPORT_DATETIME) && (NTP_SUPPORT)
if (ntpSynced()) mqttSend(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
#endif
#if (HEARTBEAT_REPORT_FREEHEAP)
if (hb_cfg & Heartbeat::Freeheap)
mqttSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str()); mqttSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str());
#endif
#if (HEARTBEAT_REPORT_RELAY)
if (hb_cfg & Heartbeat::Relay)
relayMQTT(); relayMQTT();
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE)
if (hb_cfg & Heartbeat::Light)
lightMQTT();
#endif #endif
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) & (HEARTBEAT_REPORT_LIGHT)
lightMQTT();
#endif
#if (HEARTBEAT_REPORT_VCC)
#if ADC_MODE_VALUE == ADC_VCC
if ((hb_cfg & Heartbeat::Vcc) && (ADC_MODE_VALUE == ADC_VCC))
mqttSend(MQTT_TOPIC_VCC, String(ESP.getVcc()).c_str()); mqttSend(MQTT_TOPIC_VCC, String(ESP.getVcc()).c_str());
#endif
#endif
#if (HEARTBEAT_REPORT_STATUS)
if (hb_cfg & Heartbeat::Status)
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true); mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
#endif
#if (LOADAVG_REPORT)
if (hb_cfg & Heartbeat::Loadavg)
mqttSend(MQTT_TOPIC_LOADAVG, String(systemLoadAverage()).c_str()); mqttSend(MQTT_TOPIC_LOADAVG, String(systemLoadAverage()).c_str());
#endif
} }
#endif #endif
@ -225,12 +288,14 @@ void heartbeat() {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
#if INFLUXDB_SUPPORT #if INFLUXDB_SUPPORT
#if (HEARTBEAT_REPORT_UPTIME)
if (hb_cfg & Heartbeat::Uptime)
idbSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str()); idbSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str());
#endif
#if (HEARTBEAT_REPORT_FREEHEAP)
if (hb_cfg & Heartbeat::Freeheap)
idbSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str()); idbSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str());
#endif
if (hb_cfg & Heartbeat::Rssi)
idbSend(MQTT_TOPIC_RSSI, String(WiFi.RSSI()).c_str());
#endif #endif
} }
@ -347,8 +412,7 @@ void info() {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[MAIN] EEPROM sectors: %s\n"), (char *) eepromSectors().c_str());
DEBUG_MSG_P(PSTR("[MAIN] EEPROM current: %lu\n"), eepromCurrent());
eepromSectorsDebug();
DEBUG_MSG_P(PSTR("\n")); DEBUG_MSG_P(PSTR("\n"));
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -464,8 +528,9 @@ unsigned char resetReason() {
} }
void resetReason(unsigned char reason) { void resetReason(unsigned char reason) {
_reset_reason = reason;
EEPROMr.write(EEPROM_CUSTOM_RESET, reason); EEPROMr.write(EEPROM_CUSTOM_RESET, reason);
EEPROMr.commit();
eepromCommit();
} }
void reset() { void reset() {
@ -473,8 +538,11 @@ void reset() {
} }
void deferredReset(unsigned long delay, unsigned char reason) { void deferredReset(unsigned long delay, unsigned char reason) {
resetReason(reason);
_defer_reset.once_ms(delay, reset);
_defer_reset.once_ms(delay, resetReason, reason);
}
bool checkNeedsReset() {
return _reset_reason > 0;
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


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

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


+ 5
- 6
code/gulpfile.js View File

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

+ 10
- 7
code/html/custom.js View File

@ -47,7 +47,8 @@ function sensorName(id) {
"Events", "PMSX003", "BMX280", "MHZ19", "SI7021", "Events", "PMSX003", "BMX280", "MHZ19", "SI7021",
"SHT3X I2C", "BH1750", "PZEM004T", "AM2320 I2C", "GUVAS12SD", "SHT3X I2C", "BH1750", "PZEM004T", "AM2320 I2C", "GUVAS12SD",
"TMP3X", "Sonar", "SenseAir", "GeigerTicks", "GeigerCPM", "TMP3X", "Sonar", "SenseAir", "GeigerTicks", "GeigerCPM",
"NTC", "SDS011", "MICS2710", "MICS5525"
"NTC", "SDS011", "MICS2710", "MICS5525", "VL53L1X", "VEML6075",
"EZOPH"
]; ];
if (1 <= id && id <= names.length) { if (1 <= id && id <= names.length) {
return names[id - 1]; return names[id - 1];
@ -61,10 +62,10 @@ function magnitudeType(type) {
"Current", "Voltage", "Active Power", "Apparent Power", "Current", "Voltage", "Active Power", "Apparent Power",
"Reactive Power", "Power Factor", "Energy", "Energy (delta)", "Reactive Power", "Power Factor", "Energy", "Energy (delta)",
"Analog", "Digital", "Event", "Analog", "Digital", "Event",
"PM1.0", "PM2.5", "PM10", "CO2", "Lux", "UV", "Distance" , "HCHO",
"PM1.0", "PM2.5", "PM10", "CO2", "Lux", "UVA", "UVB", "UV Index", "Distance" , "HCHO",
"Local Dose Rate", "Local Dose Rate", "Local Dose Rate", "Local Dose Rate",
"Count", "Count",
"NO2", "CO", "Resistance"
"NO2", "CO", "Resistance", "pH"
]; ];
if (1 <= type && type <= types.length) { if (1 <= type && type <= types.length) {
return types[type - 1]; return types[type - 1];
@ -933,15 +934,16 @@ function createCheckboxes() {
function initRelayConfig(data) { function initRelayConfig(data) {
var current = $("#relayConfig > div").length;
if (current > 0) { return; }
var current = $("#relayConfig > div").length / 6; // there are 6 divs per each relay
var template = $("#relayConfigTemplate").children(); var template = $("#relayConfigTemplate").children();
for (var i in data) { for (var i in data) {
var relay = data[i]; var relay = data[i];
if (current > relay.id) continue;
var line = $(template).clone(); var line = $(template).clone();
$("span.gpio", line).html(relay.gpio); $("span.gpio", line).html(relay.gpio);
$("span.id", line).html(i);
$("span.id", line).html(relay.id);
$("select[name='relayBoot']", line).val(relay.boot); $("select[name='relayBoot']", line).val(relay.boot);
$("select[name='relayPulse']", line).val(relay.pulse); $("select[name='relayPulse']", line).val(relay.pulse);
$("input[name='relayTime']", line).val(relay.pulse_ms); $("input[name='relayTime']", line).val(relay.pulse_ms);
@ -949,6 +951,7 @@ function initRelayConfig(data) {
$("select[name='mqttGroupInv']", line).val(relay.group_inv); $("select[name='mqttGroupInv']", line).val(relay.group_inv);
$("select[name='relayOnDisc']", line).val(relay.on_disc); $("select[name='relayOnDisc']", line).val(relay.on_disc);
line.appendTo("#relayConfig"); line.appendTo("#relayConfig");
} }
} }


+ 67
- 17
code/html/index.html View File

@ -133,6 +133,10 @@
<a href="#" class="pure-menu-link" data="panel-mqtt">MQTT</a> <a href="#" class="pure-menu-link" data="panel-mqtt">MQTT</a>
</li> </li>
<li class="pure-menu-item module module-nofuss">
<a href="#" class="pure-menu-link" data="panel-nofuss">NOFUSS</a>
</li>
<li class="pure-menu-item module module-ntp"> <li class="pure-menu-item module module-ntp">
<a href="#" class="pure-menu-link" data="panel-ntp">NTP</a> <a href="#" class="pure-menu-link" data="panel-ntp">NTP</a>
</li> </li>
@ -614,17 +618,12 @@
<div class="pure-u-1 pure-u-lg-3-4 hint">Turn ON to be able to telnet to your device while connected to your home router.<br />TELNET is always enabled in AP mode.</div> <div class="pure-u-1 pure-u-lg-3-4 hint">Turn ON to be able to telnet to your device while connected to your home router.<br />TELNET is always enabled in AP mode.</div>
</div> </div>
<div class="pure-g module module-nofuss">
<label class="pure-u-1 pure-u-lg-1-4">Automatic remote updates (NoFUSS)</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="nofussEnabled" /></div>
</div>
<div class="pure-g module module-nofuss">
<label class="pure-u-1 pure-u-lg-1-4">NoFUSS server</label>
<input name="nofussServer" class="pure-u-1 pure-u-lg-3-4" type="text" tabindex="15" />
<div class="pure-g module module-telnet">
<label class="pure-u-1 pure-u-lg-1-4">TELNET Password</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="telnetAuth" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">This name address of the NoFUSS server for automatic remote updates (see https://bitbucket.org/xoseperez/nofuss).</div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Request password when starting telnet session</div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
@ -821,6 +820,11 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">MQTT User</label> <label class="pure-u-1 pure-u-lg-1-4">MQTT User</label>
<input class="pure-u-1 pure-u-lg-1-4" name="mqttUser" type="text" tabindex="23" placeholder="Leave blank if no user" autocomplete="off" /> <input class="pure-u-1 pure-u-lg-1-4" name="mqttUser" type="text" tabindex="23" placeholder="Leave blank if no user" autocomplete="off" />
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
You can use the following placeholders: {hostname}, {mac}
</div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
@ -835,7 +839,7 @@
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"> <div class="pure-u-1 pure-u-lg-3-4 hint">
If left empty the firmware will generate a client ID based on the serial number of the chip.
If left empty the firmware will generate a client ID based on the serial number of the chip. You can use the following placeholders: {hostname}, {mac}
</div> </div>
</div> </div>
@ -913,6 +917,33 @@
</div> </div>
</form> </form>
<form id="form-nofuss" class="pure-form form-settings">
<div class="panel" id="panel-nofuss">
<div class="header">
<h1>NoFUSS</h1>
<h2>Automatically upgrade the firmware (see <a href="https://bitbucket.org/xoseperez/nofuss">xoseperez/nofuss</a> for details)</h2>
</div>
<div class="page">
<fieldset>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Enable</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="nofussEnabled" /></div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Server</label>
<input name="nofussServer" class="pure-u-1 pure-u-lg-3-4" type="text" tabindex="15" />
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Address of the NoFUSS server</div>
</div>
</fieldset>
</div>
</div>
</form>
<form id="form-ntp" class="pure-form form-settings"> <form id="form-ntp" class="pure-form form-settings">
<div class="panel" id="panel-ntp"> <div class="panel" id="panel-ntp">
@ -1085,9 +1116,21 @@
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="tspkEnabled" tabindex="30" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="tspkEnabled" tabindex="30" /></div>
</div> </div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Clear cache</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="tspkClear" tabindex="31" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
With every POST to thinkspeak.com only enqueued fields are sent.
If you select to clear the cache after every sending this will result in only those fields that have changed will be posted.
If you want all fields to be sent with every POST do not clear the cache.
</div>
</div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Thingspeak API Key</label> <label class="pure-u-1 pure-u-lg-1-4">Thingspeak API Key</label>
<input class="pure-u-1 pure-u-lg-3-4" name="tspkKey" type="text" tabindex="31" />
<input class="pure-u-1 pure-u-lg-3-4" name="tspkKey" type="text" tabindex="32" />
</div> </div>
<legend>Sensors &amp; actuators</legend> <legend>Sensors &amp; actuators</legend>
@ -1335,20 +1378,27 @@
<label class="pure-u-1 pure-u-lg-1-4">Expected Power</label> <label class="pure-u-1 pure-u-lg-1-4">Expected Power</label>
<input class="pure-u-1 pure-u-lg-3-4 pwrExpected" name="pwrExpectedP" type="text" tabindex="54" placeholder="0" /> <input class="pure-u-1 pure-u-lg-3-4 pwrExpected" name="pwrExpectedP" type="text" tabindex="54" placeholder="0" />
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">In Watts (W). Calibrate your sensor connecting a pure resistive load (like a bulb) and enter here the its nominal power or use a multimeter.</div>
<div class="pure-u-1 pure-u-lg-3-4 hint">In Watts (W). Calibrate your sensor connecting a pure resistive load (like a bulb) and enter here its nominal power or use a multimeter.</div>
</div>
<div class="pure-g module module-pm">
<label class="pure-u-1 pure-u-lg-1-4">Energy Ratio</label>
<input class="pure-u-1 pure-u-lg-3-4" name="pwrRatioE" type="text" tabindex="55" placeholder="0" />
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Energy ratio in pulses/kWh.</div>
</div> </div>
<div class="pure-g module module-hlw module-cse module-emon"> <div class="pure-g module module-hlw module-cse module-emon">
<label class="pure-u-1 pure-u-lg-1-4">Reset calibration</label> <label class="pure-u-1 pure-u-lg-1-4">Reset calibration</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="pwrResetCalibration" tabindex="55" /></div>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="pwrResetCalibration" tabindex="56" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Move this switch to ON and press "Save" to revert to factory calibration values.</div> <div class="pure-u-1 pure-u-lg-3-4 hint">Move this switch to ON and press "Save" to revert to factory calibration values.</div>
</div> </div>
<div class="pure-g module module-hlw module-cse module-emon">
<div class="pure-g module module-hlw module-cse module-emon module-pm">
<label class="pure-u-1 pure-u-lg-1-4">Reset energy</label> <label class="pure-u-1 pure-u-lg-1-4">Reset energy</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="pwrResetE" tabindex="56" /></div>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="pwrResetE" tabindex="57" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Move this switch to ON and press "Save" to set energy count to 0.</div> <div class="pure-u-1 pure-u-lg-3-4 hint">Move this switch to ON and press "Save" to set energy count to 0.</div>
@ -1564,7 +1614,7 @@
</div> </div>
<div id="relayConfigTemplate" class="template"> <div id="relayConfigTemplate" class="template">
<legend>Switch #<span class="id"></span> (GPIO<span class="gpio"></span>)</legend>
<legend>Switch #<span class="id"></span> (<span class="gpio"></span>)</legend>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Boot mode</label></div> <div class="pure-u-1 pure-u-lg-1-4"><label>Boot mode</label></div>
<select class="pure-u-1 pure-u-lg-3-4" name="relayBoot"> <select class="pure-u-1 pure-u-lg-3-4" name="relayBoot">


+ 6
- 6
code/ota.py View File

@ -55,9 +55,9 @@ def on_service_state_change(zeroconf, service_type, name, state_change):
'app_name': '', 'app_name': '',
'app_version': '', 'app_version': '',
'target_board': '', 'target_board': '',
'mem_size': '',
'sdk_size': '',
'free_space': '',
'mem_size': 0,
'sdk_size': 0,
'free_space': 0,
} }
for key, item in info.properties.items(): for key, item in info.properties.items():
@ -101,9 +101,9 @@ def list_devices():
device.get('app_name', ''), device.get('app_name', ''),
device.get('app_version', ''), device.get('app_version', ''),
device.get('target_board', ''), device.get('target_board', ''),
device.get('mem_size', ''),
device.get('sdk_size', ''),
device.get('free_space', ''),
device.get('mem_size', 0),
device.get('sdk_size', 0),
device.get('free_space', 0),
)) ))
print() print()


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


+ 2
- 5
code/package.json View File

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

+ 293
- 14
code/platformio.ini View File

@ -7,19 +7,20 @@ data_dir = espurna/data
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# PLATFORM: # PLATFORM:
# !! DO NOT confuse platformio's ESP8266 development platform with Arduino core for ESP8266 # !! DO NOT confuse platformio's ESP8266 development platform with Arduino core for ESP8266
# We use platformIO 1.5.0 as default
# We use Arduino Core 2.3.0 (platformIO 1.5.0) as default
# #
# platformIO 1.5.0 = arduino core 2.3.0
# platformIO 1.6.0 = arduino core 2.4.0
# platformIO 1.7.3 = arduino core 2.4.1
# platformIO 1.8.0 = arduino core 2.4.2
# arduino core 2.3.0 = platformIO 1.5.0
# arduino core 2.4.0 = platformIO 1.6.0
# arduino core 2.4.1 = platformIO 1.7.3
# arduino core 2.4.2 = platformIO 1.8.0
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
platform_150 = espressif8266@1.5.0
platform_160 = espressif8266@1.6.0
platform_173 = espressif8266@1.7.3
platform_180 = espressif8266@1.8.0
platform_latest = ${common.platform_180}
platform = ${common.platform_150}
arduino_core_2_3_0 = espressif8266@1.5.0
arduino_core_2_4_0 = espressif8266@1.6.0
arduino_core_2_4_1 = espressif8266@1.7.3
arduino_core_2_4_2 = espressif8266@1.8.0
platform = ${common.arduino_core_2_3_0}
platform_latest = ${common.arduino_core_2_4_2}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# FLAGS: DEBUG # FLAGS: DEBUG
@ -33,6 +34,7 @@ debug_flags = -DDEBUG_ESP_CORE -DDEBUG_ESP_SSL -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP
# build flags for 512k/1m ESP's are different because we use Linker Scripts to adjust flash split # build flags for 512k/1m ESP's are different because we use Linker Scripts to adjust flash split
# build_flags_512k ( 512 KB) = 487 KB sketch, 4 KB eeprom, 16 KB reserved # build_flags_512k ( 512 KB) = 487 KB sketch, 4 KB eeprom, 16 KB reserved
# build_flags_1m0m (1024 KB) = 999 KB sketch, 4 KB eeprom, 16 KB reserved # build_flags_1m0m (1024 KB) = 999 KB sketch, 4 KB eeprom, 16 KB reserved
# build_flags_2m1m (2048 KB) = 1019 KB sketch, 16 KB eeprom, 992 KB spiffs, 16 KB reserved
# build_flags_4m1m (4096 KB) = 1019 KB sketch, 16 KB eeprom, 992 KB spiffs, 16 KB reserved, 2048 KB empty/ota? # build_flags_4m1m (4096 KB) = 1019 KB sketch, 16 KB eeprom, 992 KB spiffs, 16 KB reserved, 2048 KB empty/ota?
# build_flags_4m3m (4096 KB) = 1019 KB sketch, 16 KB eeprom, 3040 KB spiffs, 16 KB reserved # build_flags_4m3m (4096 KB) = 1019 KB sketch, 16 KB eeprom, 3040 KB spiffs, 16 KB reserved
# #
@ -41,7 +43,7 @@ debug_flags = -DDEBUG_ESP_CORE -DDEBUG_ESP_SSL -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP
# -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY = v2 Lower Memory # -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY = v2 Lower Memory
# -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH = v2 Higher Bandwidth # -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH = v2 Higher Bandwidth
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
build_flags = -g -w -DMQTT_MAX_PACKET_SIZE=400 -DNO_GLOBAL_EEPROM ${sysenv.ESPURNA_FLAGS} -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH
build_flags = -g -w -DMQTT_MAX_PACKET_SIZE=400 -DNO_GLOBAL_EEPROM ${sysenv.ESPURNA_FLAGS} -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH
build_flags_512k = ${common.build_flags} -Wl,-Teagle.flash.512k0m1s.ld build_flags_512k = ${common.build_flags} -Wl,-Teagle.flash.512k0m1s.ld
build_flags_1m0m = ${common.build_flags} -Wl,-Teagle.flash.1m0m1s.ld build_flags_1m0m = ${common.build_flags} -Wl,-Teagle.flash.1m0m1s.ld
build_flags_2m1m = ${common.build_flags} -Wl,-Teagle.flash.2m1m4s.ld build_flags_2m1m = ${common.build_flags} -Wl,-Teagle.flash.2m1m4s.ld
@ -76,13 +78,13 @@ lib_deps =
https://github.com/marvinroger/async-mqtt-client#v0.8.1 https://github.com/marvinroger/async-mqtt-client#v0.8.1
Brzo I2C Brzo I2C
https://github.com/xoseperez/debounceevent.git#2.0.4 https://github.com/xoseperez/debounceevent.git#2.0.4
https://github.com/xoseperez/eeprom_rotate#0.9.1
https://github.com/xoseperez/eeprom_rotate#0.9.2
Embedis Embedis
Encoder Encoder
https://github.com/plerup/espsoftwareserial#3.4.1 https://github.com/plerup/espsoftwareserial#3.4.1
https://github.com/me-no-dev/ESPAsyncTCP#55cd520 https://github.com/me-no-dev/ESPAsyncTCP#55cd520
https://github.com/me-no-dev/ESPAsyncWebServer#05306e4 https://github.com/me-no-dev/ESPAsyncWebServer#05306e4
https://bitbucket.org/xoseperez/fauxmoesp.git#3.0.1
https://bitbucket.org/xoseperez/fauxmoesp.git#3.1.0
https://github.com/xoseperez/hlw8012.git#1.1.0 https://github.com/xoseperez/hlw8012.git#1.1.0
https://github.com/markszabo/IRremoteESP8266#v2.2.0 https://github.com/markszabo/IRremoteESP8266#v2.2.0
https://github.com/xoseperez/justwifi.git#2.0.2 https://github.com/xoseperez/justwifi.git#2.0.2
@ -97,6 +99,9 @@ lib_deps =
https://github.com/LowPowerLab/RFM69#1.1.3 https://github.com/LowPowerLab/RFM69#1.1.3
https://github.com/xoseperez/Time https://github.com/xoseperez/Time
NewPing NewPing
https://github.com/sparkfun/SparkFun_VEML6075_Arduino_Library#V_1.0.3
https://github.com/pololu/vl53l1x-arduino#1.0.1
https://github.com/mcleng/MAX6675-Library#2.0.1
lib_ignore = lib_ignore =
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -1274,6 +1279,54 @@ upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:magichome-zj-wfmn-a-11]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DMAGICHOME_ZJ_WFMN_A_11
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:magichome-zj-wfmn-a-11-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DMAGICHOME_ZJ_WFMN_A_11
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:magichome-zj-wfmn-b-11]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DMAGICHOME_ZJ_WFMN_B_11
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:magichome-zj-wfmn-b-11-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DMAGICHOME_ZJ_WFMN_B_11
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:huacanxing-h801] [env:huacanxing-h801]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
@ -1912,6 +1965,31 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:euromate-wifi-stecker-shuko-v2]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_2m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_2m1m} -DEUROMATE_WIFI_STECKER_SCHUKO_V2
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:euromate-wifi-stecker-shuko-v2-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_2m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_2m1m} -DEUROMATE_WIFI_STECKER_SCHUKO_V2
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:tonbux-powerstrip02] [env:tonbux-powerstrip02]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
@ -2033,6 +2111,30 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:maxcio-wde004]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DMAXCIO_WDE004
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:maxcio-wde004-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DMAXCIO_WDE004
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:yidian-xsssa05] [env:yidian-xsssa05]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
@ -2057,6 +2159,31 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:tonbux-xsssa01]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_4m1m} -DTONBUX_XSSSA01
upload_speed = ${common.upload_speed_fast}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:tonbux-xsssa01-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_4m1m} -DTONBUX_XSSSA01
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:tonbux-xsssa06] [env:tonbux-xsssa06]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
@ -2276,6 +2403,55 @@ upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:lombex-lux-nova2-tunable-white]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DLOMBEX_LUX_NOVA2_TUNABLE_WHITE
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:lombex-lux-nova2-tunable-white-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DLOMBEX_LUX_NOVA2_TUNABLE_WHITE
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:lombex-lux-nova2-white-color]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DLOMBEX_LUX_NOVA2_WHITE_COLOR
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:lombex-lux-nova2-white-color-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DLOMBEX_LUX_NOVA2_WHITE_COLOR
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# GENERIC OTA ENVIRONMENTS # GENERIC OTA ENVIRONMENTS
@ -2525,6 +2701,58 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:blitzwolf-bwshp2-v23]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHP2_V23
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:blitzwolf-bwshp2-v23-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DBLITZWOLF_BWSHP2_V23
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:teckin-sp22-v14]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DTECKIN_SP22_V14
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:teckin-sp22-v14-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DTECKIN_SP22_V14
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:homecube-16a] [env:homecube-16a]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
@ -2750,3 +2978,54 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:gblife-rgbw-socket]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGBLIFE_RGBW_SOCKET
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:gblife-rgbw-socket-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DGBLIFE_RGBW_SOCKET
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:smartlife-mini-smart-socket]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DSMARTLIFE_MINI_SMART_SOCKET
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:smartlife-mini-smart-socket-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DSMARTLIFE_MINI_SMART_SOCKET
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}

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

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

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

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

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

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

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

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

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

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

+ 19
- 7
pre-commit View File

@ -16,6 +16,7 @@ Copy this file to .git/hooks/
import os import os
import sys import sys
import string
import re import re
from subprocess import call, check_output from subprocess import call, check_output
@ -37,6 +38,14 @@ if sys.version_info[0] < 3:
FileInput = FileInputCtx FileInput = FileInputCtx
class CustomFormatter(string.Formatter):
def format_field(self, value, spec):
if spec == "escape_hyphen":
return value.replace("-", "--")
else:
return super(CustomFormatter, self).format_field(value, spec)
def run(cmd, cwd=None): def run(cmd, cwd=None):
out = check_output(cmd, cwd=cwd) out = check_output(cmd, cwd=cwd)
out = out.decode("latin1").strip() out = out.decode("latin1").strip()
@ -79,12 +88,13 @@ def espurna_get_version(base, version_h="code/espurna/config/version.h"):
return version return version
TEMPLATES = { TEMPLATES = {
"![travis]": "[![travis](https://travis-ci.org/{USER}/{REPO}.svg?branch={BRANCH})]" \
"![travis]": "[![travis](https://travis-ci.org/{USER}/{REPO}.svg?branch={BRANCH})]"
"(https://travis-ci.org/{USER}/{REPO})\n", "(https://travis-ci.org/{USER}/{REPO})\n",
"![version]": "[![version](https://img.shields.io/badge/version-{VERSION}-brightgreen.svg)](CHANGELOG.md)\n",
"![branch]": "[![branch](https://img.shields.io/badge/branch-{BRANCH}-orange.svg)]" \
"(https://github.com/{USER}/{REPO}/tree/{BRANCH}/)\n"
"![version]": "[![version](https://img.shields.io/badge/version-{VERSION:escape_hyphen}-brightgreen.svg)](CHANGELOG.md)\n",
"![branch]": "[![branch](https://img.shields.io/badge/branch-{BRANCH:escape_hyphen}-orange.svg)]"
"(https://github.com/{USER}/{REPO}/tree/{BRANCH}/)\n",
} }
README = "README.md" README = "README.md"
@ -98,10 +108,12 @@ if __name__ == "__main__":
"USER": user, "USER": user,
"REPO": repo, "REPO": repo,
"BRANCH": git_branch(), "BRANCH": git_branch(),
"VERSION": espurna_get_version(base)
"VERSION": espurna_get_version(base),
} }
formatter = CustomFormatter()
templates = [ templates = [
(k, tmpl.format(**fmt))
(k, formatter.format(tmpl, **fmt))
for k, tmpl in TEMPLATES.items() for k, tmpl in TEMPLATES.items()
] ]
@ -121,4 +133,4 @@ if __name__ == "__main__":
if call(["git", "add", README]): if call(["git", "add", README]):
sys.exit(1) sys.exit(1)
sys.exit(0);
sys.exit(0)

Loading…
Cancel
Save