v2.0.1 - Merge remote-tracking branch 'origin/dev' into main

This commit is contained in:
proddy
2020-09-13 11:20:40 +02:00
90 changed files with 4959 additions and 1150 deletions

View File

@@ -10,7 +10,7 @@ assignees: ''
*Before creating a new issue please check that you have:* *Before creating a new issue please check that you have:*
* *searched the existing [issues](https://github.com/proddy/EMS-ESP/issues) (both open and closed)* * *searched the existing [issues](https://github.com/proddy/EMS-ESP/issues) (both open and closed)*
* *searched the [wiki help pages](https://github.com/proddy/EMS-ESP/wiki/Troubleshooting)* * *searched the [wiki help pages](https://bbqkees-electronics.nl/wiki/gateway/troubleshooting.html)*
*Completing 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.* *Completing 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.*

View File

@@ -10,8 +10,7 @@ assignees: ''
*Before creating a new issue please check that you have:* *Before creating a new issue please check that you have:*
* *searched the existing [issues](https://github.com/proddy/EMS-ESP/issues) (both open and closed)* * *searched the existing [issues](https://github.com/proddy/EMS-ESP/issues) (both open and closed)*
* *searched the [wiki help pages](https://github.com/proddy/EMS-ESP/wiki/Troubleshooting)* * *searched the [wiki help pages](https://bbqkees-electronics.nl/wiki/gateway/troubleshooting.html)*
*Completing 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.* *Completing 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.*

10
.github/contribute.md vendored
View File

@@ -1,10 +0,0 @@
Do you want to do a pull request?
Excellent! Thanks for contributing!
Please do keep in mind these basic rules:
## Pull request ##
* Do the pull request against the **`dev` branch**
* **Only touch relevant files** (beware if your editor has auto-formatting feature enabled)

17
.github/stale.yml vendored
View File

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

View File

@@ -60,3 +60,4 @@ jobs:
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v1

1
.gitignore vendored
View File

@@ -16,7 +16,6 @@ pio_local.ini
# project specfic # project specfic
/scripts/stackdmp.txt /scripts/stackdmp.txt
*.bin
emsesp emsesp
/data/www/ /data/www/
/lib/framework/WWWData.h /lib/framework/WWWData.h

View File

@@ -12,8 +12,8 @@ env:
global: global:
- BUILDER_TOTAL_THREADS=1 - BUILDER_TOTAL_THREADS=1
- OWNER=${TRAVIS_REPO_SLUG%/*} - OWNER=${TRAVIS_REPO_SLUG%/*}
- DEV=${OWNER/proddy/v2} - DEV=${OWNER/proddy/dev}
- BRANCH=${TRAVIS_BRANCH/v2/} - BRANCH=${TRAVIS_BRANCH/dev/}
- TAG=${DEV}${BRANCH:+_}${BRANCH} - TAG=${DEV}${BRANCH:+_}${BRANCH}
install: install:
@@ -53,7 +53,7 @@ deploy:
token: ${GITHUB_TOKEN} token: ${GITHUB_TOKEN}
file_glob: true file_glob: true
file: "*.bin" file: "*.bin"
name: latest v2 development build name: latest development build
release_notes: release_notes:
Version $FIRMWARE_VERSION. Version $FIRMWARE_VERSION.
Automatic firmware build of the current EMS-ESP branch built on $(date +'%F %T %Z') from commit $TRAVIS_COMMIT. Automatic firmware build of the current EMS-ESP branch built on $(date +'%F %T %Z') from commit $TRAVIS_COMMIT.

View File

@@ -5,10 +5,51 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.0 beta] ## [2.0.1]
### Added
- Able to set individual MQTT publish intervals per device
- Option to automatically MQTT publish when device data is updated
- Immediately send out Rx read request after a successful write, and publish via MQTT
- Added clearer steps in documentation on how to erase & upload
- Show Boiler's pump modulation in Web
- Support parasite Dallas temperature sensors
- Improvements to `watch` command, including publishing the telegram to MQTT
- Support for analog measurements on a GPIO (fixed)
- New `read <device ID> <type ID>` command in console
### Fixed
- Sometimes the automatic upgrade from 1.9 to 2.0 bricked the ESP8266
- Thermostat `set master` wasn't preserved after restart
- Correctly detect Thermostat heating circuits in Home Assistant
- Logamatic TC100 reading of thermostat data (and other Easy devices)
- Rendering 3-byte parameters like the UBA uptime
- MM100/200 MQTT data would be mixed up between heating circuit and ww circuit
- External Dallas sensor support for DS18S20
### Changed ### Changed
- A lot! See `README.md` - Web user-interface improvements, table alignment and number formatting
- Spelling of disinfection in MQTT payload
- Many small minor code improvements and optimizations
- External dallas temperature sensors rounded to a single decimal point
- Syslog hostname always shown in Web
### Removed
- NO_LED build option
## [2.0.0] 29-08-2020
First version of v2 with
- Supporting both ESP8266 and ESP32 modules from Espressif
- A new multi-user Web interface (based on React/TypeScript)
- A new Console, accessible via Serial and Telnet
- Tighter security in both Web and Console. Admin privileges required to access core settings and commands.
- Support for Home Assistant MQTT Discovery (https://www.home-assistant.io/docs/mqtt/discovery/)
- Can be run standalone as an independent Access Point or join an existing WiFi network
- Easier first-time configuration via a web Captive Portal
- Supporting over 70 EMS devices (boilers, thermostats, solar modules, mixing modules, heat pumps, gateways)
See README.me for more details.
## [1.9.5] 30-04-2020 ## [1.9.5] 30-04-2020
@@ -38,7 +79,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- improved MQTT publishing to stop network flooding. `publish_time` of -1 is no publish, 0 is automatic otherwise its a time interval - improved MQTT publishing to stop network flooding. `publish_time` of -1 is no publish, 0 is automatic otherwise its a time interval
- External sensors (like Dallas DS18*) are sent as a nested MQTT topic including their unqiue identifier - External sensors (like Dallas DS18*) are sent as a nested MQTT topic including their unique identifier
- `mqttlog` console command renamed to `mqttqueue` to only show the current publish queue - `mqttlog` console command renamed to `mqttqueue` to only show the current publish queue
- `status` payload on start-up shows the IP and Version of EMS-ESP - `status` payload on start-up shows the IP and Version of EMS-ESP
- `thermostat mode` takes a string like manual,auto,heat,day,night,eco,comfort,holiday,nofrost - `thermostat mode` takes a string like manual,auto,heat,day,night,eco,comfort,holiday,nofrost
@@ -160,7 +201,7 @@ There are breaking changes in this release. See `publish_time` below and make su
- Fixes to the default HA climate component .yaml file to support latest Home Assistance ('heat' added) - Fixes to the default HA climate component .yaml file to support latest Home Assistance ('heat' added)
- Update documentation in Wiki on MQTT and troubleshooting - Update documentation in Wiki on MQTT and troubleshooting
- Slowed down firmware upload via the Web to prevent users rebooting too early - Slowed down firmware upload via the Web to prevent users rebooting too early
- Change way WiFi is intialized to prevent dual AP and Client - Change way WiFi is initialized to prevent dual AP and Client
### Removed ### Removed
@@ -210,7 +251,7 @@ There are breaking changes in this release. See `publish_time` below and make su
- Stopped automatic refresh of web page, which causes crashes/memory loss after a short time - Stopped automatic refresh of web page, which causes crashes/memory loss after a short time
- Support HA 0.96 climate component changes - Support HA 0.96 climate component changes
- -DDEFAULT_NO_SERIAL changed to -DFORCE_SERIAL - -DDEFAULT_NO_SERIAL changed to -DFORCE_SERIAL
- some code cleanups, removing NULLS and moving some things fron heap to stack to prevent memory fragmentation - some code cleanups, removing NULLS and moving some things frond heap to stack to prevent memory fragmentation
## [1.8.0] 2019-06-15 ## [1.8.0] 2019-06-15
@@ -455,7 +496,7 @@ There are breaking changes in this release. See `publish_time` below and make su
- Settings are saved and loaded from the ESP8266's file system (SPIFFS). Can be set using the 'set' command - Settings are saved and loaded from the ESP8266's file system (SPIFFS). Can be set using the 'set' command
- Improved support when in Access Point mode (192.168.4.1) - Improved support when in Access Point mode (192.168.4.1)
- pre-built firmwares are back - pre-built firmware's are back
## [1.2.4] 2019-01-04 ## [1.2.4] 2019-01-04
@@ -487,7 +528,7 @@ There are breaking changes in this release. See `publish_time` below and make su
- Only process broadcast messages if the offset (byte 4) is 0. (https://github.com/proddy/EMS-ESP/issues/23) - Only process broadcast messages if the offset (byte 4) is 0. (https://github.com/proddy/EMS-ESP/issues/23)
- Improved checking for duplicate sent Tx telegrams by comparing CRCs - Improved checking for duplicate sent Tx telegrams by comparing CRCs
- Removed distiquishing between noise on the line and corrupt telegrams (https://github.com/proddy/EMS-ESP/issues/24) - Removed distinguishing between noise on the line and corrupt telegrams (https://github.com/proddy/EMS-ESP/issues/24)
## [1.2.0] 2019-01-01 ## [1.2.0] 2019-01-01

110
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,110 @@
<img src="/media/EMS-ESP_logo_dark.png" alt="Logo" align="right" height="76"/>
# Contributing
**Any contribution helps EMS-ESP get better for the entire community!**
Everybody is welcome and invited to contribute to the EMS-ESP Project by:
- providing Pull Requests (Features, Fixes, suggestions)
- testing new released features and report issues on your EMS equipment
- contributing missing [documentation](https://emsesp.github.io/docs) for features and devices
This document describes rules that are in effect for this repository, meant for handling issues by contributors in the issue tracker and PRs.
## Opening New Issues
1. Opening an issue means that a problem exists in the code and should be addressed by the project contributors.
2. When opening an issue, it is required to fill out the presented template. The requested information is important! If the template is ignored or insufficient info about the issue is provided, the issue may be closed.
3. Questions of type "How do I..." or "Can you please help me with..." or "Can EMS-ESP do..." are better directed to the support channel in Gitter.
4. Issues about topics already handled in the documentation will be closed in a similar manner.
5. Issues for unmerged PRs will be closed. If there is an issue with a PR, the explanation should be added to the PR itself.
6. Issues with accompanied investigation that shows the root of the problem should be given priority.
7. Duplicate issues will be closed.
## Triaging of Issues/PR's
1. Any contributor to the project can participate in the triaging process, if he/she chooses to do so.
2. An issue that needs to be closed, either due to not complying with this policy, or for other reasons, should be closed by a contributor.
3. Issues that are accepted should be marked with appropriate labels.
4. Issues that could impact functionality for many users should be considered severe.
5. Issues caused by the SDK or chip should not be marked severe, as there usually isnt much to be done. Common sense should be applied when deciding. Such issues should be documented in the Wiki, for reference by users.
6. Issues with feature requests should be discussed for viability/desirability.
7. Feature requests or changes that are meant to address a very specific/limited use case, especially if at the expense of increased code complexity, may be denied, or may be required to be redesigned, generalized, or simplified.
8. Feature requests that are not accompanied by a PR:
* could be closed immediately (denied).
* could be closed after some predetermined period of time (left as candidate for somebody to pick up).
9. In some cases, feedback may be requested from the issue reporter, either as additional info for clarification, additional testing, or other. If no feedback is provided, the issue may be closed by a contributor or after 40 days by the STALE bot.
## Pull requests
A Pull Request (PR) is the process where code modifications are managed in GitHub.
The process is straight-forward.
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0)
- Fork the EMS-ESP Repository [git repository](https://github.com/proddy/EMS-ESP).
- Write/Change the code in your Fork for a new feature, bug fix, new sensor, optimization, etc.
- Ensure tests work.
- Create a Pull Request against the [**dev**](https://github.com/proddy/EMS-ESP/tree/dev) branch of EMS-ESP.
1. All pull requests must be done against the dev branch.
2. Only relevant files should be touched (Also beware if your editor has auto-formatting feature enabled).
3. Only one feature/fix should be added per PR.
4. PRs that don't compile (fail in CI Tests) or cause coding errors 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.
5. All pull requests should undergo peer review by at least one contributor other than the creator, excepts for the owner.
6. All pull requests should consider updates to the documentation.
7. Pull requests that address an outstanding issue, particularly an issue deemed to be severe, should be given priority.
8. If a PR is accepted, then it should undergo review and updated based on the feedback provided, then merged.
9. By submitting a PR, it is needed to use the provided PR template and check all boxes, performing the required tasks and accepting the CLA.
10. Pull requests that don't meet the above will be denied and closed.
--------------------------------------
## Contributor License Agreement (CLA)
```
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the GPL-3.0 license; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the GPL-3.0 license; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it) is maintained indefinitely
and may be redistributed consistent with this project or the open
source license(s) involved.
```
This Contributor License Agreement (CLA) was adopted on April 1st, 2019.
The text of this license is available under the [Creative Commons Attribution-ShareAlike 3.0 Unported License](http://creativecommons.org/licenses/by-sa/3.0/). It is based on the Linux [Developer Certificate Of Origin](http://elinux.org/Developer_Certificate_Of_Origin), but is modified to explicitly use the GPL-3.0 license and not mention sign-off (due to GitHub.com keeps an historial, with your user name, of PRs' commits and all editions on PR's comments).
To accept the CLA it is required to put a x between [ ] on `[ ] I accept the CLA` in the PR template when submitting it. The [ ] is an opt-in box, so you have to manually accept it.
**Why a CLA ?**
_"A Contributor Licence Agreement (CLA) is strongly recommended when accepting third party contributions to an open development project, such as an open source software project. In order to redistribute contributions, it is necessary to ensure that the project has the necessary rights to do so. A Contributor Licence Agreement is a lightweight agreement, signed by the copyright holder, that grants the necessary rights for the contribution to be redistributed as part of the project."_ [OSS Watch](http://oss-watch.ac.uk/resources/cla)
A CLA is a legal document in which you state _you are entitled to contribute the code/documentation/translation to the project_ youre contributing to and that _you are willing to have it used in distributions and derivative works_. This means that should there be any kind of legal issue in the future as to the origins and ownership of any particular piece of code, then that project has the necessary forms on file from the contributor(s) saying they were permitted to make this contribution.
CLA is a safety because it also ensures that once you have provided a contribution, you cannot try to withdraw permission for its use at a later date. People can therefore use that software, confident that they will not be asked to stop using pieces of the code at a later date.
A __license__ grants "outbound" rights to the user of project.
A __CLA__ enables a contributor to grant "inbound" rights to a project.
<Other>
<A table should be maintained for relating maintainers and components. When triaging, this is essential to figure out if someone in particular should be consulted about specific changes.>
<A stable release cadence should be established, e.g.: every month.>

281
README.md
View File

@@ -1,188 +1,171 @@
# ![logo](media/EMS-ESP_logo_dark.png) # ![logo](media/EMS-ESP_logo_dark.png)
[![version](https://img.shields.io/github/release/proddy/EMS-ESP.svg?label=Latest%20Release)](https://github.com/proddy/EMS-ESP/blob/main/CHANGELOG.md) **EMS-ESP** is an open-source firmware for the Espressif ESP8266 and ESP32 microcontroller that communicates with **EMS** (Energy Management System) based equipment from manufacturers like Bosch, Buderus, Nefit, Junkers, Worcester and Sieger.
[![release-date](https://img.shields.io/github/release-date/proddy/EMS-ESP.svg?label=Released)](https://github.com/proddy/EMS-ESP/commits/main)
<br /> [![version](https://img.shields.io/github/release/proddy/EMS-ESP.svg?label=Latest%20Release)](https://github.com/proddy/EMS-ESP/blob/master/CHANGELOG.md)
[![release-date](https://img.shields.io/github/release-date/proddy/EMS-ESP.svg?label=Released)](https://github.com/proddy/EMS-ESP/commits/master)
[![license](https://img.shields.io/github/license/proddy/EMS-ESP.svg)](LICENSE) [![license](https://img.shields.io/github/license/proddy/EMS-ESP.svg)](LICENSE)
[![travis](https://travis-ci.com/proddy/EMS-ESP.svg?branch=dev)](https://travis-ci.com/proddy/EMS-ESP) [![travis](https://travis-ci.com/proddy/EMS-ESP.svg?branch=dev)](https://travis-ci.com/proddy/EMS-ESP)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/b8880625bdf841d4adb2829732030887)](https://app.codacy.com/app/proddy/EMS-ESP?utm_source=github.com&utm_medium=referral&utm_content=proddy/EMS-ESP&utm_campaign=Badge_Grade_Settings) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/b8880625bdf841d4adb2829732030887)](https://app.codacy.com/app/proddy/EMS-ESP?utm_source=github.com&utm_medium=referral&utm_content=proddy/EMS-ESP&utm_campaign=Badge_Grade_Settings)
[![downloads](https://img.shields.io/github/downloads/proddy/EMS-ESP/total.svg)](https://github.com/proddy/EMS-ESP/releases) [![downloads](https://img.shields.io/github/downloads/proddy/EMS-ESP/total.svg)](https://github.com/proddy/EMS-ESP/releases)
<br /> [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/proddy/EMS-ESP.svg)](http://isitmaintained.com/project/proddy/EMS-ESP "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/proddy/EMS-ESP.svg)](http://isitmaintained.com/project/proddy/EMS-ESP "Percentage of issues still open")
<br/>
[![gitter](https://img.shields.io/gitter/room/EMS-ESP/EMS-ESP.svg)](https://gitter.im/EMS-ESP/community) [![gitter](https://img.shields.io/gitter/room/EMS-ESP/EMS-ESP.svg)](https://gitter.im/EMS-ESP/community)
<br>
EMS-ESP is a open-source system built for the Espressif ESP8266 microcontroller to communicate with **EMS** (Energy Management System) based boilers, thermostats and other modules from manufacturers like Bosch, Buderus, Nefit, Junkers and Sieger. If you like **EMS-ESP**, please give it a star, or fork it and contribute!
## **New Features in v2** [![GitHub stars](https://img.shields.io/github/stars/proddy/EMS-ESP.svg?style=social&label=Star)](https://github.com/proddy/EMS-ESP/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/proddy/EMS-ESP.svg?style=social&label=Fork)](https://github.com/proddy/EMS-ESP/network)
[![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/paypalme/prderbyshire/2)
- Supports both ESP8266 and ESP32 Note, EMS-ESP requires a small hardware circuit that can convert the EMS bus data to be read by the microcontroller. These can be purchased at https://bbqkees-electronics.nl/.
- New MQTT option to support Home Assistant MQTT Discovery (https://www.home-assistant.io/docs/mqtt/discovery/)
- Tighter security in Web and Console <img src="media/gateway-integration.jpg" width=40%>
- New secure web interface (based on React/TypeScript)
- Can be run on WiFi on as a Stand alone Access Point ---
## **New Features in version 2**
- Support for both ESP8266 and ESP32 modules
- A new multi-user Web interface (based on React/TypeScript)
- A new Console, accessible via Serial and Telnet
- Tighter security in both Web and Console. Admin privileges required to access core settings and commands.
- Support for Home Assistant MQTT Discovery (https://www.home-assistant.io/docs/mqtt/discovery/)
- Can be run standalone as an independent Access Point or join an existing WiFi network
- Easier first-time configuration via a web Captive Portal - Easier first-time configuration via a web Captive Portal
- Supporting over 70 EMS devices - Supporting over 70 EMS devices (boilers, thermostats, solar modules, mixing modules, heat pumps, gateways)
<img src="media/web_settings.PNG" width=70% height=70%>
<img src="media/web_status.PNG" width=70% height=70%>
<img src="media/web_devices.PNG" width=70% height=70%>
<img src="media/web_mqtt.PNG" width=70% height=70%>
- A new console. As in version 1.9 it works with both Serial and Telnet but now with a more intuitive Linux shell like behavior. It supports multiple connections and has basic security to prevent any changes to EMS-ESP. A full list of commands is below, here are the key ones:
* `help` lists the commands and keywords. This works in each context.
* `exit` will exit the console or exit the current context. CTRL-D does the same.
* `CTRL-U` for Undo
* `TAB` for auto-complete
* Some specific commands are behind contexts. Think of this as a sub-menu. e.g. `system`, `thermostat`. The path will always show you which context you are in. `$` is the root.
* `su` will switch to super-user or Admin. The default password is "ems-esp-neo" and can be changed with `passwd` from the system menu or via the Web UI (called secret password). When in Admin mode the command prompt switches from `$` to `#`.
* Some settings can be changed in the console. The `set` command will list them.
* `show` shows the data specific to the which context you're in. From the root it will show you all the EMS device information and any external temperature sensors. From a context it will be more specific to that context, e.g. `show mqtt` from `system` will list MQTT subscriptions and show the status and queue.
* `log` sets the logging level. `log off` disables logging. Use `log debug` for debugging commands and actions. This will be reset next time the console is opened.
* `watch` will output the incoming Rx telegrams directly to the console. You can also put on a watch on a specific EMS device ID or telegram ID. Also choose to output as verbose text or raw data bytes. these in its 'raw' data format and also watch a particular ID.
## **Migrating from version 1.9** ## **Screenshots**
| | |
| --- | --- |
| <img src="media/web_settings.PNG"> | <img src="media/web_status.PNG"> |
| <img src="media/web_devices.PNG"> | <img src="media/web_mqtt.PNG"> |
<img src="media/console.PNG" width=100% height=100%>
## **Migrating from versions 1.9**
EMS-ESP will attempt to automatically migrate the 1.9 settings. EMS-ESP will attempt to automatically migrate the 1.9 settings.
Note there are some noticeable different to be aware of in version 2: Note there are some noticeable differences to be aware of in version 2:
- MQTT base has been removed ### MQTT:
- There is no "serial mode" anymore like with version 1.9. When the Wifi cannot connect to the SSID it will automatically enter a "safe" mode where the Serial console is automatically activated (note Serial is always available on the ESP32 because it has multiple UARTs). The EMS-ESP will blink fast when in Serial mode. Connect via a USB with baud 115200 to see the serial console. Note in this mode the EMS will be disconnect so there will be no incoming traffic. Use only for debugging or changing settings. - MQTT base has been removed. All MQTT topics are prefixed with only the hostname, for example `ems-esp/status` as opposed to `home/ems-esp/status`.
- `heatPmp` renamed to `heatPump`
- `ServiceCodeNumber` renamed to `serviceCodeNumber`
- Firmware version has been moved to the `start` topic
- `desinfection` renamed to `disinfection`
### General:
- There is no "serial mode" anymore like with version 1.9. When the Wifi cannot connect to the SSID it will automatically enter a "safe" mode where the Serial console is activated (note Serial is always available on the ESP32 because it has multiple UARTs). The EMS-ESP's LED will blink fast when in Serial mode. When this happens connect via a USB using baud 115200.
If you run into issues try first erasing the ESP8266 with `esptool.py erase_flash` and uploading the new firmware manually. BBQKees has a good write-up at https://bbqkees-electronics.nl/wiki/gateway/firmware-update-to-v2.html.
## **Building the firmware using PlatformIO**
1. Install [PlatformIO](https://platformio.org/install) and [NodeJS](https://nodejs.org/en/).
2. Decide how you want to upload the firmware, via USB or OTA (Over The Air). OTA requires that a version of EMS-ESP is already running.
3. Create a new file called `pio_local.ini` and add these two lines for USB:
```yaml
upload_protocol = esptool
upload_port = <COM>
```
or these 2 for OTA:
```yaml
upload_protocol = espota
upload_flags =
--port=8266
--auth=ems-esp-neo
upload_port = ems-esp.local
```
3. type `pio run -t upload` to build and upload the firmware
## **Uploading the firmware** ## **Uploading the firmware**
- If you're not using PlatformIO, use the command-line and Python. You can download Python from https://www.python.org/downloads/. Make sure you also get: Here we'll use the command-line. You'll need [Python]( https://www.python.org/downloads/) (version 3) installed and these 2 scripts:
- `esptool`, install using the command `pip install esptool`
- and for OTA updates later, `espota` from https://github.com/esp8266/Arduino/blob/master/tools/espota.py using `python espota.py --debug --progress --port 8266 --auth ems-esp-neo -i ems-esp.local -f <firmware.bin>`
- Grab the latest firmware binary from https://github.com/proddy/EMS-ESP/releases - `esptool.py`. Install using `pip install esptool`.
- `espota.py` downloaded from https://github.com/esp8266/Arduino/blob/master/tools/espota.py
- Uploading directly via USB... Both these tools are also in the repo in the `scripts` directory.
Next step is to fetch the latest firmware binary from https://github.com/proddy/EMS-ESP/releases, and if you're using USB with an ESP8266:
`esptool.py -p <COM PORT> -b 921600 write_flash 0x00000 <firmware.bin>`
For ESP8266: `esptool.py -p <COM PORT> -b 921600 write_flash 0x00000 <firmware.bin>` and for OTA:
note: if this fails try a lower speed like `115200` instead of `921600`.
For ESP32: `esptool.py --chip esp32 --port "COM6" --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect 0x1000 XX\.platformio\packages\framework-arduinoespressif32\tools\sdk\bin\bootloader_dio_40m.bin 0x8000 XX\.pio\build\esp32\partitions.bin 0xe000 XX\.platformio\packages\framework-arduinoespressif32\tools\partitions\boot_app0.bin 0x10000 <firmware.bin>` `espota.py --debug --progress --port 8266 --auth ems-esp-neo -i <IP address> -f <firmware.bin>`
- Uploading over WiFi: `espota.py --debug --progress --port 8266 --auth ems-esp-neo -i <IP address> -f <firmware.bin>`
## **Setting EMS-ESP up for the first time** ## **Configuring EMS-ESP for the first time**
- After powering up the ESP, watch the onboard LED. A solid light means good connection and EMS data is coming in. A slow pulse means either the WiFi or the EMS bus is not connected yet. A very fast pulse is when the system is booting up and configuring itself, which typically takes 5 seconds. - After powering up the ESP, watch the onboard blue LED. A solid light means good connection and EMS data is coming in. A slow pulse means either the WiFi or the EMS bus is not connected yet. A very fast pulse is when the system is booting up and configuring itself which typically takes 5 seconds.
- Connect to the Access Point called ems-esp using the WPA password `ems-esp-neo`. When you see the captive portal sign-in with username `admin` and password `admin`. Set the WiFi credentials and go back to http://ems-esp - Connect to the Access Point called `ems-esp` using the WPA password `ems-esp-neo`. When you see the captive portal sign-in with username `admin` and password `admin`. Set the WiFi credentials and go back to http://ems-esp. Remember to change the passwords!
- First thing to check is if Tx is working and that you have a connect to the EMS bus. If it's showing an error try changing the Tx Mode from the settings page. Then check the Status (no need to restart EMS-ESP). - First thing to check is if Tx is working and that you have a connection to the EMS bus. If Tx fails are shown in the Web interface try changing the Tx Mode from the settings page. There is no need to re-start the EMS-ESP.
- If Rx incomplete telegrams are reported in the Web UI, don't panic. Some telegrams can be missed and this is usually due to noise on line. - If Rx incomplete telegrams are reported in the Web interface, don't panic. Some telegrams can be missed and this is usually caused by noise interference on the line.
## **Using the Console** ## **Using the Console**
Connecting to the console will give you more insight into the EMS bus traffic, MQTT queues and the actual device information. The console is reachable via Telnet (port 22) or via the Serial port if using an USB (on baud 115200). To change any settings in the console you must be admin (use `su` with the default password `ems-esp-neo`). On an ESP8266 the Serial port is disabled by default unless it's unable to connect to the WiFi. Connecting to the console will give you more insight into the EMS bus traffic, MQTT queues and the full device information.
The `call` command is to execute a command. The command names (`[cmd]`) are the same as the MQTT command listed in the next section. The console is reachable via Telnet (port 22) or via the Serial port if using an USB (on baud 115200). To change any settings in the console you must be admin (use `su` with the default password `ems-esp-neo`).
(* = available in su/Admin mode)
```
common commands available in all contexts:
exit
help
log [level]
watch <on | off | raw> [ID]
su
(from the root)
system (enters a context)
boiler (enters a context)
thermostat (enters a context)
set
fetch
scan devices [deep] *
send telegram <"XX XX ..."> *
set bus_id <device ID> *
set tx_mode <n> *
show
show devices
show ems
show values
show mqtt
system
set
show
format *
show users *
passwd *
restart *
set wifi hostname <name> *
set wifi password *
set wifi ssid <name> *
wifi reconnect *
pin <gpio> [data] *
boiler
read <type ID> *
call [cmd] [data] *
thermostat
set
set master [device ID] *
read <type ID> *
call [cmd] [data] [heating circuit] *
```
---------- Some of the most common commands are:
## **MQTT commands** * `help` lists the commands and keywords. This works in each context.
* `exit` will exit the console or exit the current context. `CTRL-D` does the same.
* `CTRL-U` for Undo
* `<TAB>` for auto-complete
* Some specific commands are behind contexts. Think of this as a sub-menu. e.g. `system`, `thermostat`. The path will always show you which context you are in. `$` is the root.
* `su` will switch to the Admin super-user. The default password is `ems-esp-neo` and can be changed with `passwd` from the system menu or via the Web interface (called secret password). When in Admin mode the command prompt switches from `$` to `#`.
* Some settings can be changed in the console. The `set` command will list them.
* `show` shows the data specific to the which context you're in. From the root it will show you all the EMS device information and any external temperature sensors.
* `log` sets the logging level. `log off` disables logging. Use `log debug` for debugging commands and actions. This will be reset next time the console is opened.
* `watch` will output the incoming Rx telegrams directly to the console. You can also put on a watch on a specific EMS device ID or telegram ID. Also choose to output as verbose text as raw data bytes.
Breaking change: The MQTT base has been removed in version 2. The hostname is only used as prefixed to the topic, e.g. `ems-esp/status`. The `call` command is to execute a command. The command names (`[cmd]`) are the same as the MQTT commands used in MQTT.
All commands must be written as `{"cmd":<cmd> ,"data":<data>, "id":<n>}`. For further details refer to the [Wiki](https://bbqkees-electronics.nl/wiki/).
The `id` can be replaced with `hc` for some devices and represented as a string or a number. `cmd` is a string, `data` can be a string or number. ## **Support Information**
``` For a list of the EMS devices currently supported see BBQKees's [EMS device compatibility list](https://bbqkees-electronics.nl/ems-device-compatibility/).
*boiler_cmd*
comfort <hot, eco, intelligent>
flowtemp <degrees>
wwtemp <degrees>
boilhyston <degrees> (negative value)
boilhystoff <degrees> (positive value)
burnperiod <minutes>
burnminpower <%>
burnmaxpower <%>
pumpdelay <minutes>
*thermostat_cmd* If you're looking for support on **EMS-ESP** there are some options available:
--- without hc ---
wwmode <off | on | auto>
calinttemp <degrees>
minexttemp <degrees>
building <light | medium | heavy>
language <n> (0=de, 1=nl, 2=fr, 3=it) only RC30
display <n> (0=int temp, 1= int set, 2=ext. temp, 3=burner, 4=ww, 5=mode, 6=time, 7=date, 8=smoke) only RC30
clockoffset <seconds> (only RC30)
--- with hc ---
mode <auto | night | day | nofrost | heat | eco>
temp <degrees>
nighttemp <degrees>
daytemp <degrees>
nofrosttemp <degrees>
ecotemp <degrees>
heattemp <degrees>
summertemp <degrees>
designtemp <degrees>
offsettemp <degrees>
holidaytemp <degrees>
remotetemp <degrees>
control <0 | 1 | 2>
pause <hours>
party <hours>
holiday <dd.mm.yyyy-dd.mm.yyyy>
date <NTP | hh:mm:ss-dd.mm.yyyy-dw-dst>
*system_cmd* ### Documentation
send <"0B XX XX ..">
pin <gpio> <on|off|1|0|true|false>
``` * [Documentation Site](https://bbqkees-electronics.nl/wiki/): For information on how to build and upload the firmware maintained by @BBQKees
* [FAQ and Troubleshooting](https://bbqkees-electronics.nl/wiki/gateway/troubleshooting.html): For information on common problems and solutions
### Support's Community
* [EMS-ESP Support Chat](https://gitter.im/EMS-ESP/community#): For support, troubleshooting and general questions. You have better chances to get fast answers from members of the community
* [Search in Issues](https://github.com/proddy/EMS-ESP/issues): You might find an answer to your question by searching current or closed issues
### Developers' Community
* [Bug Report](https://github.com/proddy/EMS-ESP/issues/new?template=bug_report.md): For reporting Bugs
* [Feature Request](https://github.com/proddy/EMS-ESP/issues/new?template=feature_request.md): For requesting features/functions
* [Troubleshooting](https://github.com/proddy/EMS-ESP/issues/new?template=questions---troubleshooting.md): As a last resort, you can open new *Troubleshooting & Question* issue on GitHub if the solution could not be found using the other channels. Just remember: the more info you provide the more chances you'll have to get an accurate answer
## **Contribute**
You can contribute to EMS-ESP by
- providing Pull Requests (Features, Fixes, suggestions)
- testing new released features and report issues on your EMS equipment
- contributing missing [documentation](https://bbqkees-electronics.nl/wiki/) for features and devices
## **Credits**
A shout out to the people helping EMS-ESP get to where it is today
- @MichaelDvP for all his amazing contributions and patience. The core UART code is his.
- @BBQkees for his endless testing and building the awesome circuits
- @susisstrolch for writing a first working version of the EMS bridge circuit which I used to design EMS-ESP version 0.1
- Plus many more providing suggestions, PRs and Donations. Thanks!
## **License**
This program is licensed under GPL-3.0

55
doc/MQTT.md Normal file
View File

@@ -0,0 +1,55 @@
# **MQTT commands**
All commands must be written as `{"cmd":<cmd> ,"data":<data>, "id":<n>}`.
The `id` can be replaced with `hc` for some devices that use heating circuits, and represented either as a string or a number. `cmd` is a string, `data` can be a string or number.
topic = *boiler_cmd*
```
comfort <hot, eco, intelligent>
flowtemp <degrees>
wwtemp <degrees>
boilhyston <degrees> (negative value)
boilhystoff <degrees> (positive value)
burnperiod <minutes>
burnminpower <%>
burnmaxpower <%>
pumpdelay <minutes>
```
topic = *thermostat_cmd*
```
--- without hc ---
wwmode <off | on | auto>
calinttemp <degrees>
minexttemp <degrees>
building <light | medium | heavy>
language <n> (0=de, 1=nl, 2=fr, 3=it) only RC30
display <n> (0=int temp, 1= int set, 2=ext. temp, 3=burner, 4=ww, 5=mode, 6=time, 7=date, 8=smoke) only RC30
clockoffset <seconds> (only RC30)
--- with hc ---
mode <auto | night | day | nofrost | heat | eco>
temp <degrees>
nighttemp <degrees>
daytemp <degrees>
nofrosttemp <degrees>
ecotemp <degrees>
heattemp <degrees>
summertemp <degrees>
designtemp <degrees>
offsettemp <degrees>
holidaytemp <degrees>
remotetemp <degrees>
control <0 | 1 | 2>
pause <hours>
party <hours>
holiday <dd.mm.yyyy-dd.mm.yyyy>
date <NTP | hh:mm:ss-dd.mm.yyyy-dw-dst>
```
topic = *system_cmd*
```
send <"0B XX XX ..">
pin <gpio> <on|off|1|0|true|false>
```

View File

@@ -1,6 +1,5 @@
# Notes on customizing the code # Notes on customizing the code
## **Basic Design Principles** ## **Basic Design Principles**
- The core services like telnet, logging and shell are based off the libraries from @nomis. I also adopted his general design pattens such as making everything as asynchronous as possible so that no one operation should starve another operation of it's time to execute (https://isocpp.org/wiki/faq/ctors#static-init-order). - The core services like telnet, logging and shell are based off the libraries from @nomis. I also adopted his general design pattens such as making everything as asynchronous as possible so that no one operation should starve another operation of it's time to execute (https://isocpp.org/wiki/faq/ctors#static-init-order).
@@ -67,5 +66,15 @@ The Web is based off Rick's awesome [esp8266-react](https://github.com/rjwats/es
* `factory_settings.ini` modified with `ems-esp-neo` as password and `ems-esp` everywhere else * `factory_settings.ini` modified with `ems-esp-neo` as password and `ems-esp` everywhere else
## To develop and test the Web UI
- uncomment the `-D ENABLE_CORS` in `platformio.ini`
```sh
cd interface
npm start
```
## To test the core, standalone with an ESP
```sh
make run
```

72
doc/console.md Normal file
View File

@@ -0,0 +1,72 @@
# **Console commands**
Connecting to the console will give you more insight into the EMS bus traffic, MQTT queues and the full device information.
The console is reachable via Telnet (port 22) or via the Serial port if using an USB (on baud 115200). To change any settings in the console you must be admin (use `su` with the default password `ems-esp-neo`).
Some of the most common commands are:
* `help` lists the commands and keywords. This works in each context.
* `exit` will exit the console or exit the current context. `CTRL-D` does the same.
* `CTRL-U` for Undo
* `<TAB>` for auto-complete
* Some specific commands are behind contexts. Think of this as a sub-menu. e.g. `system`, `thermostat`. The path will always show you which context you are in. `$` is the root.
* `su` will switch to the Admin super-user. The default password is `ems-esp-neo` and can be changed with `passwd` from the system menu or via the Web interface (called secret password). When in Admin mode the command prompt switches from `$` to `#`.
* Some settings can be changed in the console. The `set` command will list them.
* `show` shows the data specific to the which context you're in. From the root it will show you all the EMS device information and any external temperature sensors.
* `log` sets the logging level. `log off` disables logging. Use `log debug` for debugging commands and actions. This will be reset next time the console is opened.
* `watch` will output the incoming Rx telegrams directly to the console. You can also put on a watch on a specific EMS device ID or telegram ID. Also choose to output as verbose text as raw data bytes.
The `call` command is to execute a command. The command names (`[cmd]`) are the same as the MQTT commands used in MQTT.
```
(* = available in su/Admin mode)
common commands available in all contexts:
exit
help
log [level]
watch <on | off | raw> [ID]
su
(from the root)
system (enters a context)
boiler (enters a context)
thermostat (enters a context)
set
fetch
scan devices [deep] *
send telegram <"XX XX ..."> *
set bus_id <device ID> *
set tx_mode <n> *
show
show devices
show ems
show values
show mqtt
read <device ID> <type ID> *
system
set
show
format *
show users *
passwd *
restart *
set wifi hostname <name> *
set wifi password *
set wifi ssid <name> *
wifi reconnect *
pin <gpio> [data] *
boiler
read <type ID> *
call [cmd] [data] *
thermostat
set
set master [device ID] *
read <type ID> *
call [cmd] [data] [heating circuit] *
```

View File

@@ -150,15 +150,75 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
<MenuItem value={2}>2</MenuItem> <MenuItem value={2}>2</MenuItem>
</SelectValidator> </SelectValidator>
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:1', 'maxNumber:65535']} validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
errorMessages={['Publish time is required', "Must be a number", "Must be greater than 0", "Max value is 65535"]} errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]}
name="publish_time" name="publish_time_boiler"
label="MQTT Publish Time (seconds)" label="MQTT Boiler Publish Period (seconds, 0=on change)"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.publish_time} value={data.publish_time_boiler}
type="number" type="number"
onChange={handleValueChange('publish_time')} onChange={handleValueChange('publish_time_boiler')}
margin="normal"
/>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]}
name="publish_time_thermostat"
label="MQTT Thermostat Publish Period (seconds, 0=on change)"
fullWidth
variant="outlined"
value={data.publish_time_thermostat}
type="number"
onChange={handleValueChange('publish_time_thermostat')}
margin="normal"
/>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]}
name="publish_time_solar"
label="MQTT Solar Publish Period (seconds, 0=on change)"
fullWidth
variant="outlined"
value={data.publish_time_solar}
type="number"
onChange={handleValueChange('publish_time_solar')}
margin="normal"
/>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]}
name="publish_time_mixing"
label="MQTT Mixer Publish Period (seconds, 0=on change)"
fullWidth
variant="outlined"
value={data.publish_time_mixing}
type="number"
onChange={handleValueChange('publish_time_mixing')}
margin="normal"
/>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]}
name="publish_time_other"
label="MQTT other Modules Publish Period (seconds, 0=on change)"
fullWidth
variant="outlined"
value={data.publish_time_other}
type="number"
onChange={handleValueChange('publish_time_other')}
margin="normal"
/>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
errorMessages={['Publish time is required', "Must be a number", "Must be 0 or greater", "Max value is 65535"]}
name="publish_time_sensor"
label="MQTT Sensors Publish Period (seconds, 0=on change)"
fullWidth
variant="outlined"
value={data.publish_time_sensor}
type="number"
onChange={handleValueChange('publish_time_sensor')}
margin="normal" margin="normal"
/> />
<FormActions> <FormActions>

View File

@@ -27,7 +27,12 @@ export interface MqttSettings {
keep_alive: number; keep_alive: number;
clean_session: boolean; clean_session: boolean;
max_topic_length: number; max_topic_length: number;
publish_time: number; publish_time_boiler: number;
publish_time_thermostat: number;
publish_time_solar: number;
publish_time_mixing: number;
publish_time_other: number;
publish_time_sensor: number;
mqtt_format: number; mqtt_format: number;
mqtt_qos: number; mqtt_qos: number;
system_heartbeat: boolean; system_heartbeat: boolean;

View File

@@ -83,8 +83,11 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
const { width, data } = this.props; const { width, data } = this.props;
return ( return (
<TableContainer> <TableContainer>
<Typography variant="h6" color="primary" paragraph> <Typography variant="h6" color="primary" >
Devices: Devices
</Typography>
<Typography variant="caption" color="initial" paragraph>
<i>(click to show details)</i>
</Typography> </Typography>
{!this.noDevices() && ( {!this.noDevices() && (
<Table size="small" padding={isWidthDown('xs', width!) ? "none" : "default"}> <Table size="small" padding={isWidthDown('xs', width!) ? "none" : "default"}>
@@ -145,14 +148,14 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
<TableContainer> <TableContainer>
<p></p> <p></p>
<Typography variant="h6" color="primary" paragraph> <Typography variant="h6" color="primary" paragraph>
Sensors: Sensors
</Typography> </Typography>
{!this.noSensors() && ( {!this.noSensors() && (
<Table size="small" padding="default"> <Table size="small" padding="default">
<TableHead> <TableHead>
<TableRow> <TableRow>
<StyledTableCell>ID</StyledTableCell> <StyledTableCell>ID</StyledTableCell>
<StyledTableCell align="left">Temperature</StyledTableCell> <StyledTableCell align="right">Temperature</StyledTableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@@ -161,8 +164,8 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
<TableCell component="th" scope="row"> <TableCell component="th" scope="row">
{sensorData.id} {sensorData.id}
</TableCell> </TableCell>
<TableCell align="left"> <TableCell align="right">
{sensorData.temp}&deg;C {sensorData.temp.toFixed(1)}&deg;C
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}
@@ -285,7 +288,7 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
<TableCell component="th" scope="row"> <TableCell component="th" scope="row">
{deviceData.name} {deviceData.name}
</TableCell> </TableCell>
<TableCell align="left"> <TableCell align="right">
{deviceData.value} {deviceData.value}
</TableCell> </TableCell>
</TableRow> </TableRow>

View File

@@ -10,14 +10,14 @@ class EMSESPHelp extends Component {
<Box bgcolor="info.main" border={1} p={3} mt={1} mb={0}> <Box bgcolor="info.main" border={1} p={3} mt={1} mb={0}>
<Typography variant="body1"> <Typography variant="body1">
EMS-ESP is an open-source firmware to communicate with heating devices that support the EMS protocol, such as equipment from Bosch, Junkers, Nefit, Buderus and Worcester. EMS-ESP is an open-source firmware for the Espressif ESP8266 and ESP32 microcontroller that communicates with EMS (Energy Management System) based equipment from manufacturers like Bosch, Buderus, Nefit, Junkers, Worcester and Sieger.
<p></p> <p></p>
Please consider supporting this project via the GitHub page <Link href="https://github.com/proddy/EMS-ESP" color="primary">{'http://github.com/proddy/EMS-ESP'}</Link>. Please consider supporting this project via the GitHub page <Link href="https://github.com/proddy/EMS-ESP" color="primary">{'http://github.com/proddy/EMS-ESP'}</Link>.
</Typography> </Typography>
</Box> </Box>
<br></br> <br></br>
<Typography variant="body1" paragraph> <Typography variant="body1" paragraph>
Check for news and updates on the <Link href="https://emsesp.github.io/docs/#/" color="primary">{'Wiki'}</Link>. Check for news and updates on the <Link href="https://bbqkees-electronics.nl/wiki/" color="primary">{'Wiki'}</Link>.
</Typography> </Typography>
<Typography variant="body1" paragraph> <Typography variant="body1" paragraph>
For live community chat go to <Link href="https://gitter.im/EMS-ESP/community#" color="primary">{'Gitter'}</Link>. For live community chat go to <Link href="https://gitter.im/EMS-ESP/community#" color="primary">{'Gitter'}</Link>.

View File

@@ -48,9 +48,13 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
<ValidatorForm onSubmit={saveData}> <ValidatorForm onSubmit={saveData}>
<Box bgcolor="info.main" p={2} mt={2} mb={2}> <Box bgcolor="info.main" p={2} mt={2} mb={2}>
<Typography variant="body1"> <Typography variant="body1">
Customize EMS-ESP by editing the default settings here. Refer to the <Link href="https://emsesp.github.io/docs/#/" color="primary">{'Wiki'}</Link>&nbsp;for descriptions of each setting. Customize EMS-ESP by modifying the default settings here.
</Typography> </Typography>
</Box> </Box>
<br></br>
<Typography variant="h6" color="primary" >
EMS Bus Settings
</Typography>
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:255']} validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:255']}
errorMessages={['TX mode is required', "Must be a number", "Must be 0 or higher", "Max value is 255"]} errorMessages={['TX mode is required', "Must be a number", "Must be 0 or higher", "Max value is 255"]}
@@ -100,18 +104,10 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
onChange={handleValueChange('tx_gpio')} onChange={handleValueChange('tx_gpio')}
margin="normal" margin="normal"
/> />
<TextValidator <br></br>
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']} <Typography variant="h6" color="primary" >
errorMessages={['LED GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 255"]} Dallas Sensor Settings
name="led_gpio" </Typography>
label="LED GPIO pin (0=none)"
fullWidth
variant="outlined"
value={data.led_gpio}
type="number"
onChange={handleValueChange('led_gpio')}
margin="normal"
/>
<TextValidator <TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']} validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']}
errorMessages={['Dallas GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 255"]} errorMessages={['Dallas GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 255"]}
@@ -124,6 +120,32 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
onChange={handleValueChange('dallas_gpio')} onChange={handleValueChange('dallas_gpio')}
margin="normal" margin="normal"
/> />
<BlockFormControlLabel
control={
<Checkbox
checked={data.dallas_parasite}
onChange={handleValueChange('dallas_parasite')}
value="dallas_parasite"
/>
}
label="Dallas Parasite Mode"
/>
<br></br>
<Typography variant="h6" color="primary" >
LED Settings
</Typography>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']}
errorMessages={['LED GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 255"]}
name="led_gpio"
label="LED GPIO pin (0=none)"
fullWidth
variant="outlined"
value={data.led_gpio}
type="number"
onChange={handleValueChange('led_gpio')}
margin="normal"
/>
<BlockFormControlLabel <BlockFormControlLabel
control={ control={
<Checkbox <Checkbox
@@ -132,8 +154,12 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
value="hide_led" value="hide_led"
/> />
} }
label="Hide LED" label="Invert/Hide LED"
/> />
<br></br>
<Typography variant="h6" color="primary" >
Shower Settings
</Typography>
<BlockFormControlLabel <BlockFormControlLabel
control={ control={
<Checkbox <Checkbox
@@ -154,6 +180,21 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
} }
label="Shower Alert" label="Shower Alert"
/> />
<br></br>
<Typography variant="h6" color="primary" >
Syslog Settings
</Typography>
<TextValidator
validators={['isIPOrHostname']}
errorMessages={["Not a valid IP address or hostname"]}
name="syslog_host"
label="Syslog IP/Host"
fullWidth
variant="outlined"
value={data.syslog_host}
onChange={handleValueChange('syslog_host')}
margin="normal"
/>
<SelectValidator name="syslog_level" <SelectValidator name="syslog_level"
label="Syslog Log Level" label="Syslog Log Level"
value={data.syslog_level} value={data.syslog_level}
@@ -166,33 +207,18 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
<MenuItem value={6}>INFO</MenuItem> <MenuItem value={6}>INFO</MenuItem>
<MenuItem value={7}>DEBUG</MenuItem> <MenuItem value={7}>DEBUG</MenuItem>
</SelectValidator> </SelectValidator>
{data.syslog_level !== -1 && <TextValidator
<Fragment> validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
<TextValidator errorMessages={['Syslog Mark is required', "Must be a number", "Must be 0 or higher (0=off)", "Max value is 65535"]}
validators={['isIPOrHostname']} name="syslog_mark_interval"
errorMessages={["Not a valid IP address or hostname"]} label="Syslog Mark Interval (seconds, 0=off)"
name="syslog_host" fullWidth
label="Syslog IP/Host" variant="outlined"
fullWidth value={data.syslog_mark_interval}
variant="outlined" type="number"
value={data.syslog_host} onChange={handleValueChange('syslog_mark_interval')}
onChange={handleValueChange('syslog_host')} margin="normal"
margin="normal" />
/>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}
errorMessages={['Syslog Mark is required', "Must be a number", "Must be 0 or higher (0=off)", "Max value is 65535"]}
name="syslog_mark_interval"
label="Syslog Mark Interval (seconds, 0=off)"
fullWidth
variant="outlined"
value={data.syslog_mark_interval}
type="number"
onChange={handleValueChange('syslog_mark_interval')}
margin="normal"
/>
</Fragment>
}
<FormActions> <FormActions>
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit"> <FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit">
Save Save

View File

@@ -37,6 +37,10 @@ import {
import { EMSESPStatus } from "./EMSESPtypes"; import { EMSESPStatus } from "./EMSESPtypes";
function formatNumber(num: number) {
return new Intl.NumberFormat().format(num);
}
type EMSESPStatusFormProps = RestFormProps<EMSESPStatus> & WithTheme & WithWidthProps; type EMSESPStatusFormProps = RestFormProps<EMSESPStatus> & WithTheme & WithWidthProps;
const StyledTableCell = withStyles((theme: Theme) => const StyledTableCell = withStyles((theme: Theme) =>
@@ -75,7 +79,7 @@ class EMSESPStatusForm extends Component<EMSESPStatusFormProps> {
<TableHead> <TableHead>
<TableRow> <TableRow>
<StyledTableCell>Statistic</StyledTableCell> <StyledTableCell>Statistic</StyledTableCell>
<StyledTableCell align="center"># Telegrams</StyledTableCell> <StyledTableCell align="right"># Telegrams</StyledTableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@@ -83,25 +87,25 @@ class EMSESPStatusForm extends Component<EMSESPStatusFormProps> {
<TableCell> <TableCell>
(Rx) Received telegrams (Rx) Received telegrams
</TableCell> </TableCell>
<TableCell align="center">{data.rx_received}</TableCell> <TableCell align="right">{formatNumber(data.rx_received)}</TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell > <TableCell >
(Rx) Incomplete telegrams (Rx) Incomplete telegrams
</TableCell> </TableCell>
<TableCell align="center">{data.crc_errors}</TableCell> <TableCell align="right">{formatNumber(data.crc_errors)}</TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell > <TableCell >
(Tx) Successfully sent telegrams (Tx) Successfully sent telegrams
</TableCell> </TableCell>
<TableCell align="center">{data.tx_sent}</TableCell> <TableCell align="right">{formatNumber(data.tx_sent)}</TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell > <TableCell >
(Tx) Send Errors (Tx) Send Errors
</TableCell> </TableCell>
<TableCell align="center">{data.tx_errors}</TableCell> <TableCell align="right">{formatNumber(data.tx_errors)}</TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>
</Table> </Table>

View File

@@ -7,11 +7,12 @@ export interface EMSESPSettings {
master_thermostat: number; master_thermostat: number;
shower_timer: boolean; shower_timer: boolean;
shower_alert: boolean; shower_alert: boolean;
hide_led: boolean;
rx_gpio: number; rx_gpio: number;
tx_gpio : number; tx_gpio: number;
dallas_gpio : number; dallas_gpio: number;
led_gpio : number; dallas_parasite: boolean;
led_gpio: number;
hide_led: boolean;
} }
export enum busConnectionStatus { export enum busConnectionStatus {

View File

@@ -2,23 +2,29 @@
#define SPIFFSEditor_H_ #define SPIFFSEditor_H_
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
class SPIFFSEditor: public AsyncWebHandler { class SPIFFSEditor : public AsyncWebHandler {
private: private:
fs::FS _fs; fs::FS _fs;
String _username; String _username;
String _password; String _password;
bool _authenticated; bool _authenticated;
uint32_t _startTime; uint32_t _startTime;
public: public:
#ifdef ESP32 #ifdef ESP32
SPIFFSEditor(const fs::FS& fs, const String& username=String(), const String& password=String()); SPIFFSEditor(const fs::FS & fs, const String & username = String(), const String & password = String());
#else #else
SPIFFSEditor(const String& username=String(), const String& password=String(), const fs::FS& fs=SPIFFS); #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
SPIFFSEditor(const String & username = String(), const String & password = String(), const fs::FS & fs = SPIFFS);
#pragma GCC diagnostic pop
#endif #endif
virtual bool canHandle(AsyncWebServerRequest *request) override final; virtual bool canHandle(AsyncWebServerRequest * request) override final;
virtual void handleRequest(AsyncWebServerRequest *request) override final; virtual void handleRequest(AsyncWebServerRequest * request) override final;
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final; virtual void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) override final;
virtual bool isRequestHandlerTrivial() override final {return false;} virtual bool isRequestHandlerTrivial() override final {
return false;
}
}; };
#endif #endif

View File

@@ -15,12 +15,12 @@ void APSettingsService::begin() {
} }
void APSettingsService::reconfigureAP() { void APSettingsService::reconfigureAP() {
_lastManaged = millis() - MANAGE_NETWORK_DELAY; _lastManaged = uuid::get_uptime() - MANAGE_NETWORK_DELAY;
_reconfigureAp = true; _reconfigureAp = true;
} }
void APSettingsService::loop() { void APSettingsService::loop() {
unsigned long currentMillis = millis(); unsigned long currentMillis = uuid::get_uptime();
unsigned long manageElapsed = (unsigned long)(currentMillis - _lastManaged); unsigned long manageElapsed = (unsigned long)(currentMillis - _lastManaged);
if (manageElapsed >= MANAGE_NETWORK_DELAY) { if (manageElapsed >= MANAGE_NETWORK_DELAY) {
_lastManaged = currentMillis; _lastManaged = currentMillis;

View File

@@ -7,6 +7,8 @@
#include <DNSServer.h> #include <DNSServer.h>
#include <IPAddress.h> #include <IPAddress.h>
#include <uuid/common.h>
#define MANAGE_NETWORK_DELAY 10000 #define MANAGE_NETWORK_DELAY 10000

View File

@@ -31,6 +31,12 @@ class FSPersistence {
DeserializationError error = deserializeJson(jsonDocument, settingsFile); DeserializationError error = deserializeJson(jsonDocument, settingsFile);
if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) { if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) {
JsonObject jsonObject = jsonDocument.as<JsonObject>(); JsonObject jsonObject = jsonDocument.as<JsonObject>();
// debug added by Proddy
// Serial.printf("Read File: %s: ", _filePath);
// serializeJson(jsonDocument, Serial);
// Serial.println();
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
settingsFile.close(); settingsFile.close();
return; return;
@@ -57,9 +63,12 @@ class FSPersistence {
return false; return false;
} }
// Serial.printf("Write File: %s: ", _filePath); // debug added by Proddy
// serializeJson(jsonDocument, Serial); #if defined(EMSESP_DEBUG)
// Serial.println(); Serial.printf("Write File: %s: ", _filePath);
serializeJson(jsonDocument, Serial);
Serial.println();
#endif
// serialize the data to the file // serialize the data to the file
serializeJson(jsonDocument, settingsFile); serializeJson(jsonDocument, settingsFile);

View File

@@ -62,7 +62,7 @@ void MqttSettingsService::begin() {
} }
void MqttSettingsService::loop() { void MqttSettingsService::loop() {
if (_reconfigureMqtt || (_disconnectedAt && (unsigned long)(millis() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) { if (_reconfigureMqtt || (_disconnectedAt && (unsigned long)(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
// reconfigure MQTT client // reconfigure MQTT client
configureMqtt(); configureMqtt();
@@ -107,7 +107,7 @@ void MqttSettingsService::onMqttDisconnect(AsyncMqttClientDisconnectReason reaso
// Serial.print(F("Disconnected from MQTT reason: ")); // Serial.print(F("Disconnected from MQTT reason: "));
// Serial.println((uint8_t)reason); // Serial.println((uint8_t)reason);
_disconnectReason = reason; _disconnectReason = reason;
_disconnectedAt = millis(); _disconnectedAt = uuid::get_uptime();
} }
void MqttSettingsService::onConfigUpdated() { void MqttSettingsService::onConfigUpdated() {
@@ -184,10 +184,15 @@ void MqttSettings::read(MqttSettings & settings, JsonObject & root) {
root["max_topic_length"] = settings.maxTopicLength; root["max_topic_length"] = settings.maxTopicLength;
// added by proddy for EMS-ESP // added by proddy for EMS-ESP
root["system_heartbeat"] = settings.system_heartbeat; root["system_heartbeat"] = settings.system_heartbeat;
root["publish_time"] = settings.publish_time; root["publish_time_boiler"] = settings.publish_time_boiler;
root["mqtt_format"] = settings.mqtt_format; root["publish_time_thermostat"] = settings.publish_time_thermostat;
root["mqtt_qos"] = settings.mqtt_qos; root["publish_time_solar"] = settings.publish_time_solar;
root["publish_time_mixing"] = settings.publish_time_mixing;
root["publish_time_other"] = settings.publish_time_other;
root["publish_time_sensor"] = settings.publish_time_sensor;
root["mqtt_format"] = settings.mqtt_format;
root["mqtt_qos"] = settings.mqtt_qos;
} }
StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & settings) { StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & settings) {
@@ -203,10 +208,15 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION; newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
newSettings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH; newSettings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH;
newSettings.system_heartbeat = root["system_heartbeat"] | EMSESP_DEFAULT_SYSTEM_HEARTBEAT; newSettings.system_heartbeat = root["system_heartbeat"] | EMSESP_DEFAULT_SYSTEM_HEARTBEAT;
newSettings.publish_time = root["publish_time"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_boiler = root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME;
newSettings.mqtt_format = root["mqtt_format"] | EMSESP_DEFAULT_MQTT_FORMAT; newSettings.publish_time_thermostat = root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME;
newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS; newSettings.publish_time_solar = root["publish_time_solar"] | EMSESP_DEFAULT_PUBLISH_TIME;
newSettings.publish_time_mixing = root["publish_time_mixing"] | EMSESP_DEFAULT_PUBLISH_TIME;
newSettings.publish_time_other = root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME;
newSettings.publish_time_sensor = root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME;
newSettings.mqtt_format = root["mqtt_format"] | EMSESP_DEFAULT_MQTT_FORMAT;
newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS;
if (newSettings.system_heartbeat != settings.system_heartbeat) { if (newSettings.system_heartbeat != settings.system_heartbeat) {
emsesp::EMSESP::system_.set_heartbeat(newSettings.system_heartbeat); emsesp::EMSESP::system_.set_heartbeat(newSettings.system_heartbeat);
@@ -216,8 +226,23 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos); emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos);
} }
if (newSettings.publish_time != settings.publish_time) { if (newSettings.publish_time_boiler != settings.publish_time_boiler) {
emsesp::EMSESP::mqtt_.set_publish_time(newSettings.publish_time); emsesp::EMSESP::mqtt_.set_publish_time_boiler(newSettings.publish_time_boiler);
}
if (newSettings.publish_time_thermostat != settings.publish_time_thermostat) {
emsesp::EMSESP::mqtt_.set_publish_time_thermostat(newSettings.publish_time_thermostat);
}
if (newSettings.publish_time_solar != settings.publish_time_solar) {
emsesp::EMSESP::mqtt_.set_publish_time_solar(newSettings.publish_time_solar);
}
if (newSettings.publish_time_mixing != settings.publish_time_mixing) {
emsesp::EMSESP::mqtt_.set_publish_time_mixing(newSettings.publish_time_mixing);
}
if (newSettings.publish_time_other != settings.publish_time_other) {
emsesp::EMSESP::mqtt_.set_publish_time_other(newSettings.publish_time_other);
}
if (newSettings.publish_time_sensor != settings.publish_time_sensor) {
emsesp::EMSESP::mqtt_.set_publish_time_sensor(newSettings.publish_time_sensor);
} }
emsesp::EMSESP::mqtt_.reset_publish_fails(); // reset fail counter back to 0 emsesp::EMSESP::mqtt_.reset_publish_fails(); // reset fail counter back to 0

View File

@@ -6,12 +6,13 @@
#include <FSPersistence.h> #include <FSPersistence.h>
#include <AsyncMqttClient.h> #include <AsyncMqttClient.h>
#include <ESPUtils.h> #include <ESPUtils.h>
#include <uuid/common.h>
#include "../../src/system.h" #include "../../src/system.h"
#include "../../src/mqtt.h" #include "../../src/mqtt.h"
#include "../../src/sensors.h" #include "../../src/sensors.h"
#define MQTT_RECONNECTION_DELAY 5000 #define MQTT_RECONNECTION_DELAY 1000
#define MQTT_SETTINGS_FILE "/config/mqttSettings.json" #define MQTT_SETTINGS_FILE "/config/mqttSettings.json"
#define MQTT_SETTINGS_SERVICE_PATH "/rest/mqttSettings" #define MQTT_SETTINGS_SERVICE_PATH "/rest/mqttSettings"
@@ -85,7 +86,12 @@ class MqttSettings {
uint16_t maxTopicLength; uint16_t maxTopicLength;
// proddy EMS-ESP specific // proddy EMS-ESP specific
uint16_t publish_time; // seconds uint16_t publish_time_boiler;
uint16_t publish_time_thermostat;
uint16_t publish_time_solar;
uint16_t publish_time_mixing;
uint16_t publish_time_other;
uint16_t publish_time_sensor;
uint8_t mqtt_format; // 1=single, 2=nested, 3=ha, 4=custom uint8_t mqtt_format; // 1=single, 2=nested, 3=ha, 4=custom
uint8_t mqtt_qos; uint8_t mqtt_qos;
bool system_heartbeat; bool system_heartbeat;

View File

@@ -33,7 +33,7 @@ void NTPStatus::ntpStatus(AsyncWebServerRequest* request) {
root["server"] = sntp_getservername(0); root["server"] = sntp_getservername(0);
// device uptime in seconds // device uptime in seconds
root["uptime"] = millis() / 1000; root["uptime"] = uuid::get_uptime() / 1000;
response->setLength(); response->setLength();
request->send(response); request->send(response);

View File

@@ -16,6 +16,8 @@
#include <AsyncJson.h> #include <AsyncJson.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SecurityManager.h> #include <SecurityManager.h>
#include <uuid/common.h>
#define MAX_NTP_STATUS_SIZE 1024 #define MAX_NTP_STATUS_SIZE 1024
#define NTP_STATUS_SERVICE_PATH "/rest/ntpStatus" #define NTP_STATUS_SERVICE_PATH "/rest/ntpStatus"

View File

@@ -8,7 +8,7 @@
#elif defined(ESP8266) #elif defined(ESP8266)
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
#include <FS.h> // #include <FS.h>
#include <LittleFS.h> // proddy added #include <LittleFS.h> // proddy added
#endif #endif

View File

@@ -24,12 +24,18 @@ class DummySettings {
bool shower_alert = false; bool shower_alert = false;
bool hide_led = false; bool hide_led = false;
uint16_t publish_time = 10; // seconds uint16_t publish_time = 10; // seconds
uint8_t mqtt_format = 1; // 1=single, 2=nested, 3=ha, 4=custom uint8_t mqtt_format = 3; // 1=single, 2=nested, 3=ha, 4=custom
uint8_t mqtt_qos = 0; uint8_t mqtt_qos = 0;
String hostname = "ems-esp"; String hostname = "ems-esp";
String jwtSecret = "ems-esp"; String jwtSecret = "ems-esp";
String ssid = "ems-esp"; String ssid = "ems-esp";
String password = "ems-esp"; String password = "ems-esp";
uint16_t publish_time_boiler;
uint16_t publish_time_thermostat;
uint16_t publish_time_solar;
uint16_t publish_time_mixing;
uint16_t publish_time_other;
uint16_t publish_time_sensor;
static void read(DummySettings & settings, JsonObject & root){}; static void read(DummySettings & settings, JsonObject & root){};
static void read(DummySettings & settings){}; static void read(DummySettings & settings){};

View File

@@ -26,7 +26,7 @@ CXX_STANDARD := -std=c++11
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# Defined Symbols # Defined Symbols
#---------------------------------------------------------------------- #----------------------------------------------------------------------
DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_NO_LED DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DEMSESP_DEBUG -DEMSESP_STANDALONE
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# Sources & Files # Sources & Files

BIN
media/console.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -10,11 +10,9 @@ extra_configs =
pio_local.ini pio_local.ini
[common] [common]
;debug_flags = -DDEBUG_ESP_PORT=Serial -DDEBUG_ESP_CORE -DDEBUG_ESP_SSL -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM
debug_flags = debug_flags =
; -D EMSESP_DEBUG ; -D EMSESP_DEBUG
; -D EMSESP_FORCE_SERIAL ; -D EMSESP_FORCE_SERIAL
; -D EMSESP_NO_LED
; default platformio compile flags are: -fno-rtti -std=c++11 -Os -mlongcalls -mtext-section-literals -falign-functions=4 -ffunction-sections -fdata-sections -fno-exceptions -Wall ; default platformio compile flags are: -fno-rtti -std=c++11 -Os -mlongcalls -mtext-section-literals -falign-functions=4 -ffunction-sections -fdata-sections -fno-exceptions -Wall
build_flags = build_flags =
@@ -85,8 +83,8 @@ lib_ignore =
[env:esp32] [env:esp32]
board = esp32dev board = esp32dev
build_type = release build_type = release
; platform = espressif32 platform = espressif32
platform = https://github.com/platformio/platform-espressif32.git ; platform = https://github.com/platformio/platform-espressif32.git
board_build.partitions = min_spiffs.csv ; https://github.com/espressif/arduino-esp32/blob/master/tools/partitions/ board_build.partitions = min_spiffs.csv ; https://github.com/espressif/arduino-esp32/blob/master/tools/partitions/
lib_deps = ${common.libs_core} lib_deps = ${common.libs_core}
build_flags = ${common.build_flags} ${common.debug_flags} build_flags = ${common.build_flags} ${common.debug_flags}

0
scripts/analyze_stackdmp.py Normal file → Executable file
View File

BIN
scripts/boot_app0.bin Normal file

Binary file not shown.

Binary file not shown.

0
scripts/build_interface.py Normal file → Executable file
View File

0
scripts/clean_fw.py Normal file → Executable file
View File

0
scripts/decoder.py Normal file → Executable file
View File

0
scripts/decoder_linux.py Normal file → Executable file
View File

0
scripts/espota.py Normal file → Executable file
View File

2959
scripts/esptool.py Executable file

File diff suppressed because it is too large Load Diff

0
scripts/main_script.py Normal file → Executable file
View File

0
scripts/memanalyzer.py Normal file → Executable file
View File

View File

@@ -1,4 +0,0 @@
# python espota.py -i 10.10.10.189 --port 8267 --auth neo -f ../.pio/build/debug/firmware.bin
python espota.py --debug --progress -i 10.10.10.100 -f ../.pio/build/debug/firmware.bin

BIN
scripts/partitions.bin Normal file

Binary file not shown.

15
scripts/upload_esp32.py Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python
import subprocess
import os, argparse
print("\n** Starting upload...")
ap = argparse.ArgumentParser()
ap.add_argument("-p", "--port", required=True, help="port")
args = vars(ap.parse_args())
# esptool.py --chip esp32 --port "COM4" --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect 0x1000 bootloader_dio_40m.bin 0x8000 partitions.bin 0xe000 boot_app0.bin 0x10000 EMS-ESP-dev-esp32.bin
subprocess.call(["esptool.py", "--chip esp32", "-p", args['port'], "--baud", "921600", "--before", "default_reset", "--after", "hard_reset", "write_flash", "-z", "--flash_mode", "dio", "--flash_freq", "40m","--flash_size", "detect", "0x1000", "bootloader_dio_40m.bin", "0x8000", "partitions.bin","0xe000", "boot_app0.bin", "0x10000", "EMS-ESP-dev-esp32.bin"])
print("\n** Finished upload.")

View File

@@ -81,7 +81,7 @@ void EMSESPDevicesService::device_data(AsyncWebServerRequest * request, JsonVari
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_DEVICE_SIZE); AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_DEVICE_SIZE);
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
uint8_t id = json["id"]; // get id from selected table row uint8_t id = json["id"]; // get id from selected table row
EMSESP::device_info(id, (JsonObject &)response->getRoot()); EMSESP::device_info_web(id, (JsonObject &)response->getRoot());
#endif #endif
response->setLength(); response->setLength();
request->send(response); request->send(response);

View File

@@ -24,7 +24,7 @@
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SecurityManager.h> #include <SecurityManager.h>
#define MAX_EMSESP_DEVICE_SIZE 1536 #define MAX_EMSESP_DEVICE_SIZE 1700
#define EMSESP_DEVICES_SERVICE_PATH "/rest/allDevices" #define EMSESP_DEVICES_SERVICE_PATH "/rest/allDevices"
#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices" #define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices"

View File

@@ -36,11 +36,12 @@ void EMSESPSettings::read(EMSESPSettings & settings, JsonObject & root) {
root["master_thermostat"] = settings.master_thermostat; root["master_thermostat"] = settings.master_thermostat;
root["shower_timer"] = settings.shower_timer; root["shower_timer"] = settings.shower_timer;
root["shower_alert"] = settings.shower_alert; root["shower_alert"] = settings.shower_alert;
root["hide_led"] = settings.hide_led;
root["rx_gpio"] = settings.rx_gpio; root["rx_gpio"] = settings.rx_gpio;
root["tx_gpio"] = settings.tx_gpio; root["tx_gpio"] = settings.tx_gpio;
root["dallas_gpio"] = settings.dallas_gpio; root["dallas_gpio"] = settings.dallas_gpio;
root["dallas_parasite"] = settings.dallas_parasite;
root["led_gpio"] = settings.led_gpio; root["led_gpio"] = settings.led_gpio;
root["hide_led"] = settings.hide_led;
} }
StateUpdateResult EMSESPSettings::update(JsonObject & root, EMSESPSettings & settings) { StateUpdateResult EMSESPSettings::update(JsonObject & root, EMSESPSettings & settings) {
@@ -52,11 +53,12 @@ StateUpdateResult EMSESPSettings::update(JsonObject & root, EMSESPSettings & set
settings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT; settings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
settings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER; settings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT; settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
settings.rx_gpio = root["rx_gpio"] | EMSESP_DEFAULT_RX_GPIO; settings.rx_gpio = root["rx_gpio"] | EMSESP_DEFAULT_RX_GPIO;
settings.tx_gpio = root["tx_gpio"] | EMSESP_DEFAULT_TX_GPIO; settings.tx_gpio = root["tx_gpio"] | EMSESP_DEFAULT_TX_GPIO;
settings.dallas_gpio = root["dallas_gpio"] | EMSESP_DEFAULT_DALLAS_GPIO; settings.dallas_gpio = root["dallas_gpio"] | EMSESP_DEFAULT_DALLAS_GPIO;
settings.dallas_parasite = root["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE;
settings.led_gpio = root["led_gpio"] | EMSESP_DEFAULT_LED_GPIO; settings.led_gpio = root["led_gpio"] | EMSESP_DEFAULT_LED_GPIO;
settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
} }
@@ -68,6 +70,8 @@ void EMSESPSettingsService::onUpdate() {
// EMSESP::system_.syslog_init(); // changing SysLog will require a restart // EMSESP::system_.syslog_init(); // changing SysLog will require a restart
EMSESP::init_tx(); EMSESP::init_tx();
System::set_led(); System::set_led();
Sensors sensors_; // Dallas sensors
sensors_.start();
} }
void EMSESPSettingsService::begin() { void EMSESPSettingsService::begin() {

View File

@@ -34,6 +34,7 @@
#define EMSESP_DEFAULT_SHOWER_TIMER false #define EMSESP_DEFAULT_SHOWER_TIMER false
#define EMSESP_DEFAULT_SHOWER_ALERT false #define EMSESP_DEFAULT_SHOWER_ALERT false
#define EMSESP_DEFAULT_HIDE_LED false #define EMSESP_DEFAULT_HIDE_LED false
#define EMSESP_DEFAULT_DALLAS_PARASITE false
// Default GPIO PIN definitions // Default GPIO PIN definitions
#if defined(ESP8266) #if defined(ESP8266)
@@ -65,14 +66,15 @@ class EMSESPSettings {
uint8_t master_thermostat; uint8_t master_thermostat;
bool shower_timer; bool shower_timer;
bool shower_alert; bool shower_alert;
bool hide_led;
int8_t syslog_level; // uuid::log::Level int8_t syslog_level; // uuid::log::Level
uint32_t syslog_mark_interval; uint32_t syslog_mark_interval;
String syslog_host; String syslog_host;
uint8_t rx_gpio; uint8_t rx_gpio;
uint8_t tx_gpio; uint8_t tx_gpio;
uint8_t dallas_gpio; uint8_t dallas_gpio;
bool dallas_parasite;
uint8_t led_gpio; uint8_t led_gpio;
bool hide_led;
static void read(EMSESPSettings & settings, JsonObject & root); static void read(EMSESPSettings & settings, JsonObject & root);
static StateUpdateResult update(JsonObject & root, EMSESPSettings & settings); static StateUpdateResult update(JsonObject & root, EMSESPSettings & settings);

View File

@@ -88,9 +88,13 @@ void EMSESPShell::display_banner() {
// load the list of commands // load the list of commands
add_console_commands(); add_console_commands();
// turn off watch // turn off watch, unless is test mode
emsesp::EMSESP::watch_id(WATCH_ID_NONE); emsesp::EMSESP::watch_id(WATCH_ID_NONE);
#if defined(EMSESP_STANDALONE)
emsesp::EMSESP::watch(EMSESP::WATCH_ON);
#else
emsesp::EMSESP::watch(EMSESP::WATCH_OFF); emsesp::EMSESP::watch(EMSESP::WATCH_OFF);
#endif
} }
// pre-loads all the console commands into the MAIN context // pre-loads all the console commands into the MAIN context
@@ -240,6 +244,17 @@ void EMSESPShell::add_console_commands() {
}); });
}); });
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
flash_string_vector{F_(read)},
flash_string_vector{F_(deviceid_mandatory), F_(typeid_mandatory)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint8_t device_id = Helpers::hextoint(arguments.front().c_str());
uint16_t type_id = Helpers::hextoint(arguments.back().c_str());
EMSESP::set_read_id(type_id);
EMSESP::send_read_request(type_id, device_id);
});
/* /*
* add all the submenu contexts... * add all the submenu contexts...
*/ */
@@ -393,7 +408,7 @@ void Console::load_standard_commands(unsigned int context) {
flash_string_vector{F_(watch)}, flash_string_vector{F_(watch)},
flash_string_vector{F_(watch_format_optional), F_(watchid_optional)}, flash_string_vector{F_(watch_format_optional), F_(watchid_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
uint16_t watch_id; uint16_t watch_id = WATCH_ID_NONE;
if (!arguments.empty()) { if (!arguments.empty()) {
// get raw/pretty // get raw/pretty
@@ -403,16 +418,16 @@ void Console::load_standard_commands(unsigned int context) {
emsesp::EMSESP::watch(EMSESP::WATCH_ON); // on emsesp::EMSESP::watch(EMSESP::WATCH_ON); // on
} else if (arguments[0] == read_flash_string(F_(off))) { } else if (arguments[0] == read_flash_string(F_(off))) {
emsesp::EMSESP::watch(EMSESP::WATCH_OFF); // off emsesp::EMSESP::watch(EMSESP::WATCH_OFF); // off
} else { } else if (emsesp::EMSESP::watch() == EMSESP::WATCH_OFF) {
shell.printfln(F_(invalid_watch)); shell.printfln(F_(invalid_watch));
return; return;
} else {
watch_id = Helpers::hextoint(arguments[0].c_str());
} }
if (arguments.size() == 2) { if (arguments.size() == 2) {
// get the watch_id if its set // get the watch_id if its set
watch_id = Helpers::hextoint(arguments[1].c_str()); watch_id = Helpers::hextoint(arguments[1].c_str());
} else {
watch_id = WATCH_ID_NONE;
} }
emsesp::EMSESP::watch_id(watch_id); emsesp::EMSESP::watch_id(watch_id);
@@ -436,7 +451,9 @@ void Console::load_standard_commands(unsigned int context) {
} }
watch_id = emsesp::EMSESP::watch_id(); watch_id = emsesp::EMSESP::watch_id();
if (watch_id != WATCH_ID_NONE) { if (watch_id > 0x80) {
shell.printfln(F("Filtering only telegrams that match a telegram type of 0x%02X"), watch_id);
} else if (watch_id != WATCH_ID_NONE) {
shell.printfln(F("Filtering only telegrams that match a device ID or telegram type of 0x%02X"), watch_id); shell.printfln(F("Filtering only telegrams that match a device ID or telegram type of 0x%02X"), watch_id);
} }
}); });
@@ -526,6 +543,7 @@ std::string EMSESPStreamConsole::console_name() {
} }
// Start up telnet and logging // Start up telnet and logging
// Log order is off, err, warning, notice, info, debug, trace, all
void Console::start() { void Console::start() {
// if we've detected a boot into safe mode on ESP8266, start the Serial console too // if we've detected a boot into safe mode on ESP8266, start the Serial console too
// Serial is always on with the ESP32 as it has 2 UARTs // Serial is always on with the ESP32 as it has 2 UARTs
@@ -540,10 +558,15 @@ void Console::start() {
#ifndef ESP8266 #ifndef ESP8266
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
shell->log_level(uuid::log::Level::DEBUG); // order is: err, warning, notice, info, debug, trace, all shell->log_level(uuid::log::Level::DEBUG);
#endif #endif
#endif #endif
#if defined(EMSESP_FORCE_SERIAL)
shell->log_level(uuid::log::Level::DEBUG);
#endif
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
// always start in su/admin mode when running tests // always start in su/admin mode when running tests
shell->add_flags(CommandFlags::ADMIN); shell->add_flags(CommandFlags::ADMIN);
@@ -558,7 +581,7 @@ void Console::start() {
#endif #endif
// turn watch off in case it was still set in the last session // turn watch off in case it was still set in the last session
emsesp::EMSESP::watch(EMSESP::WATCH_OFF); // emsesp::EMSESP::watch(EMSESP::WATCH_OFF);
} }
// handles telnet sync and logging to console // handles telnet sync and logging to console

View File

@@ -39,7 +39,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMaintenanceStatus(t); }); register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMaintenanceStatus(t); });
register_telegram_type(0x2A, F("MC10Status"), false, [&](std::shared_ptr<const Telegram> t) { process_MC10Status(t); }); register_telegram_type(0x2A, F("MC10Status"), false, [&](std::shared_ptr<const Telegram> t) { process_MC10Status(t); });
register_telegram_type(0x33, F("UBAParameterWW"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParameterWW(t); }); register_telegram_type(0x33, F("UBAParameterWW"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParameterWW(t); });
register_telegram_type(0x14, F("UBATotalUptime"), false, [&](std::shared_ptr<const Telegram> t) { process_UBATotalUptime(t); }); register_telegram_type(0x14, F("UBATotalUptime"), true, [&](std::shared_ptr<const Telegram> t) { process_UBATotalUptime(t); });
register_telegram_type(0x35, F("UBAFlags"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAFlags(t); }); register_telegram_type(0x35, F("UBAFlags"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAFlags(t); });
register_telegram_type(0x15, F("UBAMaintenanceData"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMaintenanceData(t); }); register_telegram_type(0x15, F("UBAMaintenanceData"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMaintenanceData(t); });
register_telegram_type(0x16, F("UBAParameters"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParameters(t); }); register_telegram_type(0x16, F("UBAParameters"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParameters(t); });
@@ -64,6 +64,14 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_mqtt_cmd(F("boilhystoff"), [&](const char * value, const int8_t id) { set_hyst_off(value, id); }); register_mqtt_cmd(F("boilhystoff"), [&](const char * value, const int8_t id) { set_hyst_off(value, id); });
register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { set_burn_period(value, id); }); register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { set_burn_period(value, id); });
register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { set_pump_delay(value, id); }); register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { set_pump_delay(value, id); });
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
mqtt_format_ = settings.mqtt_format; // single, nested or ha
if (mqtt_format_ == MQTT_format::HA) {
register_mqtt_ha_config();
}
});
} }
// add submenu context // add submenu context
@@ -77,9 +85,36 @@ void Boiler::add_context_menu() {
}); });
} }
void Boiler::device_info(JsonArray & root) { // create the config topic for Home Assistant MQTT Discovery
// homeassistant/sensor/ems-esp/boiler
// state is /state
// config is /config
void Boiler::register_mqtt_ha_config() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
/*
* not finished yet - see https://github.com/proddy/EMS-ESP/issues/288
doc["name"] = "boiler";
doc["uniq_id"] = "boiler";
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
Mqtt::publish("homeassistant/sensor/ems-esp/boiler/config", doc, true); // publish the config payload with retain flag
*/
}
void Boiler::device_info_web(JsonArray & root) {
JsonObject dataElement; JsonObject dataElement;
if (serviceCodeChar_[0] && Helpers::hasValue(serviceCode_)) {
dataElement = root.createNestedObject();
dataElement["name"] = F("Service Code");
char s[12];
snprintf_P(s, 12, PSTR("%s (%d)"), serviceCodeChar_, serviceCode_);
dataElement["value"] = s;
}
if (Helpers::hasValue(tap_water_active_, EMS_VALUE_BOOL)) { if (Helpers::hasValue(tap_water_active_, EMS_VALUE_BOOL)) {
dataElement = root.createNestedObject(); dataElement = root.createNestedObject();
dataElement["name"] = F("Hot tap water"); dataElement["name"] = F("Hot tap water");
@@ -98,6 +133,8 @@ void Boiler::device_info(JsonArray & root) {
render_value_json(root, "", F("Warm Water set temperature"), wWSetTmp_, F_(degrees)); render_value_json(root, "", F("Warm Water set temperature"), wWSetTmp_, F_(degrees));
render_value_json(root, "", F("Warm Water current temperature (intern)"), wWCurTmp_, F_(degrees), 10); render_value_json(root, "", F("Warm Water current temperature (intern)"), wWCurTmp_, F_(degrees), 10);
render_value_json(root, "", F("Warm Water current temperature (extern)"), wWCurTmp2_, F_(degrees), 10); render_value_json(root, "", F("Warm Water current temperature (extern)"), wWCurTmp2_, F_(degrees), 10);
render_value_json(root, "", F("Pump modulation"), pumpMod_, F_(percent));
render_value_json(root, "", F("Heat Pump modulation"), pumpMod2_, F_(percent));
} }
// publish values via MQTT // publish values via MQTT
@@ -196,8 +233,8 @@ void Boiler::publish_values() {
if (Helpers::hasValue(wWOneTime_, EMS_VALUE_BOOL)) { if (Helpers::hasValue(wWOneTime_, EMS_VALUE_BOOL)) {
doc["wWOnetime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL); doc["wWOnetime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL);
} }
if (Helpers::hasValue(wWDesinfecting_, EMS_VALUE_BOOL)) { if (Helpers::hasValue(wWDisinfecting_, EMS_VALUE_BOOL)) {
doc["wWDesinfecting"] = Helpers::render_value(s, wWDesinfecting_, EMS_VALUE_BOOL); doc["wWDisinfecting"] = Helpers::render_value(s, wWDisinfecting_, EMS_VALUE_BOOL);
} }
if (Helpers::hasValue(wWReadiness_, EMS_VALUE_BOOL)) { if (Helpers::hasValue(wWReadiness_, EMS_VALUE_BOOL)) {
doc["wWReady"] = Helpers::render_value(s, wWReadiness_, EMS_VALUE_BOOL); doc["wWReady"] = Helpers::render_value(s, wWReadiness_, EMS_VALUE_BOOL);
@@ -287,12 +324,16 @@ void Boiler::publish_values() {
// if we have data, publish it // if we have data, publish it
if (!doc.isNull()) { if (!doc.isNull()) {
Mqtt::publish("boiler_data", doc); Mqtt::publish(F("boiler_data"), doc);
} }
} }
// called after a process command is called, to check values and see if we need to force an MQTT publish // called after a process command is called, to check values and see if we need to force an MQTT publish
bool Boiler::updated_values() { bool Boiler::updated_values() {
if (changed_) {
changed_ = false;
return true;
}
return false; return false;
} }
@@ -353,7 +394,7 @@ void Boiler::show_values(uuid::console::Shell & shell) {
shell.printfln(F(" Warm Water active time: %d days %d hours %d minutes"), wWWorkM_ / 1440, (wWWorkM_ % 1440) / 60, wWWorkM_ % 60); shell.printfln(F(" Warm Water active time: %d days %d hours %d minutes"), wWWorkM_ / 1440, (wWWorkM_ % 1440) / 60, wWWorkM_ % 60);
} }
print_value(shell, 2, F("Warm Water charging"), wWHeat_, nullptr, EMS_VALUE_BOOL); print_value(shell, 2, F("Warm Water charging"), wWHeat_, nullptr, EMS_VALUE_BOOL);
print_value(shell, 2, F("Warm Water disinfecting"), wWDesinfecting_, nullptr, EMS_VALUE_BOOL); print_value(shell, 2, F("Warm Water disinfecting"), wWDisinfecting_, nullptr, EMS_VALUE_BOOL);
print_value(shell, 2, F("Selected flow temperature"), selFlowTemp_, F_(degrees)); print_value(shell, 2, F("Selected flow temperature"), selFlowTemp_, F_(degrees));
print_value(shell, 2, F("Current flow temperature"), curFlowTemp_, F_(degrees), 10); print_value(shell, 2, F("Current flow temperature"), curFlowTemp_, F_(degrees), 10);
print_value(shell, 2, F("Max boiler temperature"), boilTemp_, F_(degrees), 10); print_value(shell, 2, F("Max boiler temperature"), boilTemp_, F_(degrees), 10);
@@ -396,7 +437,7 @@ void Boiler::show_values(uuid::console::Shell & shell) {
print_value(shell, 2, F("Exhaust temperature"), exhaustTemp_, F_(degrees), 10); print_value(shell, 2, F("Exhaust temperature"), exhaustTemp_, F_(degrees), 10);
print_value(shell, 2, F("Pump modulation"), pumpMod_, F_(percent)); print_value(shell, 2, F("Pump modulation"), pumpMod_, F_(percent));
print_value(shell, 2, F("Pump modulation2"), pumpMod2_, F_(percent)); print_value(shell, 2, F("Heat Pump modulation"), pumpMod2_, F_(percent));
print_value(shell, 2, F("Burner # starts"), burnStarts_, nullptr); print_value(shell, 2, F("Burner # starts"), burnStarts_, nullptr);
if (Helpers::hasValue(burnWorkMin_)) { if (Helpers::hasValue(burnWorkMin_)) {
shell.printfln(F(" Total burner operating time: %d days %d hours %d minutes"), burnWorkMin_ / 1440, (burnWorkMin_ % 1440) / 60, burnWorkMin_ % 60); shell.printfln(F(" Total burner operating time: %d days %d hours %d minutes"), burnWorkMin_ / 1440, (burnWorkMin_ % 1440) / 60, burnWorkMin_ % 60);
@@ -435,54 +476,54 @@ void Boiler::check_active() {
uint8_t latest_boilerState = (tap_water_active_ << 1) + heating_active_; uint8_t latest_boilerState = (tap_water_active_ << 1) + heating_active_;
if (latest_boilerState != last_boilerState) { if (latest_boilerState != last_boilerState) {
last_boilerState = latest_boilerState; last_boilerState = latest_boilerState;
Mqtt::publish("tapwater_active", tap_water_active_); Mqtt::publish(F("tapwater_active"), tap_water_active_);
Mqtt::publish("heating_active", heating_active_); Mqtt::publish(F("heating_active"), heating_active_);
} }
} }
} }
// 0x33 // 0x33
void Boiler::process_UBAParameterWW(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAParameterWW(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(wWActivated_, 1); // 0xFF means on changed_ |= telegram->read_value(wWActivated_, 1); // 0xFF means on
telegram->read_value(wWCircPump_, 6); // 0xFF means on changed_ |= telegram->read_value(wWCircPump_, 6); // 0xFF means on
telegram->read_value(wWCircPumpMode_, 7); // 1=1x3min... 6=6x3min, 7=continuous changed_ |= telegram->read_value(wWCircPumpMode_, 7); // 1=1x3min... 6=6x3min, 7=continuous
telegram->read_value(wWCircPumpType_, 10); // 0 = charge pump, 0xff = 3-way valve changed_ |= telegram->read_value(wWCircPumpType_, 10); // 0 = charge pump, 0xff = 3-way valve
telegram->read_value(wWSelTemp_, 2); changed_ |= telegram->read_value(wWSelTemp_, 2);
telegram->read_value(wWDisinfectTemp_, 8); changed_ |= telegram->read_value(wWDisinfectTemp_, 8);
telegram->read_value(wWComfort_, 9); changed_ |= telegram->read_value(wWComfort_, 9);
} }
// 0x18 // 0x18
void Boiler::process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(selFlowTemp_, 0); changed_ |= telegram->read_value(selFlowTemp_, 0);
telegram->read_value(curFlowTemp_, 1); changed_ |= telegram->read_value(curFlowTemp_, 1);
telegram->read_value(selBurnPow_, 3); // burn power max setting changed_ |= telegram->read_value(selBurnPow_, 3); // burn power max setting
telegram->read_value(curBurnPow_, 4); changed_ |= telegram->read_value(curBurnPow_, 4);
telegram->read_bitvalue(burnGas_, 7, 0); changed_ |= telegram->read_bitvalue(burnGas_, 7, 0);
telegram->read_bitvalue(fanWork_, 7, 2); changed_ |= telegram->read_bitvalue(fanWork_, 7, 2);
telegram->read_bitvalue(ignWork_, 7, 3); changed_ |= telegram->read_bitvalue(ignWork_, 7, 3);
telegram->read_bitvalue(heatPmp_, 7, 5); changed_ |= telegram->read_bitvalue(heatPmp_, 7, 5);
telegram->read_bitvalue(wWHeat_, 7, 6); changed_ |= telegram->read_bitvalue(wWHeat_, 7, 6);
telegram->read_bitvalue(wWCirc_, 7, 7); changed_ |= telegram->read_bitvalue(wWCirc_, 7, 7);
// warm water storage sensors (if present) // warm water storage sensors (if present)
// wwStorageTemp2 is also used by some brands as the boiler temperature - see https://github.com/proddy/EMS-ESP/issues/206 // wwStorageTemp2 is also used by some brands as the boiler temperature - see https://github.com/proddy/EMS-ESP/issues/206
telegram->read_value(wwStorageTemp1_, 9); // 0x8300 if not available changed_ |= telegram->read_value(wwStorageTemp1_, 9); // 0x8300 if not available
telegram->read_value(wwStorageTemp2_, 11); // 0x8000 if not available - this is boiler temp changed_ |= telegram->read_value(wwStorageTemp2_, 11); // 0x8000 if not available - this is boiler temp
telegram->read_value(retTemp_, 13); changed_ |= telegram->read_value(retTemp_, 13);
telegram->read_value(flameCurr_, 15); changed_ |= telegram->read_value(flameCurr_, 15);
telegram->read_value(serviceCode_, 20); changed_ |= telegram->read_value(serviceCode_, 20);
// system pressure. FF means missing // system pressure. FF means missing
telegram->read_value(sysPress_, 17); // is *10 changed_ |= telegram->read_value(sysPress_, 17); // is *10
// read the service code / installation status as appears on the display // read the service code / installation status as appears on the display
if ((telegram->message_length > 18) && (telegram->offset == 0)) { if ((telegram->message_length > 18) && (telegram->offset == 0)) {
serviceCodeChar_[0] = char(telegram->message_data[18]); // ascii character 1 changed_ |= telegram->read_value(serviceCodeChar_[0], 18);
serviceCodeChar_[1] = char(telegram->message_data[19]); // ascii character 2 changed_ |= telegram->read_value(serviceCodeChar_[1], 19);
serviceCodeChar_[2] = '\0'; // null terminate string serviceCodeChar_[2] = '\0'; // null terminate string
} }
// at this point do a quick check to see if the hot water or heating is active // at this point do a quick check to see if the hot water or heating is active
@@ -494,22 +535,22 @@ void Boiler::process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram) {
* received only after requested (not broadcasted) * received only after requested (not broadcasted)
*/ */
void Boiler::process_UBATotalUptime(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBATotalUptime(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(UBAuptime_, 0, 3); // force to 3 bytes changed_ |= telegram->read_value(UBAuptime_, 0, 3); // force to 3 bytes
} }
/* /*
* UBAParameters - type 0x16 * UBAParameters - type 0x16
*/ */
void Boiler::process_UBAParameters(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAParameters(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(heating_temp_, 1); changed_ |= telegram->read_value(heating_temp_, 1);
telegram->read_value(burnPowermax_, 2); changed_ |= telegram->read_value(burnPowermax_, 2);
telegram->read_value(burnPowermin_, 3); changed_ |= telegram->read_value(burnPowermin_, 3);
telegram->read_value(boilTemp_off_, 4); changed_ |= telegram->read_value(boilTemp_off_, 4);
telegram->read_value(boilTemp_on_, 5); changed_ |= telegram->read_value(boilTemp_on_, 5);
telegram->read_value(burnPeriod_, 6); changed_ |= telegram->read_value(burnPeriod_, 6);
telegram->read_value(pumpDelay_, 8); changed_ |= telegram->read_value(pumpDelay_, 8);
telegram->read_value(pump_mod_max_, 9); changed_ |= telegram->read_value(pump_mod_max_, 9);
telegram->read_value(pump_mod_min_, 10); changed_ |= telegram->read_value(pump_mod_min_, 10);
} }
/* /*
@@ -517,19 +558,19 @@ void Boiler::process_UBAParameters(std::shared_ptr<const Telegram> telegram) {
* received every 10 seconds * received every 10 seconds
*/ */
void Boiler::process_UBAMonitorWW(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAMonitorWW(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(wWSetTmp_, 0); changed_ |= telegram->read_value(wWSetTmp_, 0);
telegram->read_value(wWCurTmp_, 1); changed_ |= telegram->read_value(wWCurTmp_, 1);
telegram->read_value(wWCurTmp2_, 3); changed_ |= telegram->read_value(wWCurTmp2_, 3);
telegram->read_value(wWCurFlow_, 9); changed_ |= telegram->read_value(wWCurFlow_, 9);
telegram->read_value(wWWorkM_, 10, 3); // force to 3 bytes changed_ |= telegram->read_value(wWWorkM_, 10, 3); // force to 3 bytes
telegram->read_value(wWStarts_, 13, 3); // force to 3 bytes changed_ |= telegram->read_value(wWStarts_, 13, 3); // force to 3 bytes
telegram->read_bitvalue(wWOneTime_, 5, 1); changed_ |= telegram->read_bitvalue(wWOneTime_, 5, 1);
telegram->read_bitvalue(wWDesinfecting_, 5, 2); changed_ |= telegram->read_bitvalue(wWDisinfecting_, 5, 2);
telegram->read_bitvalue(wWReadiness_, 5, 3); changed_ |= telegram->read_bitvalue(wWReadiness_, 5, 3);
telegram->read_bitvalue(wWRecharging_, 5, 4); changed_ |= telegram->read_bitvalue(wWRecharging_, 5, 4);
telegram->read_bitvalue(wWTemperatureOK_, 5, 5); changed_ |= telegram->read_bitvalue(wWTemperatureOK_, 5, 5);
} }
/* /*
@@ -537,18 +578,18 @@ void Boiler::process_UBAMonitorWW(std::shared_ptr<const Telegram> telegram) {
* Still to figure out are: serviceCode, retTemp, sysPress * Still to figure out are: serviceCode, retTemp, sysPress
*/ */
void Boiler::process_UBAMonitorFastPlus(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAMonitorFastPlus(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(selFlowTemp_, 6); changed_ |= telegram->read_value(selFlowTemp_, 6);
telegram->read_bitvalue(burnGas_, 11, 0); changed_ |= telegram->read_bitvalue(burnGas_, 11, 0);
telegram->read_bitvalue(wWHeat_, 11, 2); changed_ |= telegram->read_bitvalue(wWHeat_, 11, 2);
telegram->read_value(curBurnPow_, 10); changed_ |= telegram->read_value(curBurnPow_, 10);
telegram->read_value(selBurnPow_, 9); changed_ |= telegram->read_value(selBurnPow_, 9);
telegram->read_value(curFlowTemp_, 7); changed_ |= telegram->read_value(curFlowTemp_, 7);
telegram->read_value(flameCurr_, 19); changed_ |= telegram->read_value(flameCurr_, 19);
// read the service code / installation status as appears on the display // read the service code / installation status as appears on the display
if ((telegram->message_length > 4) && (telegram->offset == 0)) { if ((telegram->message_length > 4) && (telegram->offset == 0)) {
serviceCodeChar_[0] = char(telegram->message_data[4]); // ascii character 1 changed_ |= telegram->read_value(serviceCodeChar_[0], 4);
serviceCodeChar_[1] = char(telegram->message_data[5]); // ascii character 2 changed_ |= telegram->read_value(serviceCodeChar_[1], 5);
serviceCodeChar_[2] = '\0'; serviceCodeChar_[2] = '\0';
} }
@@ -564,79 +605,79 @@ void Boiler::process_UBAMonitorFastPlus(std::shared_ptr<const Telegram> telegram
* 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 17 19 20 21 22 23 24 * 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 17 19 20 21 22 23 24
*/ */
void Boiler::process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(extTemp_, 0); changed_ |= telegram->read_value(extTemp_, 0);
telegram->read_value(boilTemp_, 2); changed_ |= telegram->read_value(boilTemp_, 2);
telegram->read_value(exhaustTemp_, 4); changed_ |= telegram->read_value(exhaustTemp_, 4);
telegram->read_value(switchTemp_, 25); // only if there is a mixing module present changed_ |= telegram->read_value(switchTemp_, 25); // only if there is a mixing module present
telegram->read_value(pumpMod_, 9); changed_ |= telegram->read_value(pumpMod_, 9);
telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes changed_ |= telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes
telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes changed_ |= telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes
telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes changed_ |= telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes
} }
/* /*
* UBAMonitorSlowPlus2 - type 0xE3 * UBAMonitorSlowPlus2 - type 0xE3
*/ */
void Boiler::process_UBAMonitorSlowPlus2(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAMonitorSlowPlus2(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(pumpMod2_, 13); changed_ |= telegram->read_value(pumpMod2_, 13); // Heat Pump Modulation
} }
/* /*
* UBAMonitorSlowPlus - type 0xE5 - central heating monitor EMS+ * UBAMonitorSlowPlus - type 0xE5 - central heating monitor EMS+
*/ */
void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram) {
telegram->read_bitvalue(fanWork_, 2, 2); changed_ |= telegram->read_bitvalue(fanWork_, 2, 2);
telegram->read_bitvalue(ignWork_, 2, 3); changed_ |= telegram->read_bitvalue(ignWork_, 2, 3);
telegram->read_bitvalue(heatPmp_, 2, 5); changed_ |= telegram->read_bitvalue(heatPmp_, 2, 5);
telegram->read_bitvalue(wWCirc_, 2, 7); changed_ |= telegram->read_bitvalue(wWCirc_, 2, 7);
telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes changed_ |= telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes
telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes changed_ |= telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes
telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes changed_ |= telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes
telegram->read_value(pumpMod_, 25); changed_ |= telegram->read_value(pumpMod_, 25);
} }
// 0xE9 - DHW Status // 0xE9 - DHW Status
// e.g. 08 00 E9 00 37 01 F6 01 ED 00 00 00 00 41 3C 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 (CRC=77) #data=27 // e.g. 08 00 E9 00 37 01 F6 01 ED 00 00 00 00 41 3C 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 (CRC=77) #data=27
void Boiler::process_UBADHWStatus(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBADHWStatus(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(wWSetTmp_, 0); changed_ |= telegram->read_value(wWSetTmp_, 0);
telegram->read_value(wWCurTmp_, 1); changed_ |= telegram->read_value(wWCurTmp_, 1);
telegram->read_value(wWCurTmp2_, 3); changed_ |= telegram->read_value(wWCurTmp2_, 3);
telegram->read_value(wWWorkM_, 17, 3); // force to 3 bytes changed_ |= telegram->read_value(wWWorkM_, 17, 3); // force to 3 bytes
telegram->read_value(wWStarts_, 14, 3); // force to 3 bytes changed_ |= telegram->read_value(wWStarts_, 14, 3); // force to 3 bytes
telegram->read_bitvalue(wWOneTime_, 12, 2); changed_ |= telegram->read_bitvalue(wWOneTime_, 12, 2);
telegram->read_bitvalue(wWDesinfecting_, 12, 3); changed_ |= telegram->read_bitvalue(wWDisinfecting_, 12, 3);
telegram->read_bitvalue(wWReadiness_, 12, 4); changed_ |= telegram->read_bitvalue(wWReadiness_, 12, 4);
telegram->read_bitvalue(wWRecharging_, 13, 4); changed_ |= telegram->read_bitvalue(wWRecharging_, 13, 4);
telegram->read_bitvalue(wWTemperatureOK_, 13, 5); changed_ |= telegram->read_bitvalue(wWTemperatureOK_, 13, 5);
telegram->read_bitvalue(wWCircPump_, 13, 2); changed_ |= telegram->read_bitvalue(wWCircPump_, 13, 2);
telegram->read_value(wWActivated_, 20); changed_ |= telegram->read_value(wWActivated_, 20);
telegram->read_value(wWSelTemp_, 10); changed_ |= telegram->read_value(wWSelTemp_, 10);
telegram->read_value(wWDisinfectTemp_, 9); changed_ |= telegram->read_value(wWDisinfectTemp_, 9);
} }
// 0x2A - MC10Status // 0x2A - MC10Status
// e.g. 88 00 2A 00 00 00 00 00 00 00 00 00 D2 00 00 80 00 00 01 08 80 00 02 47 00 // e.g. 88 00 2A 00 00 00 00 00 00 00 00 00 D2 00 00 80 00 00 01 08 80 00 02 47 00
// see https://github.com/proddy/EMS-ESP/issues/397 // see https://github.com/proddy/EMS-ESP/issues/397
void Boiler::process_MC10Status(std::shared_ptr<const Telegram> telegram) { void Boiler::process_MC10Status(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(wwMixTemperature_, 14); changed_ |= telegram->read_value(wwMixTemperature_, 14);
telegram->read_value(wwBufferBoilerTemperature_, 18); changed_ |= telegram->read_value(wwBufferBoilerTemperature_, 18);
} }
/* /*
* UBAOutdoorTemp - type 0xD1 - external temperature EMS+ * UBAOutdoorTemp - type 0xD1 - external temperature EMS+
*/ */
void Boiler::process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(extTemp_, 0); changed_ |= telegram->read_value(extTemp_, 0);
} }
// UBASetPoint 0x1A // UBASetPoint 0x1A
void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) { void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(setFlowTemp_, 0); // boiler set temp from thermostat changed_ |= telegram->read_value(setFlowTemp_, 0); // boiler set temp from thermostat
telegram->read_value(setBurnPow_, 1); // max output power in % changed_ |= telegram->read_value(setBurnPow_, 1); // max output power in %
telegram->read_value(setWWPumpPow_, 2); // ww pump speed/power? changed_ |= telegram->read_value(setWWPumpPow_, 2); // ww pump speed/power?
} }
#pragma GCC diagnostic push #pragma GCC diagnostic push
@@ -683,8 +724,8 @@ void Boiler::set_warmwater_temp(const char * value, const int8_t id) {
} }
LOG_INFO(F("Setting boiler warm water temperature to %d C"), v); LOG_INFO(F("Setting boiler warm water temperature to %d C"), v);
write_command(EMS_TYPE_UBAParameterWW, 2, v); write_command(EMS_TYPE_UBAParameterWW, 2, v, EMS_TYPE_UBAParameterWW);
write_command(EMS_TYPE_UBAFlags, 3, v); // for i9000, see #397 write_command(EMS_TYPE_UBAFlags, 3, v, EMS_TYPE_UBAParameterWW); // for i9000, see #397
} }
// flow temp // flow temp
@@ -695,7 +736,7 @@ void Boiler::set_flow_temp(const char * value, const int8_t id) {
} }
LOG_INFO(F("Setting boiler flow temperature to %d C"), v); LOG_INFO(F("Setting boiler flow temperature to %d C"), v);
write_command(EMS_TYPE_UBASetPoints, 0, v); write_command(EMS_TYPE_UBASetPoints, 0, v, EMS_TYPE_UBASetPoints);
} }
// set min boiler output // set min boiler output
@@ -705,7 +746,7 @@ void Boiler::set_min_power(const char * value, const int8_t id) {
return; return;
} }
LOG_INFO(F("Setting boiler min power to "), v); LOG_INFO(F("Setting boiler min power to "), v);
write_command(EMS_TYPE_UBAParameters, 3, v); write_command(EMS_TYPE_UBAParameters, 3, v, EMS_TYPE_UBAParameters);
} }
// set max temp // set max temp
@@ -716,10 +757,10 @@ void Boiler::set_max_power(const char * value, const int8_t id) {
} }
LOG_INFO(F("Setting boiler max power to %d C"), v); LOG_INFO(F("Setting boiler max power to %d C"), v);
write_command(EMS_TYPE_UBAParameters, 2, v); write_command(EMS_TYPE_UBAParameters, 2, v, EMS_TYPE_UBAParameters);
} }
// set oiler on hysteresis // set boiler on hysteresis
void Boiler::set_hyst_on(const char * value, const int8_t id) { void Boiler::set_hyst_on(const char * value, const int8_t id) {
int v = 0; int v = 0;
if (!Helpers::value2number(value, v)) { if (!Helpers::value2number(value, v)) {
@@ -727,7 +768,7 @@ void Boiler::set_hyst_on(const char * value, const int8_t id) {
} }
LOG_INFO(F("Setting boiler hysteresis on to %d C"), v); LOG_INFO(F("Setting boiler hysteresis on to %d C"), v);
write_command(EMS_TYPE_UBAParameters, 5, v); write_command(EMS_TYPE_UBAParameters, 5, v, EMS_TYPE_UBAParameters);
} }
// set boiler off hysteresis // set boiler off hysteresis
@@ -738,7 +779,7 @@ void Boiler::set_hyst_off(const char * value, const int8_t id) {
} }
LOG_INFO(F("Setting boiler hysteresis off to %d C"), v); LOG_INFO(F("Setting boiler hysteresis off to %d C"), v);
write_command(EMS_TYPE_UBAParameters, 4, v); write_command(EMS_TYPE_UBAParameters, 4, v, EMS_TYPE_UBAParameters);
} }
// set min burner period // set min burner period
@@ -749,7 +790,7 @@ void Boiler::set_burn_period(const char * value, const int8_t id) {
} }
LOG_INFO(F("Setting burner min. period to %d min"), v); LOG_INFO(F("Setting burner min. period to %d min"), v);
write_command(EMS_TYPE_UBAParameters, 6, v); write_command(EMS_TYPE_UBAParameters, 6, v, EMS_TYPE_UBAParameters);
} }
// set pump delay // set pump delay
@@ -760,7 +801,7 @@ void Boiler::set_pump_delay(const char * value, const int8_t id) {
} }
LOG_INFO(F("Setting boiler pump delay to %d min"), v); LOG_INFO(F("Setting boiler pump delay to %d min"), v);
write_command(EMS_TYPE_UBAParameters, 8, v); write_command(EMS_TYPE_UBAParameters, 8, v, EMS_TYPE_UBAParameters);
} }
// note some boilers do not have this setting, than it's done by thermostat // note some boilers do not have this setting, than it's done by thermostat
@@ -782,7 +823,7 @@ void Boiler::set_warmwater_mode(const char * value, const int8_t id) {
} else { } else {
return; // do nothing return; // do nothing
} }
write_command(EMS_TYPE_UBAParameterWW, 9, set); write_command(EMS_TYPE_UBAParameterWW, 9, set, EMS_TYPE_UBAParameterWW);
} }
// turn on/off warm water // turn on/off warm water
@@ -801,7 +842,7 @@ void Boiler::set_warmwater_activated(const char * value, const int8_t id) {
} else { } else {
n = (v ? 0xFF : 0x00); // 0xFF is on, 0x00 is off n = (v ? 0xFF : 0x00); // 0xFF is on, 0x00 is off
} }
write_command(EMS_TYPE_UBAParameterWW, 1, n); write_command(EMS_TYPE_UBAParameterWW, 1, n, EMS_TYPE_UBAParameterWW);
} }
// Activate / De-activate the Warm Tap Water // Activate / De-activate the Warm Tap Water
@@ -846,7 +887,7 @@ void Boiler::set_warmwater_onetime(const char * value, const int8_t id) {
} }
LOG_INFO(F("Setting boiler warm water OneTime loading %s"), v ? "on" : "off"); LOG_INFO(F("Setting boiler warm water OneTime loading %s"), v ? "on" : "off");
write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02)); write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02), 0x18);
} }
// Activate / De-activate circulation of warm water 0x35 // Activate / De-activate circulation of warm water 0x35
@@ -858,7 +899,7 @@ void Boiler::set_warmwater_circulation(const char * value, const int8_t id) {
} }
LOG_INFO(F("Setting boiler warm water circulation %s"), v ? "on" : "off"); LOG_INFO(F("Setting boiler warm water circulation %s"), v ? "on" : "off");
write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02)); write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02), 0x18);
} }
// add console commands // add console commands

View File

@@ -40,7 +40,7 @@ class Boiler : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell); virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values(); virtual void publish_values();
virtual void device_info(JsonArray & root); virtual void device_info_web(JsonArray & root);
virtual bool updated_values(); virtual bool updated_values();
virtual void add_context_menu(); virtual void add_context_menu();
@@ -48,8 +48,12 @@ class Boiler : public EMSdevice {
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
void console_commands(Shell & shell, unsigned int context); void console_commands(Shell & shell, unsigned int context);
void register_mqtt_ha_config();
void check_active();
uint8_t last_boilerState = 0xFF; // remember last state of heating and warm water on/off uint8_t last_boilerState = 0xFF; // remember last state of heating and warm water on/off
uint8_t mqtt_format_; // single, nested or ha
bool changed_ = false;
static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33; static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33;
static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D; static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D;
@@ -108,7 +112,7 @@ class Boiler : public EMSdevice {
uint32_t wWStarts_ = EMS_VALUE_ULONG_NOTSET; // Warm Water # starts uint32_t wWStarts_ = EMS_VALUE_ULONG_NOTSET; // Warm Water # starts
uint32_t wWWorkM_ = EMS_VALUE_ULONG_NOTSET; // Warm Water # minutes uint32_t wWWorkM_ = EMS_VALUE_ULONG_NOTSET; // Warm Water # minutes
uint8_t wWOneTime_ = EMS_VALUE_BOOL_NOTSET; // Warm Water one time function on/off uint8_t wWOneTime_ = EMS_VALUE_BOOL_NOTSET; // Warm Water one time function on/off
uint8_t wWDesinfecting_ = EMS_VALUE_BOOL_NOTSET; // Warm Water disinfection on/off uint8_t wWDisinfecting_ = EMS_VALUE_BOOL_NOTSET; // Warm Water disinfection on/off
uint8_t wWReadiness_ = EMS_VALUE_BOOL_NOTSET; // Warm Water readiness on/off uint8_t wWReadiness_ = EMS_VALUE_BOOL_NOTSET; // Warm Water readiness on/off
uint8_t wWRecharging_ = EMS_VALUE_BOOL_NOTSET; // Warm Water recharge on/off uint8_t wWRecharging_ = EMS_VALUE_BOOL_NOTSET; // Warm Water recharge on/off
uint8_t wWTemperatureOK_ = EMS_VALUE_BOOL_NOTSET; // Warm Water temperature ok on/off uint8_t wWTemperatureOK_ = EMS_VALUE_BOOL_NOTSET; // Warm Water temperature ok on/off
@@ -147,7 +151,6 @@ class Boiler : public EMSdevice {
void process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram); void process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram); void process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorSlowPlus2(std::shared_ptr<const Telegram> telegram); void process_UBAMonitorSlowPlus2(std::shared_ptr<const Telegram> telegram);
void process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram); void process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram);
void process_UBASetPoints(std::shared_ptr<const Telegram> telegram); void process_UBASetPoints(std::shared_ptr<const Telegram> telegram);
void process_UBAFlags(std::shared_ptr<const Telegram> telegram); void process_UBAFlags(std::shared_ptr<const Telegram> telegram);
@@ -155,11 +158,8 @@ class Boiler : public EMSdevice {
void process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram); void process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram);
void process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram); void process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram);
void process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram); void process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram);
void process_UBADHWStatus(std::shared_ptr<const Telegram> telegram); void process_UBADHWStatus(std::shared_ptr<const Telegram> telegram);
void check_active();
// commands - none of these use the additional id parameter // commands - none of these use the additional id parameter
void set_warmwater_mode(const char * value, const int8_t id); void set_warmwater_mode(const char * value, const int8_t id);
void set_warmwater_activated(const char * value, const int8_t id); void set_warmwater_activated(const char * value, const int8_t id);

View File

@@ -28,7 +28,7 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
} }
void Connect::device_info(JsonArray & root) { void Connect::device_info_web(JsonArray & root) {
} }
void Connect::add_context_menu() { void Connect::add_context_menu() {

View File

@@ -37,7 +37,7 @@ class Connect : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell); virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values(); virtual void publish_values();
virtual void device_info(JsonArray & root); virtual void device_info_web(JsonArray & root);
virtual bool updated_values(); virtual bool updated_values();
virtual void add_context_menu(); virtual void add_context_menu();

View File

@@ -31,7 +31,7 @@ Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_i
void Controller::add_context_menu() { void Controller::add_context_menu() {
} }
void Controller::device_info(JsonArray & root) { void Controller::device_info_web(JsonArray & root) {
} }
// display all values into the shell console // display all values into the shell console

View File

@@ -37,7 +37,7 @@ class Controller : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell); virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values(); virtual void publish_values();
virtual void device_info(JsonArray & root); virtual void device_info_web(JsonArray & root);
virtual bool updated_values(); virtual bool updated_values();
virtual void add_context_menu(); virtual void add_context_menu();

View File

@@ -31,7 +31,7 @@ Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
void Gateway::add_context_menu() { void Gateway::add_context_menu() {
} }
void Gateway::device_info(JsonArray & root) { void Gateway::device_info_web(JsonArray & root) {
} }
// display all values into the shell console // display all values into the shell console

View File

@@ -37,7 +37,7 @@ class Gateway : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell); virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values(); virtual void publish_values();
virtual void device_info(JsonArray & root); virtual void device_info_web(JsonArray & root);
virtual bool updated_values(); virtual bool updated_values();
virtual void add_context_menu(); virtual void add_context_menu();

View File

@@ -37,7 +37,7 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
void Heatpump::add_context_menu() { void Heatpump::add_context_menu() {
} }
void Heatpump::device_info(JsonArray & root) { void Heatpump::device_info_web(JsonArray & root) {
} }
// display all values into the shell console // display all values into the shell console

View File

@@ -37,7 +37,7 @@ class Heatpump : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell); virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values(); virtual void publish_values();
virtual void device_info(JsonArray & root); virtual void device_info_web(JsonArray & root);
virtual bool updated_values(); virtual bool updated_values();
virtual void add_context_menu(); virtual void add_context_menu();

View File

@@ -60,25 +60,31 @@ void Mixing::add_context_menu() {
} }
// output json to web UI // output json to web UI
void Mixing::device_info(JsonArray & root) { void Mixing::device_info_web(JsonArray & root) {
if (type_ == Type::NONE) { if (type_ == Type::NONE) {
return; // don't have any values yet return; // don't have any values yet
} }
if (type_ == Type::WWC) { if (type_ == Type::WWC) {
render_value_json(root, "", F("Warm Water Circuit"), hc_, nullptr); render_value_json(root, "", F("Warm Water Circuit"), hc_, nullptr);
render_value_json(root, "", F("Current warm water temperature"), flowTemp_, F_(degrees), 10);
render_value_json(root, "", F("Current pump status"), pump_, nullptr);
render_value_json(root, "", F("Current temperature status"), status_, nullptr);
} else { } else {
render_value_json(root, "", F("Heating Circuit"), hc_, nullptr); render_value_json(root, "", F("Heating Circuit"), hc_, nullptr);
render_value_json(root, "", F("Current flow temperature"), flowTemp_, F_(degrees), 10);
render_value_json(root, "", F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
render_value_json(root, "", F("Current pump status"), pump_, nullptr, EMS_VALUE_BOOL);
render_value_json(root, "", F("Current valve status"), status_, F_(percent));
} }
render_value_json(root, "", F("Current flow temperature"), flowTemp_, F_(degrees), 10);
render_value_json(root, "", F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
render_value_json(root, "", F("Current pump modulation"), pumpMod_, F_(percent));
render_value_json(root, "", F("Current valve status"), status_, nullptr);
} }
// check to see if values have been updated // check to see if values have been updated
bool Mixing::updated_values() { bool Mixing::updated_values() {
if (changed_) {
changed_ = false;
return true;
}
return false; return false;
} }
@@ -96,14 +102,17 @@ void Mixing::show_values(uuid::console::Shell & shell) {
if (type_ == Type::WWC) { if (type_ == Type::WWC) {
print_value(shell, 2, F("Warm Water Circuit"), hc_, nullptr); print_value(shell, 2, F("Warm Water Circuit"), hc_, nullptr);
print_value(shell, 4, F("Current warm water temperature"), flowTemp_, F_(degrees), 10);
print_value(shell, 4, F("Current pump status"), pump_, nullptr);
print_value(shell, 4, F("Current temperature status"), status_, nullptr);
} else { } else {
print_value(shell, 2, F("Heating Circuit"), hc_, nullptr); print_value(shell, 2, F("Heating Circuit"), hc_, nullptr);
print_value(shell, 4, F("Current flow temperature"), flowTemp_, F_(degrees), 10);
print_value(shell, 4, F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
print_value(shell, 4, F("Current pump status"), pump_, nullptr, EMS_VALUE_BOOL);
print_value(shell, 4, F("Current valve status"), status_, F_(percent));
} }
print_value(shell, 4, F("Current flow temperature"), flowTemp_, F_(degrees), 10);
print_value(shell, 4, F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
print_value(shell, 4, F("Current pump modulation"), pumpMod_, F_(percent));
print_value(shell, 4, F("Current valve status"), status_, nullptr);
shell.println(); shell.println();
} }
@@ -112,37 +121,42 @@ void Mixing::show_values(uuid::console::Shell & shell) {
// ideally we should group up all the mixing units together into a nested JSON but for now we'll send them individually // ideally we should group up all the mixing units together into a nested JSON but for now we'll send them individually
void Mixing::publish_values() { void Mixing::publish_values() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
char s[5]; // for formatting strings
switch (type_) { switch (type_) {
case Type::HC: case Type::HC:
doc["type"] = "hc"; doc["type"] = "hc";
if (Helpers::hasValue(flowTemp_)) {
doc["flowTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(flowSetTemp_)) {
doc["flowSetTemp"] = flowSetTemp_;
}
if (Helpers::hasValue(pump_)) {
doc["pumpStatus"] = Helpers::render_value(s, pump_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(status_)) {
doc["valveStatus"] = status_;
}
break; break;
case Type::WWC: case Type::WWC:
doc["type"] = "wwc"; doc["type"] = "wwc";
if (Helpers::hasValue(flowTemp_)) {
doc["wwTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(pump_)) {
doc["pumpStatus"] = pump_;
}
if (Helpers::hasValue(status_)) {
doc["tempStatus"] = status_;
}
break; break;
case Type::NONE: case Type::NONE:
default: default:
return; return;
} }
if (Helpers::hasValue(flowTemp_)) {
doc["flowTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(pumpMod_)) {
doc["pumpMod"] = pumpMod_;
}
if (Helpers::hasValue(status_)) {
doc["status"] = status_;
}
if (Helpers::hasValue(flowSetTemp_)) {
doc["flowSetTemp"] = flowSetTemp_;
}
char topic[30]; char topic[30];
char s[3]; // for formatting strings
strlcpy(topic, "mixing_data", 30); strlcpy(topic, "mixing_data", 30);
strlcat(topic, Helpers::itoa(s, get_device_id() - 0x20 + 1), 30); // append hc to topic strlcat(topic, Helpers::itoa(s, get_device_id() - 0x20 + 1), 30); // append hc to topic
Mqtt::publish(topic, doc); Mqtt::publish(topic, doc);
@@ -153,11 +167,11 @@ void Mixing::publish_values() {
// A0 0B FF 00 01 D7 00 00 00 80 00 00 00 00 03 80 // A0 0B FF 00 01 D7 00 00 00 80 00 00 00 00 03 80
void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram) { void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram) {
type_ = Type::HC; type_ = Type::HC;
hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is
telegram->read_value(flowTemp_, 3); // is * 10 changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
telegram->read_value(flowSetTemp_, 5); changed_ |= telegram->read_value(flowSetTemp_, 5);
telegram->read_value(pumpMod_, 2); changed_ |= telegram->read_value(pump_, 0);
telegram->read_value(status_, 1); // valve status changed_ |= telegram->read_value(status_, 2); // valve status
} }
// Mixing module warm water loading/DHW - 0x0331, 0x0332 // Mixing module warm water loading/DHW - 0x0331, 0x0332
@@ -165,10 +179,10 @@ void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> tele
// A8 00 FF 00 02 31 02 35 00 3C 00 3C 3C 46 02 03 03 00 3C // in 0x29 // A8 00 FF 00 02 31 02 35 00 3C 00 3C 3C 46 02 03 03 00 3C // in 0x29
void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram) { void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram) {
type_ = Type::WWC; type_ = Type::WWC;
hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2. hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2.
telegram->read_value(flowTemp_, 0); // is * 10 changed_ |= telegram->read_value(flowTemp_, 0); // is * 10
telegram->read_value(pumpMod_, 2); changed_ |= telegram->read_value(pump_, 2);
telegram->read_value(status_, 11); // temp status changed_ |= telegram->read_value(status_, 11); // temp status
} }
// Mixing IMP - 0x010C // Mixing IMP - 0x010C
@@ -178,20 +192,16 @@ void Mixing::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram)
type_ = Type::HC; type_ = Type::HC;
hc_ = get_device_id() - 0x20 + 1; hc_ = get_device_id() - 0x20 + 1;
uint8_t ismixed = 0; uint8_t ismixed = 0;
telegram->read_value(ismixed, 0); // check if circuit is active, 0-off, 1-unmixed, 2-mixed changed_ |= telegram->read_value(ismixed, 0); // check if circuit is active, 0-off, 1-unmixed, 2-mixed
if (ismixed == 0) { if (ismixed == 0) {
return; return;
} }
if (ismixed == 2) { // we have a mixed circuit if (ismixed == 2) { // we have a mixed circuit
telegram->read_value(flowTemp_, 3); // is * 10 changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
telegram->read_value(flowSetTemp_, 5); changed_ |= telegram->read_value(flowSetTemp_, 5);
telegram->read_value(status_, 2); // valve status changed_ |= telegram->read_value(status_, 2); // valve status
}
uint8_t pump = 0xFF;
telegram->read_bitvalue(pump, 1, 0); // pump is also in unmixed circuits
if (pump != 0xFF) {
pumpMod_ = 100 * pump;
} }
changed_ |= telegram->read_bitvalue(pump_, 1, 0); // pump is also in unmixed circuits
} }
// Mixing on a MM10 - 0xAB // Mixing on a MM10 - 0xAB
@@ -204,9 +214,10 @@ void Mixing::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
// 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module // 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module
// see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918 // see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918
hc_ = get_device_id() - 0x20 + 1; hc_ = get_device_id() - 0x20 + 1;
telegram->read_value(flowTemp_, 1); // is * 10 changed_ |= telegram->read_value(flowTemp_, 1); // is * 10
telegram->read_value(pumpMod_, 3); changed_ |= telegram->read_value(pump_, 3);
telegram->read_value(flowSetTemp_, 0); changed_ |= telegram->read_value(flowSetTemp_, 0);
changed_ |= telegram->read_value(status_, 4); // valve status -100 to 100
} }
#pragma GCC diagnostic push #pragma GCC diagnostic push

View File

@@ -37,7 +37,7 @@ class Mixing : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell); virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values(); virtual void publish_values();
virtual void device_info(JsonArray & root); virtual void device_info_web(JsonArray & root);
virtual bool updated_values(); virtual bool updated_values();
virtual void add_context_menu(); virtual void add_context_menu();
@@ -62,10 +62,11 @@ class Mixing : public EMSdevice {
private: private:
uint16_t hc_ = EMS_VALUE_USHORT_NOTSET; uint16_t hc_ = EMS_VALUE_USHORT_NOTSET;
uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET; uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET;
uint8_t pumpMod_ = EMS_VALUE_UINT_NOTSET; uint8_t pump_ = EMS_VALUE_UINT_NOTSET;
uint8_t status_ = EMS_VALUE_UINT_NOTSET; int8_t status_ = EMS_VALUE_UINT_NOTSET;
uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET; uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET;
Type type_ = Type::NONE; Type type_ = Type::NONE;
bool changed_ = false;
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -54,7 +54,7 @@ void Solar::add_context_menu() {
} }
// print to web // print to web
void Solar::device_info(JsonArray & root) { void Solar::device_info_web(JsonArray & root) {
render_value_json(root, "", F("Collector temperature (TS1)"), collectorTemp_, F_(degrees), 10); render_value_json(root, "", F("Collector temperature (TS1)"), collectorTemp_, F_(degrees), 10);
render_value_json(root, "", F("Tank bottom temperature (TS2)"), tankBottomTemp_, F_(degrees), 10); render_value_json(root, "", F("Tank bottom temperature (TS2)"), tankBottomTemp_, F_(degrees), 10);
render_value_json(root, "", F("Tank bottom temperature (TS5)"), tankBottomTemp2_, F_(degrees), 10); render_value_json(root, "", F("Tank bottom temperature (TS5)"), tankBottomTemp2_, F_(degrees), 10);
@@ -145,7 +145,7 @@ void Solar::publish_values() {
} }
if (Helpers::hasValue(pumpWorkMin_)) { if (Helpers::hasValue(pumpWorkMin_)) {
doc["pumpWorkMin"] = (float)pumpWorkMin_; doc["pumpWorkMin"] = pumpWorkMin_;
} }
if (Helpers::hasValue(tankHeated_, EMS_VALUE_BOOL)) { if (Helpers::hasValue(tankHeated_, EMS_VALUE_BOOL)) {
@@ -168,11 +168,18 @@ void Solar::publish_values() {
doc["energyTotal"] = (float)energyTotal_ / 10; doc["energyTotal"] = (float)energyTotal_ / 10;
} }
Mqtt::publish("sm_data", doc); // if we have data, publish it
if (!doc.isNull()) {
Mqtt::publish(F("sm_data"), doc);
}
} }
// check to see if values have been updated // check to see if values have been updated
bool Solar::updated_values() { bool Solar::updated_values() {
if (changed_) {
changed_ = false;
return true;
}
return false; return false;
} }
@@ -182,11 +189,11 @@ void Solar::console_commands() {
// SM10Monitor - type 0x97 // SM10Monitor - type 0x97
void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(collectorTemp_, 2); // collector temp from SM10, is *10 changed_ |= telegram->read_value(collectorTemp_, 2); // collector temp from SM10, is *10
telegram->read_value(tankBottomTemp_, 5); // bottom temp from SM10, is *10 changed_ |= telegram->read_value(tankBottomTemp_, 5); // bottom temp from SM10, is *10
telegram->read_value(solarPumpModulation_, 4); // modulation solar pump changed_ |= telegram->read_value(solarPumpModulation_, 4); // modulation solar pump
telegram->read_bitvalue(solarPump_, 7, 1); changed_ |= telegram->read_bitvalue(solarPump_, 7, 1);
telegram->read_value(pumpWorkMin_, 8); changed_ |= telegram->read_value(pumpWorkMin_, 8);
} }
/* /*
@@ -201,10 +208,10 @@ void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
* bytes 20+21 = TS6 Temperature sensor external heat exchanger * bytes 20+21 = TS6 Temperature sensor external heat exchanger
*/ */
void Solar::process_SM100Monitor(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100Monitor(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(collectorTemp_, 0); // is *10 - TS1: Temperature sensor for collector array 1 changed_ |= telegram->read_value(collectorTemp_, 0); // is *10 - TS1: Temperature sensor for collector array 1
telegram->read_value(tankBottomTemp_, 2); // is *10 - TS2: Temperature sensor 1 cylinder, bottom changed_ |= telegram->read_value(tankBottomTemp_, 2); // is *10 - TS2: Temperature sensor 1 cylinder, bottom
telegram->read_value(tankBottomTemp2_, 16); // is *10 - TS5: Temperature sensor 2 cylinder, bottom, or swimming pool changed_ |= telegram->read_value(tankBottomTemp2_, 16); // is *10 - TS5: Temperature sensor 2 cylinder, bottom, or swimming pool
telegram->read_value(heatExchangerTemp_, 20); // is *10 - TS6: Heat exchanger temperature sensor changed_ |= telegram->read_value(heatExchangerTemp_, 20); // is *10 - TS6: Heat exchanger temperature sensor
} }
#pragma GCC diagnostic push #pragma GCC diagnostic push
@@ -221,9 +228,9 @@ void Solar::process_SM100Monitor2(std::shared_ptr<const Telegram> telegram) {
// SM100Config - 0x0366 // SM100Config - 0x0366
// e.g. B0 00 FF 00 02 66 01 62 00 13 40 14 // e.g. B0 00 FF 00 02 66 01 62 00 13 40 14
void Solar::process_SM100Config(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100Config(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(availabilityFlag_, 0); changed_ |= telegram->read_value(availabilityFlag_, 0);
telegram->read_value(configFlag_, 1); changed_ |= telegram->read_value(configFlag_, 1);
telegram->read_value(userFlag_, 2); changed_ |= telegram->read_value(userFlag_, 2);
} }
/* /*
@@ -235,8 +242,8 @@ void Solar::process_SM100Config(std::shared_ptr<const Telegram> telegram) {
void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
uint8_t solarpumpmod = solarPumpModulation_; uint8_t solarpumpmod = solarPumpModulation_;
uint8_t cylinderpumpmod = cylinderPumpModulation_; uint8_t cylinderpumpmod = cylinderPumpModulation_;
telegram->read_value(cylinderPumpModulation_, 8); changed_ |= telegram->read_value(cylinderPumpModulation_, 8);
telegram->read_value(solarPumpModulation_, 9); changed_ |= telegram->read_value(solarPumpModulation_, 9);
if (solarpumpmod == 0 && solarPumpModulation_ == 100) { // mask out boosts if (solarpumpmod == 0 && solarPumpModulation_ == 100) { // mask out boosts
solarPumpModulation_ = 15; // set to minimum solarPumpModulation_ = 15; // set to minimum
@@ -245,8 +252,8 @@ void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
if (cylinderpumpmod == 0 && cylinderPumpModulation_ == 100) { // mask out boosts if (cylinderpumpmod == 0 && cylinderPumpModulation_ == 100) { // mask out boosts
cylinderPumpModulation_ = 15; // set to minimum cylinderPumpModulation_ = 15; // set to minimum
} }
telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422 changed_ |= telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422
telegram->read_bitvalue(collectorShutdown_, 3, 0); // collector shutdown changed_ |= telegram->read_bitvalue(collectorShutdown_, 3, 0); // collector shutdown
} }
/* /*
@@ -256,8 +263,8 @@ void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
* byte 10 = PS1 Solar circuit pump for collector array 1: test=b0001(1), on=b0100(4) and off=b0011(3) * byte 10 = PS1 Solar circuit pump for collector array 1: test=b0001(1), on=b0100(4) and off=b0011(3)
*/ */
void Solar::process_SM100Status2(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100Status2(std::shared_ptr<const Telegram> telegram) {
telegram->read_bitvalue(valveStatus_, 4, 2); // on if bit 2 set changed_ |= telegram->read_bitvalue(valveStatus_, 4, 2); // on if bit 2 set
telegram->read_bitvalue(solarPump_, 10, 2); // on if bit 2 set changed_ |= telegram->read_bitvalue(solarPump_, 10, 2); // on if bit 2 set
} }
/* /*
@@ -265,9 +272,9 @@ void Solar::process_SM100Status2(std::shared_ptr<const Telegram> telegram) {
* e.g. 30 00 FF 00 02 8E 00 00 00 00 00 00 06 C5 00 00 76 35 * e.g. 30 00 FF 00 02 8E 00 00 00 00 00 00 06 C5 00 00 76 35
*/ */
void Solar::process_SM100Energy(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100Energy(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(energyLastHour_, 0); // last hour / 10 in Wh changed_ |= telegram->read_value(energyLastHour_, 0); // last hour / 10 in Wh
telegram->read_value(energyToday_, 4); // todays in Wh changed_ |= telegram->read_value(energyToday_, 4); // todays in Wh
telegram->read_value(energyTotal_, 8); // total / 10 in kWh changed_ |= telegram->read_value(energyTotal_, 8); // total / 10 in kWh
} }
/* /*
@@ -275,26 +282,26 @@ void Solar::process_SM100Energy(std::shared_ptr<const Telegram> telegram) {
* e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0 * e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0
*/ */
void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram) { void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(collectorTemp_, 4); // Collector Temperature changed_ |= telegram->read_value(collectorTemp_, 4); // Collector Temperature
telegram->read_value(tankBottomTemp_, 6); // Temperature Bottom of Solar Boiler changed_ |= telegram->read_value(tankBottomTemp_, 6); // Temperature Bottom of Solar Boiler
uint16_t Wh = 0xFFFF; uint16_t Wh = 0xFFFF;
telegram->read_value(Wh, 2); // Solar Energy produced in last hour only ushort, is not * 10 changed_ |= telegram->read_value(Wh, 2); // Solar Energy produced in last hour only ushort, is not * 10
if (Wh != 0xFFFF) { if (Wh != 0xFFFF) {
energyLastHour_ = Wh * 10; // set to *10 energyLastHour_ = Wh * 10; // set to *10
} }
telegram->read_bitvalue(solarPump_, 8, 0); // PS1 Solar pump on (1) or off (0) changed_ |= telegram->read_bitvalue(solarPump_, 8, 0); // PS1 Solar pump on (1) or off (0)
telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes changed_ |= telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes
telegram->read_bitvalue(collectorShutdown_, 9, 0); // collector shutdown on/off changed_ |= telegram->read_bitvalue(collectorShutdown_, 9, 0); // collector shutdown on/off
telegram->read_bitvalue(tankHeated_, 9, 2); // tank full changed_ |= telegram->read_bitvalue(tankHeated_, 9, 2); // tank full
} }
/* /*
* Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values * Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values
*/ */
void Solar::process_ISM1Set(std::shared_ptr<const Telegram> telegram) { void Solar::process_ISM1Set(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(setpoint_maxBottomTemp_, 6); changed_ |= telegram->read_value(setpoint_maxBottomTemp_, 6);
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -37,7 +37,7 @@ class Solar : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell); virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values(); virtual void publish_values();
virtual void device_info(JsonArray & root); virtual void device_info_web(JsonArray & root);
virtual bool updated_values(); virtual bool updated_values();
virtual void add_context_menu(); virtual void add_context_menu();
@@ -65,6 +65,7 @@ class Solar : public EMSdevice {
uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET; uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET;
uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET; uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET;
uint8_t userFlag_ = EMS_VALUE_BOOL_NOTSET; uint8_t userFlag_ = EMS_VALUE_BOOL_NOTSET;
bool changed_ = false;
void process_SM10Monitor(std::shared_ptr<const Telegram> telegram); void process_SM10Monitor(std::shared_ptr<const Telegram> telegram);
void process_SM100Monitor(std::shared_ptr<const Telegram> telegram); void process_SM100Monitor(std::shared_ptr<const Telegram> telegram);

View File

@@ -31,7 +31,7 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
void Switch::add_context_menu() { void Switch::add_context_menu() {
} }
void Switch::device_info(JsonArray & root) { void Switch::device_info_web(JsonArray & root) {
} }
// display all values into the shell console // display all values into the shell console

View File

@@ -37,7 +37,7 @@ class Switch : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell); virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values(); virtual void publish_values();
virtual void device_info(JsonArray & root); virtual void device_info_web(JsonArray & root);
virtual bool updated_values(); virtual bool updated_values();
virtual void add_context_menu(); virtual void add_context_menu();

View File

@@ -26,7 +26,22 @@ uuid::log::Logger Thermostat::logger_{F_(thermostat), uuid::log::Facility::CONSO
Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
if (EMSESP::actual_master_thermostat() == 0) { uint8_t actual_master_thermostat = EMSESP::actual_master_thermostat(); // what we're actually using
uint8_t master_thermostat = EMSESP_DEFAULT_MASTER_THERMOSTAT;
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
master_thermostat = settings.master_thermostat; // what the user has defined
});
uint8_t model = this->model();
// if we're on auto mode, register this thermostat if it has a device id of 0x10, 0x17 or 0x18
// or if its the master thermostat we defined
// see https://github.com/proddy/EMS-ESP/issues/362#issuecomment-629628161
if ((master_thermostat == device_id)
|| ((master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) && (device_id < 0x19)
&& ((actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) || (device_id < actual_master_thermostat)))) {
EMSESP::actual_master_thermostat(device_id);
actual_master_thermostat = device_id;
this->reserve_mem(25); // reserve some space for the telegram registries, to avoid memory fragmentation this->reserve_mem(25); // reserve some space for the telegram registries, to avoid memory fragmentation
// common telegram handlers // common telegram handlers
@@ -34,7 +49,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(EMS_TYPE_RCTime, F("RCTime"), false, [&](std::shared_ptr<const Telegram> t) { process_RCTime(t); }); register_telegram_type(EMS_TYPE_RCTime, F("RCTime"), false, [&](std::shared_ptr<const Telegram> t) { process_RCTime(t); });
} }
// RC10 // RC10
if (flags == EMSdevice::EMS_DEVICE_FLAG_RC10) { if (model == EMSdevice::EMS_DEVICE_FLAG_RC10) {
monitor_typeids = {0xB1}; monitor_typeids = {0xB1};
set_typeids = {0xB0}; set_typeids = {0xB0};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
@@ -43,7 +58,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
} }
// RC35 // RC35
} else if ((flags == EMSdevice::EMS_DEVICE_FLAG_RC35) || (flags == EMSdevice::EMS_DEVICE_FLAG_RC30_1)) { } else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC35) || (model == EMSdevice::EMS_DEVICE_FLAG_RC30_1)) {
monitor_typeids = {0x3E, 0x48, 0x52, 0x5C}; monitor_typeids = {0x3E, 0x48, 0x52, 0x5C};
set_typeids = {0x3D, 0x47, 0x51, 0x5B}; set_typeids = {0x3D, 0x47, 0x51, 0x5B};
timer_typeids = {0x3F, 0x49, 0x53, 0x5D}; timer_typeids = {0x3F, 0x49, 0x53, 0x5D};
@@ -55,10 +70,10 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(EMS_TYPE_wwSettings, F("WWSettings"), true, [&](std::shared_ptr<const Telegram> t) { process_RC35wwSettings(t); }); register_telegram_type(EMS_TYPE_wwSettings, F("WWSettings"), true, [&](std::shared_ptr<const Telegram> t) { process_RC35wwSettings(t); });
// RC20 // RC20
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20) {
monitor_typeids = {0x91}; monitor_typeids = {0x91};
set_typeids = {0xA8}; set_typeids = {0xA8};
if (EMSESP::actual_master_thermostat() == 0) { if (actual_master_thermostat == device_id) {
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Monitor(t); }); register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Monitor(t); });
register_telegram_type(set_typeids[i], F("RC20Set"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Set(t); }); register_telegram_type(set_typeids[i], F("RC20Set"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Set(t); });
@@ -67,10 +82,10 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(0xAF, F("RC20Remote"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Remote(t); }); register_telegram_type(0xAF, F("RC20Remote"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Remote(t); });
} }
// RC20 newer // RC20 newer
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20_2) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20_2) {
monitor_typeids = {0xAE}; monitor_typeids = {0xAE};
set_typeids = {0xAD}; set_typeids = {0xAD};
if (EMSESP::actual_master_thermostat() == 0) { if (actual_master_thermostat == device_id) {
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Monitor_2(t); }); register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Monitor_2(t); });
register_telegram_type(set_typeids[i], F("RC20Set"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Set_2(t); }); register_telegram_type(set_typeids[i], F("RC20Set"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Set_2(t); });
@@ -79,7 +94,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(0xAF, F("RC20Remote"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Remote(t); }); register_telegram_type(0xAF, F("RC20Remote"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Remote(t); });
} }
// RC30 // RC30
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_RC30) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_RC30) {
monitor_typeids = {0x41}; monitor_typeids = {0x41};
set_typeids = {0xA7}; set_typeids = {0xA7};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
@@ -88,13 +103,13 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
} }
// EASY // EASY
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_EASY) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_EASY) {
monitor_typeids = {0x0A}; monitor_typeids = {0x0A};
set_typeids = {}; set_typeids = {};
register_telegram_type(monitor_typeids[0], F("EasyMonitor"), false, [&](std::shared_ptr<const Telegram> t) { process_EasyMonitor(t); }); register_telegram_type(monitor_typeids[0], F("EasyMonitor"), true, [&](std::shared_ptr<const Telegram> t) { process_EasyMonitor(t); });
// RC300/RC100 // RC300/RC100
} else if ((flags == EMSdevice::EMS_DEVICE_FLAG_RC300) || (flags == EMSdevice::EMS_DEVICE_FLAG_RC100)) { } else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC300) || (model == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8}; monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8};
set_typeids = {0x02B9, 0x02BA, 0x02BB, 0x02BC}; set_typeids = {0x02B9, 0x02BA, 0x02BB, 0x02BC};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
@@ -105,7 +120,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(0x31E, F("RC300WWmode"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300WWmode(t); }); register_telegram_type(0x31E, F("RC300WWmode"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300WWmode(t); });
// JUNKERS/HT3 // JUNKERS/HT3
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172}; monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172};
set_typeids = {0x0165, 0x0166, 0x0167, 0x0168}; set_typeids = {0x0165, 0x0166, 0x0167, 0x0168};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
@@ -113,7 +128,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(set_typeids[i], F("JunkersSet"), false, [&](std::shared_ptr<const Telegram> t) { process_JunkersSet(t); }); register_telegram_type(set_typeids[i], F("JunkersSet"), false, [&](std::shared_ptr<const Telegram> t) { process_JunkersSet(t); });
} }
} else if (flags == (EMSdevice::EMS_DEVICE_FLAG_JUNKERS | EMSdevice::EMS_DEVICE_FLAG_JUNKERS_2)) { } else if (model == (EMSdevice::EMS_DEVICE_FLAG_JUNKERS | EMSdevice::EMS_DEVICE_FLAG_JUNKERS_2)) {
monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172}; monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172};
set_typeids = {0x0179, 0x017A, 0x017B, 0x017C}; set_typeids = {0x0179, 0x017A, 0x017B, 0x017C};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
@@ -122,29 +137,16 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
} }
} }
uint8_t master_thermostat = 0;
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
master_thermostat = settings.master_thermostat; // what the user has defined
});
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
mqtt_format_ = settings.mqtt_format; // single, nested or ha mqtt_format_ = settings.mqtt_format; // single, nested or ha
}); });
uint8_t actual_master_thermostat = EMSESP::actual_master_thermostat(); // what we're actually using if (actual_master_thermostat != device_id) {
uint8_t num_devices = EMSESP::count_devices(EMSdevice::DeviceType::THERMOSTAT) + 1; // including this thermostat
// if we're on auto mode, register this thermostat if it has a device id of 0x10, 0x17 or 0x18
// or if its the master thermostat we defined
// see https://github.com/proddy/EMS-ESP/issues/362#issuecomment-629628161
if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT)) || (master_thermostat == device_id)) {
EMSESP::actual_master_thermostat(device_id);
LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X (as master)"), device_id);
add_commands();
} else {
LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X"), device_id); LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X"), device_id);
return; // don't fetch data if more than 1 thermostat return; // don't fetch data if more than 1 thermostat
} }
LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X (as master)"), device_id);
add_commands();
// reserve some memory for the heating circuits (max 4 to start with) // reserve some memory for the heating circuits (max 4 to start with)
heating_circuits_.reserve(4); heating_circuits_.reserve(4);
@@ -161,8 +163,8 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
} }
// prepare data for Web UI // prepare data for Web UI
void Thermostat::device_info(JsonArray & root) { void Thermostat::device_info_web(JsonArray & root) {
uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, strip the option bits uint8_t flags = this->model();
for (const auto & hc : heating_circuits_) { for (const auto & hc : heating_circuits_) {
if (!Helpers::hasValue(hc->setpoint_roomTemp)) { if (!Helpers::hasValue(hc->setpoint_roomTemp)) {
@@ -240,23 +242,10 @@ bool Thermostat::updated_values() {
if (EMSESP::actual_master_thermostat() != this->get_device_id()) { if (EMSESP::actual_master_thermostat() != this->get_device_id()) {
return false; return false;
} }
if (changed_) {
// quick hack to see if it changed. We simply just add up all the raw values changed_ = false;
uint16_t new_value = 0;
static uint16_t current_value_ = 0;
for (const auto & hc : heating_circuits_) {
// don't publish if we haven't yet received some data
if (!Helpers::hasValue(hc->setpoint_roomTemp)) {
return false;
}
new_value += hc->setpoint_roomTemp + hc->curr_roomTemp + hc->mode;
}
if (new_value != current_value_) {
current_value_ = new_value;
return true; return true;
} }
return false; return false;
} }
@@ -267,15 +256,15 @@ void Thermostat::publish_values() {
return; return;
} }
uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, stripping the option bits uint8_t flags = this->model();
bool has_data = false;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
JsonObject rootThermostat = doc.to<JsonObject>(); JsonObject rootThermostat = doc.to<JsonObject>();
JsonObject dataThermostat; JsonObject dataThermostat;
// add external temp and other stuff specific to the RC30 and RC35 // add external temp and other stuff specific to the RC30 and RC35
if ((flags == EMS_DEVICE_FLAG_RC35 || flags == EMS_DEVICE_FLAG_RC30_1) && (mqtt_format_ == MQTT_format::SINGLE || mqtt_format_ == MQTT_format::CUSTOM)) { // if ((flags == EMS_DEVICE_FLAG_RC35 || flags == EMS_DEVICE_FLAG_RC30_1) && (mqtt_format_ == MQTT_format::SINGLE || mqtt_format_ == MQTT_format::CUSTOM)) {
if (flags == EMS_DEVICE_FLAG_RC35 || flags == EMS_DEVICE_FLAG_RC30_1) {
if (datetime_.size()) { if (datetime_.size()) {
rootThermostat["time"] = datetime_.c_str(); rootThermostat["time"] = datetime_.c_str();
} }
@@ -314,135 +303,140 @@ void Thermostat::publish_values() {
} }
// send this specific data using the thermostat_data topic // send this specific data using the thermostat_data topic
// if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::HA)) {
if (mqtt_format_ != MQTT_format::NESTED) { if (mqtt_format_ != MQTT_format::NESTED) {
Mqtt::publish("thermostat_data", doc); Mqtt::publish(F("thermostat_data"), doc);
rootThermostat = doc.to<JsonObject>(); // clear object rootThermostat = doc.to<JsonObject>(); // clear object
} }
} }
// go through all the heating circuits // go through all the heating circuits
bool has_data = false;
for (const auto & hc : heating_circuits_) { for (const auto & hc : heating_circuits_) {
if (!Helpers::hasValue(hc->setpoint_roomTemp)) { if (hc->is_active()) {
break; // skip this HC has_data = true;
}
has_data = true; // if the MQTT format is 'nested' or 'ha' then create the parent object hc<n>
// if the MQTT format is 'nested' or 'ha' then create the parent object hc<n> // if (mqtt_format_ != MQTT_format::SINGLE) {
// if (mqtt_format_ != MQTT_format::SINGLE) { if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::HA)) {
if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::HA)) { char hc_name[10]; // hc{1-4}
char hc_name[10]; // hc{1-4} strlcpy(hc_name, "hc", 10);
strlcpy(hc_name, "hc", 10); char s[3];
char s[3]; strlcat(hc_name, Helpers::itoa(s, hc->hc_num()), 10);
strlcat(hc_name, Helpers::itoa(s, hc->hc_num()), 10); dataThermostat = rootThermostat.createNestedObject(hc_name);
dataThermostat = rootThermostat.createNestedObject(hc_name);
} else {
dataThermostat = rootThermostat;
}
// different logic on how temperature values are stored, depending on model
uint8_t setpoint_temp_divider;
uint8_t curr_temp_divider;
if (flags == EMS_DEVICE_FLAG_EASY) {
setpoint_temp_divider = 100;
curr_temp_divider = 100;
} else if (flags == EMS_DEVICE_FLAG_JUNKERS) {
setpoint_temp_divider = 10;
curr_temp_divider = 10;
} else {
setpoint_temp_divider = 2;
curr_temp_divider = 10;
}
if (Helpers::hasValue(hc->setpoint_roomTemp)) {
dataThermostat["seltemp"] = Helpers::round2((float)hc->setpoint_roomTemp / setpoint_temp_divider);
}
if (Helpers::hasValue(hc->curr_roomTemp)) {
dataThermostat["currtemp"] = Helpers::round2((float)hc->curr_roomTemp / curr_temp_divider);
}
if (Helpers::hasValue(hc->daytemp)) {
if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
dataThermostat["heattemp"] = (float)hc->daytemp / 2;
} else { } else {
dataThermostat["daytemp"] = (float)hc->daytemp / 2; dataThermostat = rootThermostat;
} }
}
if (Helpers::hasValue(hc->nighttemp)) { // different logic on how temperature values are stored, depending on model
if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) { uint8_t setpoint_temp_divider;
dataThermostat["ecotemp"] = (float)hc->nighttemp / 2; uint8_t curr_temp_divider;
if (flags == EMS_DEVICE_FLAG_EASY) {
setpoint_temp_divider = 100;
curr_temp_divider = 100;
} else if (flags == EMS_DEVICE_FLAG_JUNKERS) {
setpoint_temp_divider = 10;
curr_temp_divider = 10;
} else { } else {
dataThermostat["nighttemp"] = (float)hc->nighttemp / 2; setpoint_temp_divider = 2;
curr_temp_divider = 10;
} }
}
if (Helpers::hasValue(hc->holidaytemp)) {
dataThermostat["holidaytemp"] = (float)hc->holidaytemp / 2;
}
if (Helpers::hasValue(hc->nofrosttemp)) { if (Helpers::hasValue(hc->setpoint_roomTemp)) {
dataThermostat["nofrosttemp"] = (float)hc->nofrosttemp / 2; dataThermostat["seltemp"] = Helpers::round2((float)hc->setpoint_roomTemp / setpoint_temp_divider);
} }
if (Helpers::hasValue(hc->heatingtype)) { if (Helpers::hasValue(hc->curr_roomTemp)) {
dataThermostat["heatingtype"] = hc->heatingtype; dataThermostat["currtemp"] = Helpers::round2((float)hc->curr_roomTemp / curr_temp_divider);
} }
if (Helpers::hasValue(hc->targetflowtemp)) { if (Helpers::hasValue(hc->daytemp)) {
dataThermostat["targetflowtemp"] = hc->targetflowtemp; if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
} dataThermostat["heattemp"] = (float)hc->daytemp / 2;
if (Helpers::hasValue(hc->offsettemp)) {
dataThermostat["offsettemp"] = hc->offsettemp / 2;
}
if (Helpers::hasValue(hc->designtemp)) {
dataThermostat["designtemp"] = hc->designtemp;
}
if (Helpers::hasValue(hc->summertemp)) {
dataThermostat["summertemp"] = hc->summertemp;
}
// when using HA always send the mode otherwise it'll may break the component/widget and report an error
if ((Helpers::hasValue(hc->mode)) || (mqtt_format_ == MQTT_format::HA)) {
uint8_t hc_mode = hc->get_mode(flags);
// if we're sending to HA the only valid mode types are heat, auto and off
if (mqtt_format_ == MQTT_format::HA) {
if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) {
hc_mode = HeatingCircuit::Mode::HEAT;
} else if ((hc_mode == HeatingCircuit::Mode::NIGHT) || (hc_mode == HeatingCircuit::Mode::OFF)) {
hc_mode = HeatingCircuit::Mode::OFF;
} else { } else {
hc_mode = HeatingCircuit::Mode::AUTO; dataThermostat["daytemp"] = (float)hc->daytemp / 2;
} }
} }
dataThermostat["mode"] = mode_tostring(hc_mode); if (Helpers::hasValue(hc->nighttemp)) {
} if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
dataThermostat["ecotemp"] = (float)hc->nighttemp / 2;
} else {
dataThermostat["nighttemp"] = (float)hc->nighttemp / 2;
}
}
if (Helpers::hasValue(hc->holidaytemp)) {
dataThermostat["holidaytemp"] = (float)hc->holidaytemp / 2;
}
// special handling of mode type, for the RC35 replace with summer/holiday if set if (Helpers::hasValue(hc->nofrosttemp)) {
// https://github.com/proddy/EMS-ESP/issues/373#issuecomment-619810209 dataThermostat["nofrosttemp"] = (float)hc->nofrosttemp / 2;
if (Helpers::hasValue(hc->summer_mode) && hc->summer_mode) { }
dataThermostat["modetype"] = F("summer");
} else if (Helpers::hasValue(hc->holiday_mode) && hc->holiday_mode) {
dataThermostat["modetype"] = F("holiday");
} else if (Helpers::hasValue(hc->mode_type)) {
dataThermostat["modetype"] = mode_tostring(hc->get_mode_type(flags));
}
// if format is single, send immediately and clear object for next hc if (Helpers::hasValue(hc->heatingtype)) {
// the topic will have the hc number appended dataThermostat["heatingtype"] = hc->heatingtype;
// if (mqtt_format_ == MQTT_format::SINGLE) { }
if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::CUSTOM)) {
char topic[30]; if (Helpers::hasValue(hc->targetflowtemp)) {
char s[3]; dataThermostat["targetflowtemp"] = hc->targetflowtemp;
strlcpy(topic, "thermostat_data", 30); }
strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic
Mqtt::publish(topic, doc); if (Helpers::hasValue(hc->offsettemp)) {
rootThermostat = doc.to<JsonObject>(); // clear object dataThermostat["offsettemp"] = hc->offsettemp / 2;
} else if (mqtt_format_ == MQTT_format::HA) { }
std::string topic(100, '\0');
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/state"), hc->hc_num()); if (Helpers::hasValue(hc->designtemp)) {
Mqtt::publish(topic, doc); dataThermostat["designtemp"] = hc->designtemp;
}
if (Helpers::hasValue(hc->summertemp)) {
dataThermostat["summertemp"] = hc->summertemp;
}
// when using HA always send the mode otherwise it'll may break the component/widget and report an error
if ((Helpers::hasValue(hc->mode)) || (mqtt_format_ == MQTT_format::HA)) {
uint8_t hc_mode = hc->get_mode(flags);
// if we're sending to HA the only valid mode types are heat, auto and off
if (mqtt_format_ == MQTT_format::HA) {
if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) {
hc_mode = HeatingCircuit::Mode::HEAT;
} else if ((hc_mode == HeatingCircuit::Mode::NIGHT) || (hc_mode == HeatingCircuit::Mode::OFF)) {
hc_mode = HeatingCircuit::Mode::OFF;
} else {
hc_mode = HeatingCircuit::Mode::AUTO;
}
}
dataThermostat["mode"] = mode_tostring(hc_mode);
}
// special handling of mode type, for the RC35 replace with summer/holiday if set
// https://github.com/proddy/EMS-ESP/issues/373#issuecomment-619810209
if (Helpers::hasValue(hc->summer_mode) && hc->summer_mode) {
dataThermostat["modetype"] = F("summer");
} else if (Helpers::hasValue(hc->holiday_mode) && hc->holiday_mode) {
dataThermostat["modetype"] = F("holiday");
} else if (Helpers::hasValue(hc->mode_type)) {
dataThermostat["modetype"] = mode_tostring(hc->get_mode_type(flags));
}
// if format is single, send immediately and clear object for next hc
// the topic will have the hc number appended
// if (mqtt_format_ == MQTT_format::SINGLE) {
if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::CUSTOM)) {
char topic[30];
char s[3];
strlcpy(topic, "thermostat_data", 30);
strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic
Mqtt::publish(topic, doc);
rootThermostat = doc.to<JsonObject>(); // clear object
} else if (mqtt_format_ == MQTT_format::HA) {
// see if we have already registered this with HA MQTT Discovery, if not send the config
if (!hc->ha_registered()) {
register_mqtt_ha_config(hc->hc_num());
hc->ha_registered(true);
}
// send the thermostat topic and payload data
std::string topic(100, '\0');
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/state"), hc->hc_num());
Mqtt::publish(topic, doc);
}
} }
} }
@@ -451,9 +445,8 @@ void Thermostat::publish_values() {
} }
// if we're using nested json, send all in one go under one topic called thermostat_data // if we're using nested json, send all in one go under one topic called thermostat_data
// if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::CUSTOM)) {
if (mqtt_format_ == MQTT_format::NESTED) { if (mqtt_format_ == MQTT_format::NESTED) {
Mqtt::publish("thermostat_data", doc); Mqtt::publish(F("thermostat_data"), doc);
} }
} }
@@ -461,7 +454,7 @@ void Thermostat::publish_values() {
// of nullptr if it doesn't exist yet // of nullptr if it doesn't exist yet
std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(const uint8_t hc_num) { std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(const uint8_t hc_num) {
// if hc_num is 0 then return the first existing hc in the list // if hc_num is 0 then return the first existing hc in the list
if (hc_num == 0) { if (hc_num == AUTO_HEATING_CIRCUIT) {
for (const auto & heating_circuit : heating_circuits_) { for (const auto & heating_circuit : heating_circuits_) {
return heating_circuit; return heating_circuit;
} }
@@ -522,19 +515,17 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
} }
// create a new heating circuit object // create a new heating circuit object
auto new_hc = std::make_shared<Thermostat::HeatingCircuit>(hc_num, monitor_typeids[hc_num - 1], set_typeids[hc_num - 1]); auto new_hc = std::make_shared<Thermostat::HeatingCircuit>(hc_num);
heating_circuits_.push_back(new_hc); heating_circuits_.push_back(new_hc);
std::sort(heating_circuits_.begin(), heating_circuits_.end()); // sort based on hc number std::sort(heating_circuits_.begin(), heating_circuits_.end()); // sort based on hc number
// if we're using Home Assistant and HA discovery, register the new config
if (mqtt_format_ == MQTT_format::HA) {
register_mqtt_ha_config(hc_num);
}
// set the flag saying we want its data during the next auto fetch // set the flag saying we want its data during the next auto fetch
toggle_fetch(monitor_typeids[hc_num - 1], toggle_); toggle_fetch(monitor_typeids[hc_num - 1], toggle_);
toggle_fetch(set_typeids[hc_num - 1], toggle_);
if (set_typeids.size()) {
toggle_fetch(set_typeids[hc_num - 1], toggle_);
}
return heating_circuits_.back(); // even after sorting, this should still point back to the newly created HC return heating_circuits_.back(); // even after sorting, this should still point back to the newly created HC
} }
@@ -579,9 +570,24 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
doc["temp_step"] = "0.5"; doc["temp_step"] = "0.5";
JsonArray modes = doc.createNestedArray("modes"); JsonArray modes = doc.createNestedArray("modes");
modes.add("off"); uint8_t flags = this->model();
modes.add("heat"); if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20_2) {
modes.add("auto"); modes.add("night");
modes.add("day");
} else if ((flags == EMSdevice::EMS_DEVICE_FLAG_RC300) || (flags == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
modes.add("eco");
modes.add("comfort");
modes.add("auto");
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
modes.add("nofrost");
modes.add("eco");
modes.add("heat");
modes.add("auto");
} else { // default for all other thermostats
modes.add("night");
modes.add("day");
modes.add("auto");
}
std::string topic(100, '\0'); // e.g homeassistant/climate/hc1/thermostat/config std::string topic(100, '\0'); // e.g homeassistant/climate/hc1/thermostat/config
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/config"), hc_num); snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/config"), hc_num);
@@ -725,7 +731,7 @@ std::string Thermostat::mode_tostring(uint8_t mode) {
void Thermostat::show_values(uuid::console::Shell & shell) { void Thermostat::show_values(uuid::console::Shell & shell) {
EMSdevice::show_values(shell); // always call this to show header EMSdevice::show_values(shell); // always call this to show header
uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, strip the option bits uint8_t flags = this->model();
if (datetime_.size()) { if (datetime_.size()) {
shell.printfln(F(" Clock: %s"), datetime_.c_str()); shell.printfln(F(" Clock: %s"), datetime_.c_str());
@@ -804,9 +810,10 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
} }
for (const auto & hc : heating_circuits_) { for (const auto & hc : heating_circuits_) {
if (!Helpers::hasValue(hc->setpoint_roomTemp)) { if (!hc->is_active()) {
break; // skip this HC break; // skip this HC
} }
shell.printfln(F(" Heating Circuit %d:"), hc->hc_num()); shell.printfln(F(" Heating Circuit %d:"), hc->hc_num());
// different thermostat types store their temperature values differently // different thermostat types store their temperature values differently
@@ -882,16 +889,16 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
void Thermostat::process_RC20Set(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC20Set(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->mode, 23); changed_ |= telegram->read_value(hc->mode, 23);
} }
// type 0xAE - data from the RC20 thermostat (0x17) // type 0xAE - data from the RC20 thermostat (0x17)
void Thermostat::process_RC20Monitor_2(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC20Monitor_2(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_bitvalue(hc->mode_type, 0, 7); // day/night MSB 7th bit is day changed_ |= telegram->read_bitvalue(hc->mode_type, 0, 7); // day/night MSB 7th bit is day
telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force as single byte changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force as single byte
telegram->read_value(hc->curr_roomTemp, 3); // is * 10 changed_ |= telegram->read_value(hc->curr_roomTemp, 3); // is * 10
} }
// 0xAD - for reading the mode from the RC20/ES72 thermostat (0x17) // 0xAD - for reading the mode from the RC20/ES72 thermostat (0x17)
@@ -899,21 +906,21 @@ void Thermostat::process_RC20Monitor_2(std::shared_ptr<const Telegram> telegram)
void Thermostat::process_RC20Set_2(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC20Set_2(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->mode, 3); changed_ |= telegram->read_value(hc->mode, 3);
} }
// 0xAF - for reading the roomtemperature from the RC20/ES72 thermostat (0x18, 0x19, ..) // 0xAF - for reading the roomtemperature from the RC20/ES72 thermostat (0x18, 0x19, ..)
void Thermostat::process_RC20Remote(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC20Remote(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->curr_roomTemp, 0); changed_ |= telegram->read_value(hc->curr_roomTemp, 0);
} }
// type 0xB1 - data from the RC10 thermostat (0x17) // type 0xB1 - data from the RC10 thermostat (0x17)
void Thermostat::process_RC10Monitor(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC10Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte changed_ |= telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
telegram->read_value(hc->curr_roomTemp, 2); // is * 10 changed_ |= telegram->read_value(hc->curr_roomTemp, 2); // is * 10
} }
#pragma GCC diagnostic push #pragma GCC diagnostic push
@@ -928,56 +935,57 @@ void Thermostat::process_RC10Set(std::shared_ptr<const Telegram> telegram) {
// type 0x0165, ff // type 0x0165, ff
void Thermostat::process_JunkersSet(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_JunkersSet(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->daytemp, 17); // is * 2 changed_ |= telegram->read_value(hc->daytemp, 17); // is * 2
telegram->read_value(hc->nighttemp, 16); // is * 2 changed_ |= telegram->read_value(hc->nighttemp, 16); // is * 2
telegram->read_value(hc->nofrosttemp, 15); // is * 2 changed_ |= telegram->read_value(hc->nofrosttemp, 15); // is * 2
} }
// type 0x0179, ff // type 0x0179, ff
void Thermostat::process_JunkersSet2(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_JunkersSet2(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->daytemp, 7); // is * 2 changed_ |= telegram->read_value(hc->daytemp, 7); // is * 2
telegram->read_value(hc->nighttemp, 6); // is * 2 changed_ |= telegram->read_value(hc->nighttemp, 6); // is * 2
telegram->read_value(hc->nofrosttemp, 5); // is * 2 changed_ |= telegram->read_value(hc->nofrosttemp, 5); // is * 2
} }
// type 0xA3 - for external temp settings from the the RC* thermostats (e.g. RC35) // type 0xA3 - for external temp settings from the the RC* thermostats (e.g. RC35)
void Thermostat::process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(dampedoutdoortemp_, 0); changed_ |= telegram->read_value(dampedoutdoortemp_, 0);
telegram->read_value(tempsensor1_, 3); // sensor 1 - is * 10 changed_ |= telegram->read_value(tempsensor1_, 3); // sensor 1 - is * 10
telegram->read_value(tempsensor2_, 5); // sensor 2 - is * 10 changed_ |= telegram->read_value(tempsensor2_, 5); // sensor 2 - is * 10
} }
// 0x91 - data from the RC20 thermostat (0x17) - 15 bytes long // 0x91 - data from the RC20 thermostat (0x17) - 15 bytes long
void Thermostat::process_RC20Monitor(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC20Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte changed_ |= telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
telegram->read_value(hc->curr_roomTemp, 2); // is * 10 changed_ |= telegram->read_value(hc->curr_roomTemp, 2); // is * 10
} }
// type 0x0A - data from the Nefit Easy/TC100 thermostat (0x18) - 31 bytes long // type 0x0A - data from the Nefit Easy/TC100 thermostat (0x18) - 31 bytes long
void Thermostat::process_EasyMonitor(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_EasyMonitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->curr_roomTemp, 8); // is * 100 changed_ |= telegram->read_value(hc->curr_roomTemp, 8); // is * 100
telegram->read_value(hc->setpoint_roomTemp, 10); // is * 100 changed_ |= telegram->read_value(hc->setpoint_roomTemp, 10); // is * 100
} }
// Settings Parameters - 0xA5 - RC30_1 // Settings Parameters - 0xA5 - RC30_1
void Thermostat::process_IBASettings(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_IBASettings(std::shared_ptr<const Telegram> telegram) {
// 22 - display line on RC35 // 22 - display line on RC35
telegram->read_value(ibaMainDisplay_, changed_ |=
0); // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 8 smoke temp telegram->read_value(ibaMainDisplay_,
telegram->read_value(ibaLanguage_, 1); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian 0); // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 8 smoke temp
telegram->read_value(ibaCalIntTemperature_, 2); // offset int. temperature sensor, by * 0.1 Kelvin changed_ |= telegram->read_value(ibaLanguage_, 1); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
telegram->read_value(ibaBuildingType_, 6); // building type: 0 = light, 1 = medium, 2 = heavy changed_ |= telegram->read_value(ibaCalIntTemperature_, 2); // offset int. temperature sensor, by * 0.1 Kelvin
telegram->read_value(ibaMinExtTemperature_, 5); // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1 changed_ |= telegram->read_value(ibaBuildingType_, 6); // building type: 0 = light, 1 = medium, 2 = heavy
telegram->read_value(ibaClockOffset_, 12); // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s changed_ |= telegram->read_value(ibaMinExtTemperature_, 5); // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1
changed_ |= telegram->read_value(ibaClockOffset_, 12); // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s
} }
// Settings WW 0x37 - RC35 // Settings WW 0x37 - RC35
void Thermostat::process_RC35wwSettings(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC35wwSettings(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(wwMode_, 2); // 0 off, 1-on, 2-auto changed_ |= telegram->read_value(wwMode_, 2); // 0 off, 1-on, 2-auto
} }
// type 0x6F - FR10/FR50/FR100 Junkers // type 0x6F - FR10/FR50/FR100 Junkers
@@ -989,21 +997,21 @@ void Thermostat::process_JunkersMonitor(std::shared_ptr<const Telegram> telegram
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->curr_roomTemp, 4); // value is * 10 changed_ |= telegram->read_value(hc->curr_roomTemp, 4); // value is * 10
telegram->read_value(hc->setpoint_roomTemp, 2); // value is * 10 changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2); // value is * 10
telegram->read_value(hc->mode_type, 0); // 1 = nofrost, 2 = eco, 3 = heat changed_ |= telegram->read_value(hc->mode_type, 0); // 1 = nofrost, 2 = eco, 3 = heat
telegram->read_value(hc->mode, 1); // 1 = manual, 2 = auto changed_ |= telegram->read_value(hc->mode, 1); // 1 = manual, 2 = auto
} }
// type 0x02A5 - data from the Nefit RC1010/3000 thermostat (0x18) and RC300/310s on 0x10 // type 0x02A5 - data from the Nefit RC1010/3000 thermostat (0x18) and RC300/310s on 0x10
void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->curr_roomTemp, 0); // is * 10 changed_ |= telegram->read_value(hc->curr_roomTemp, 0); // is * 10
telegram->read_bitvalue(hc->mode_type, 10, 1); changed_ |= telegram->read_bitvalue(hc->mode_type, 10, 1);
telegram->read_bitvalue(hc->mode, 10, 0); // bit 1, mode (auto=1 or manual=0) changed_ |= telegram->read_bitvalue(hc->mode, 10, 0); // bit 1, mode (auto=1 or manual=0)
// if manual, take the current setpoint temp at pos 6 // if manual, take the current setpoint temp at pos 6
// if auto, take the next setpoint temp at pos 7 // if auto, take the next setpoint temp at pos 7
@@ -1012,9 +1020,9 @@ void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram)
// pos 3 actual setpoint (optimized), i.e. changes with temporary change, summer/holiday-modes // pos 3 actual setpoint (optimized), i.e. changes with temporary change, summer/holiday-modes
// pos 6 actual setpoint according to programmed changes eco/comfort // pos 6 actual setpoint according to programmed changes eco/comfort
// pos 7 next setpoint in the future, time to next setpoint in pos 8/9 // pos 7 next setpoint in the future, time to next setpoint in pos 8/9
telegram->read_value(hc->setpoint_roomTemp, 3, 1); // is * 2, force as single byte changed_ |= telegram->read_value(hc->setpoint_roomTemp, 3, 1); // is * 2, force as single byte
telegram->read_bitvalue(hc->summer_mode, 2, 4); changed_ |= telegram->read_bitvalue(hc->summer_mode, 2, 4);
telegram->read_value(hc->targetflowtemp, 4); changed_ |= telegram->read_value(hc->targetflowtemp, 4);
} }
// type 0x02B9 EMS+ for reading from RC300/RC310 thermostat // type 0x02B9 EMS+ for reading from RC300/RC310 thermostat
@@ -1026,21 +1034,21 @@ void Thermostat::process_RC300Set(std::shared_ptr<const Telegram> telegram) {
// comfort is position 2 // comfort is position 2
// I think auto is position 8? // I think auto is position 8?
// actual setpoint taken from RC300Monitor (Michael 12.06.2020) // actual setpoint taken from RC300Monitor (Michael 12.06.2020)
// telegram->read_value(hc->setpoint_roomTemp, 8, 1); // single byte conversion, value is * 2 - auto? // changed_ |= telegram->read_value(hc->setpoint_roomTemp, 8, 1); // single byte conversion, value is * 2 - auto?
// telegram->read_value(hc->setpoint_roomTemp, 10, 1); // single byte conversion, value is * 2 - manual // changed_ |= telegram->read_value(hc->setpoint_roomTemp, 10, 1); // single byte conversion, value is * 2 - manual
// check why mode is both in the Monitor and Set for the RC300. It'll be read twice! // check why mode is both in the Monitor and Set for the RC300. It'll be read twice!
// telegram->read_value(hc->mode, 0); // Auto = xFF, Manual = x00 eg. 10 00 FF 08 01 B9 FF // changed_ |= telegram->read_value(hc->mode, 0); // Auto = xFF, Manual = x00 eg. 10 00 FF 08 01 B9 FF
telegram->read_value(hc->daytemp, 2); // is * 2 changed_ |= telegram->read_value(hc->daytemp, 2); // is * 2
telegram->read_value(hc->nighttemp, 4); // is * 2 changed_ |= telegram->read_value(hc->nighttemp, 4); // is * 2
} }
// types 0x31D and 0x31E // types 0x31D and 0x31E
void Thermostat::process_RC300WWmode(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC300WWmode(std::shared_ptr<const Telegram> telegram) {
// 0x31D for WW system 1, 0x31E for WW system 2 // 0x31D for WW system 1, 0x31E for WW system 2
wwSystem_ = telegram->type_id - 0x31D + 1; wwSystem_ = telegram->type_id - 0x31D + 1;
telegram->read_value(wwExtra_, 0); // 0=no, 1=yes changed_ |= telegram->read_value(wwExtra_, 0); // 0=no, 1=yes
// pos 1 = holiday mode // pos 1 = holiday mode
// pos 2 = current status of DHW setpoint // pos 2 = current status of DHW setpoint
// pos 3 = current status of DHW circulation pump // pos 3 = current status of DHW circulation pump
@@ -1050,15 +1058,15 @@ void Thermostat::process_RC300WWmode(std::shared_ptr<const Telegram> telegram) {
void Thermostat::process_RC30Monitor(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC30Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte changed_ |= telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
telegram->read_value(hc->curr_roomTemp, 2); changed_ |= telegram->read_value(hc->curr_roomTemp, 2);
} }
// type 0xA7 - for reading the mode from the RC30 thermostat (0x10) // type 0xA7 - for reading the mode from the RC30 thermostat (0x10)
void Thermostat::process_RC30Set(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC30Set(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->mode, 23); changed_ |= telegram->read_value(hc->mode, 23);
} }
// type 0x3E (HC1), 0x48 (HC2), 0x52 (HC3), 0x5C (HC4) - data from the RC35 thermostat (0x10) - 16 bytes // type 0x3E (HC1), 0x48 (HC2), 0x52 (HC3), 0x5C (HC4) - data from the RC35 thermostat (0x10) - 16 bytes
@@ -1072,14 +1080,14 @@ void Thermostat::process_RC35Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force to single byte, is 0 in summermode changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force to single byte, is 0 in summermode
telegram->read_value(hc->curr_roomTemp, 3); // is * 10 - or 0x7D00 if thermostat is mounted on boiler changed_ |= telegram->read_value(hc->curr_roomTemp, 3); // is * 10 - or 0x7D00 if thermostat is mounted on boiler
telegram->read_bitvalue(hc->mode_type, 1, 1); changed_ |= telegram->read_bitvalue(hc->mode_type, 1, 1);
telegram->read_bitvalue(hc->summer_mode, 1, 0); changed_ |= telegram->read_bitvalue(hc->summer_mode, 1, 0);
telegram->read_bitvalue(hc->holiday_mode, 0, 5); changed_ |= telegram->read_bitvalue(hc->holiday_mode, 0, 5);
telegram->read_value(hc->targetflowtemp, 14); changed_ |= telegram->read_value(hc->targetflowtemp, 14);
} }
// type 0x3D (HC1), 0x47 (HC2), 0x51 (HC3), 0x5B (HC4) - Working Mode Heating - for reading the mode from the RC35 thermostat (0x10) // type 0x3D (HC1), 0x47 (HC2), 0x51 (HC3), 0x5B (HC4) - Working Mode Heating - for reading the mode from the RC35 thermostat (0x10)
@@ -1091,16 +1099,16 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->mode, 7); // night, day, auto changed_ |= telegram->read_value(hc->mode, 7); // night, day, auto
telegram->read_value(hc->daytemp, 2); // is * 2 changed_ |= telegram->read_value(hc->daytemp, 2); // is * 2
telegram->read_value(hc->nighttemp, 1); // is * 2 changed_ |= telegram->read_value(hc->nighttemp, 1); // is * 2
telegram->read_value(hc->holidaytemp, 3); // is * 2 changed_ |= telegram->read_value(hc->holidaytemp, 3); // is * 2
telegram->read_value(hc->heatingtype, 0); // 0- off, 1-radiator, 2-convector, 3-floor changed_ |= telegram->read_value(hc->heatingtype, 0); // 0- off, 1-radiator, 2-convector, 3-floor
telegram->read_value(hc->summertemp, 22); // is * 1 changed_ |= telegram->read_value(hc->summertemp, 22); // is * 1
telegram->read_value(hc->nofrosttemp, 23); // is * 1 changed_ |= telegram->read_value(hc->nofrosttemp, 23); // is * 1
telegram->read_value(hc->designtemp, 17); // is * 1 changed_ |= telegram->read_value(hc->designtemp, 17); // is * 1
telegram->read_value(hc->offsettemp, 6); // is * 2 changed_ |= telegram->read_value(hc->offsettemp, 6); // is * 2
} }
// process_RCTime - type 0x06 - date and time from a thermostat - 14 bytes long // process_RCTime - type 0x06 - date and time from a thermostat - 14 bytes long
@@ -1118,6 +1126,7 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
if (datetime_.empty()) { if (datetime_.empty()) {
datetime_.resize(25, '\0'); datetime_.resize(25, '\0');
} }
auto timeold = datetime_;
// render time to HH:MM:SS DD/MM/YYYY // render time to HH:MM:SS DD/MM/YYYY
// had to create separate buffers because of how printf works // had to create separate buffers because of how printf works
char buf1[6]; char buf1[6];
@@ -1136,6 +1145,9 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
Helpers::smallitoa(buf5, telegram->message_data[1]), // month Helpers::smallitoa(buf5, telegram->message_data[1]), // month
Helpers::itoa(buf6, telegram->message_data[0] + 2000, 10) // year Helpers::itoa(buf6, telegram->message_data[0] + 2000, 10) // year
); );
if (timeold != datetime_) {
changed_ = true;
}
} }
// add console commands // add console commands
@@ -1143,15 +1155,9 @@ void Thermostat::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT, EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(master)}, flash_string_vector{F_(set), F_(master)},
flash_string_vector{F_(deviceid_optional)}, flash_string_vector{F_(deviceid_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t value; uint8_t value = Helpers::hextoint(arguments.front().c_str());
if (arguments.empty()) {
value = EMSESP_DEFAULT_MASTER_THERMOSTAT;
} else {
value = Helpers::hextoint(arguments.front().c_str());
}
EMSESP::emsespSettingsService.update( EMSESP::emsespSettingsService.update(
[&](EMSESPSettings & settings) { [&](EMSESPSettings & settings) {
settings.master_thermostat = value; settings.master_thermostat = value;
@@ -1203,7 +1209,7 @@ void Thermostat::set_minexttemp(const char * value, const int8_t id) {
return; return;
} }
LOG_INFO(F("Setting min external temperature to %d"), mt); LOG_INFO(F("Setting min external temperature to %d"), mt);
write_command(EMS_TYPE_IBASettings, 5, mt); write_command(EMS_TYPE_IBASettings, 5, mt, EMS_TYPE_IBASettings);
} }
// 0xA5 - Clock offset // 0xA5 - Clock offset
@@ -1213,7 +1219,7 @@ void Thermostat::set_clockoffset(const char * value, const int8_t id) {
return; return;
} }
LOG_INFO(F("Setting clock offset to %d"), co); LOG_INFO(F("Setting clock offset to %d"), co);
write_command(EMS_TYPE_IBASettings, 12, co); write_command(EMS_TYPE_IBASettings, 12, co, EMS_TYPE_IBASettings);
} }
// 0xA5 - Calibrate internal temperature // 0xA5 - Calibrate internal temperature
@@ -1224,7 +1230,7 @@ void Thermostat::set_calinttemp(const char * value, const int8_t id) {
} }
// does this value need to be multiple by 10? // does this value need to be multiple by 10?
LOG_INFO(F("Calibrating internal temperature to %d.%d"), ct / 10, ct < 0 ? -ct % 10 : ct % 10); LOG_INFO(F("Calibrating internal temperature to %d.%d"), ct / 10, ct < 0 ? -ct % 10 : ct % 10);
write_command(EMS_TYPE_IBASettings, 2, ct); write_command(EMS_TYPE_IBASettings, 2, ct, EMS_TYPE_IBASettings);
} }
// 0xA5 - Set the display settings // 0xA5 - Set the display settings
@@ -1234,7 +1240,7 @@ void Thermostat::set_display(const char * value, const int8_t id) {
return; return;
} }
LOG_INFO(F("Setting display to %d"), ds); LOG_INFO(F("Setting display to %d"), ds);
write_command(EMS_TYPE_IBASettings, 0, ds); write_command(EMS_TYPE_IBASettings, 0, ds, EMS_TYPE_IBASettings);
} }
void Thermostat::set_remotetemp(const char * value, const int8_t id) { void Thermostat::set_remotetemp(const char * value, const int8_t id) {
@@ -1271,7 +1277,7 @@ void Thermostat::set_building(const char * value, const int8_t id) {
} }
LOG_INFO(F("Setting building to %d"), bg); LOG_INFO(F("Setting building to %d"), bg);
write_command(EMS_TYPE_wwSettings, 6, bg); write_command(EMS_TYPE_IBASettings, 6, bg, EMS_TYPE_IBASettings);
} }
// 0xA5 Set the language settings // 0xA5 Set the language settings
@@ -1281,7 +1287,7 @@ void Thermostat::set_language(const char * value, const int8_t id) {
return; return;
} }
LOG_INFO(F("Setting language to %d"), lg); LOG_INFO(F("Setting language to %d"), lg);
write_command(EMS_TYPE_wwSettings, 1, lg); write_command(EMS_TYPE_IBASettings, 1, lg, EMS_TYPE_IBASettings);
} }
// Set the control-mode for hc 0-off, 1-RC20, 2-RC3x // Set the control-mode for hc 0-off, 1-RC20, 2-RC3x
@@ -1326,7 +1332,7 @@ void Thermostat::set_wwmode(const char * value, const int8_t id) {
if (set != 0xFF) { if (set != 0xFF) {
LOG_INFO(F("Setting thermostat warm water mode to %s"), v.c_str()); LOG_INFO(F("Setting thermostat warm water mode to %s"), v.c_str());
write_command(EMS_TYPE_wwSettings, 2, set); write_command(EMS_TYPE_wwSettings, 2, set, EMS_TYPE_wwSettings);
} else { } else {
LOG_WARNING(F("Set thermostat warm water mode: Invalid mode: %s"), v.c_str()); LOG_WARNING(F("Set thermostat warm water mode: Invalid mode: %s"), v.c_str());
} }
@@ -1430,7 +1436,7 @@ void Thermostat::set_datetime(const char * value, const int8_t id) {
data[7] = (dt[22] - '0') + 2; // DST and flag data[7] = (dt[22] - '0') + 2; // DST and flag
} }
LOG_INFO(F("Setting date and time")); LOG_INFO(F("Setting date and time"));
write_command(EMS_TYPE_time, 0, data, 8, 0); write_command(EMS_TYPE_time, 0, data, 8, EMS_TYPE_time);
} }
// sets the thermostat working mode, where mode is a string // sets the thermostat working mode, where mode is a string
@@ -1503,7 +1509,7 @@ void Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
break; break;
} }
switch (this->flags() & 0x0F) { switch (this->model()) {
case EMSdevice::EMS_DEVICE_FLAG_RC20: case EMSdevice::EMS_DEVICE_FLAG_RC20:
offset = EMS_OFFSET_RC20Set_mode; offset = EMS_OFFSET_RC20Set_mode;
validate_typeid = set_typeids[hc_p]; validate_typeid = set_typeids[hc_p];
@@ -1557,7 +1563,6 @@ void Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
// add the write command to the Tx queue // add the write command to the Tx queue
// post validate is the corresponding monitor or set type IDs as they can differ per model // post validate is the corresponding monitor or set type IDs as they can differ per model
// write_command(set_typeids[hc->hc_num() - 1], offset, set_mode_value, validate_typeid);
write_command(set_typeids[hc->hc_num() - 1], offset, set_mode_value, validate_typeid); write_command(set_typeids[hc->hc_num() - 1], offset, set_mode_value, validate_typeid);
} }
@@ -1600,7 +1605,7 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
return; return;
} }
uint8_t model = this->flags() & 0x0F; uint8_t model = this->model();
int8_t offset = -1; // we use -1 to check if there is a value int8_t offset = -1; // we use -1 to check if there is a value
uint8_t factor = 2; // some temperatures only use 1 uint8_t factor = 2; // some temperatures only use 1
uint16_t validate_typeid = monitor_typeids[hc->hc_num() - 1]; uint16_t validate_typeid = monitor_typeids[hc->hc_num() - 1];
@@ -1616,12 +1621,18 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
} else if ((model == EMS_DEVICE_FLAG_RC300) || (model == EMS_DEVICE_FLAG_RC100)) { } else if ((model == EMS_DEVICE_FLAG_RC300) || (model == EMS_DEVICE_FLAG_RC100)) {
validate_typeid = set_typeids[hc->hc_num() - 1]; validate_typeid = set_typeids[hc->hc_num() - 1];
if (mode == HeatingCircuit::Mode::AUTO) { switch (mode) {
offset = 0x08; // auto offset case HeatingCircuit::Mode::MANUAL:
} else if (mode == HeatingCircuit::Mode::MANUAL) {
offset = 0x0A; // manual offset offset = 0x0A; // manual offset
} else if (mode == HeatingCircuit::Mode::COMFORT) { break;
case HeatingCircuit::Mode::COMFORT:
offset = 0x02; // comfort offset offset = 0x02; // comfort offset
break;
default:
case HeatingCircuit::Mode::AUTO:
offset = 0x08; // auto offset
validate_typeid = monitor_typeids[hc->hc_num() - 1]; // get setpoint roomtemp back
break;
} }
} else if (model == EMS_DEVICE_FLAG_RC20_2) { } else if (model == EMS_DEVICE_FLAG_RC20_2) {
@@ -1632,6 +1643,11 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
case HeatingCircuit::Mode::DAY: // change the day temp case HeatingCircuit::Mode::DAY: // change the day temp
offset = EMS_OFFSET_RC20_2_Set_temp_day; offset = EMS_OFFSET_RC20_2_Set_temp_day;
break; break;
default:
case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code
uint8_t mode_type = hc->get_mode_type(this->flags());
offset = (mode_type == HeatingCircuit::Mode::NIGHT) ? EMS_OFFSET_RC20_2_Set_temp_night : EMS_OFFSET_RC20_2_Set_temp_day;
break;
} }
} else if ((model == EMS_DEVICE_FLAG_RC35) || (model == EMS_DEVICE_FLAG_RC30_1)) { } else if ((model == EMS_DEVICE_FLAG_RC35) || (model == EMS_DEVICE_FLAG_RC30_1)) {
@@ -1662,7 +1678,8 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
factor = 1; factor = 1;
break; break;
default: default:
case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code
validate_typeid = monitor_typeids[hc->hc_num() - 1]; //get setpoint roomtemp back
if (model == EMS_DEVICE_FLAG_RC35) { if (model == EMS_DEVICE_FLAG_RC35) {
uint8_t mode_ = hc->get_mode(this->flags()); uint8_t mode_ = hc->get_mode(this->flags());
if (mode_ == HeatingCircuit::Mode::NIGHT) { if (mode_ == HeatingCircuit::Mode::NIGHT) {
@@ -1699,8 +1716,13 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
default: default:
case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code
uint8_t mode_type = hc->get_mode_type(this->flags()); uint8_t mode_type = hc->get_mode_type(this->flags());
offset = (mode_type == HeatingCircuit::Mode::NIGHT || mode_type == HeatingCircuit::Mode::ECO) ? EMS_OFFSET_JunkersSetMessage_night_temp if (mode_type == HeatingCircuit::Mode::NIGHT || mode_type == HeatingCircuit::Mode::ECO) {
: EMS_OFFSET_JunkersSetMessage_day_temp; offset = EMS_OFFSET_JunkersSetMessage_night_temp;
} else if (mode_type == HeatingCircuit::Mode::DAY || mode_type == HeatingCircuit::Mode::HEAT) {
offset = EMS_OFFSET_JunkersSetMessage_day_temp;
} else {
offset = EMS_OFFSET_JunkersSetMessage_no_frost_temp;
}
break; break;
} }
@@ -1714,10 +1736,20 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
case HeatingCircuit::Mode::NIGHT: case HeatingCircuit::Mode::NIGHT:
offset = EMS_OFFSET_JunkersSetMessage2_eco_temp; offset = EMS_OFFSET_JunkersSetMessage2_eco_temp;
break; break;
default:
case HeatingCircuit::Mode::HEAT: case HeatingCircuit::Mode::HEAT:
case HeatingCircuit::Mode::DAY: case HeatingCircuit::Mode::DAY:
offset = EMS_OFFSET_JunkersSetMessage3_heat; offset = EMS_OFFSET_JunkersSetMessage2_heat_temp;
break;
default:
case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code
uint8_t mode_type = hc->get_mode_type(this->flags());
if (mode_type == HeatingCircuit::Mode::NIGHT || mode_type == HeatingCircuit::Mode::ECO) {
offset = EMS_OFFSET_JunkersSetMessage2_eco_temp;
} else if (mode_type == HeatingCircuit::Mode::DAY || mode_type == HeatingCircuit::Mode::HEAT) {
offset = EMS_OFFSET_JunkersSetMessage2_heat_temp;
} else {
offset = EMS_OFFSET_JunkersSetMessage2_no_frost_temp;
}
break; break;
} }
} }
@@ -1809,7 +1841,7 @@ void Thermostat::add_commands() {
register_mqtt_cmd(F("temp"), [&](const char * value, const int8_t id) { set_temp(value, id); }); register_mqtt_cmd(F("temp"), [&](const char * value, const int8_t id) { set_temp(value, id); });
register_mqtt_cmd(F("mode"), [&](const char * value, const int8_t id) { set_mode(value, id); }); register_mqtt_cmd(F("mode"), [&](const char * value, const int8_t id) { set_mode(value, id); });
uint8_t model = this->flags() & 0x0F; uint8_t model = this->model();
switch (model) { switch (model) {
case EMS_DEVICE_FLAG_RC20_2: case EMS_DEVICE_FLAG_RC20_2:
register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { set_nighttemp(value, id); }); register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { set_nighttemp(value, id); });

View File

@@ -40,10 +40,9 @@ class Thermostat : public EMSdevice {
Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
class HeatingCircuit { class HeatingCircuit {
public: public:
HeatingCircuit(const uint8_t hc_num, const uint16_t monitor_typeid, const uint16_t set_typeid) HeatingCircuit(const uint8_t hc_num)
: hc_num_(hc_num) : hc_num_(hc_num)
, monitor_typeid_(monitor_typeid) , ha_registered_(false) {
, set_typeid_(set_typeid) {
} }
~HeatingCircuit() = default; ~HeatingCircuit() = default;
@@ -60,42 +59,46 @@ class Thermostat : public EMSdevice {
uint8_t targetflowtemp = EMS_VALUE_UINT_NOTSET; uint8_t targetflowtemp = EMS_VALUE_UINT_NOTSET;
uint8_t summertemp = EMS_VALUE_UINT_NOTSET; uint8_t summertemp = EMS_VALUE_UINT_NOTSET;
uint8_t nofrosttemp = EMS_VALUE_UINT_NOTSET; uint8_t nofrosttemp = EMS_VALUE_UINT_NOTSET;
uint8_t designtemp = EMS_VALUE_UINT_NOTSET; // heatingcurve design temp at MinExtTemp uint8_t designtemp = EMS_VALUE_UINT_NOTSET; // heating curve design temp at MinExtTemp
int8_t offsettemp = EMS_VALUE_INT_NOTSET; // heatingcurve offest temp at roomtemp signed! int8_t offsettemp = EMS_VALUE_INT_NOTSET; // heating curve offest temp at roomtemp signed!
uint8_t hc_num() const { uint8_t hc_num() const {
return hc_num_; // 1..10 return hc_num_;
}
bool ha_registered() const {
return ha_registered_;
}
void ha_registered(bool b) {
ha_registered_ = b;
}
// determines if the heating circuit is actually present and has data
bool is_active() {
return Helpers::hasValue(setpoint_roomTemp);
} }
uint8_t get_mode(uint8_t flags) const; uint8_t get_mode(uint8_t flags) const;
uint8_t get_mode_type(uint8_t flags) const; uint8_t get_mode_type(uint8_t flags) const;
uint16_t monitor_typeid() const {
return monitor_typeid_;
}
uint16_t set_typeid() const {
return set_typeid_;
}
enum Mode : uint8_t { UNKNOWN, OFF, MANUAL, AUTO, DAY, NIGHT, HEAT, NOFROST, ECO, HOLIDAY, COMFORT, OFFSET, DESIGN, SUMMER }; enum Mode : uint8_t { UNKNOWN, OFF, MANUAL, AUTO, DAY, NIGHT, HEAT, NOFROST, ECO, HOLIDAY, COMFORT, OFFSET, DESIGN, SUMMER };
// for sorting // for sorting based on hc number
friend inline bool operator<(const std::shared_ptr<HeatingCircuit> & lhs, const std::shared_ptr<HeatingCircuit> & rhs) { friend inline bool operator<(const std::shared_ptr<HeatingCircuit> & lhs, const std::shared_ptr<HeatingCircuit> & rhs) {
return (lhs->hc_num_ < rhs->hc_num_); return (lhs->hc_num_ < rhs->hc_num_);
} }
private: private:
uint8_t hc_num_; // 1..10 uint8_t hc_num_; // heating circuit number 1..10
uint16_t monitor_typeid_; bool ha_registered_; // whether it has been registered for HA MQTT Discovery
uint16_t set_typeid_;
}; };
static std::string mode_tostring(uint8_t mode); static std::string mode_tostring(uint8_t mode);
virtual void show_values(uuid::console::Shell & shell); virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values(); virtual void publish_values();
virtual void device_info(JsonArray & root); virtual void device_info_web(JsonArray & root);
virtual bool updated_values(); virtual bool updated_values();
virtual void add_context_menu(); virtual void add_context_menu();
@@ -105,6 +108,11 @@ class Thermostat : public EMSdevice {
void console_commands(Shell & shell, unsigned int context); void console_commands(Shell & shell, unsigned int context);
void add_commands(); void add_commands();
// specific thermostat characteristics, stripping the option bits at pos 6 and 7
inline uint8_t model() const {
return (this->flags() & 0x0F);
}
// each thermostat has a list of heating controller type IDs for reading and writing // each thermostat has a list of heating controller type IDs for reading and writing
std::vector<uint16_t> monitor_typeids; std::vector<uint16_t> monitor_typeids;
std::vector<uint16_t> set_typeids; std::vector<uint16_t> set_typeids;
@@ -113,10 +121,11 @@ class Thermostat : public EMSdevice {
std::string datetime_; // date and time stamp std::string datetime_; // date and time stamp
uint8_t mqtt_format_; // single, nested or ha uint8_t mqtt_format_; // single, nested or ha
bool changed_ = false;
// Installation parameters // Installation parameters
uint8_t ibaMainDisplay_ = uint8_t ibaMainDisplay_ =
EMS_VALUE_UINT_NOTSET; // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp EMS_VALUE_UINT_NOTSET; // display on Thermostat: 0 int temp, 1 int setpoint, 2 ext temp, 3 burner temp, 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp
uint8_t ibaLanguage_ = EMS_VALUE_UINT_NOTSET; // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian uint8_t ibaLanguage_ = EMS_VALUE_UINT_NOTSET; // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
int8_t ibaCalIntTemperature_ = EMS_VALUE_INT_NOTSET; // offset int. temperature sensor, by * 0.1 Kelvin (-5.0 to 5.0K) int8_t ibaCalIntTemperature_ = EMS_VALUE_INT_NOTSET; // offset int. temperature sensor, by * 0.1 Kelvin (-5.0 to 5.0K)
int8_t ibaMinExtTemperature_ = EMS_VALUE_INT_NOTSET; // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1 int8_t ibaMinExtTemperature_ = EMS_VALUE_INT_NOTSET; // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1
@@ -198,10 +207,9 @@ class Thermostat : public EMSdevice {
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_set_mode = 4; // EMS offset to set mode on thermostat static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_set_mode = 4; // EMS offset to set mode on thermostat
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_no_frost_temp = 5; static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_no_frost_temp = 5;
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_eco_temp = 6; static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_eco_temp = 6;
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage3_heat = 7; static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_heat_temp = 7;
#define AUTO_HEATING_CIRCUIT 0 static constexpr uint8_t AUTO_HEATING_CIRCUIT = 0;
#define DEFAULT_HEATING_CIRCUIT 1
// Installation settings // Installation settings
static constexpr uint8_t EMS_TYPE_IBASettings = 0xA5; // installation settings static constexpr uint8_t EMS_TYPE_IBASettings = 0xA5; // installation settings
@@ -276,7 +284,7 @@ class Thermostat : public EMSdevice {
void set_display(const char * value, const int8_t id); void set_display(const char * value, const int8_t id);
void set_building(const char * value, const int8_t id); void set_building(const char * value, const int8_t id);
void set_language(const char * value, const int8_t id); void set_language(const char * value, const int8_t id);
}; }; // namespace emsesp
} // namespace emsesp } // namespace emsesp

View File

@@ -46,7 +46,7 @@ std::string EMSdevice::brand_to_string() const {
break; break;
case EMSdevice::Brand::NO_BRAND: case EMSdevice::Brand::NO_BRAND:
default: default:
return read_flash_string(F("")); return read_flash_string(F("---"));
break; break;
} }

View File

@@ -140,7 +140,7 @@ class EMSdevice {
virtual void publish_values() = 0; virtual void publish_values() = 0;
virtual bool updated_values() = 0; virtual bool updated_values() = 0;
virtual void add_context_menu() = 0; virtual void add_context_menu() = 0;
virtual void device_info(JsonArray & root) = 0; virtual void device_info_web(JsonArray & root) = 0;
std::string telegram_type_name(std::shared_ptr<const Telegram> telegram); std::string telegram_type_name(std::shared_ptr<const Telegram> telegram);

View File

@@ -58,6 +58,7 @@ uint8_t EMSESP::actual_master_thermostat_ = EMSESP_DEFAULT_MASTER_THERMOSTAT; /
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set
uint8_t EMSESP::watch_ = 0; // trace off uint8_t EMSESP::watch_ = 0; // trace off
uint16_t EMSESP::read_id_ = WATCH_ID_NONE; uint16_t EMSESP::read_id_ = WATCH_ID_NONE;
uint16_t EMSESP::publish_id_ = 0;
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower() bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
uint32_t EMSESP::last_fetch_ = 0; uint32_t EMSESP::last_fetch_ = 0;
uint8_t EMSESP::unique_id_count_ = 0; uint8_t EMSESP::unique_id_count_ = 0;
@@ -130,12 +131,7 @@ uint8_t EMSESP::actual_master_thermostat() {
// to watch both type IDs and device IDs // to watch both type IDs and device IDs
void EMSESP::watch_id(uint16_t watch_id) { void EMSESP::watch_id(uint16_t watch_id) {
// if it's a device ID, which is a single byte, remove the MSB so to support both Buderus and HT3 protocols watch_id_ = watch_id;
if (watch_id <= 0xFF) {
watch_id_ = (watch_id & 0x7F);
} else {
watch_id_ = watch_id;
}
} }
// change the tx_mode // change the tx_mode
@@ -286,27 +282,63 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
char valuestr[8] = {0}; // for formatting temp char valuestr[8] = {0}; // for formatting temp
shell.printfln(F("Dallas temperature sensors:")); shell.printfln(F("Dallas temperature sensors:"));
for (const auto & device : sensor_devices()) { for (const auto & device : sensor_devices()) {
shell.printfln(F(" ID: %s, Temperature: %s°C"), device.to_string().c_str(), Helpers::render_value(valuestr, device.temperature_c, 2)); shell.printfln(F(" ID: %s, Temperature: %s°C"), device.to_string().c_str(), Helpers::render_value(valuestr, device.temperature_c, 1));
} }
shell.println(); shell.println();
} }
// publish all values from each EMS device to MQTT void EMSESP::publish_device_values(uint8_t device_type) {
// plus the heartbeat and sensor if activated
void EMSESP::publish_all_values() {
if (Mqtt::connected()) { if (Mqtt::connected()) {
// Dallas sensors first
sensors_.publish_values();
// all the connected EMS devices we known about
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
if (emsdevice) { if (emsdevice && (emsdevice->device_type() == device_type)) {
emsdevice->publish_values(); emsdevice->publish_values();
} }
} }
} }
} }
void EMSESP::publish_other_values() {
if (Mqtt::connected()) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() != EMSdevice::DeviceType::BOILER) && (emsdevice->device_type() != EMSdevice::DeviceType::THERMOSTAT)
&& (emsdevice->device_type() != EMSdevice::DeviceType::SOLAR) && (emsdevice->device_type() != EMSdevice::DeviceType::MIXING)) {
emsdevice->publish_values();
}
}
}
}
void EMSESP::publish_sensor_values(const bool force) {
if (Mqtt::connected()) {
if (sensors_.updated_values() || force) {
sensors_.publish_values();
}
}
}
// MQTT publish a telegram as raw data
void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
char buffer[100];
doc["src"] = Helpers::hextoa(buffer, telegram->src);
doc["dest"] = Helpers::hextoa(buffer, telegram->dest);
doc["type"] = Helpers::hextoa(buffer, telegram->type_id);
doc["offset"] = Helpers::hextoa(buffer, telegram->offset);
strcpy(buffer, Helpers::data_to_hex(telegram->message_data, telegram->message_length).c_str());
doc["data"] = buffer;
if (telegram->message_length <= 4) {
uint32_t value = 0;
for (uint8_t i = 0; i < telegram->message_length; i++) {
value = (value << 8) + telegram->message_data[i];
}
doc["value"] = value;
}
Mqtt::publish(F("response"), doc);
}
// search for recognized device_ids : Me, All, otherwise print hex value // search for recognized device_ids : Me, All, otherwise print hex value
std::string EMSESP::device_tostring(const uint8_t device_id) { std::string EMSESP::device_tostring(const uint8_t device_id) {
if ((device_id & 0x7F) == rxservice_.ems_bus_id()) { if ((device_id & 0x7F) == rxservice_.ems_bus_id()) {
@@ -475,9 +507,11 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
// if watching... // if watching...
if (telegram->type_id == read_id_) { if (telegram->type_id == read_id_) {
LOG_NOTICE(pretty_telegram(telegram).c_str()); LOG_NOTICE(pretty_telegram(telegram).c_str());
publish_response(telegram);
read_id_ = WATCH_ID_NONE; read_id_ = WATCH_ID_NONE;
} else if (watch() == WATCH_ON) { } else if (watch() == WATCH_ON) {
if ((watch_id_ == WATCH_ID_NONE) || (telegram->src == watch_id_) || (telegram->dest == watch_id_) || (telegram->type_id == watch_id_)) { if ((watch_id_ == WATCH_ID_NONE) || (telegram->type_id == watch_id_)
|| ((watch_id_ < 0x80) && ((telegram->src == watch_id_) || (telegram->dest == watch_id_)))) {
LOG_NOTICE(pretty_telegram(telegram).c_str()); LOG_NOTICE(pretty_telegram(telegram).c_str());
} }
} }
@@ -507,7 +541,10 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
found = emsdevice->handle_telegram(telegram); found = emsdevice->handle_telegram(telegram);
// check to see if we need to follow up after the telegram has been processed // check to see if we need to follow up after the telegram has been processed
if (found) { if (found) {
if (emsdevice->updated_values()) { if ((mqtt_.get_publish_onchange(emsdevice->device_type()) && emsdevice->updated_values()) || telegram->type_id == publish_id_) {
if (telegram->type_id == publish_id_) {
publish_id_ = 0;
}
emsdevice->publish_values(); // publish to MQTT if we explicitly have too emsdevice->publish_values(); // publish to MQTT if we explicitly have too
} }
} }
@@ -524,13 +561,14 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
} }
// calls the device handler's function to populate a json doc with device info // calls the device handler's function to populate a json doc with device info
void EMSESP::device_info(const uint8_t unique_id, JsonObject & root) { // to be used in the Web UI
void EMSESP::device_info_web(const uint8_t unique_id, JsonObject & root) {
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
if (emsdevice) { if (emsdevice) {
if (emsdevice->unique_id() == unique_id) { if (emsdevice->unique_id() == unique_id) {
root["deviceName"] = emsdevice->to_string_short(); // can;t use c_str() because of scope root["deviceName"] = emsdevice->to_string_short(); // can;t use c_str() because of scope
JsonArray data = root.createNestedArray("deviceData"); JsonArray data = root.createNestedArray("deviceData");
emsdevice->device_info(data); emsdevice->device_info_web(data);
return; return;
} }
} }
@@ -709,7 +747,6 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
if (tx_state != Telegram::Operation::NONE) { if (tx_state != Telegram::Operation::NONE) {
bool tx_successful = false; bool tx_successful = false;
EMSbus::tx_state(Telegram::Operation::NONE); // reset Tx wait state EMSbus::tx_state(Telegram::Operation::NONE); // reset Tx wait state
// txservice_.print_last_tx();
// if we're waiting on a Write operation, we want a single byte 1 or 4 // if we're waiting on a Write operation, we want a single byte 1 or 4
if ((tx_state == Telegram::Operation::TX_WRITE) && (length == 1)) { if ((tx_state == Telegram::Operation::TX_WRITE) && (length == 1)) {
@@ -717,7 +754,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
LOG_DEBUG(F("Last Tx write successful")); LOG_DEBUG(F("Last Tx write successful"));
txservice_.increment_telegram_write_count(); // last tx/write was confirmed ok txservice_.increment_telegram_write_count(); // last tx/write was confirmed ok
txservice_.send_poll(); // close the bus txservice_.send_poll(); // close the bus
txservice_.post_send_query(); // follow up with any post-read publish_id_ = txservice_.post_send_query(); // follow up with any post-read if set
txservice_.reset_retry_count(); txservice_.reset_retry_count();
tx_successful = true; tx_successful = true;
} else if (first_value == TxService::TX_WRITE_FAIL) { } else if (first_value == TxService::TX_WRITE_FAIL) {
@@ -771,11 +808,9 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
#ifdef EMSESP_DEBUG #ifdef EMSESP_DEBUG
LOG_TRACE(F("[DEBUG] Reply after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str()); LOG_TRACE(F("[DEBUG] Reply after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
#endif #endif
// check if there is a message for the roomcontroller Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data); // check if there is a message for the roomcontroller
Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data);
// add to RxQueue, what ever it is. rxservice_.add(data, length); // add to RxQueue
// in add() the CRC will be checked
rxservice_.add(data, length);
} }
} }

View File

@@ -59,7 +59,9 @@ class EMSESP {
static void start(); static void start();
static void loop(); static void loop();
static void publish_all_values(); static void publish_device_values(uint8_t device_type);
static void publish_other_values();
static void publish_sensor_values(const bool force = false);
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE
static void run_test(uuid::console::Shell & shell, const std::string & command); // only for testing static void run_test(uuid::console::Shell & shell, const std::string & command); // only for testing
@@ -84,7 +86,7 @@ class EMSESP {
static void send_raw_telegram(const char * data); static void send_raw_telegram(const char * data);
static bool device_exists(const uint8_t device_id); static bool device_exists(const uint8_t device_id);
static void device_info(const uint8_t unique_id, JsonObject & root); static void device_info_web(const uint8_t unique_id, JsonObject & root);
static uint8_t count_devices(const uint8_t device_type); static uint8_t count_devices(const uint8_t device_type);
@@ -174,6 +176,7 @@ class EMSESP {
static void process_UBADevices(std::shared_ptr<const Telegram> telegram); static void process_UBADevices(std::shared_ptr<const Telegram> telegram);
static void process_version(std::shared_ptr<const Telegram> telegram); static void process_version(std::shared_ptr<const Telegram> telegram);
static void publish_response(std::shared_ptr<const Telegram> telegram);
static constexpr uint32_t EMS_FETCH_FREQUENCY = 60000; // check every minute static constexpr uint32_t EMS_FETCH_FREQUENCY = 60000; // check every minute
static uint32_t last_fetch_; static uint32_t last_fetch_;
@@ -191,6 +194,7 @@ class EMSESP {
static uint16_t watch_id_; static uint16_t watch_id_;
static uint8_t watch_; static uint8_t watch_;
static uint16_t read_id_; static uint16_t read_id_;
static uint16_t publish_id_;
static bool tap_water_active_; static bool tap_water_active_;
static uint8_t unique_id_count_; static uint8_t unique_id_count_;

View File

@@ -329,27 +329,27 @@ bool Helpers::check_abs(const int32_t i) {
} }
// for booleans, use isBool true (EMS_VALUE_BOOL) // for booleans, use isBool true (EMS_VALUE_BOOL)
bool Helpers::hasValue(const uint8_t v, const uint8_t isBool) { bool Helpers::hasValue(const uint8_t &v, const uint8_t isBool) {
if (isBool == EMS_VALUE_BOOL) { if (isBool == EMS_VALUE_BOOL) {
return (v != EMS_VALUE_BOOL_NOTSET); return (v != EMS_VALUE_BOOL_NOTSET);
} }
return (v != EMS_VALUE_UINT_NOTSET); return (v != EMS_VALUE_UINT_NOTSET);
} }
bool Helpers::hasValue(const int8_t v) { bool Helpers::hasValue(const int8_t &v) {
return (v != EMS_VALUE_INT_NOTSET); return (v != EMS_VALUE_INT_NOTSET);
} }
// for short these are typically 0x8300, 0x7D00 and sometimes 0x8000 // for short these are typically 0x8300, 0x7D00 and sometimes 0x8000
bool Helpers::hasValue(const int16_t v) { bool Helpers::hasValue(const int16_t &v) {
return (abs(v) < EMS_VALUE_USHORT_NOTSET); return (abs(v) < EMS_VALUE_USHORT_NOTSET);
} }
bool Helpers::hasValue(const uint16_t v) { bool Helpers::hasValue(const uint16_t &v) {
return (v < EMS_VALUE_USHORT_NOTSET); return (v < EMS_VALUE_USHORT_NOTSET);
} }
bool Helpers::hasValue(const uint32_t v) { bool Helpers::hasValue(const uint32_t &v) {
return (v != EMS_VALUE_ULONG_NOTSET); return (v != EMS_VALUE_ULONG_NOTSET);
} }

View File

@@ -50,11 +50,11 @@ class Helpers {
static char * ultostr(char * ptr, uint32_t value, const uint8_t base); static char * ultostr(char * ptr, uint32_t value, const uint8_t base);
#endif #endif
static bool hasValue(const uint8_t v, const uint8_t isBool = 0); static bool hasValue(const uint8_t &v, const uint8_t isBool = 0);
static bool hasValue(const int8_t v); static bool hasValue(const int8_t &v);
static bool hasValue(const int16_t v); static bool hasValue(const int16_t &v);
static bool hasValue(const uint16_t v); static bool hasValue(const uint16_t &v);
static bool hasValue(const uint32_t v); static bool hasValue(const uint32_t &v);
static std::string toLower(std::string const & s); static std::string toLower(std::string const & s);

View File

@@ -108,7 +108,6 @@ MAKE_PSTR(gpio_mandatory, "<gpio>")
MAKE_PSTR(data_optional, "[data]") MAKE_PSTR(data_optional, "[data]")
MAKE_PSTR(typeid_mandatory, "<type ID>") MAKE_PSTR(typeid_mandatory, "<type ID>")
MAKE_PSTR(deviceid_mandatory, "<device ID>") MAKE_PSTR(deviceid_mandatory, "<device ID>")
MAKE_PSTR(deviceid_optional, "[device ID]")
MAKE_PSTR(invalid_log_level, "Invalid log level") MAKE_PSTR(invalid_log_level, "Invalid log level")
MAKE_PSTR(log_level_fmt, "Log level = %s") MAKE_PSTR(log_level_fmt, "Log level = %s")
MAKE_PSTR(log_level_optional, "[level]") MAKE_PSTR(log_level_optional, "[level]")

View File

@@ -27,8 +27,13 @@ AsyncMqttClient * Mqtt::mqttClient_;
// static parameters we make global // static parameters we make global
std::string Mqtt::hostname_; std::string Mqtt::hostname_;
uint8_t Mqtt::mqtt_qos_; uint8_t Mqtt::mqtt_qos_;
uint16_t Mqtt::publish_time_;
uint8_t Mqtt::bus_id_; uint8_t Mqtt::bus_id_;
uint32_t Mqtt::publish_time_boiler_;
uint32_t Mqtt::publish_time_thermostat_;
uint32_t Mqtt::publish_time_solar_;
uint32_t Mqtt::publish_time_mixing_;
uint32_t Mqtt::publish_time_other_;
uint32_t Mqtt::publish_time_sensor_;
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_; std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
std::vector<Mqtt::MQTTCmdFunction> Mqtt::mqtt_cmdfunctions_; std::vector<Mqtt::MQTTCmdFunction> Mqtt::mqtt_cmdfunctions_;
@@ -111,11 +116,30 @@ void Mqtt::loop() {
uint32_t currentMillis = uuid::get_uptime(); uint32_t currentMillis = uuid::get_uptime();
// create publish messages for each of the EMS device values, adding to queue // create publish messages for each of the EMS device values, adding to queue
if (publish_time_ && (currentMillis - last_publish_ > publish_time_)) { if (publish_time_boiler_ && (currentMillis - last_publish_boiler_ > publish_time_boiler_)) {
last_publish_ = currentMillis; last_publish_boiler_ = currentMillis;
EMSESP::publish_all_values(); EMSESP::publish_device_values(EMSdevice::DeviceType::BOILER);
}
if (publish_time_thermostat_ && (currentMillis - last_publish_thermostat_ > publish_time_thermostat_)) {
last_publish_thermostat_ = currentMillis;
EMSESP::publish_device_values(EMSdevice::DeviceType::THERMOSTAT);
}
if (publish_time_solar_ && (currentMillis - last_publish_solar_ > publish_time_solar_)) {
last_publish_solar_ = currentMillis;
EMSESP::publish_device_values(EMSdevice::DeviceType::SOLAR);
}
if (publish_time_mixing_ && (currentMillis - last_publish_mixing_ > publish_time_mixing_)) {
last_publish_mixing_ = currentMillis;
EMSESP::publish_device_values(EMSdevice::DeviceType::MIXING);
}
if (publish_time_other_ && (currentMillis - last_publish_other_ > publish_time_other_)) {
last_publish_other_ = currentMillis;
EMSESP::publish_other_values();
}
if (currentMillis - last_publish_sensor_ > publish_time_sensor_) {
last_publish_sensor_ = currentMillis;
EMSESP::publish_sensor_values(publish_time_sensor_ != 0);
} }
// publish top item from MQTT queue to stop flooding // publish top item from MQTT queue to stop flooding
if ((uint32_t)(currentMillis - last_mqtt_poll_) > MQTT_PUBLISH_WAIT) { if ((uint32_t)(currentMillis - last_mqtt_poll_) > MQTT_PUBLISH_WAIT) {
last_mqtt_poll_ = currentMillis; last_mqtt_poll_ = currentMillis;
@@ -280,7 +304,7 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
} }
if (!cmd_known) { if (!cmd_known) {
LOG_ERROR(F("MQTT: no matching cmd or invalid data: %s"), command); LOG_ERROR(F("MQTT: no matching cmd or invalid data: %s"), message);
} }
return; return;
@@ -342,8 +366,13 @@ void Mqtt::start() {
// fetch MQTT settings // fetch MQTT settings
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) { EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) {
publish_time_ = mqttSettings.publish_time * 1000; // convert to milliseconds publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000; // convert to milliseconds
mqtt_qos_ = mqttSettings.mqtt_qos; publish_time_thermostat_ = mqttSettings.publish_time_thermostat * 1000;
publish_time_solar_ = mqttSettings.publish_time_solar * 1000;
publish_time_mixing_ = mqttSettings.publish_time_mixing * 1000;
publish_time_other_ = mqttSettings.publish_time_other * 1000;
publish_time_sensor_ = mqttSettings.publish_time_sensor * 1000;
mqtt_qos_ = mqttSettings.mqtt_qos;
}); });
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { bus_id_ = settings.ems_bus_id; }); EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { bus_id_ = settings.ems_bus_id; });
@@ -390,8 +419,51 @@ void Mqtt::start() {
mqtt_subfunctions_.reserve(10); mqtt_subfunctions_.reserve(10);
} }
void Mqtt::set_publish_time(uint16_t publish_time) { void Mqtt::set_publish_time_boiler(uint16_t publish_time) {
publish_time_ = publish_time * 1000; // convert to milliseconds publish_time_boiler_ = publish_time * 1000; // convert to milliseconds
}
void Mqtt::set_publish_time_thermostat(uint16_t publish_time) {
publish_time_thermostat_ = publish_time * 1000; // convert to milliseconds
}
void Mqtt::set_publish_time_solar(uint16_t publish_time) {
publish_time_solar_ = publish_time * 1000; // convert to milliseconds
}
void Mqtt::set_publish_time_mixing(uint16_t publish_time) {
publish_time_mixing_ = publish_time * 1000; // convert to milliseconds
}
void Mqtt::set_publish_time_other(uint16_t publish_time) {
publish_time_other_ = publish_time * 1000; // convert to milliseconds
}
void Mqtt::set_publish_time_sensor(uint16_t publish_time) {
publish_time_sensor_ = publish_time * 1000; // convert to milliseconds
}
bool Mqtt::get_publish_onchange(uint8_t device_type) {
if (device_type == EMSdevice::DeviceType::BOILER) {
if (!publish_time_boiler_) {
return true;
}
} else if (device_type == EMSdevice::DeviceType::THERMOSTAT) {
if (!publish_time_thermostat_) {
return true;
}
} else if (device_type == EMSdevice::DeviceType::SOLAR) {
if (!publish_time_solar_) {
return true;
}
} else if (device_type == EMSdevice::DeviceType::MIXING) {
if (!publish_time_mixing_) {
return true;
}
} else if (!publish_time_other_) {
return true;
}
return false;
} }
void Mqtt::set_qos(uint8_t mqtt_qos) { void Mqtt::set_qos(uint8_t mqtt_qos) {
@@ -407,9 +479,9 @@ void Mqtt::on_connect() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
doc["ip"] = WiFi.localIP().toString(); doc["ip"] = WiFi.localIP().toString();
#endif #endif
publish("info", doc, false); // send with retain off publish(F("info"), doc, false); // send with retain off
publish("status", "online", true); // say we're alive to the Last Will topic, with retain on publish(F("status"), "online", true); // say we're alive to the Last Will topic, with retain on
reset_publish_fails(); // reset fail count to 0 reset_publish_fails(); // reset fail count to 0
@@ -466,6 +538,15 @@ void Mqtt::publish(const std::string & topic, const std::string & payload, bool
queue_publish_message(topic, payload, retain); queue_publish_message(topic, payload, retain);
} }
// MQTT Publish, using a specific retain flag, topic is a flash string
void Mqtt::publish(const __FlashStringHelper * topic, const std::string & payload, bool retain) {
queue_publish_message(uuid::read_flash_string(topic), payload, retain);
}
void Mqtt::publish(const __FlashStringHelper * topic, const JsonDocument & payload, bool retain) {
publish(uuid::read_flash_string(topic), payload, retain);
}
void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool retain) { void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool retain) {
std::string payload_text; std::string payload_text;
serializeJson(payload, payload_text); // convert json to string serializeJson(payload, payload_text); // convert json to string
@@ -476,19 +557,15 @@ void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool
void Mqtt::publish(const std::string & topic, const bool value) { void Mqtt::publish(const std::string & topic, const bool value) {
queue_publish_message(topic, value ? "1" : "0", false); queue_publish_message(topic, value ? "1" : "0", false);
} }
void Mqtt::publish(const __FlashStringHelper * topic, const bool value) {
queue_publish_message(uuid::read_flash_string(topic), value ? "1" : "0", false);
}
// no payload // no payload
void Mqtt::publish(const std::string & topic) { void Mqtt::publish(const std::string & topic) {
queue_publish_message(topic, "", false); queue_publish_message(topic, "", false);
} }
// publish all queued messages to MQTT
void Mqtt::process_all_queue() {
while (!mqtt_messages_.empty()) {
process_queue();
}
}
// take top from queue and perform the publish or subscribe action // take top from queue and perform the publish or subscribe action
// assumes there is an MQTT connection // assumes there is an MQTT connection
void Mqtt::process_queue() { void Mqtt::process_queue() {

View File

@@ -67,8 +67,14 @@ class Mqtt {
void loop(); void loop();
void start(); void start();
void set_publish_time(uint16_t publish_time); void set_publish_time_boiler(uint16_t publish_time);
void set_publish_time_thermostat(uint16_t publish_time);
void set_publish_time_solar(uint16_t publish_time);
void set_publish_time_mixing(uint16_t publish_time);
void set_publish_time_other(uint16_t publish_time);
void set_publish_time_sensor(uint16_t publish_time);
void set_qos(uint8_t mqtt_qos); void set_qos(uint8_t mqtt_qos);
bool get_publish_onchange(uint8_t device_type);
enum Operation { PUBLISH, SUBSCRIBE }; enum Operation { PUBLISH, SUBSCRIBE };
@@ -82,7 +88,10 @@ class Mqtt {
static void publish(const std::string & topic, const std::string & payload, bool retain = false); static void publish(const std::string & topic, const std::string & payload, bool retain = false);
static void publish(const std::string & topic, const JsonDocument & payload, bool retain = false); static void publish(const std::string & topic, const JsonDocument & payload, bool retain = false);
static void publish(const __FlashStringHelper * topic, const JsonDocument & payload, bool retain = false);
static void publish(const __FlashStringHelper * topic, const std::string & payload, bool retain = false);
static void publish(const std::string & topic, const bool value); static void publish(const std::string & topic, const bool value);
static void publish(const __FlashStringHelper * topi, const bool value);
static void publish(const std::string & topic); static void publish(const std::string & topic);
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type); static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
@@ -167,7 +176,6 @@ class Mqtt {
void on_publish(uint16_t packetId); void on_publish(uint16_t packetId);
void on_message(const char * topic, const char * payload, size_t len); void on_message(const char * topic, const char * payload, size_t len);
void process_queue(); void process_queue();
void process_all_queue();
static uint16_t mqtt_publish_fails_; static uint16_t mqtt_publish_fails_;
@@ -189,14 +197,25 @@ class Mqtt {
static std::vector<MQTTSubFunction> mqtt_subfunctions_; // list of mqtt subscribe callbacks for all devices static std::vector<MQTTSubFunction> mqtt_subfunctions_; // list of mqtt subscribe callbacks for all devices
static std::vector<MQTTCmdFunction> mqtt_cmdfunctions_; // list of commands static std::vector<MQTTCmdFunction> mqtt_cmdfunctions_; // list of commands
uint32_t last_mqtt_poll_ = 0; uint32_t last_mqtt_poll_ = 0;
uint32_t last_publish_ = 0; uint32_t last_publish_boiler_ = 0;
uint32_t last_publish_thermostat_ = 0;
uint32_t last_publish_solar_ = 0;
uint32_t last_publish_mixing_ = 0;
uint32_t last_publish_other_ = 0;
uint32_t last_publish_sensor_ = 0;
// settings, copied over // settings, copied over
static std::string hostname_; static std::string hostname_;
static uint8_t mqtt_qos_; static uint8_t mqtt_qos_;
static uint16_t publish_time_; static uint32_t publish_time_;
static uint8_t bus_id_; static uint8_t bus_id_;
static uint32_t publish_time_boiler_;
static uint32_t publish_time_thermostat_;
static uint32_t publish_time_solar_;
static uint32_t publish_time_mixing_;
static uint32_t publish_time_other_;
static uint32_t publish_time_sensor_;
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -49,7 +49,10 @@ void Sensors::reload() {
mqtt_format_ = settings.mqtt_format; // single, nested or ha mqtt_format_ = settings.mqtt_format; // single, nested or ha
}); });
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { dallas_gpio_ = settings.dallas_gpio; }); EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
dallas_gpio_ = settings.dallas_gpio;
parasite_ = settings.dallas_parasite;
});
if (mqtt_format_ == MQTT_format::HA) { if (mqtt_format_ == MQTT_format::HA) {
for (uint8_t i = 0; i < MAX_SENSORS; registered_ha_[i++] = false) for (uint8_t i = 0; i < MAX_SENSORS; registered_ha_[i++] = false)
@@ -64,10 +67,10 @@ void Sensors::loop() {
if (state_ == State::IDLE) { if (state_ == State::IDLE) {
if (time_now - last_activity_ >= READ_INTERVAL_MS) { if (time_now - last_activity_ >= READ_INTERVAL_MS) {
// LOG_DEBUG(F("Read sensor temperature")); // uncomment for debug // LOG_DEBUG(F("Read sensor temperature")); // uncomment for debug
if (bus_.reset()) { if (bus_.reset() || parasite_) {
YIELD; YIELD;
bus_.skip(); bus_.skip();
bus_.write(CMD_CONVERT_TEMP); bus_.write(CMD_CONVERT_TEMP, parasite_ ? 1 : 0);
state_ = State::READING; state_ = State::READING;
} else { } else {
// no sensors found // no sensors found
@@ -94,8 +97,9 @@ void Sensors::loop() {
uint8_t addr[ADDR_LEN] = {0}; uint8_t addr[ADDR_LEN] = {0};
if (bus_.search(addr)) { if (bus_.search(addr)) {
bus_.depower(); if (!parasite_) {
bus_.depower();
}
if (bus_.crc8(addr, ADDR_LEN - 1) == addr[ADDR_LEN - 1]) { if (bus_.crc8(addr, ADDR_LEN - 1) == addr[ADDR_LEN - 1]) {
switch (addr[0]) { switch (addr[0]) {
case TYPE_DS18B20: case TYPE_DS18B20:
@@ -122,8 +126,19 @@ void Sensors::loop() {
LOG_ERROR(F("Invalid sensor %s"), Device(addr).to_string().c_str()); LOG_ERROR(F("Invalid sensor %s"), Device(addr).to_string().c_str());
} }
} else { } else {
bus_.depower(); if (!parasite_) {
bus_.depower();
}
if ((found_.size() >= devices_.size()) || (retrycnt_ > 5)) { if ((found_.size() >= devices_.size()) || (retrycnt_ > 5)) {
if (found_.size() == devices_.size()) {
for (uint8_t i = 0; i < devices_.size(); i++) {
if (found_[i].temperature_c != devices_[i].temperature_c) {
changed_ = true;
}
}
} else {
changed_ = true;
}
devices_ = std::move(found_); devices_ = std::move(found_);
retrycnt_ = 0; retrycnt_ = 0;
} else { } else {
@@ -140,9 +155,12 @@ void Sensors::loop() {
bool Sensors::temperature_convert_complete() { bool Sensors::temperature_convert_complete() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (parasite_) {
return true; // don't care, use the minimum time in loop
}
return bus_.read_bit() == 1; return bus_.read_bit() == 1;
#else #else
return 1; return true;
#endif #endif
} }
@@ -183,27 +201,27 @@ float Sensors::get_temperature_c(const uint8_t addr[]) {
int16_t raw_value = ((int16_t)scratchpad[SCRATCHPAD_TEMP_MSB] << 8) | scratchpad[SCRATCHPAD_TEMP_LSB]; int16_t raw_value = ((int16_t)scratchpad[SCRATCHPAD_TEMP_MSB] << 8) | scratchpad[SCRATCHPAD_TEMP_LSB];
// Adjust based on device resolution if (addr[0] == TYPE_DS18S20) {
int resolution = 9 + ((scratchpad[SCRATCHPAD_CONFIG] >> 5) & 0x3); raw_value = (raw_value << 3) + 12 - scratchpad[SCRATCHPAD_CNT_REM];
switch (resolution) { } else {
case 9: // Adjust based on device resolution
raw_value &= ~0x1; int resolution = 9 + ((scratchpad[SCRATCHPAD_CONFIG] >> 5) & 0x3);
break; switch (resolution) {
case 9:
case 10: raw_value &= ~0x7;
raw_value &= ~0x3; break;
break; case 10:
raw_value &= ~0x3;
case 11: break;
raw_value &= ~0x7; case 11:
break; raw_value &= ~0x1;
break;
case 12: case 12:
break; break;
}
} }
uint32_t raw = (raw_value * 625 + 500) / 1000; // round to 0.1
uint32_t raw = (raw_value * 625) / 100; // round to 0.01 return (float)raw / 10;
return (float)raw / 100;
#else #else
return NAN; return NAN;
#endif #endif
@@ -237,6 +255,14 @@ std::string Sensors::Device::to_string() const {
return str; return str;
} }
// check to see if values have been updated
bool Sensors::updated_values() {
if (changed_) {
changed_ = false;
return true;
}
return false;
}
// send all dallas sensor values as a JSON package to MQTT // send all dallas sensor values as a JSON package to MQTT
// assumes there are devices // assumes there are devices
@@ -254,7 +280,7 @@ void Sensors::publish_values() {
StaticJsonDocument<100> doc; StaticJsonDocument<100> doc;
for (const auto & device : devices_) { for (const auto & device : devices_) {
char s[7]; // sensorrange -55.00 to 125.00 char s[7]; // sensorrange -55.00 to 125.00
doc["temp"] = Helpers::render_value(s, device.temperature_c, 2); doc["temp"] = Helpers::render_value(s, device.temperature_c, 1);
char topic[60]; // sensors{1-n} char topic[60]; // sensors{1-n}
strlcpy(topic, "sensor_", 50); // create topic, e.g. home/ems-esp/sensor_28-EA41-9497-0E03-5F strlcpy(topic, "sensor_", 50); // create topic, e.g. home/ems-esp/sensor_28-EA41-9497-0E03-5F
strlcat(topic, device.to_string().c_str(), 60); strlcat(topic, device.to_string().c_str(), 60);
@@ -264,28 +290,22 @@ void Sensors::publish_values() {
return; return;
} }
// const size_t capacity = num_devices * JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(num_devices);
DynamicJsonDocument doc(100 * num_devices); DynamicJsonDocument doc(100 * num_devices);
uint8_t i = 1; // sensor count uint8_t i = 1; // sensor count
for (const auto & device : devices_) { for (const auto & device : devices_) {
char s[7]; char s[7];
if (mqtt_format_ == MQTT_format::CUSTOM) { if (mqtt_format_ == MQTT_format::CUSTOM) {
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c, 2); // e.g. sensors = {28-EA41-9497-0E03-5F":23.30,"28-233D-9497-0C03-8B":24.0}
} else if (mqtt_format_ == MQTT_format::SINGLE) { doc[device.to_string()] = Helpers::render_value(s, device.temperature_c, 1);
doc["id"] = device.to_string();
doc["temp"] = Helpers::render_value(s, device.temperature_c, 2);
std::string topic(100, '\0');
snprintf_P(&topic[0], 50, PSTR("sensor%d"), i);
Mqtt::publish(topic, doc);
} else if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::HA)) { } else if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::HA)) {
// e.g. {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.30"},"sensor2":{"id":"28-233D-9497-0C03-8B","temp":"24.0"}} // e.g. sensors = {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.30"},"sensor2":{"id":"28-233D-9497-0C03-8B","temp":"24.0"}}
char sensorID[10]; // sensor{1-n} char sensorID[10]; // sensor{1-n}
strlcpy(sensorID, "sensor", 10); strlcpy(sensorID, "sensor", 10);
strlcat(sensorID, Helpers::itoa(s, i), 10); strlcat(sensorID, Helpers::itoa(s, i), 10);
JsonObject dataSensor = doc.createNestedObject(sensorID); JsonObject dataSensor = doc.createNestedObject(sensorID);
dataSensor["id"] = device.to_string(); dataSensor["id"] = device.to_string();
dataSensor["temp"] = Helpers::render_value(s, device.temperature_c, 2); dataSensor["temp"] = Helpers::render_value(s, device.temperature_c, 1);
} }
// special for HA // special for HA
@@ -327,9 +347,9 @@ void Sensors::publish_values() {
} }
if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::CUSTOM)) { if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::CUSTOM)) {
Mqtt::publish("sensors", doc); Mqtt::publish(F("sensors"), doc);
} else if (mqtt_format_ == MQTT_format::HA) { } else if (mqtt_format_ == MQTT_format::HA) {
Mqtt::publish("homeassistant/sensor/ems-esp/state", doc); Mqtt::publish(F("homeassistant/sensor/ems-esp/state"), doc);
} }
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -60,6 +60,7 @@ class Sensors {
void loop(); void loop();
void publish_values(); void publish_values();
void reload(); void reload();
bool updated_values();
const std::vector<Device> devices() const; const std::vector<Device> devices() const;
@@ -74,12 +75,13 @@ class Sensors {
static constexpr size_t SCRATCHPAD_TEMP_MSB = 1; static constexpr size_t SCRATCHPAD_TEMP_MSB = 1;
static constexpr size_t SCRATCHPAD_TEMP_LSB = 0; static constexpr size_t SCRATCHPAD_TEMP_LSB = 0;
static constexpr size_t SCRATCHPAD_CONFIG = 4; static constexpr size_t SCRATCHPAD_CONFIG = 4;
static constexpr size_t SCRATCHPAD_CNT_REM = 6;
// dallas chips // dallas chips
static constexpr uint8_t TYPE_DS18B20 = 0x28; static constexpr uint8_t TYPE_DS18B20 = 0x28;
static constexpr uint8_t TYPE_DS18S20 = 0x10; static constexpr uint8_t TYPE_DS18S20 = 0x10;
static constexpr uint8_t TYPE_DS1822 = 0x22; static constexpr uint8_t TYPE_DS1822 = 0x22;
static constexpr uint8_t TYPE_DS1825 = 0x3B; static constexpr uint8_t TYPE_DS1825 = 0x3B; // also DS1826
static constexpr uint32_t READ_INTERVAL_MS = 5000; // 5 seconds static constexpr uint32_t READ_INTERVAL_MS = 5000; // 5 seconds
static constexpr uint32_t CONVERSION_MS = 1000; // 1 seconds static constexpr uint32_t CONVERSION_MS = 1000; // 1 seconds
@@ -109,6 +111,8 @@ class Sensors {
uint8_t mqtt_format_; uint8_t mqtt_format_;
uint8_t retrycnt_ = 0; uint8_t retrycnt_ = 0;
uint8_t dallas_gpio_ = 0; uint8_t dallas_gpio_ = 0;
bool parasite_ = false;
bool changed_ = false;
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -53,7 +53,7 @@ void Shower::loop() {
// first check to see if hot water has been on long enough to be recognized as a Shower/Bath // first check to see if hot water has been on long enough to be recognized as a Shower/Bath
if (!shower_on_ && (time_now - timer_start_) > SHOWER_MIN_DURATION) { if (!shower_on_ && (time_now - timer_start_) > SHOWER_MIN_DURATION) {
shower_on_ = true; shower_on_ = true;
Mqtt::publish("shower_active", (bool)true); Mqtt::publish(F("shower_active"), (bool)true);
LOG_DEBUG(F("[Shower] hot water still running, starting shower timer")); LOG_DEBUG(F("[Shower] hot water still running, starting shower timer"));
} }
// check if the shower has been on too long // check if the shower has been on too long
@@ -74,7 +74,7 @@ void Shower::loop() {
if ((timer_pause_ - timer_start_) > SHOWER_OFFSET_TIME) { if ((timer_pause_ - timer_start_) > SHOWER_OFFSET_TIME) {
duration_ = (timer_pause_ - timer_start_ - SHOWER_OFFSET_TIME); duration_ = (timer_pause_ - timer_start_ - SHOWER_OFFSET_TIME);
if (duration_ > SHOWER_MIN_DURATION) { if (duration_ > SHOWER_MIN_DURATION) {
Mqtt::publish("shower_active", (bool)false); Mqtt::publish(F("shower_active"), (bool)false);
LOG_DEBUG(F("[Shower] finished with duration %d"), duration_); LOG_DEBUG(F("[Shower] finished with duration %d"), duration_);
publish_values(); publish_values();
} }
@@ -129,7 +129,7 @@ void Shower::publish_values() {
doc["duration"] = s; doc["duration"] = s;
} }
Mqtt::publish("shower_data", doc); Mqtt::publish(F("shower_data"), doc);
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -35,6 +35,7 @@ int System::reset_counter_ = 0;
bool System::upload_status_ = false; bool System::upload_status_ = false;
bool System::hide_led_ = false; bool System::hide_led_ = false;
uint8_t System::led_gpio_ = 0; uint8_t System::led_gpio_ = 0;
uint16_t System::analog_ = 0;
// send on/off to a gpio pin // send on/off to a gpio pin
// value: true = HIGH, false = LOW // value: true = HIGH, false = LOW
@@ -188,6 +189,7 @@ void System::loop() {
#endif #endif
led_monitor(); // check status and report back using the LED led_monitor(); // check status and report back using the LED
system_check(); // check system health system_check(); // check system health
measure_analog();
// send out heartbeat // send out heartbeat
uint32_t currentMillis = uuid::get_uptime(); uint32_t currentMillis = uuid::get_uptime();
@@ -233,8 +235,34 @@ void System::send_heartbeat() {
doc["mqttpublishfails"] = Mqtt::publish_fails(); doc["mqttpublishfails"] = Mqtt::publish_fails();
doc["txfails"] = EMSESP::txservice_.telegram_fail_count(); doc["txfails"] = EMSESP::txservice_.telegram_fail_count();
doc["rxfails"] = EMSESP::rxservice_.telegram_error_count(); doc["rxfails"] = EMSESP::rxservice_.telegram_error_count();
doc["adc"] = analog_; //analogRead(A0);
Mqtt::publish("heartbeat", doc, false); // send to MQTT with retain off. This will add to MQTT queue. Mqtt::publish(F("heartbeat"), doc, false); // send to MQTT with retain off. This will add to MQTT queue.
}
// measure and moving average adc
void System::measure_analog() {
static uint32_t measure_last_ = 0;
if (!measure_last_ || (uint32_t)(uuid::get_uptime() - measure_last_) >= SYSTEM_MEASURE_ANALOG_INTERVAL) {
measure_last_ = uuid::get_uptime();
#if defined(ESP8266)
uint16_t a = analogRead(A0);
#elif defined(ESP32)
uint16_t a = analogRead(36);
#else
uint16_t a = 0; // standalone
#endif
static uint32_t sum_ = 0;
if (!analog_) { // init first time
analog_ = a;
sum_ = a * 256;
} else { // simple moving average filter
sum_ = sum_ * 255 / 256 + a;
analog_ = sum_ / 256;
}
}
} }
// sets rate of led flash // sets rate of led flash
@@ -594,9 +622,23 @@ void System::console_commands(Shell & shell, unsigned int context) {
// upgrade from previous versions of EMS-ESP, based on SPIFFS on an ESP8266 // upgrade from previous versions of EMS-ESP, based on SPIFFS on an ESP8266
// returns true if an upgrade was done // returns true if an upgrade was done
// the logic is bit abnormal (loading both filesystems and testing) but this was the only way I could get it to work reliably
bool System::check_upgrade() { bool System::check_upgrade() {
#if defined(ESP8266) #if defined(ESP8266)
LittleFSConfig l_cfg;
l_cfg.setAutoFormat(false);
LittleFS.setConfig(l_cfg); // do not auto format if it can't find LittleFS
if (LittleFS.begin()) {
#if defined(EMSESP_DEBUG)
Serial.begin(115200);
Serial.println(F("FS is Littlefs"));
Serial.flush();
Serial.end();
#endif
return false;
}
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
@@ -604,120 +646,172 @@ bool System::check_upgrade() {
cfg.setAutoFormat(false); // prevent formatting when opening SPIFFS filesystem cfg.setAutoFormat(false); // prevent formatting when opening SPIFFS filesystem
SPIFFS.setConfig(cfg); SPIFFS.setConfig(cfg);
if (!SPIFFS.begin()) { if (!SPIFFS.begin()) {
return false; // is not SPIFFS #if defined(EMSESP_DEBUG)
Serial.begin(115200);
Serial.println(F("No old SPIFFS found!"));
Serial.flush();
Serial.end();
#endif
return false;
} }
Serial.begin(115200); Serial.begin(115200);
// open the two files
File file1 = SPIFFS.open("/myesp.json", "r");
File file2 = SPIFFS.open("/customconfig.json", "r");
if (!file1 || !file2) {
Serial.println(F("Unable to read the config files"));
file1.close();
file2.close();
SPIFFS.end();
return false; // can't open files
}
// read the content of the files
DeserializationError error;
StaticJsonDocument<1024> doc1; // for myESP settings
StaticJsonDocument<1024> doc2; // for custom EMS-ESP settings
bool failed = false; bool failed = false;
File file;
JsonObject network, general, mqtt, custom_settings;
StaticJsonDocument<1024> doc;
error = deserializeJson(doc1, file1); // open the system settings:
if (error) { // {
Serial.printf("Error. Failed to deserialize json, doc1, error %s", error.c_str()); // "command":"configfile",
// "network":{"ssid":"xxxx","password":"yyyy","wmode":1,"staticip":null,"gatewayip":null,"nmask":null,"dnsip":null},
// "general":{"password":"admin","serial":false,"hostname":"ems-esp","log_events":false,"log_ip":null,"version":"1.9.5"},
// "mqtt":{"enabled":false,"heartbeat":false,"ip":null,"user":null,"port":1883,"qos":0,"keepalive":60,"retain":false,"password":null,"base":null,"nestedjson":false},
// "ntp":{"server":"pool.ntp.org","interval":720,"enabled":false,"timezone":2}
// }
file = SPIFFS.open("/myesp.json", "r");
if (!file) {
Serial.println(F("Unable to read the system config file"));
failed = true; failed = true;
} else {
DeserializationError error = deserializeJson(doc, file);
if (error) {
Serial.printf(PSTR("Error. Failed to deserialize system json, error %s\n"), error.c_str());
failed = true;
} else {
Serial.println(F("Migrating settings from EMS-ESP v1.9..."));
#if defined(EMSESP_DEBUG)
serializeJson(doc, Serial);
Serial.println();
#endif
network = doc["network"];
general = doc["general"];
mqtt = doc["mqtt"];
// start up LittleFS. If it doesn't exist it will format it
l_cfg.setAutoFormat(true);
LittleFS.setConfig(l_cfg);
LittleFS.begin();
EMSESP::esp8266React.begin();
EMSESP::emsespSettingsService.begin();
EMSESP::esp8266React.getWiFiSettingsService()->update(
[&](WiFiSettings & wifiSettings) {
wifiSettings.hostname = general["hostname"] | FACTORY_WIFI_HOSTNAME;
wifiSettings.ssid = network["ssid"] | FACTORY_WIFI_SSID;
wifiSettings.password = network["password"] | FACTORY_WIFI_PASSWORD;
wifiSettings.staticIPConfig = false;
JsonUtils::readIP(network, "staticip", wifiSettings.localIP);
JsonUtils::readIP(network, "dnsip", wifiSettings.dnsIP1);
JsonUtils::readIP(network, "gatewayip", wifiSettings.gatewayIP);
JsonUtils::readIP(network, "nmask", wifiSettings.subnetMask);
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::esp8266React.getSecuritySettingsService()->update(
[&](SecuritySettings & securitySettings) {
securitySettings.jwtSecret = general["password"] | FACTORY_JWT_SECRET;
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::esp8266React.getMqttSettingsService()->update(
[&](MqttSettings & mqttSettings) {
mqttSettings.host = mqtt["ip"] | FACTORY_MQTT_HOST;
mqttSettings.mqtt_format = (mqtt["nestedjson"] ? MQTT_format::NESTED : MQTT_format::SINGLE);
mqttSettings.mqtt_qos = mqtt["qos"] | 0;
mqttSettings.username = mqtt["user"] | "";
mqttSettings.password = mqtt["password"] | "";
mqttSettings.port = mqtt["port"] | FACTORY_MQTT_PORT;
mqttSettings.clientId = FACTORY_MQTT_CLIENT_ID;
mqttSettings.enabled = mqtt["enabled"];
mqttSettings.system_heartbeat = mqtt["heartbeat"];
mqttSettings.keepAlive = FACTORY_MQTT_KEEP_ALIVE;
mqttSettings.cleanSession = FACTORY_MQTT_CLEAN_SESSION;
mqttSettings.maxTopicLength = FACTORY_MQTT_MAX_TOPIC_LENGTH;
return StateUpdateResult::CHANGED;
},
"local");
}
} }
error = deserializeJson(doc2, file2); file.close();
if (error) {
Serial.printf("Error. Failed to deserialize json, doc2, error %s", error.c_str()); if (failed) {
failed = true; #if defined(EMSESP_DEBUG)
Serial.println(F("Failed to read system config. Quitting."));
#endif
SPIFFS.end();
Serial.end();
return false;
} }
file1.close(); // open the custom settings file next:
file2.close(); // {
// "command":"custom_configfile",
// "settings":{"led":true,"led_gpio":2,"dallas_gpio":14,"dallas_parasite":false,"listen_mode":false,"shower_timer":false,"shower_alert":false,"publish_time":0,"tx_mode":1,"bus_id":11,"master_thermostat":0,"known_devices":""}
// }
doc.clear();
failed = false;
file = SPIFFS.open("/customconfig.json", "r");
if (!file) {
Serial.println(F("Unable to read custom config file"));
failed = true;
} else {
DeserializationError error = deserializeJson(doc, file);
if (error) {
Serial.printf(PSTR("Error. Failed to deserialize custom json, error %s\n"), error.c_str());
failed = true;
} else {
#if defined(EMSESP_DEBUG)
serializeJson(doc, Serial);
Serial.println();
#endif
custom_settings = doc["settings"];
EMSESP::emsespSettingsService.update(
[&](EMSESPSettings & settings) {
settings.tx_mode = custom_settings["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
settings.shower_alert = custom_settings["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
settings.shower_timer = custom_settings["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
settings.master_thermostat = custom_settings["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
settings.ems_bus_id = custom_settings["bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
settings.syslog_host = EMSESP_DEFAULT_SYSLOG_HOST;
settings.syslog_level = EMSESP_DEFAULT_SYSLOG_LEVEL;
settings.syslog_mark_interval = EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
settings.dallas_gpio = custom_settings["dallas_gpio"] | EMSESP_DEFAULT_DALLAS_GPIO;
settings.dallas_parasite = custom_settings["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE;
settings.led_gpio = custom_settings["led_gpio"] | EMSESP_DEFAULT_LED_GPIO;
return StateUpdateResult::CHANGED;
},
"local");
}
}
file.close();
SPIFFS.end(); SPIFFS.end();
if (failed) { if (failed) {
return false; // parse error #if defined(EMSESP_DEBUG)
Serial.println(F("Failed to read custom config. Quitting."));
#endif
Serial.end();
return false;
} }
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
LittleFS.begin(); Serial.println(F("Restarting..."));
EMSESP::esp8266React.begin(); // loads system settings (wifi, mqtt, etc) Serial.flush();
EMSESP::emsespSettingsService.begin(); // load EMS-ESP specific settings delay(1000);
Serial.println(F("Migrating settings from EMS-ESP 1.9.x..."));
// get the json objects
JsonObject network = doc1["network"];
JsonObject general = doc1["general"];
JsonObject mqtt = doc1["mqtt"];
JsonObject custom_settings = doc2["settings"]; // from 2nd file
EMSESP::esp8266React.getWiFiSettingsService()->update(
[&](WiFiSettings & wifiSettings) {
wifiSettings.hostname = general["hostname"] | FACTORY_WIFI_HOSTNAME;
wifiSettings.ssid = network["ssid"] | FACTORY_WIFI_SSID;
wifiSettings.password = network["password"] | FACTORY_WIFI_PASSWORD;
wifiSettings.staticIPConfig = false;
JsonUtils::readIP(network, "staticip", wifiSettings.localIP);
JsonUtils::readIP(network, "dnsip", wifiSettings.dnsIP1);
JsonUtils::readIP(network, "gatewayip", wifiSettings.gatewayIP);
JsonUtils::readIP(network, "nmask", wifiSettings.subnetMask);
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::esp8266React.getMqttSettingsService()->update(
[&](MqttSettings & mqttSettings) {
mqttSettings.host = mqtt["ip"] | FACTORY_MQTT_HOST;
mqttSettings.mqtt_format = (mqtt["nestedjson"] ? 2 : 1);
mqttSettings.mqtt_qos = mqtt["qos"] | 0;
mqttSettings.username = mqtt["user"] | "";
mqttSettings.password = mqtt["password"] | "";
mqttSettings.port = mqtt["port"] | FACTORY_MQTT_PORT;
mqttSettings.clientId = FACTORY_MQTT_CLIENT_ID;
mqttSettings.enabled = mqtt["enabled"];
mqttSettings.system_heartbeat = mqtt["heartbeat"];
mqttSettings.keepAlive = FACTORY_MQTT_KEEP_ALIVE;
mqttSettings.cleanSession = FACTORY_MQTT_CLEAN_SESSION;
mqttSettings.maxTopicLength = FACTORY_MQTT_MAX_TOPIC_LENGTH;
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::esp8266React.getSecuritySettingsService()->update(
[&](SecuritySettings & securitySettings) {
securitySettings.jwtSecret = general["password"] | FACTORY_JWT_SECRET;
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::emsespSettingsService.update(
[&](EMSESPSettings & settings) {
settings.tx_mode = custom_settings["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
settings.shower_alert = custom_settings["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
settings.shower_timer = custom_settings["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
settings.master_thermostat = custom_settings["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
settings.ems_bus_id = custom_settings["bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
settings.syslog_host = EMSESP_DEFAULT_SYSLOG_HOST;
settings.syslog_level = EMSESP_DEFAULT_SYSLOG_LEVEL;
settings.syslog_mark_interval = EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
return StateUpdateResult::CHANGED;
},
"local");
Serial.end(); Serial.end();
delay(1000);
restart();
return true; return true;
#else #else
return false; return false;

View File

@@ -68,29 +68,19 @@ class System {
static uuid::syslog::SyslogService syslog_; static uuid::syslog::SyslogService syslog_;
#endif #endif
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // check every 5 seconds static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // check every 5 seconds
static constexpr uint32_t LED_WARNING_BLINK = 1000; // pulse to show no connection, 1 sec static constexpr uint32_t LED_WARNING_BLINK = 1000; // pulse to show no connection, 1 sec
static constexpr uint32_t LED_WARNING_BLINK_FAST = 100; // flash quickly for boot up sequence static constexpr uint32_t LED_WARNING_BLINK_FAST = 100; // flash quickly for boot up sequence
static constexpr uint32_t SYSTEM_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min) static constexpr uint32_t SYSTEM_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min)
static constexpr uint32_t SYSTEM_MEASURE_ANALOG_INTERVAL = 1100;
// internal LED // internal LED
#ifndef EMSESP_NO_LED
#if defined(ESP8266)
static constexpr uint8_t LED_ON = LOW; static constexpr uint8_t LED_ON = LOW;
#elif defined(ESP32)
#ifdef WEMOS_D1_32
static constexpr uint8_t LED_ON = HIGH;
#else
static constexpr uint8_t LED_ON = LOW;
#endif
#endif
#else
static constexpr uint8_t LED_ON = 0;
#endif
void led_monitor(); void led_monitor();
void set_led_speed(uint32_t speed); void set_led_speed(uint32_t speed);
void system_check(); void system_check();
void measure_analog();
static void show_system(uuid::console::Shell & shell); static void show_system(uuid::console::Shell & shell);
static void show_users(uuid::console::Shell & shell); static void show_users(uuid::console::Shell & shell);
@@ -103,6 +93,7 @@ class System {
static int reset_counter_; static int reset_counter_;
uint32_t last_heartbeat_ = 0; uint32_t last_heartbeat_ = 0;
static bool upload_status_; // true if we're in the middle of a OTA firmware upload static bool upload_status_; // true if we're in the middle of a OTA firmware upload
static uint16_t analog_;
// settings // settings
bool system_heartbeat_; bool system_heartbeat_;

View File

@@ -73,7 +73,7 @@ Telegram::Telegram(const uint8_t operation,
, offset(offset) , offset(offset)
, message_length(message_length) { , message_length(message_length) {
// copy complete telegram data over, preventing buffer overflow // copy complete telegram data over, preventing buffer overflow
for (uint8_t i = 0; ((i < message_length) && (i != EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 1)); i++) { for (uint8_t i = 0; ((i < message_length) && (i < EMS_MAX_TELEGRAM_MESSAGE_LENGTH)); i++) {
message_data[i] = data[i]; message_data[i] = data[i];
} }
} }
@@ -95,7 +95,7 @@ std::string Telegram::to_string() const {
data[2] = this->type_id; data[2] = this->type_id;
length = 5; length = 5;
} }
} else if (this->operation == Telegram::Operation::TX_WRITE) { } else {
data[1] = this->dest; data[1] = this->dest;
if (this->type_id > 0xFF) { if (this->type_id > 0xFF) {
data[2] = 0xFF; data[2] = 0xFF;
@@ -109,10 +109,6 @@ std::string Telegram::to_string() const {
for (uint8_t i = 0; i < this->message_length; i++) { for (uint8_t i = 0; i < this->message_length; i++) {
data[length++] = this->message_data[i]; data[length++] = this->message_data[i];
} }
} else {
for (uint8_t i = 0; i < this->message_length; i++) {
data[length++] = this->message_data[i];
}
} }
return Helpers::data_to_hex(data, length); return Helpers::data_to_hex(data, length);
@@ -189,13 +185,14 @@ void RxService::add(uint8_t * data, uint8_t length) {
} }
type_id = (data[4 + shift] << 8) + data[5 + shift] + 256; type_id = (data[4 + shift] << 8) + data[5 + shift] + 256;
message_data = data + 6 + shift; message_data = data + 6 + shift;
message_length = length - 6 - shift; message_length = length - 7 - shift;
} }
// if we're watching and "raw" print out actual telegram as bytes to the console // if we're watching and "raw" print out actual telegram as bytes to the console
if (EMSESP::watch() == EMSESP::Watch::WATCH_RAW) { if (EMSESP::watch() == EMSESP::Watch::WATCH_RAW) {
uint16_t trace_watch_id = EMSESP::watch_id(); uint16_t trace_watch_id = EMSESP::watch_id();
if ((trace_watch_id == WATCH_ID_NONE) || (src == trace_watch_id) || (dest == trace_watch_id) || (type_id == trace_watch_id)) { if ((trace_watch_id == WATCH_ID_NONE) || (type_id == trace_watch_id)
|| ((trace_watch_id < 0x80) && ((src == trace_watch_id) || (dest == trace_watch_id)))) {
LOG_NOTICE(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str()); LOG_NOTICE(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str());
} }
} }
@@ -213,16 +210,16 @@ void RxService::add(uint8_t * data, uint8_t length) {
// if we receive a hc2.. telegram from 0x19.. match it to master_thermostat if master is 0x18 // if we receive a hc2.. telegram from 0x19.. match it to master_thermostat if master is 0x18
src = EMSESP::check_master_device(src, type_id, true); src = EMSESP::check_master_device(src, type_id, true);
// create the telegram // create the telegram
auto telegram = std::make_shared<Telegram>(Telegram::Operation::RX, src, dest, type_id, offset, message_data, message_length); auto telegram = std::make_shared<Telegram>(Telegram::Operation::RX, src, dest, type_id, offset, message_data, message_length);
// check if queue is full, if so remove top item to make space // check if queue is full, if so remove top item to make space
if (rx_telegrams_.size() >= MAX_RX_TELEGRAMS) { if (rx_telegrams_.size() >= MAX_RX_TELEGRAMS) {
rx_telegrams_.pop_front(); rx_telegrams_.pop_front();
increment_telegram_error_count();
} }
rx_telegrams_.emplace_back(rx_telegram_id_++, std::move(telegram)); // add to queue rx_telegrams_.emplace_back(rx_telegram_id_++, std::move(telegram)); // add to queue
} }
// //
@@ -319,7 +316,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
telegram_raw[2] = 0xFF; // fixed value indicating an extended message telegram_raw[2] = 0xFF; // fixed value indicating an extended message
telegram_raw[3] = telegram->offset; telegram_raw[3] = telegram->offset;
// EMS+ has different format for read and write. See https://github.com/proddy/EMS-ESP/wiki/RC3xx-Thermostats // EMS+ has different format for read and write
if (telegram->operation == Telegram::Operation::TX_WRITE) { if (telegram->operation == Telegram::Operation::TX_WRITE) {
// WRITE // WRITE
telegram_raw[4] = (telegram->type_id >> 8) - 1; // type, 1st byte, high-byte, subtract 0x100 telegram_raw[4] = (telegram->type_id >> 8) - 1; // type, 1st byte, high-byte, subtract 0x100
@@ -472,7 +469,9 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
operation = Telegram::Operation::TX_READ; operation = Telegram::Operation::TX_READ;
} else { } else {
operation = Telegram::Operation::TX_WRITE; operation = Telegram::Operation::TX_WRITE;
set_post_send_query(type_id);
} }
EMSESP::set_read_id(type_id);
} }
auto telegram = std::make_shared<Telegram>(operation, src, dest, type_id, offset, message_data, message_length); // operation is TX_WRITE or TX_READ auto telegram = std::make_shared<Telegram>(operation, src, dest, type_id, offset, message_data, message_length); // operation is TX_WRITE or TX_READ
@@ -535,7 +534,7 @@ void TxService::send_raw(const char * telegram_data) {
return; // nothing to send return; // nothing to send
} }
add(Telegram::Operation::TX_RAW, data, count + 1); // add to Tx queue add(Telegram::Operation::TX_RAW, data, count + 1, true); // add to front of Tx queue
} }
// add last Tx to tx queue and increment count // add last Tx to tx queue and increment count
@@ -578,21 +577,22 @@ bool TxService::is_last_tx(const uint8_t src, const uint8_t dest) const {
} }
// sends a type_id read request to fetch values after a successful Tx write operation // sends a type_id read request to fetch values after a successful Tx write operation
void TxService::post_send_query() { // unless the post_send_query has a type_id of 0
if (telegram_last_post_send_query_) { uint16_t TxService::post_send_query() {
uint8_t dest = (telegram_last_->dest & 0x7F); uint16_t post_typeid = this->get_post_send_query();
uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
add(Telegram::Operation::TX_READ, dest, telegram_last_post_send_query_, 0, message_data, 1, true);
// read_request(telegram_last_post_send_query_, dest, 0); // no offset
LOG_DEBUG(F("Sending post validate read, type ID 0x%02X to dest 0x%02X"), telegram_last_post_send_query_, dest);
}
}
// print out the last Tx that was sent if (post_typeid) {
void TxService::print_last_tx() { uint8_t dest = (this->telegram_last_->dest & 0x7F);
LOG_DEBUG(F("Last Tx %s operation: %s"), // when set a value with large offset before and validate on same type, we have to add offset 0, 26, 52, ...
(telegram_last_->operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"), uint8_t offset = (this->telegram_last_->type_id == post_typeid) ? ((this->telegram_last_->offset / 26) * 26) : 0;
telegram_last_->to_string().c_str()); uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
this->add(Telegram::Operation::TX_READ, dest, post_typeid, offset, message_data, 1, true);
// read_request(telegram_last_post_send_query_, dest, 0); // no offset
LOG_DEBUG(F("Sending post validate read, type ID 0x%02X to dest 0x%02X"), post_typeid, dest);
set_post_send_query(0); // reset
}
return post_typeid;
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -83,36 +83,43 @@ class Telegram {
std::string to_string() const; std::string to_string() const;
// reads a bit value from a given telegram position // reads a bit value from a given telegram position
void read_bitvalue(uint8_t & value, const uint8_t index, const uint8_t bit) const { bool read_bitvalue(uint8_t & value, const uint8_t index, const uint8_t bit) const {
uint8_t abs_index = (index - offset); uint8_t abs_index = (index - this->offset);
if (abs_index >= message_length - 1) { if (abs_index >= this->message_length) {
return; // out of bounds return false; // out of bounds
} }
uint8_t val = value;
value = (uint8_t)(((message_data[abs_index]) >> (bit)) & 0x01); value = (uint8_t)(((this->message_data[abs_index]) >> (bit)) & 0x01);
if (val != value) {
return true;
}
return false;
} }
// read values from a telegram. We always store the value, regardless if its garbage // read a value from a telegram. We always store the value, regardless if its garbage
template <typename Value> template <typename Value>
// assuming negative numbers are stored as 2's-complement // assuming negative numbers are stored as 2's-complement
// https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c // https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c
// 2-compliment : https://www.rapidtables.com/convert/number/decimal-to-hex.html // 2-compliment : https://www.rapidtables.com/convert/number/decimal-to-hex.html
// https://en.wikipedia.org/wiki/Two%27s_complement // https://en.wikipedia.org/wiki/Two%27s_complement
// s is to override number of bytes read (e.g. use 3 to simulat a uint24_t) // s is to override number of bytes read (e.g. use 3 to simulate a uint24_t)
void read_value(Value & value, const uint8_t index, uint8_t s = 0) const { bool read_value(Value & value, const uint8_t index, uint8_t s = 0) const {
uint8_t size = (!s) ? sizeof(Value) : s; uint8_t num_bytes = (!s) ? sizeof(Value) : s;
int8_t abs_index = ((index - offset + size - 1) >= message_length - 1) ? -1 : (index - offset); // check for out of bounds, if so don't modify the value
if (abs_index < 0) { if ((index < this->offset) || ((index - this->offset + num_bytes - 1) >= this->message_length)) {
return; // out of bounds, we don't change the value return false;
} }
auto val = value;
value = 0; value = 0;
for (uint8_t i = 0; i < size; i++) { for (uint8_t i = 0; i < num_bytes; i++) {
value = (value << 8) + message_data[abs_index + i]; // shift value = (value << 8) + this->message_data[index - this->offset + i]; // shift by byte
} }
if (val != value) {
return true;
}
return false;
} }
private: private:
int8_t _getDataPosition(const uint8_t index, const uint8_t size) const; int8_t _getDataPosition(const uint8_t index, const uint8_t size) const;
}; };
@@ -182,13 +189,12 @@ class EMSbus {
private: private:
static constexpr uint32_t EMS_BUS_TIMEOUT = 30000; // timeout in ms before recognizing the ems bus is offline (30 seconds) static constexpr uint32_t EMS_BUS_TIMEOUT = 30000; // timeout in ms before recognizing the ems bus is offline (30 seconds)
static uint32_t last_bus_activity_; // timestamp of last time a valid Rx came in
static uint32_t last_bus_activity_; // timestamp of last time a valid Rx came in static bool bus_connected_; // start assuming the bus hasn't been connected
static bool bus_connected_; // start assuming the bus hasn't been connected static uint8_t ems_mask_; // unset=0xFF, buderus=0x00, junkers/ht3=0x80
static uint8_t ems_mask_; // unset=0xFF, buderus=0x00, junkers/ht3=0x80 static uint8_t ems_bus_id_; // the bus id, which configurable and stored in settings
static uint8_t ems_bus_id_; // the bus id, which configurable and stored in settings static uint8_t tx_mode_; // local copy of the tx mode
static uint8_t tx_mode_; // local copy of the tx mode static uint8_t tx_state_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE)
static uint8_t tx_state_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE)
}; };
class RxService : public EMSbus { class RxService : public EMSbus {
@@ -201,7 +207,7 @@ class RxService : public EMSbus {
void loop(); void loop();
void add(uint8_t * data, uint8_t length); void add(uint8_t * data, uint8_t length);
uint16_t telegram_count() const { uint32_t telegram_count() const {
return telegram_count_; return telegram_count_;
} }
@@ -209,7 +215,7 @@ class RxService : public EMSbus {
telegram_count_++; telegram_count_++;
} }
uint16_t telegram_error_count() const { uint32_t telegram_error_count() const {
return telegram_error_count_; return telegram_error_count_;
} }
@@ -235,44 +241,38 @@ class RxService : public EMSbus {
private: private:
uint8_t rx_telegram_id_ = 0; // queue counter uint8_t rx_telegram_id_ = 0; // queue counter
uint16_t telegram_count_ = 0; // # Rx received uint32_t telegram_count_ = 0; // # Rx received
uint16_t telegram_error_count_ = 0; // # Rx CRC errors uint32_t telegram_error_count_ = 0; // # Rx CRC errors
std::shared_ptr<const Telegram> rx_telegram; // the incoming Rx telegram std::shared_ptr<const Telegram> rx_telegram; // the incoming Rx telegram
std::list<QueuedRxTelegram> rx_telegrams_; // the Rx Queue
std::list<QueuedRxTelegram> rx_telegrams_; // the Rx Queue
}; };
class TxService : public EMSbus { class TxService : public EMSbus {
public: public:
static constexpr size_t MAX_TX_TELEGRAMS = 20; // size of Tx queue static constexpr size_t MAX_TX_TELEGRAMS = 20; // size of Tx queue
static constexpr uint8_t TX_WRITE_FAIL = 4; // EMS return code for fail
static constexpr uint8_t TX_WRITE_FAIL = 4; // EMS return code for fail static constexpr uint8_t TX_WRITE_SUCCESS = 1; // EMS return code for success
static constexpr uint8_t TX_WRITE_SUCCESS = 1; // EMS return code for success
TxService() = default; TxService() = default;
~TxService() = default; ~TxService() = default;
void start(); void start();
void send(); void send();
void add(const uint8_t operation,
void add(const uint8_t operation, const uint8_t dest,
const uint8_t dest, const uint16_t type_id,
const uint16_t type_id, const uint8_t offset,
const uint8_t offset, uint8_t * message_data,
uint8_t * message_data, const uint8_t message_length,
const uint8_t message_length, const bool front = false);
const bool front = false); void add(const uint8_t operation, const uint8_t * data, const uint8_t length, const bool front = false);
void add(const uint8_t operation, const uint8_t * data, const uint8_t length, const bool front = false); void read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0);
void send_raw(const char * telegram_data);
void read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0); void send_poll();
void flush_tx_queue();
void send_raw(const char * telegram_data); void retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length);
bool is_last_tx(const uint8_t src, const uint8_t dest) const;
void send_poll(); uint16_t post_send_query();
void flush_tx_queue();
void retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length);
uint8_t retry_count() const { uint8_t retry_count() const {
return retry_count_; return retry_count_;
@@ -282,13 +282,15 @@ class TxService : public EMSbus {
retry_count_ = 0; retry_count_ = 0;
} }
bool is_last_tx(const uint8_t src, const uint8_t dest) const;
void set_post_send_query(uint16_t type_id) { void set_post_send_query(uint16_t type_id) {
telegram_last_post_send_query_ = type_id; telegram_last_post_send_query_ = type_id;
} }
uint16_t telegram_read_count() const { uint16_t get_post_send_query() {
return telegram_last_post_send_query_;
}
uint32_t telegram_read_count() const {
return telegram_read_count_; return telegram_read_count_;
} }
@@ -300,7 +302,7 @@ class TxService : public EMSbus {
telegram_read_count_++; telegram_read_count_++;
} }
uint16_t telegram_fail_count() const { uint32_t telegram_fail_count() const {
return telegram_fail_count_; return telegram_fail_count_;
} }
@@ -312,7 +314,7 @@ class TxService : public EMSbus {
telegram_fail_count_++; telegram_fail_count_++;
} }
uint16_t telegram_write_count() const { uint32_t telegram_write_count() const {
return telegram_write_count_; return telegram_write_count_;
} }
@@ -324,10 +326,6 @@ class TxService : public EMSbus {
telegram_write_count_++; telegram_write_count_++;
} }
void post_send_query();
void print_last_tx();
class QueuedTxTelegram { class QueuedTxTelegram {
public: public:
const uint16_t id_; const uint16_t id_;
@@ -355,9 +353,9 @@ class TxService : public EMSbus {
private: private:
std::list<QueuedTxTelegram> tx_telegrams_; // the Tx queue std::list<QueuedTxTelegram> tx_telegrams_; // the Tx queue
uint16_t telegram_read_count_ = 0; // # Tx successful reads uint32_t telegram_read_count_ = 0; // # Tx successful reads
uint16_t telegram_write_count_ = 0; // # Tx successful writes uint32_t telegram_write_count_ = 0; // # Tx successful writes
uint16_t telegram_fail_count_ = 0; // # Tx unsuccessful transmits uint32_t telegram_fail_count_ = 0; // # Tx unsuccessful transmits
std::shared_ptr<Telegram> telegram_last_; std::shared_ptr<Telegram> telegram_last_;
uint16_t telegram_last_post_send_query_; // which type ID to query after a successful send, to read back the values just written uint16_t telegram_last_post_send_query_; // which type ID to query after a successful send, to read back the values just written

View File

@@ -123,10 +123,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
uint8bitb = EMS_VALUE_UINT_NOTSET; uint8bitb = EMS_VALUE_UINT_NOTSET;
telegram->read_bitvalue(uint8bitb, 0, 0); // value is 0x01 = 0000 0001 telegram->read_bitvalue(uint8bitb, 0, 0); // value is 0x01 = 0000 0001
shell.printfln("uint8 bit read: expecting 1, got:%d", uint8bitb); shell.printfln("uint8 bit read: expecting 1, got:%d", uint8bitb);
shell.loop_all();
return;
} }
if (command == "devices") { if (command == "devices") {
@@ -142,6 +138,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// question: do we need to set the mask? // question: do we need to set the mask?
std::string version("1.2.3"); std::string version("1.2.3");
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
// UBAuptime
uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70});
} }
// unknown device - // unknown device -
@@ -159,7 +158,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// note there is no brand (byte 9) // note there is no brand (byte 9)
rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x59, 0x09, 0x0a}); rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x59, 0x09, 0x0a});
shell.loop_all();
EMSESP::show_device_values(shell); EMSESP::show_device_values(shell);
} }
@@ -210,8 +208,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
shell.invoke_command("show"); shell.invoke_command("show");
// shell.invoke_command("system"); // shell.invoke_command("system");
// shell.invoke_command("show mqtt"); // shell.invoke_command("show mqtt");
// shell.loop_all();
} }
if (command == "thermostat") { if (command == "thermostat") {
@@ -230,13 +226,34 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline // EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
// add a thermostat // add a thermostat
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355 EMSESP::add_device(0x10, 192, version, EMSdevice::Brand::JUNKERS); // FW120
// RCPLUSStatusMessage_HC1(0x01A5) // HC1
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24, uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); 0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
shell.loop_all(); // HC2
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x70, 0x00, 0xCF, 0x22, 0x2F, 0x10, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
// HC3
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
}
if (command == "tc100") {
shell.printfln(F("Testing adding a TC100 thermostat to the EMS bus..."));
std::string version("02.21");
// add a boiler
// EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
// add a thermostat
EMSESP::add_device(0x18, 202, version, EMSdevice::Brand::BOSCH); // Bosch TC100 - https://github.com/proddy/EMS-ESP/issues/474
// 0x0A
uart_telegram({0x98, 0x0B, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
} }
if (command == "solar") { if (command == "solar") {
@@ -272,6 +289,39 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::show_device_values(shell); EMSESP::show_device_values(shell);
} }
if (command == "solar200") {
shell.printfln(F("Testing Solar SM200"));
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
std::string version("1.2.3");
EMSESP::add_device(0x30, 164, version, EMSdevice::Brand::BUDERUS); // SM200
// SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200
// B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80
rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
EMSESP::show_device_values(shell);
rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
EMSESP::show_device_values(shell);
rx_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8});
EMSESP::show_device_values(shell);
EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8");
uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on 1
uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation
EMSESP::show_device_values(shell);
uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0
EMSESP::show_device_values(shell);
}
if (command == "km") { if (command == "km") {
shell.printfln(F("Testing KM200 Gateway")); shell.printfln(F("Testing KM200 Gateway"));
@@ -353,7 +403,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); 0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
uart_telegram("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03"); // without CRC uart_telegram("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03"); // without CRC
uart_telegram_withCRC("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 13"); // with CRC uart_telegram_withCRC("98 00 FF 00 01 A6 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 6B"); // with CRC
EMSESP::txservice_.flush_tx_queue(); EMSESP::txservice_.flush_tx_queue();
shell.loop_all(); shell.loop_all();
@@ -524,8 +574,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
shell.invoke_command("help"); shell.invoke_command("help");
shell.invoke_command("pin"); shell.invoke_command("pin");
shell.invoke_command("pin 1 true"); shell.invoke_command("pin 1 true");
shell.loop_all();
} }
if (command == "mqtt") { if (command == "mqtt") {
@@ -539,7 +587,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// add a thermostat // add a thermostat
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355 EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
// RCPLUSStatusMessage_HC1(0x01A5) // RCPLUSStatusMessage_HC1(0x01A5) - HC1
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24, uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); 0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
uart_telegram("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03"); // without CRC uart_telegram("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03"); // without CRC
@@ -576,6 +624,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"control\",\"data\":1}"); EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"control\",\"data\":1}");
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"id\":2}"); EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"id\":2}");
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":2}"); // hc as number EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":2}"); // hc as number
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":19.5,\"hc\":1}"); // data as number
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":\"2\"}"); // hc as string EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":\"2\"}"); // hc as string
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":22.56}"); EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":22.56}");
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":22}"); EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":22}");
@@ -596,8 +645,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
Mqtt::resubscribe(); Mqtt::resubscribe();
Mqtt::show_mqtt(shell); // show queue Mqtt::show_mqtt(shell); // show queue
shell.loop_all();
} }
if (command == "poll2") { if (command == "poll2") {
@@ -656,7 +703,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
} }
// finally dump to console // finally dump to console
shell.loop_all(); EMSESP::loop();
} }
// simulates a telegram in the Rx queue, but without the CRC which is added automatically // simulates a telegram in the Rx queue, but without the CRC which is added automatically
@@ -683,6 +730,7 @@ void Test::uart_telegram(const std::vector<uint8_t> & rx_data) {
} }
data[i] = EMSESP::rxservice_.calculate_crc(data, i); data[i] = EMSESP::rxservice_.calculate_crc(data, i);
EMSESP::incoming_telegram(data, i + 1); EMSESP::incoming_telegram(data, i + 1);
EMSESP::rxservice_.loop();
} }
// takes raw string, assuming it contains the CRC. This is what is output from 'watch raw' // takes raw string, assuming it contains the CRC. This is what is output from 'watch raw'
@@ -720,6 +768,7 @@ void Test::uart_telegram_withCRC(const char * rx_data) {
} }
EMSESP::incoming_telegram(data, count + 1); EMSESP::incoming_telegram(data, count + 1);
EMSESP::rxservice_.loop();
} }
// takes raw string, adds CRC to end // takes raw string, adds CRC to end
@@ -759,6 +808,7 @@ void Test::uart_telegram(const char * rx_data) {
data[count + 1] = EMSESP::rxservice_.calculate_crc(data, count + 1); // add CRC data[count + 1] = EMSESP::rxservice_.calculate_crc(data, count + 1); // add CRC
EMSESP::incoming_telegram(data, count + 2); EMSESP::incoming_telegram(data, count + 2);
EMSESP::rxservice_.loop();
} }
#pragma GCC diagnostic push #pragma GCC diagnostic push

View File

@@ -46,21 +46,25 @@ void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) {
if (USIS(EMSUART_UART) & ((1 << UIBD))) { // BREAK detection = End of EMS data block if (USIS(EMSUART_UART) & ((1 << UIBD))) { // BREAK detection = End of EMS data block
USC0(EMSUART_UART) &= ~(1 << UCBRK); // reset tx-brk USC0(EMSUART_UART) &= ~(1 << UCBRK); // reset tx-brk
if (emsTxBufIdx < emsTxBufLen) { // irq tx_mode is interrupted by <brk> if (sending_) { // irq tx_mode is interrupted by <brk>, should never happen
emsTxBufIdx = emsTxBufLen + 1; // stop tx drop_next_rx = true; // we have trash in buffer
// drop_next_rx = true; // we have trash in buffer
} }
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
length = 0; length = 0;
while ((USS(EMSUART_UART) >> USRXC) & 0x0FF) { // read fifo into buffer while ((USS(EMSUART_UART) >> USRXC) & 0x0FF) { // read fifo into buffer
uint8_t rx = USF(EMSUART_UART); uint8_t rx = USF(EMSUART_UART);
if (length < EMS_MAXBUFFERSIZE) { if (length < EMS_MAXBUFFERSIZE) {
uart_buffer[length++] = rx; if (length || rx) { // skip a leading zero
uart_buffer[length++] = rx;
}
} else { } else {
drop_next_rx = true; drop_next_rx = true;
} }
} }
if (!drop_next_rx) { if (!drop_next_rx) {
if (uart_buffer[length - 1]) { // check if last byte is break
length++;
}
pEMSRxBuf->length = length; pEMSRxBuf->length = length;
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, pEMSRxBuf->length); // copy data into transfer buffer, including the BRK 0x00 at the end os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, pEMSRxBuf->length); // copy data into transfer buffer, including the BRK 0x00 at the end
system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
@@ -89,6 +93,9 @@ void ICACHE_FLASH_ATTR EMSuart::emsuart_recvTask(os_event_t * events) {
// ISR to Fire when Timer is triggered // ISR to Fire when Timer is triggered
void ICACHE_RAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() { void ICACHE_RAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() {
if (!sending_) {
return;
}
emsTxBufIdx++; emsTxBufIdx++;
if (emsTxBufIdx < emsTxBufLen) { if (emsTxBufIdx < emsTxBufLen) {
USF(EMSUART_UART) = emsTxBuf[emsTxBufIdx]; USF(EMSUART_UART) = emsTxBuf[emsTxBufIdx];

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "2.0.0" #define EMSESP_APP_VERSION "2.0.1"