mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-09 01:09:51 +03:00
v2.0.1 - Merge remote-tracking branch 'origin/dev' into main
This commit is contained in:
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -10,7 +10,7 @@ assignees: ''
|
||||
*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 [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.*
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ assignees: ''
|
||||
*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 [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.*
|
||||
|
||||
|
||||
10
.github/contribute.md
vendored
10
.github/contribute.md
vendored
@@ -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
17
.github/stale.yml
vendored
@@ -1,15 +1,16 @@
|
||||
# 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.
|
||||
# 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
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- enhancement
|
||||
- bug
|
||||
- staged for release
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
@@ -23,19 +24,17 @@ staleLabel: stale
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed in 7 days if no further activity occurs.
|
||||
Thank you for your contributions.
|
||||
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when removing the stale label.
|
||||
# unmarkComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
closeComment: >
|
||||
This issue will be auto-closed because there hasn't been any activity for 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
|
||||
limitPerRun: 30
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
only: issues
|
||||
#only: issues
|
||||
1
.github/workflows/codeql-analysis.yml
vendored
1
.github/workflows/codeql-analysis.yml
vendored
@@ -60,3 +60,4 @@ jobs:
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,7 +16,6 @@ pio_local.ini
|
||||
|
||||
# project specfic
|
||||
/scripts/stackdmp.txt
|
||||
*.bin
|
||||
emsesp
|
||||
/data/www/
|
||||
/lib/framework/WWWData.h
|
||||
|
||||
@@ -12,8 +12,8 @@ env:
|
||||
global:
|
||||
- BUILDER_TOTAL_THREADS=1
|
||||
- OWNER=${TRAVIS_REPO_SLUG%/*}
|
||||
- DEV=${OWNER/proddy/v2}
|
||||
- BRANCH=${TRAVIS_BRANCH/v2/}
|
||||
- DEV=${OWNER/proddy/dev}
|
||||
- BRANCH=${TRAVIS_BRANCH/dev/}
|
||||
- TAG=${DEV}${BRANCH:+_}${BRANCH}
|
||||
|
||||
install:
|
||||
@@ -53,7 +53,7 @@ deploy:
|
||||
token: ${GITHUB_TOKEN}
|
||||
file_glob: true
|
||||
file: "*.bin"
|
||||
name: latest v2 development build
|
||||
name: latest development build
|
||||
release_notes:
|
||||
Version $FIRMWARE_VERSION.
|
||||
Automatic firmware build of the current EMS-ESP branch built on $(date +'%F %T %Z') from commit $TRAVIS_COMMIT.
|
||||
|
||||
55
CHANGELOG.md
55
CHANGELOG.md
@@ -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/),
|
||||
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
|
||||
- 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
|
||||
|
||||
@@ -38,7 +79,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Changed
|
||||
- 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
|
||||
- `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
|
||||
@@ -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)
|
||||
- Update documentation in Wiki on MQTT and troubleshooting
|
||||
- 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
|
||||
|
||||
@@ -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
|
||||
- Support HA 0.96 climate component changes
|
||||
- -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
|
||||
|
||||
@@ -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
|
||||
- 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
|
||||
|
||||
@@ -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)
|
||||
- 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
|
||||
|
||||
|
||||
110
CONTRIBUTING.md
Normal file
110
CONTRIBUTING.md
Normal 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 isn’t 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_ you’re 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
281
README.md
@@ -1,188 +1,171 @@
|
||||
# 
|
||||
|
||||
[](https://github.com/proddy/EMS-ESP/blob/main/CHANGELOG.md)
|
||||
[](https://github.com/proddy/EMS-ESP/commits/main)
|
||||
<br />
|
||||
**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.
|
||||
|
||||
[](https://github.com/proddy/EMS-ESP/blob/master/CHANGELOG.md)
|
||||
[](https://github.com/proddy/EMS-ESP/commits/master)
|
||||
[](LICENSE)
|
||||
[](https://travis-ci.com/proddy/EMS-ESP)
|
||||
[](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)
|
||||
[](https://github.com/proddy/EMS-ESP/releases)
|
||||
<br />
|
||||
[](http://isitmaintained.com/project/proddy/EMS-ESP "Average time to resolve an issue")
|
||||
[](http://isitmaintained.com/project/proddy/EMS-ESP "Percentage of issues still open")
|
||||
<br/>
|
||||
[](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**
|
||||
[](https://github.com/proddy/EMS-ESP/stargazers)
|
||||
[](https://github.com/proddy/EMS-ESP/network)
|
||||
[](https://www.paypal.com/paypalme/prderbyshire/2)
|
||||
|
||||
- Supports both ESP8266 and ESP32
|
||||
- New MQTT option to support Home Assistant MQTT Discovery (https://www.home-assistant.io/docs/mqtt/discovery/)
|
||||
- Tighter security in Web and Console
|
||||
- New secure web interface (based on React/TypeScript)
|
||||
- Can be run on WiFi on as a Stand alone Access Point
|
||||
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/.
|
||||
|
||||
<img src="media/gateway-integration.jpg" width=40%>
|
||||
|
||||
---
|
||||
|
||||
## **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
|
||||
- Supporting over 70 EMS devices
|
||||
|
||||
<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.
|
||||
- Supporting over 70 EMS devices (boilers, thermostats, solar modules, mixing modules, heat pumps, gateways)
|
||||
|
||||
## **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.
|
||||
|
||||
Note there are some noticeable different to be aware of in version 2:
|
||||
- MQTT base has been removed
|
||||
- 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.
|
||||
Note there are some noticeable differences to be aware of in version 2:
|
||||
### MQTT:
|
||||
- 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**
|
||||
|
||||
- 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:
|
||||
- `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>`
|
||||
Here we'll use the command-line. You'll need [Python]( https://www.python.org/downloads/) (version 3) installed and these 2 scripts:
|
||||
|
||||
- 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>`
|
||||
note: if this fails try a lower speed like `115200` instead of `921600`.
|
||||
and for OTA:
|
||||
|
||||
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>`
|
||||
- Uploading over WiFi: `espota.py --debug --progress --port 8266 --auth ems-esp-neo -i <IP address> -f <firmware.bin>`
|
||||
`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.
|
||||
|
||||
(* = 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] *
|
||||
|
||||
```
|
||||
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`).
|
||||
|
||||
----------
|
||||
## **MQTT commands**
|
||||
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.
|
||||
|
||||
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**
|
||||
|
||||
```
|
||||
*boiler_cmd*
|
||||
comfort <hot, eco, intelligent>
|
||||
flowtemp <degrees>
|
||||
wwtemp <degrees>
|
||||
boilhyston <degrees> (negative value)
|
||||
boilhystoff <degrees> (positive value)
|
||||
burnperiod <minutes>
|
||||
burnminpower <%>
|
||||
burnmaxpower <%>
|
||||
pumpdelay <minutes>
|
||||
For a list of the EMS devices currently supported see BBQKees's [EMS device compatibility list](https://bbqkees-electronics.nl/ems-device-compatibility/).
|
||||
|
||||
*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>
|
||||
If you're looking for support on **EMS-ESP** there are some options available:
|
||||
|
||||
*system_cmd*
|
||||
send <"0B XX XX ..">
|
||||
pin <gpio> <on|off|1|0|true|false>
|
||||
### Documentation
|
||||
|
||||
```
|
||||
* [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
55
doc/MQTT.md
Normal 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>
|
||||
```
|
||||
@@ -1,6 +1,5 @@
|
||||
# Notes on customizing the code
|
||||
|
||||
|
||||
## **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).
|
||||
@@ -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
|
||||
|
||||
|
||||
## 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
72
doc/console.md
Normal 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] *
|
||||
|
||||
```
|
||||
@@ -150,15 +150,75 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
|
||||
<MenuItem value={2}>2</MenuItem>
|
||||
</SelectValidator>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:1', 'maxNumber:65535']}
|
||||
errorMessages={['Publish time is required', "Must be a number", "Must be greater than 0", "Max value is 65535"]}
|
||||
name="publish_time"
|
||||
label="MQTT Publish Time (seconds)"
|
||||
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_boiler"
|
||||
label="MQTT Boiler Publish Period (seconds, 0=on change)"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.publish_time}
|
||||
value={data.publish_time_boiler}
|
||||
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"
|
||||
/>
|
||||
<FormActions>
|
||||
|
||||
@@ -27,7 +27,12 @@ export interface MqttSettings {
|
||||
keep_alive: number;
|
||||
clean_session: boolean;
|
||||
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_qos: number;
|
||||
system_heartbeat: boolean;
|
||||
|
||||
@@ -83,8 +83,11 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
|
||||
const { width, data } = this.props;
|
||||
return (
|
||||
<TableContainer>
|
||||
<Typography variant="h6" color="primary" paragraph>
|
||||
Devices:
|
||||
<Typography variant="h6" color="primary" >
|
||||
Devices
|
||||
</Typography>
|
||||
<Typography variant="caption" color="initial" paragraph>
|
||||
<i>(click to show details)</i>
|
||||
</Typography>
|
||||
{!this.noDevices() && (
|
||||
<Table size="small" padding={isWidthDown('xs', width!) ? "none" : "default"}>
|
||||
@@ -145,14 +148,14 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
|
||||
<TableContainer>
|
||||
<p></p>
|
||||
<Typography variant="h6" color="primary" paragraph>
|
||||
Sensors:
|
||||
Sensors
|
||||
</Typography>
|
||||
{!this.noSensors() && (
|
||||
<Table size="small" padding="default">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<StyledTableCell>ID</StyledTableCell>
|
||||
<StyledTableCell align="left">Temperature</StyledTableCell>
|
||||
<StyledTableCell align="right">Temperature</StyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
@@ -161,8 +164,8 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
|
||||
<TableCell component="th" scope="row">
|
||||
{sensorData.id}
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
{sensorData.temp}°C
|
||||
<TableCell align="right">
|
||||
{sensorData.temp.toFixed(1)}°C
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
@@ -285,7 +288,7 @@ class EMSESPDevicesForm extends Component<EMSESPDevicesFormProps, EMSESPDevicesF
|
||||
<TableCell component="th" scope="row">
|
||||
{deviceData.name}
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<TableCell align="right">
|
||||
{deviceData.value}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -10,14 +10,14 @@ class EMSESPHelp extends Component {
|
||||
|
||||
<Box bgcolor="info.main" border={1} p={3} mt={1} mb={0}>
|
||||
<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>
|
||||
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>
|
||||
</Box>
|
||||
<br></br>
|
||||
<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 variant="body1" paragraph>
|
||||
For live community chat go to <Link href="https://gitter.im/EMS-ESP/community#" color="primary">{'Gitter'}</Link>.
|
||||
|
||||
@@ -48,9 +48,13 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
|
||||
<ValidatorForm onSubmit={saveData}>
|
||||
<Box bgcolor="info.main" p={2} mt={2} mb={2}>
|
||||
<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> for descriptions of each setting.
|
||||
Customize EMS-ESP by modifying the default settings here.
|
||||
</Typography>
|
||||
</Box>
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary" >
|
||||
EMS Bus Settings
|
||||
</Typography>
|
||||
<TextValidator
|
||||
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"]}
|
||||
@@ -100,18 +104,10 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
|
||||
onChange={handleValueChange('tx_gpio')}
|
||||
margin="normal"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary" >
|
||||
Dallas Sensor Settings
|
||||
</Typography>
|
||||
<TextValidator
|
||||
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"]}
|
||||
@@ -124,6 +120,32 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
|
||||
onChange={handleValueChange('dallas_gpio')}
|
||||
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
|
||||
control={
|
||||
<Checkbox
|
||||
@@ -132,8 +154,12 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
|
||||
value="hide_led"
|
||||
/>
|
||||
}
|
||||
label="Hide LED"
|
||||
label="Invert/Hide LED"
|
||||
/>
|
||||
<br></br>
|
||||
<Typography variant="h6" color="primary" >
|
||||
Shower Settings
|
||||
</Typography>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
@@ -154,6 +180,21 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
|
||||
}
|
||||
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"
|
||||
label="Syslog Log Level"
|
||||
value={data.syslog_level}
|
||||
@@ -166,33 +207,18 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
|
||||
<MenuItem value={6}>INFO</MenuItem>
|
||||
<MenuItem value={7}>DEBUG</MenuItem>
|
||||
</SelectValidator>
|
||||
{data.syslog_level !== -1 &&
|
||||
<Fragment>
|
||||
<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"
|
||||
/>
|
||||
<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>
|
||||
}
|
||||
<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"
|
||||
/>
|
||||
<FormActions>
|
||||
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit">
|
||||
Save
|
||||
|
||||
@@ -37,6 +37,10 @@ import {
|
||||
|
||||
import { EMSESPStatus } from "./EMSESPtypes";
|
||||
|
||||
function formatNumber(num: number) {
|
||||
return new Intl.NumberFormat().format(num);
|
||||
}
|
||||
|
||||
type EMSESPStatusFormProps = RestFormProps<EMSESPStatus> & WithTheme & WithWidthProps;
|
||||
|
||||
const StyledTableCell = withStyles((theme: Theme) =>
|
||||
@@ -75,7 +79,7 @@ class EMSESPStatusForm extends Component<EMSESPStatusFormProps> {
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<StyledTableCell>Statistic</StyledTableCell>
|
||||
<StyledTableCell align="center"># Telegrams</StyledTableCell>
|
||||
<StyledTableCell align="right"># Telegrams</StyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
@@ -83,25 +87,25 @@ class EMSESPStatusForm extends Component<EMSESPStatusFormProps> {
|
||||
<TableCell>
|
||||
(Rx) Received telegrams
|
||||
</TableCell>
|
||||
<TableCell align="center">{data.rx_received}</TableCell>
|
||||
<TableCell align="right">{formatNumber(data.rx_received)}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell >
|
||||
(Rx) Incomplete telegrams
|
||||
</TableCell>
|
||||
<TableCell align="center">{data.crc_errors}</TableCell>
|
||||
<TableCell align="right">{formatNumber(data.crc_errors)}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell >
|
||||
(Tx) Successfully sent telegrams
|
||||
</TableCell>
|
||||
<TableCell align="center">{data.tx_sent}</TableCell>
|
||||
<TableCell align="right">{formatNumber(data.tx_sent)}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell >
|
||||
(Tx) Send Errors
|
||||
</TableCell>
|
||||
<TableCell align="center">{data.tx_errors}</TableCell>
|
||||
<TableCell align="right">{formatNumber(data.tx_errors)}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
@@ -7,11 +7,12 @@ export interface EMSESPSettings {
|
||||
master_thermostat: number;
|
||||
shower_timer: boolean;
|
||||
shower_alert: boolean;
|
||||
hide_led: boolean;
|
||||
rx_gpio: number;
|
||||
tx_gpio : number;
|
||||
dallas_gpio : number;
|
||||
led_gpio : number;
|
||||
tx_gpio: number;
|
||||
dallas_gpio: number;
|
||||
dallas_parasite: boolean;
|
||||
led_gpio: number;
|
||||
hide_led: boolean;
|
||||
}
|
||||
|
||||
export enum busConnectionStatus {
|
||||
|
||||
@@ -2,23 +2,29 @@
|
||||
#define SPIFFSEditor_H_
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
class SPIFFSEditor: public AsyncWebHandler {
|
||||
class SPIFFSEditor : public AsyncWebHandler {
|
||||
private:
|
||||
fs::FS _fs;
|
||||
String _username;
|
||||
String _password;
|
||||
bool _authenticated;
|
||||
fs::FS _fs;
|
||||
String _username;
|
||||
String _password;
|
||||
bool _authenticated;
|
||||
uint32_t _startTime;
|
||||
|
||||
public:
|
||||
#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
|
||||
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
|
||||
virtual bool canHandle(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 bool isRequestHandlerTrivial() override final {return false;}
|
||||
virtual bool canHandle(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 bool isRequestHandlerTrivial() override final {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -15,12 +15,12 @@ void APSettingsService::begin() {
|
||||
}
|
||||
|
||||
void APSettingsService::reconfigureAP() {
|
||||
_lastManaged = millis() - MANAGE_NETWORK_DELAY;
|
||||
_lastManaged = uuid::get_uptime() - MANAGE_NETWORK_DELAY;
|
||||
_reconfigureAp = true;
|
||||
}
|
||||
|
||||
void APSettingsService::loop() {
|
||||
unsigned long currentMillis = millis();
|
||||
unsigned long currentMillis = uuid::get_uptime();
|
||||
unsigned long manageElapsed = (unsigned long)(currentMillis - _lastManaged);
|
||||
if (manageElapsed >= MANAGE_NETWORK_DELAY) {
|
||||
_lastManaged = currentMillis;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include <DNSServer.h>
|
||||
#include <IPAddress.h>
|
||||
#include <uuid/common.h>
|
||||
|
||||
|
||||
#define MANAGE_NETWORK_DELAY 10000
|
||||
|
||||
|
||||
@@ -31,6 +31,12 @@ class FSPersistence {
|
||||
DeserializationError error = deserializeJson(jsonDocument, settingsFile);
|
||||
if (error == DeserializationError::Ok && jsonDocument.is<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);
|
||||
settingsFile.close();
|
||||
return;
|
||||
@@ -57,9 +63,12 @@ class FSPersistence {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Serial.printf("Write File: %s: ", _filePath);
|
||||
// serializeJson(jsonDocument, Serial);
|
||||
// Serial.println();
|
||||
// debug added by Proddy
|
||||
#if defined(EMSESP_DEBUG)
|
||||
Serial.printf("Write File: %s: ", _filePath);
|
||||
serializeJson(jsonDocument, Serial);
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
// serialize the data to the file
|
||||
serializeJson(jsonDocument, settingsFile);
|
||||
|
||||
@@ -62,7 +62,7 @@ void MqttSettingsService::begin() {
|
||||
}
|
||||
|
||||
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
|
||||
configureMqtt();
|
||||
|
||||
@@ -107,7 +107,7 @@ void MqttSettingsService::onMqttDisconnect(AsyncMqttClientDisconnectReason reaso
|
||||
// Serial.print(F("Disconnected from MQTT reason: "));
|
||||
// Serial.println((uint8_t)reason);
|
||||
_disconnectReason = reason;
|
||||
_disconnectedAt = millis();
|
||||
_disconnectedAt = uuid::get_uptime();
|
||||
}
|
||||
|
||||
void MqttSettingsService::onConfigUpdated() {
|
||||
@@ -184,10 +184,15 @@ void MqttSettings::read(MqttSettings & settings, JsonObject & root) {
|
||||
root["max_topic_length"] = settings.maxTopicLength;
|
||||
|
||||
// added by proddy for EMS-ESP
|
||||
root["system_heartbeat"] = settings.system_heartbeat;
|
||||
root["publish_time"] = settings.publish_time;
|
||||
root["mqtt_format"] = settings.mqtt_format;
|
||||
root["mqtt_qos"] = settings.mqtt_qos;
|
||||
root["system_heartbeat"] = settings.system_heartbeat;
|
||||
root["publish_time_boiler"] = settings.publish_time_boiler;
|
||||
root["publish_time_thermostat"] = settings.publish_time_thermostat;
|
||||
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) {
|
||||
@@ -203,10 +208,15 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
|
||||
newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
|
||||
newSettings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH;
|
||||
|
||||
newSettings.system_heartbeat = root["system_heartbeat"] | EMSESP_DEFAULT_SYSTEM_HEARTBEAT;
|
||||
newSettings.publish_time = root["publish_time"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||
newSettings.mqtt_format = root["mqtt_format"] | EMSESP_DEFAULT_MQTT_FORMAT;
|
||||
newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS;
|
||||
newSettings.system_heartbeat = root["system_heartbeat"] | EMSESP_DEFAULT_SYSTEM_HEARTBEAT;
|
||||
newSettings.publish_time_boiler = root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||
newSettings.publish_time_thermostat = root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (newSettings.publish_time != settings.publish_time) {
|
||||
emsesp::EMSESP::mqtt_.set_publish_time(newSettings.publish_time);
|
||||
if (newSettings.publish_time_boiler != settings.publish_time_boiler) {
|
||||
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
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
#include <FSPersistence.h>
|
||||
#include <AsyncMqttClient.h>
|
||||
#include <ESPUtils.h>
|
||||
#include <uuid/common.h>
|
||||
|
||||
#include "../../src/system.h"
|
||||
#include "../../src/mqtt.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_SERVICE_PATH "/rest/mqttSettings"
|
||||
@@ -85,7 +86,12 @@ class MqttSettings {
|
||||
uint16_t maxTopicLength;
|
||||
|
||||
// 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_qos;
|
||||
bool system_heartbeat;
|
||||
|
||||
@@ -33,7 +33,7 @@ void NTPStatus::ntpStatus(AsyncWebServerRequest* request) {
|
||||
root["server"] = sntp_getservername(0);
|
||||
|
||||
// device uptime in seconds
|
||||
root["uptime"] = millis() / 1000;
|
||||
root["uptime"] = uuid::get_uptime() / 1000;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include <AsyncJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
#include <uuid/common.h>
|
||||
|
||||
|
||||
#define MAX_NTP_STATUS_SIZE 1024
|
||||
#define NTP_STATUS_SERVICE_PATH "/rest/ntpStatus"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#include <FS.h>
|
||||
// #include <FS.h>
|
||||
#include <LittleFS.h> // proddy added
|
||||
#endif
|
||||
|
||||
|
||||
@@ -24,12 +24,18 @@ class DummySettings {
|
||||
bool shower_alert = false;
|
||||
bool hide_led = false;
|
||||
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;
|
||||
String hostname = "ems-esp";
|
||||
String jwtSecret = "ems-esp";
|
||||
String ssid = "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){};
|
||||
|
||||
2
makefile
2
makefile
@@ -26,7 +26,7 @@ CXX_STANDARD := -std=c++11
|
||||
#----------------------------------------------------------------------
|
||||
# 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
|
||||
|
||||
BIN
media/console.PNG
Normal file
BIN
media/console.PNG
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 920 KiB |
BIN
media/gateway-integration.jpg
Normal file
BIN
media/gateway-integration.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
@@ -10,11 +10,9 @@ extra_configs =
|
||||
pio_local.ini
|
||||
|
||||
[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 =
|
||||
; -D EMSESP_DEBUG
|
||||
; -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
|
||||
build_flags =
|
||||
@@ -85,8 +83,8 @@ lib_ignore =
|
||||
[env:esp32]
|
||||
board = esp32dev
|
||||
build_type = release
|
||||
; platform = espressif32
|
||||
platform = https://github.com/platformio/platform-espressif32.git
|
||||
platform = espressif32
|
||||
; platform = https://github.com/platformio/platform-espressif32.git
|
||||
board_build.partitions = min_spiffs.csv ; https://github.com/espressif/arduino-esp32/blob/master/tools/partitions/
|
||||
lib_deps = ${common.libs_core}
|
||||
build_flags = ${common.build_flags} ${common.debug_flags}
|
||||
|
||||
0
scripts/analyze_stackdmp.py
Normal file → Executable file
0
scripts/analyze_stackdmp.py
Normal file → Executable file
BIN
scripts/boot_app0.bin
Normal file
BIN
scripts/boot_app0.bin
Normal file
Binary file not shown.
BIN
scripts/bootloader_dio_40m.bin
Normal file
BIN
scripts/bootloader_dio_40m.bin
Normal file
Binary file not shown.
0
scripts/build_interface.py
Normal file → Executable file
0
scripts/build_interface.py
Normal file → Executable file
0
scripts/clean_fw.py
Normal file → Executable file
0
scripts/clean_fw.py
Normal file → Executable file
0
scripts/decoder.py
Normal file → Executable file
0
scripts/decoder.py
Normal file → Executable file
0
scripts/decoder_linux.py
Normal file → Executable file
0
scripts/decoder_linux.py
Normal file → Executable file
0
scripts/espota.py
Normal file → Executable file
0
scripts/espota.py
Normal file → Executable file
2959
scripts/esptool.py
Executable 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
0
scripts/main_script.py
Normal file → Executable file
0
scripts/memanalyzer.py
Normal file → Executable file
0
scripts/memanalyzer.py
Normal file → Executable 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
BIN
scripts/partitions.bin
Normal file
Binary file not shown.
15
scripts/upload_esp32.py
Executable file
15
scripts/upload_esp32.py
Executable 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.")
|
||||
@@ -81,7 +81,7 @@ void EMSESPDevicesService::device_data(AsyncWebServerRequest * request, JsonVari
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_DEVICE_SIZE);
|
||||
#ifndef EMSESP_STANDALONE
|
||||
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
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
#define MAX_EMSESP_DEVICE_SIZE 1536
|
||||
#define MAX_EMSESP_DEVICE_SIZE 1700
|
||||
|
||||
#define EMSESP_DEVICES_SERVICE_PATH "/rest/allDevices"
|
||||
#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices"
|
||||
|
||||
@@ -36,11 +36,12 @@ void EMSESPSettings::read(EMSESPSettings & settings, JsonObject & root) {
|
||||
root["master_thermostat"] = settings.master_thermostat;
|
||||
root["shower_timer"] = settings.shower_timer;
|
||||
root["shower_alert"] = settings.shower_alert;
|
||||
root["hide_led"] = settings.hide_led;
|
||||
root["rx_gpio"] = settings.rx_gpio;
|
||||
root["tx_gpio"] = settings.tx_gpio;
|
||||
root["dallas_gpio"] = settings.dallas_gpio;
|
||||
root["dallas_parasite"] = settings.dallas_parasite;
|
||||
root["led_gpio"] = settings.led_gpio;
|
||||
root["hide_led"] = settings.hide_led;
|
||||
}
|
||||
|
||||
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.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
|
||||
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.tx_gpio = root["tx_gpio"] | EMSESP_DEFAULT_TX_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.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
|
||||
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
@@ -68,6 +70,8 @@ void EMSESPSettingsService::onUpdate() {
|
||||
// EMSESP::system_.syslog_init(); // changing SysLog will require a restart
|
||||
EMSESP::init_tx();
|
||||
System::set_led();
|
||||
Sensors sensors_; // Dallas sensors
|
||||
sensors_.start();
|
||||
}
|
||||
|
||||
void EMSESPSettingsService::begin() {
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#define EMSESP_DEFAULT_SHOWER_TIMER false
|
||||
#define EMSESP_DEFAULT_SHOWER_ALERT false
|
||||
#define EMSESP_DEFAULT_HIDE_LED false
|
||||
#define EMSESP_DEFAULT_DALLAS_PARASITE false
|
||||
|
||||
// Default GPIO PIN definitions
|
||||
#if defined(ESP8266)
|
||||
@@ -65,14 +66,15 @@ class EMSESPSettings {
|
||||
uint8_t master_thermostat;
|
||||
bool shower_timer;
|
||||
bool shower_alert;
|
||||
bool hide_led;
|
||||
int8_t syslog_level; // uuid::log::Level
|
||||
uint32_t syslog_mark_interval;
|
||||
String syslog_host;
|
||||
uint8_t rx_gpio;
|
||||
uint8_t tx_gpio;
|
||||
uint8_t dallas_gpio;
|
||||
bool dallas_parasite;
|
||||
uint8_t led_gpio;
|
||||
bool hide_led;
|
||||
|
||||
static void read(EMSESPSettings & settings, JsonObject & root);
|
||||
static StateUpdateResult update(JsonObject & root, EMSESPSettings & settings);
|
||||
|
||||
@@ -88,9 +88,13 @@ void EMSESPShell::display_banner() {
|
||||
// load the list of commands
|
||||
add_console_commands();
|
||||
|
||||
// turn off watch
|
||||
// turn off watch, unless is test mode
|
||||
emsesp::EMSESP::watch_id(WATCH_ID_NONE);
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_ON);
|
||||
#else
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_OFF);
|
||||
#endif
|
||||
}
|
||||
|
||||
// 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...
|
||||
*/
|
||||
@@ -393,7 +408,7 @@ void Console::load_standard_commands(unsigned int context) {
|
||||
flash_string_vector{F_(watch)},
|
||||
flash_string_vector{F_(watch_format_optional), F_(watchid_optional)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
uint16_t watch_id;
|
||||
uint16_t watch_id = WATCH_ID_NONE;
|
||||
|
||||
if (!arguments.empty()) {
|
||||
// get raw/pretty
|
||||
@@ -403,16 +418,16 @@ void Console::load_standard_commands(unsigned int context) {
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_ON); // on
|
||||
} else if (arguments[0] == read_flash_string(F_(off))) {
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_OFF); // off
|
||||
} else {
|
||||
} else if (emsesp::EMSESP::watch() == EMSESP::WATCH_OFF) {
|
||||
shell.printfln(F_(invalid_watch));
|
||||
return;
|
||||
} else {
|
||||
watch_id = Helpers::hextoint(arguments[0].c_str());
|
||||
}
|
||||
|
||||
if (arguments.size() == 2) {
|
||||
// get the watch_id if its set
|
||||
watch_id = Helpers::hextoint(arguments[1].c_str());
|
||||
} else {
|
||||
watch_id = WATCH_ID_NONE;
|
||||
}
|
||||
|
||||
emsesp::EMSESP::watch_id(watch_id);
|
||||
@@ -436,7 +451,9 @@ void Console::load_standard_commands(unsigned int context) {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
@@ -526,6 +543,7 @@ std::string EMSESPStreamConsole::console_name() {
|
||||
}
|
||||
|
||||
// Start up telnet and logging
|
||||
// Log order is off, err, warning, notice, info, debug, trace, all
|
||||
void Console::start() {
|
||||
// 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
|
||||
@@ -540,10 +558,15 @@ void Console::start() {
|
||||
|
||||
#ifndef ESP8266
|
||||
#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
|
||||
|
||||
#if defined(EMSESP_FORCE_SERIAL)
|
||||
shell->log_level(uuid::log::Level::DEBUG);
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
// always start in su/admin mode when running tests
|
||||
shell->add_flags(CommandFlags::ADMIN);
|
||||
@@ -558,7 +581,7 @@ void Console::start() {
|
||||
#endif
|
||||
|
||||
// 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
|
||||
|
||||
@@ -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(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(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(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); });
|
||||
@@ -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("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); });
|
||||
|
||||
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
|
||||
@@ -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;
|
||||
|
||||
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)) {
|
||||
dataElement = root.createNestedObject();
|
||||
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 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("Pump modulation"), pumpMod_, F_(percent));
|
||||
render_value_json(root, "", F("Heat Pump modulation"), pumpMod2_, F_(percent));
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
@@ -196,8 +233,8 @@ void Boiler::publish_values() {
|
||||
if (Helpers::hasValue(wWOneTime_, EMS_VALUE_BOOL)) {
|
||||
doc["wWOnetime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWDesinfecting_, EMS_VALUE_BOOL)) {
|
||||
doc["wWDesinfecting"] = Helpers::render_value(s, wWDesinfecting_, EMS_VALUE_BOOL);
|
||||
if (Helpers::hasValue(wWDisinfecting_, EMS_VALUE_BOOL)) {
|
||||
doc["wWDisinfecting"] = Helpers::render_value(s, wWDisinfecting_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(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 (!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
|
||||
bool Boiler::updated_values() {
|
||||
if (changed_) {
|
||||
changed_ = false;
|
||||
return true;
|
||||
}
|
||||
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);
|
||||
}
|
||||
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("Current flow temperature"), curFlowTemp_, 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("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);
|
||||
if (Helpers::hasValue(burnWorkMin_)) {
|
||||
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_;
|
||||
if (latest_boilerState != last_boilerState) {
|
||||
last_boilerState = latest_boilerState;
|
||||
Mqtt::publish("tapwater_active", tap_water_active_);
|
||||
Mqtt::publish("heating_active", heating_active_);
|
||||
Mqtt::publish(F("tapwater_active"), tap_water_active_);
|
||||
Mqtt::publish(F("heating_active"), heating_active_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0x33
|
||||
void Boiler::process_UBAParameterWW(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(wWActivated_, 1); // 0xFF means on
|
||||
telegram->read_value(wWCircPump_, 6); // 0xFF means on
|
||||
telegram->read_value(wWCircPumpMode_, 7); // 1=1x3min... 6=6x3min, 7=continuous
|
||||
telegram->read_value(wWCircPumpType_, 10); // 0 = charge pump, 0xff = 3-way valve
|
||||
telegram->read_value(wWSelTemp_, 2);
|
||||
telegram->read_value(wWDisinfectTemp_, 8);
|
||||
telegram->read_value(wWComfort_, 9);
|
||||
changed_ |= telegram->read_value(wWActivated_, 1); // 0xFF means on
|
||||
changed_ |= telegram->read_value(wWCircPump_, 6); // 0xFF means on
|
||||
changed_ |= telegram->read_value(wWCircPumpMode_, 7); // 1=1x3min... 6=6x3min, 7=continuous
|
||||
changed_ |= telegram->read_value(wWCircPumpType_, 10); // 0 = charge pump, 0xff = 3-way valve
|
||||
changed_ |= telegram->read_value(wWSelTemp_, 2);
|
||||
changed_ |= telegram->read_value(wWDisinfectTemp_, 8);
|
||||
changed_ |= telegram->read_value(wWComfort_, 9);
|
||||
}
|
||||
|
||||
// 0x18
|
||||
void Boiler::process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(selFlowTemp_, 0);
|
||||
telegram->read_value(curFlowTemp_, 1);
|
||||
telegram->read_value(selBurnPow_, 3); // burn power max setting
|
||||
telegram->read_value(curBurnPow_, 4);
|
||||
changed_ |= telegram->read_value(selFlowTemp_, 0);
|
||||
changed_ |= telegram->read_value(curFlowTemp_, 1);
|
||||
changed_ |= telegram->read_value(selBurnPow_, 3); // burn power max setting
|
||||
changed_ |= telegram->read_value(curBurnPow_, 4);
|
||||
|
||||
telegram->read_bitvalue(burnGas_, 7, 0);
|
||||
telegram->read_bitvalue(fanWork_, 7, 2);
|
||||
telegram->read_bitvalue(ignWork_, 7, 3);
|
||||
telegram->read_bitvalue(heatPmp_, 7, 5);
|
||||
telegram->read_bitvalue(wWHeat_, 7, 6);
|
||||
telegram->read_bitvalue(wWCirc_, 7, 7);
|
||||
changed_ |= telegram->read_bitvalue(burnGas_, 7, 0);
|
||||
changed_ |= telegram->read_bitvalue(fanWork_, 7, 2);
|
||||
changed_ |= telegram->read_bitvalue(ignWork_, 7, 3);
|
||||
changed_ |= telegram->read_bitvalue(heatPmp_, 7, 5);
|
||||
changed_ |= telegram->read_bitvalue(wWHeat_, 7, 6);
|
||||
changed_ |= telegram->read_bitvalue(wWCirc_, 7, 7);
|
||||
|
||||
// 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
|
||||
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(wwStorageTemp1_, 9); // 0x8300 if not available
|
||||
changed_ |= telegram->read_value(wwStorageTemp2_, 11); // 0x8000 if not available - this is boiler temp
|
||||
|
||||
telegram->read_value(retTemp_, 13);
|
||||
telegram->read_value(flameCurr_, 15);
|
||||
telegram->read_value(serviceCode_, 20);
|
||||
changed_ |= telegram->read_value(retTemp_, 13);
|
||||
changed_ |= telegram->read_value(flameCurr_, 15);
|
||||
changed_ |= telegram->read_value(serviceCode_, 20);
|
||||
|
||||
// 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
|
||||
if ((telegram->message_length > 18) && (telegram->offset == 0)) {
|
||||
serviceCodeChar_[0] = char(telegram->message_data[18]); // ascii character 1
|
||||
serviceCodeChar_[1] = char(telegram->message_data[19]); // ascii character 2
|
||||
serviceCodeChar_[2] = '\0'; // null terminate string
|
||||
changed_ |= telegram->read_value(serviceCodeChar_[0], 18);
|
||||
changed_ |= telegram->read_value(serviceCodeChar_[1], 19);
|
||||
serviceCodeChar_[2] = '\0'; // null terminate string
|
||||
}
|
||||
|
||||
// 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)
|
||||
*/
|
||||
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
|
||||
*/
|
||||
void Boiler::process_UBAParameters(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(heating_temp_, 1);
|
||||
telegram->read_value(burnPowermax_, 2);
|
||||
telegram->read_value(burnPowermin_, 3);
|
||||
telegram->read_value(boilTemp_off_, 4);
|
||||
telegram->read_value(boilTemp_on_, 5);
|
||||
telegram->read_value(burnPeriod_, 6);
|
||||
telegram->read_value(pumpDelay_, 8);
|
||||
telegram->read_value(pump_mod_max_, 9);
|
||||
telegram->read_value(pump_mod_min_, 10);
|
||||
changed_ |= telegram->read_value(heating_temp_, 1);
|
||||
changed_ |= telegram->read_value(burnPowermax_, 2);
|
||||
changed_ |= telegram->read_value(burnPowermin_, 3);
|
||||
changed_ |= telegram->read_value(boilTemp_off_, 4);
|
||||
changed_ |= telegram->read_value(boilTemp_on_, 5);
|
||||
changed_ |= telegram->read_value(burnPeriod_, 6);
|
||||
changed_ |= telegram->read_value(pumpDelay_, 8);
|
||||
changed_ |= telegram->read_value(pump_mod_max_, 9);
|
||||
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
|
||||
*/
|
||||
void Boiler::process_UBAMonitorWW(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(wWSetTmp_, 0);
|
||||
telegram->read_value(wWCurTmp_, 1);
|
||||
telegram->read_value(wWCurTmp2_, 3);
|
||||
telegram->read_value(wWCurFlow_, 9);
|
||||
changed_ |= telegram->read_value(wWSetTmp_, 0);
|
||||
changed_ |= telegram->read_value(wWCurTmp_, 1);
|
||||
changed_ |= telegram->read_value(wWCurTmp2_, 3);
|
||||
changed_ |= telegram->read_value(wWCurFlow_, 9);
|
||||
|
||||
telegram->read_value(wWWorkM_, 10, 3); // force to 3 bytes
|
||||
telegram->read_value(wWStarts_, 13, 3); // force to 3 bytes
|
||||
changed_ |= telegram->read_value(wWWorkM_, 10, 3); // force to 3 bytes
|
||||
changed_ |= telegram->read_value(wWStarts_, 13, 3); // force to 3 bytes
|
||||
|
||||
telegram->read_bitvalue(wWOneTime_, 5, 1);
|
||||
telegram->read_bitvalue(wWDesinfecting_, 5, 2);
|
||||
telegram->read_bitvalue(wWReadiness_, 5, 3);
|
||||
telegram->read_bitvalue(wWRecharging_, 5, 4);
|
||||
telegram->read_bitvalue(wWTemperatureOK_, 5, 5);
|
||||
changed_ |= telegram->read_bitvalue(wWOneTime_, 5, 1);
|
||||
changed_ |= telegram->read_bitvalue(wWDisinfecting_, 5, 2);
|
||||
changed_ |= telegram->read_bitvalue(wWReadiness_, 5, 3);
|
||||
changed_ |= telegram->read_bitvalue(wWRecharging_, 5, 4);
|
||||
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
|
||||
*/
|
||||
void Boiler::process_UBAMonitorFastPlus(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(selFlowTemp_, 6);
|
||||
telegram->read_bitvalue(burnGas_, 11, 0);
|
||||
telegram->read_bitvalue(wWHeat_, 11, 2);
|
||||
telegram->read_value(curBurnPow_, 10);
|
||||
telegram->read_value(selBurnPow_, 9);
|
||||
telegram->read_value(curFlowTemp_, 7);
|
||||
telegram->read_value(flameCurr_, 19);
|
||||
changed_ |= telegram->read_value(selFlowTemp_, 6);
|
||||
changed_ |= telegram->read_bitvalue(burnGas_, 11, 0);
|
||||
changed_ |= telegram->read_bitvalue(wWHeat_, 11, 2);
|
||||
changed_ |= telegram->read_value(curBurnPow_, 10);
|
||||
changed_ |= telegram->read_value(selBurnPow_, 9);
|
||||
changed_ |= telegram->read_value(curFlowTemp_, 7);
|
||||
changed_ |= telegram->read_value(flameCurr_, 19);
|
||||
|
||||
// read the service code / installation status as appears on the display
|
||||
if ((telegram->message_length > 4) && (telegram->offset == 0)) {
|
||||
serviceCodeChar_[0] = char(telegram->message_data[4]); // ascii character 1
|
||||
serviceCodeChar_[1] = char(telegram->message_data[5]); // ascii character 2
|
||||
changed_ |= telegram->read_value(serviceCodeChar_[0], 4);
|
||||
changed_ |= telegram->read_value(serviceCodeChar_[1], 5);
|
||||
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
|
||||
*/
|
||||
void Boiler::process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(extTemp_, 0);
|
||||
telegram->read_value(boilTemp_, 2);
|
||||
telegram->read_value(exhaustTemp_, 4);
|
||||
telegram->read_value(switchTemp_, 25); // only if there is a mixing module present
|
||||
telegram->read_value(pumpMod_, 9);
|
||||
telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes
|
||||
telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes
|
||||
telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes
|
||||
changed_ |= telegram->read_value(extTemp_, 0);
|
||||
changed_ |= telegram->read_value(boilTemp_, 2);
|
||||
changed_ |= telegram->read_value(exhaustTemp_, 4);
|
||||
changed_ |= telegram->read_value(switchTemp_, 25); // only if there is a mixing module present
|
||||
changed_ |= telegram->read_value(pumpMod_, 9);
|
||||
changed_ |= telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes
|
||||
changed_ |= telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes
|
||||
changed_ |= telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes
|
||||
}
|
||||
|
||||
/*
|
||||
* UBAMonitorSlowPlus2 - type 0xE3
|
||||
*/
|
||||
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+
|
||||
*/
|
||||
void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_bitvalue(fanWork_, 2, 2);
|
||||
telegram->read_bitvalue(ignWork_, 2, 3);
|
||||
telegram->read_bitvalue(heatPmp_, 2, 5);
|
||||
telegram->read_bitvalue(wWCirc_, 2, 7);
|
||||
telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes
|
||||
telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes
|
||||
telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes
|
||||
telegram->read_value(pumpMod_, 25);
|
||||
changed_ |= telegram->read_bitvalue(fanWork_, 2, 2);
|
||||
changed_ |= telegram->read_bitvalue(ignWork_, 2, 3);
|
||||
changed_ |= telegram->read_bitvalue(heatPmp_, 2, 5);
|
||||
changed_ |= telegram->read_bitvalue(wWCirc_, 2, 7);
|
||||
changed_ |= telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes
|
||||
changed_ |= telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes
|
||||
changed_ |= telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes
|
||||
changed_ |= telegram->read_value(pumpMod_, 25);
|
||||
}
|
||||
|
||||
// 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
|
||||
void Boiler::process_UBADHWStatus(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(wWSetTmp_, 0);
|
||||
telegram->read_value(wWCurTmp_, 1);
|
||||
telegram->read_value(wWCurTmp2_, 3);
|
||||
changed_ |= telegram->read_value(wWSetTmp_, 0);
|
||||
changed_ |= telegram->read_value(wWCurTmp_, 1);
|
||||
changed_ |= telegram->read_value(wWCurTmp2_, 3);
|
||||
|
||||
telegram->read_value(wWWorkM_, 17, 3); // force to 3 bytes
|
||||
telegram->read_value(wWStarts_, 14, 3); // force to 3 bytes
|
||||
changed_ |= telegram->read_value(wWWorkM_, 17, 3); // force to 3 bytes
|
||||
changed_ |= telegram->read_value(wWStarts_, 14, 3); // force to 3 bytes
|
||||
|
||||
telegram->read_bitvalue(wWOneTime_, 12, 2);
|
||||
telegram->read_bitvalue(wWDesinfecting_, 12, 3);
|
||||
telegram->read_bitvalue(wWReadiness_, 12, 4);
|
||||
telegram->read_bitvalue(wWRecharging_, 13, 4);
|
||||
telegram->read_bitvalue(wWTemperatureOK_, 13, 5);
|
||||
telegram->read_bitvalue(wWCircPump_, 13, 2);
|
||||
changed_ |= telegram->read_bitvalue(wWOneTime_, 12, 2);
|
||||
changed_ |= telegram->read_bitvalue(wWDisinfecting_, 12, 3);
|
||||
changed_ |= telegram->read_bitvalue(wWReadiness_, 12, 4);
|
||||
changed_ |= telegram->read_bitvalue(wWRecharging_, 13, 4);
|
||||
changed_ |= telegram->read_bitvalue(wWTemperatureOK_, 13, 5);
|
||||
changed_ |= telegram->read_bitvalue(wWCircPump_, 13, 2);
|
||||
|
||||
telegram->read_value(wWActivated_, 20);
|
||||
telegram->read_value(wWSelTemp_, 10);
|
||||
telegram->read_value(wWDisinfectTemp_, 9);
|
||||
changed_ |= telegram->read_value(wWActivated_, 20);
|
||||
changed_ |= telegram->read_value(wWSelTemp_, 10);
|
||||
changed_ |= telegram->read_value(wWDisinfectTemp_, 9);
|
||||
}
|
||||
|
||||
// 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
|
||||
// see https://github.com/proddy/EMS-ESP/issues/397
|
||||
void Boiler::process_MC10Status(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(wwMixTemperature_, 14);
|
||||
telegram->read_value(wwBufferBoilerTemperature_, 18);
|
||||
changed_ |= telegram->read_value(wwMixTemperature_, 14);
|
||||
changed_ |= telegram->read_value(wwBufferBoilerTemperature_, 18);
|
||||
}
|
||||
|
||||
/*
|
||||
* UBAOutdoorTemp - type 0xD1 - external temperature EMS+
|
||||
*/
|
||||
void Boiler::process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(extTemp_, 0);
|
||||
changed_ |= telegram->read_value(extTemp_, 0);
|
||||
}
|
||||
|
||||
// UBASetPoint 0x1A
|
||||
void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(setFlowTemp_, 0); // boiler set temp from thermostat
|
||||
telegram->read_value(setBurnPow_, 1); // max output power in %
|
||||
telegram->read_value(setWWPumpPow_, 2); // ww pump speed/power?
|
||||
changed_ |= telegram->read_value(setFlowTemp_, 0); // boiler set temp from thermostat
|
||||
changed_ |= telegram->read_value(setBurnPow_, 1); // max output power in %
|
||||
changed_ |= telegram->read_value(setWWPumpPow_, 2); // ww pump speed/power?
|
||||
}
|
||||
|
||||
#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);
|
||||
write_command(EMS_TYPE_UBAParameterWW, 2, v);
|
||||
write_command(EMS_TYPE_UBAFlags, 3, v); // for i9000, see #397
|
||||
write_command(EMS_TYPE_UBAParameterWW, 2, v, EMS_TYPE_UBAParameterWW);
|
||||
write_command(EMS_TYPE_UBAFlags, 3, v, EMS_TYPE_UBAParameterWW); // for i9000, see #397
|
||||
}
|
||||
|
||||
// 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);
|
||||
write_command(EMS_TYPE_UBASetPoints, 0, v);
|
||||
write_command(EMS_TYPE_UBASetPoints, 0, v, EMS_TYPE_UBASetPoints);
|
||||
}
|
||||
|
||||
// set min boiler output
|
||||
@@ -705,7 +746,7 @@ void Boiler::set_min_power(const char * value, const int8_t id) {
|
||||
return;
|
||||
}
|
||||
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
|
||||
@@ -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);
|
||||
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) {
|
||||
int v = 0;
|
||||
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);
|
||||
write_command(EMS_TYPE_UBAParameters, 5, v);
|
||||
write_command(EMS_TYPE_UBAParameters, 5, v, EMS_TYPE_UBAParameters);
|
||||
}
|
||||
|
||||
// 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);
|
||||
write_command(EMS_TYPE_UBAParameters, 4, v);
|
||||
write_command(EMS_TYPE_UBAParameters, 4, v, EMS_TYPE_UBAParameters);
|
||||
}
|
||||
|
||||
// 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);
|
||||
write_command(EMS_TYPE_UBAParameters, 6, v);
|
||||
write_command(EMS_TYPE_UBAParameters, 6, v, EMS_TYPE_UBAParameters);
|
||||
}
|
||||
|
||||
// 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);
|
||||
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
|
||||
@@ -782,7 +823,7 @@ void Boiler::set_warmwater_mode(const char * value, const int8_t id) {
|
||||
} else {
|
||||
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
|
||||
@@ -801,7 +842,7 @@ void Boiler::set_warmwater_activated(const char * value, const int8_t id) {
|
||||
} else {
|
||||
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
|
||||
@@ -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");
|
||||
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
|
||||
@@ -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");
|
||||
write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02));
|
||||
write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02), 0x18);
|
||||
}
|
||||
|
||||
// add console commands
|
||||
|
||||
@@ -40,7 +40,7 @@ class Boiler : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual void device_info_web(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
@@ -48,8 +48,12 @@ class Boiler : public EMSdevice {
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
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 mqtt_format_; // single, nested or ha
|
||||
bool changed_ = false;
|
||||
|
||||
static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33;
|
||||
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 wWWorkM_ = EMS_VALUE_ULONG_NOTSET; // Warm Water # minutes
|
||||
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 wWRecharging_ = EMS_VALUE_BOOL_NOTSET; // Warm Water recharge 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_UBAMonitorSlowPlus(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_UBASetPoints(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_UBAMaintenanceData(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 check_active();
|
||||
|
||||
// commands - none of these use the additional id parameter
|
||||
void set_warmwater_mode(const char * value, const int8_t id);
|
||||
void set_warmwater_activated(const char * value, const int8_t id);
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
void Connect::device_info(JsonArray & root) {
|
||||
void Connect::device_info_web(JsonArray & root) {
|
||||
}
|
||||
|
||||
void Connect::add_context_menu() {
|
||||
|
||||
@@ -37,7 +37,7 @@ class Connect : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual void device_info_web(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -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::device_info(JsonArray & root) {
|
||||
void Controller::device_info_web(JsonArray & root) {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
|
||||
@@ -37,7 +37,7 @@ class Controller : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual void device_info_web(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -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::device_info(JsonArray & root) {
|
||||
void Gateway::device_info_web(JsonArray & root) {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
|
||||
@@ -37,7 +37,7 @@ class Gateway : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual void device_info_web(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -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::device_info(JsonArray & root) {
|
||||
void Heatpump::device_info_web(JsonArray & root) {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
|
||||
@@ -37,7 +37,7 @@ class Heatpump : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual void device_info_web(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -60,25 +60,31 @@ void Mixing::add_context_menu() {
|
||||
}
|
||||
|
||||
// output json to web UI
|
||||
void Mixing::device_info(JsonArray & root) {
|
||||
void Mixing::device_info_web(JsonArray & root) {
|
||||
if (type_ == Type::NONE) {
|
||||
return; // don't have any values yet
|
||||
}
|
||||
|
||||
if (type_ == Type::WWC) {
|
||||
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 {
|
||||
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
|
||||
bool Mixing::updated_values() {
|
||||
if (changed_) {
|
||||
changed_ = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -96,14 +102,17 @@ void Mixing::show_values(uuid::console::Shell & shell) {
|
||||
|
||||
if (type_ == Type::WWC) {
|
||||
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 {
|
||||
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();
|
||||
}
|
||||
@@ -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
|
||||
void Mixing::publish_values() {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
char s[5]; // for formatting strings
|
||||
|
||||
switch (type_) {
|
||||
case 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;
|
||||
case 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;
|
||||
case Type::NONE:
|
||||
default:
|
||||
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 s[3]; // for formatting strings
|
||||
strlcpy(topic, "mixing_data", 30);
|
||||
strlcat(topic, Helpers::itoa(s, get_device_id() - 0x20 + 1), 30); // append hc to topic
|
||||
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
|
||||
void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram) {
|
||||
type_ = Type::HC;
|
||||
hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is
|
||||
telegram->read_value(flowTemp_, 3); // is * 10
|
||||
telegram->read_value(flowSetTemp_, 5);
|
||||
telegram->read_value(pumpMod_, 2);
|
||||
telegram->read_value(status_, 1); // valve status
|
||||
hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is
|
||||
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
|
||||
changed_ |= telegram->read_value(flowSetTemp_, 5);
|
||||
changed_ |= telegram->read_value(pump_, 0);
|
||||
changed_ |= telegram->read_value(status_, 2); // valve status
|
||||
}
|
||||
|
||||
// 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
|
||||
void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram) {
|
||||
type_ = Type::WWC;
|
||||
hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2.
|
||||
telegram->read_value(flowTemp_, 0); // is * 10
|
||||
telegram->read_value(pumpMod_, 2);
|
||||
telegram->read_value(status_, 11); // temp status
|
||||
hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2.
|
||||
changed_ |= telegram->read_value(flowTemp_, 0); // is * 10
|
||||
changed_ |= telegram->read_value(pump_, 2);
|
||||
changed_ |= telegram->read_value(status_, 11); // temp status
|
||||
}
|
||||
|
||||
// Mixing IMP - 0x010C
|
||||
@@ -178,20 +192,16 @@ void Mixing::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram)
|
||||
type_ = Type::HC;
|
||||
hc_ = get_device_id() - 0x20 + 1;
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
if (ismixed == 2) { // we have a mixed circuit
|
||||
telegram->read_value(flowTemp_, 3); // is * 10
|
||||
telegram->read_value(flowSetTemp_, 5);
|
||||
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;
|
||||
if (ismixed == 2) { // we have a mixed circuit
|
||||
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
|
||||
changed_ |= telegram->read_value(flowSetTemp_, 5);
|
||||
changed_ |= telegram->read_value(status_, 2); // valve status
|
||||
}
|
||||
changed_ |= telegram->read_bitvalue(pump_, 1, 0); // pump is also in unmixed circuits
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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;
|
||||
telegram->read_value(flowTemp_, 1); // is * 10
|
||||
telegram->read_value(pumpMod_, 3);
|
||||
telegram->read_value(flowSetTemp_, 0);
|
||||
changed_ |= telegram->read_value(flowTemp_, 1); // is * 10
|
||||
changed_ |= telegram->read_value(pump_, 3);
|
||||
changed_ |= telegram->read_value(flowSetTemp_, 0);
|
||||
changed_ |= telegram->read_value(status_, 4); // valve status -100 to 100
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
|
||||
@@ -37,7 +37,7 @@ class Mixing : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual void device_info_web(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
@@ -62,10 +62,11 @@ class Mixing : public EMSdevice {
|
||||
private:
|
||||
uint16_t hc_ = EMS_VALUE_USHORT_NOTSET;
|
||||
uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET;
|
||||
uint8_t pumpMod_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t status_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t pump_ = EMS_VALUE_UINT_NOTSET;
|
||||
int8_t status_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET;
|
||||
Type type_ = Type::NONE;
|
||||
bool changed_ = false;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -54,7 +54,7 @@ void Solar::add_context_menu() {
|
||||
}
|
||||
|
||||
// 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("Tank bottom temperature (TS2)"), tankBottomTemp_, 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_)) {
|
||||
doc["pumpWorkMin"] = (float)pumpWorkMin_;
|
||||
doc["pumpWorkMin"] = pumpWorkMin_;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(tankHeated_, EMS_VALUE_BOOL)) {
|
||||
@@ -168,11 +168,18 @@ void Solar::publish_values() {
|
||||
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
|
||||
bool Solar::updated_values() {
|
||||
if (changed_) {
|
||||
changed_ = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -182,11 +189,11 @@ void Solar::console_commands() {
|
||||
|
||||
// SM10Monitor - type 0x97
|
||||
void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(collectorTemp_, 2); // collector temp from SM10, is *10
|
||||
telegram->read_value(tankBottomTemp_, 5); // bottom temp from SM10, is *10
|
||||
telegram->read_value(solarPumpModulation_, 4); // modulation solar pump
|
||||
telegram->read_bitvalue(solarPump_, 7, 1);
|
||||
telegram->read_value(pumpWorkMin_, 8);
|
||||
changed_ |= telegram->read_value(collectorTemp_, 2); // collector temp from SM10, is *10
|
||||
changed_ |= telegram->read_value(tankBottomTemp_, 5); // bottom temp from SM10, is *10
|
||||
changed_ |= telegram->read_value(solarPumpModulation_, 4); // modulation solar pump
|
||||
changed_ |= telegram->read_bitvalue(solarPump_, 7, 1);
|
||||
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
|
||||
*/
|
||||
void Solar::process_SM100Monitor(std::shared_ptr<const Telegram> telegram) {
|
||||
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
|
||||
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(collectorTemp_, 0); // is *10 - TS1: Temperature sensor for collector array 1
|
||||
changed_ |= telegram->read_value(tankBottomTemp_, 2); // is *10 - TS2: Temperature sensor 1 cylinder, bottom
|
||||
changed_ |= telegram->read_value(tankBottomTemp2_, 16); // is *10 - TS5: Temperature sensor 2 cylinder, bottom, or swimming pool
|
||||
changed_ |= telegram->read_value(heatExchangerTemp_, 20); // is *10 - TS6: Heat exchanger temperature sensor
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
@@ -221,9 +228,9 @@ void Solar::process_SM100Monitor2(std::shared_ptr<const Telegram> telegram) {
|
||||
// SM100Config - 0x0366
|
||||
// e.g. B0 00 FF 00 02 66 01 62 00 13 40 14
|
||||
void Solar::process_SM100Config(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(availabilityFlag_, 0);
|
||||
telegram->read_value(configFlag_, 1);
|
||||
telegram->read_value(userFlag_, 2);
|
||||
changed_ |= telegram->read_value(availabilityFlag_, 0);
|
||||
changed_ |= telegram->read_value(configFlag_, 1);
|
||||
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) {
|
||||
uint8_t solarpumpmod = solarPumpModulation_;
|
||||
uint8_t cylinderpumpmod = cylinderPumpModulation_;
|
||||
telegram->read_value(cylinderPumpModulation_, 8);
|
||||
telegram->read_value(solarPumpModulation_, 9);
|
||||
changed_ |= telegram->read_value(cylinderPumpModulation_, 8);
|
||||
changed_ |= telegram->read_value(solarPumpModulation_, 9);
|
||||
|
||||
if (solarpumpmod == 0 && solarPumpModulation_ == 100) { // mask out boosts
|
||||
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
|
||||
cylinderPumpModulation_ = 15; // set to minimum
|
||||
}
|
||||
telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422
|
||||
telegram->read_bitvalue(collectorShutdown_, 3, 0); // collector shutdown
|
||||
changed_ |= telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422
|
||||
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)
|
||||
*/
|
||||
void Solar::process_SM100Status2(std::shared_ptr<const Telegram> telegram) {
|
||||
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(valveStatus_, 4, 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
|
||||
*/
|
||||
void Solar::process_SM100Energy(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(energyLastHour_, 0); // last hour / 10 in Wh
|
||||
telegram->read_value(energyToday_, 4); // todays in Wh
|
||||
telegram->read_value(energyTotal_, 8); // total / 10 in kWh
|
||||
changed_ |= telegram->read_value(energyLastHour_, 0); // last hour / 10 in Wh
|
||||
changed_ |= telegram->read_value(energyToday_, 4); // todays in Wh
|
||||
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
|
||||
*/
|
||||
void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(collectorTemp_, 4); // Collector Temperature
|
||||
telegram->read_value(tankBottomTemp_, 6); // Temperature Bottom of Solar Boiler
|
||||
changed_ |= telegram->read_value(collectorTemp_, 4); // Collector Temperature
|
||||
changed_ |= telegram->read_value(tankBottomTemp_, 6); // Temperature Bottom of Solar Boiler
|
||||
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) {
|
||||
energyLastHour_ = Wh * 10; // set to *10
|
||||
}
|
||||
|
||||
telegram->read_bitvalue(solarPump_, 8, 0); // PS1 Solar pump on (1) or off (0)
|
||||
telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes
|
||||
telegram->read_bitvalue(collectorShutdown_, 9, 0); // collector shutdown on/off
|
||||
telegram->read_bitvalue(tankHeated_, 9, 2); // tank full
|
||||
changed_ |= telegram->read_bitvalue(solarPump_, 8, 0); // PS1 Solar pump on (1) or off (0)
|
||||
changed_ |= telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes
|
||||
changed_ |= telegram->read_bitvalue(collectorShutdown_, 9, 0); // collector shutdown on/off
|
||||
changed_ |= telegram->read_bitvalue(tankHeated_, 9, 2); // tank full
|
||||
}
|
||||
|
||||
/*
|
||||
* Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -37,7 +37,7 @@ class Solar : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual void device_info_web(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
@@ -65,6 +65,7 @@ class Solar : public EMSdevice {
|
||||
uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET;
|
||||
uint8_t configFlag_ = 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_SM100Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
@@ -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::device_info(JsonArray & root) {
|
||||
void Switch::device_info_web(JsonArray & root) {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
|
||||
@@ -37,7 +37,7 @@ class Switch : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual void device_info_web(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -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)
|
||||
: 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
|
||||
|
||||
// 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); });
|
||||
}
|
||||
// RC10
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_RC10) {
|
||||
if (model == EMSdevice::EMS_DEVICE_FLAG_RC10) {
|
||||
monitor_typeids = {0xB1};
|
||||
set_typeids = {0xB0};
|
||||
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
|
||||
} 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};
|
||||
set_typeids = {0x3D, 0x47, 0x51, 0x5B};
|
||||
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); });
|
||||
|
||||
// RC20
|
||||
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20) {
|
||||
} else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20) {
|
||||
monitor_typeids = {0x91};
|
||||
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++) {
|
||||
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); });
|
||||
@@ -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); });
|
||||
}
|
||||
// RC20 newer
|
||||
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20_2) {
|
||||
} else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20_2) {
|
||||
monitor_typeids = {0xAE};
|
||||
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++) {
|
||||
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); });
|
||||
@@ -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); });
|
||||
}
|
||||
// RC30
|
||||
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_RC30) {
|
||||
} else if (model == EMSdevice::EMS_DEVICE_FLAG_RC30) {
|
||||
monitor_typeids = {0x41};
|
||||
set_typeids = {0xA7};
|
||||
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
|
||||
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_EASY) {
|
||||
} else if (model == EMSdevice::EMS_DEVICE_FLAG_EASY) {
|
||||
monitor_typeids = {0x0A};
|
||||
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
|
||||
} 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};
|
||||
set_typeids = {0x02B9, 0x02BA, 0x02BB, 0x02BC};
|
||||
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); });
|
||||
|
||||
// JUNKERS/HT3
|
||||
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
||||
} else if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
||||
monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172};
|
||||
set_typeids = {0x0165, 0x0166, 0x0167, 0x0168};
|
||||
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); });
|
||||
}
|
||||
|
||||
} 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};
|
||||
set_typeids = {0x0179, 0x017A, 0x017B, 0x017C};
|
||||
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) {
|
||||
mqtt_format_ = settings.mqtt_format; // single, nested or ha
|
||||
});
|
||||
|
||||
uint8_t actual_master_thermostat = EMSESP::actual_master_thermostat(); // what we're actually using
|
||||
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 {
|
||||
if (actual_master_thermostat != 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
|
||||
}
|
||||
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)
|
||||
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
|
||||
void Thermostat::device_info(JsonArray & root) {
|
||||
uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, strip the option bits
|
||||
void Thermostat::device_info_web(JsonArray & root) {
|
||||
uint8_t flags = this->model();
|
||||
|
||||
for (const auto & hc : heating_circuits_) {
|
||||
if (!Helpers::hasValue(hc->setpoint_roomTemp)) {
|
||||
@@ -240,23 +242,10 @@ bool Thermostat::updated_values() {
|
||||
if (EMSESP::actual_master_thermostat() != this->get_device_id()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// quick hack to see if it changed. We simply just add up all the raw values
|
||||
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;
|
||||
if (changed_) {
|
||||
changed_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -267,15 +256,15 @@ void Thermostat::publish_values() {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, stripping the option bits
|
||||
bool has_data = false;
|
||||
uint8_t flags = this->model();
|
||||
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||
JsonObject rootThermostat = doc.to<JsonObject>();
|
||||
JsonObject dataThermostat;
|
||||
|
||||
// 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()) {
|
||||
rootThermostat["time"] = datetime_.c_str();
|
||||
}
|
||||
@@ -314,135 +303,140 @@ void Thermostat::publish_values() {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
Mqtt::publish("thermostat_data", doc);
|
||||
Mqtt::publish(F("thermostat_data"), doc);
|
||||
rootThermostat = doc.to<JsonObject>(); // clear object
|
||||
}
|
||||
}
|
||||
|
||||
// go through all the heating circuits
|
||||
bool has_data = false;
|
||||
for (const auto & hc : heating_circuits_) {
|
||||
if (!Helpers::hasValue(hc->setpoint_roomTemp)) {
|
||||
break; // skip this HC
|
||||
}
|
||||
if (hc->is_active()) {
|
||||
has_data = true;
|
||||
|
||||
has_data = true;
|
||||
// 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::NESTED) || (mqtt_format_ == MQTT_format::HA)) {
|
||||
char hc_name[10]; // hc{1-4}
|
||||
strlcpy(hc_name, "hc", 10);
|
||||
char s[3];
|
||||
strlcat(hc_name, Helpers::itoa(s, hc->hc_num()), 10);
|
||||
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;
|
||||
// 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::NESTED) || (mqtt_format_ == MQTT_format::HA)) {
|
||||
char hc_name[10]; // hc{1-4}
|
||||
strlcpy(hc_name, "hc", 10);
|
||||
char s[3];
|
||||
strlcat(hc_name, Helpers::itoa(s, hc->hc_num()), 10);
|
||||
dataThermostat = rootThermostat.createNestedObject(hc_name);
|
||||
} else {
|
||||
dataThermostat["daytemp"] = (float)hc->daytemp / 2;
|
||||
dataThermostat = rootThermostat;
|
||||
}
|
||||
}
|
||||
if (Helpers::hasValue(hc->nighttemp)) {
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
||||
dataThermostat["ecotemp"] = (float)hc->nighttemp / 2;
|
||||
|
||||
// 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 {
|
||||
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)) {
|
||||
dataThermostat["nofrosttemp"] = (float)hc->nofrosttemp / 2;
|
||||
}
|
||||
if (Helpers::hasValue(hc->setpoint_roomTemp)) {
|
||||
dataThermostat["seltemp"] = Helpers::round2((float)hc->setpoint_roomTemp / setpoint_temp_divider);
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(hc->heatingtype)) {
|
||||
dataThermostat["heatingtype"] = hc->heatingtype;
|
||||
}
|
||||
if (Helpers::hasValue(hc->curr_roomTemp)) {
|
||||
dataThermostat["currtemp"] = Helpers::round2((float)hc->curr_roomTemp / curr_temp_divider);
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(hc->targetflowtemp)) {
|
||||
dataThermostat["targetflowtemp"] = hc->targetflowtemp;
|
||||
}
|
||||
|
||||
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;
|
||||
if (Helpers::hasValue(hc->daytemp)) {
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
||||
dataThermostat["heattemp"] = (float)hc->daytemp / 2;
|
||||
} 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
|
||||
// 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 (Helpers::hasValue(hc->nofrosttemp)) {
|
||||
dataThermostat["nofrosttemp"] = (float)hc->nofrosttemp / 2;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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);
|
||||
if (Helpers::hasValue(hc->heatingtype)) {
|
||||
dataThermostat["heatingtype"] = hc->heatingtype;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(hc->targetflowtemp)) {
|
||||
dataThermostat["targetflowtemp"] = hc->targetflowtemp;
|
||||
}
|
||||
|
||||
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 {
|
||||
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 ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::CUSTOM)) {
|
||||
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
|
||||
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 == 0) {
|
||||
if (hc_num == AUTO_HEATING_CIRCUIT) {
|
||||
for (const auto & heating_circuit : heating_circuits_) {
|
||||
return heating_circuit;
|
||||
}
|
||||
@@ -522,19 +515,17 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
@@ -579,9 +570,24 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
|
||||
doc["temp_step"] = "0.5";
|
||||
|
||||
JsonArray modes = doc.createNestedArray("modes");
|
||||
modes.add("off");
|
||||
modes.add("heat");
|
||||
modes.add("auto");
|
||||
uint8_t flags = this->model();
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20_2) {
|
||||
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
|
||||
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) {
|
||||
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()) {
|
||||
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_) {
|
||||
if (!Helpers::hasValue(hc->setpoint_roomTemp)) {
|
||||
if (!hc->is_active()) {
|
||||
break; // skip this HC
|
||||
}
|
||||
|
||||
shell.printfln(F(" Heating Circuit %d:"), hc->hc_num());
|
||||
|
||||
// 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) {
|
||||
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)
|
||||
void Thermostat::process_RC20Monitor_2(std::shared_ptr<const Telegram> 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
|
||||
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_bitvalue(hc->mode_type, 0, 7); // day/night MSB 7th bit is day
|
||||
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force as single byte
|
||||
changed_ |= telegram->read_value(hc->curr_roomTemp, 3); // is * 10
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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, ..)
|
||||
void Thermostat::process_RC20Remote(std::shared_ptr<const Telegram> 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)
|
||||
void Thermostat::process_RC10Monitor(std::shared_ptr<const Telegram> telegram) {
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
|
||||
|
||||
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->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
|
||||
changed_ |= telegram->read_value(hc->curr_roomTemp, 2); // is * 10
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
@@ -928,56 +935,57 @@ void Thermostat::process_RC10Set(std::shared_ptr<const Telegram> telegram) {
|
||||
// type 0x0165, ff
|
||||
void Thermostat::process_JunkersSet(std::shared_ptr<const Telegram> telegram) {
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
|
||||
telegram->read_value(hc->daytemp, 17); // is * 2
|
||||
telegram->read_value(hc->nighttemp, 16); // is * 2
|
||||
telegram->read_value(hc->nofrosttemp, 15); // is * 2
|
||||
changed_ |= telegram->read_value(hc->daytemp, 17); // is * 2
|
||||
changed_ |= telegram->read_value(hc->nighttemp, 16); // is * 2
|
||||
changed_ |= telegram->read_value(hc->nofrosttemp, 15); // is * 2
|
||||
}
|
||||
// type 0x0179, ff
|
||||
void Thermostat::process_JunkersSet2(std::shared_ptr<const Telegram> telegram) {
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
|
||||
telegram->read_value(hc->daytemp, 7); // is * 2
|
||||
telegram->read_value(hc->nighttemp, 6); // is * 2
|
||||
telegram->read_value(hc->nofrosttemp, 5); // is * 2
|
||||
changed_ |= telegram->read_value(hc->daytemp, 7); // is * 2
|
||||
changed_ |= telegram->read_value(hc->nighttemp, 6); // 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)
|
||||
void Thermostat::process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(dampedoutdoortemp_, 0);
|
||||
telegram->read_value(tempsensor1_, 3); // sensor 1 - is * 10
|
||||
telegram->read_value(tempsensor2_, 5); // sensor 2 - is * 10
|
||||
changed_ |= telegram->read_value(dampedoutdoortemp_, 0);
|
||||
changed_ |= telegram->read_value(tempsensor1_, 3); // sensor 1 - is * 10
|
||||
changed_ |= telegram->read_value(tempsensor2_, 5); // sensor 2 - is * 10
|
||||
}
|
||||
|
||||
// 0x91 - data from the RC20 thermostat (0x17) - 15 bytes long
|
||||
void Thermostat::process_RC20Monitor(std::shared_ptr<const Telegram> telegram) {
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
|
||||
|
||||
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->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
|
||||
changed_ |= telegram->read_value(hc->curr_roomTemp, 2); // is * 10
|
||||
}
|
||||
|
||||
// type 0x0A - data from the Nefit Easy/TC100 thermostat (0x18) - 31 bytes long
|
||||
void Thermostat::process_EasyMonitor(std::shared_ptr<const Telegram> telegram) {
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
|
||||
|
||||
telegram->read_value(hc->curr_roomTemp, 8); // is * 100
|
||||
telegram->read_value(hc->setpoint_roomTemp, 10); // is * 100
|
||||
changed_ |= telegram->read_value(hc->curr_roomTemp, 8); // is * 100
|
||||
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 10); // is * 100
|
||||
}
|
||||
|
||||
// Settings Parameters - 0xA5 - RC30_1
|
||||
void Thermostat::process_IBASettings(std::shared_ptr<const Telegram> telegram) {
|
||||
// 22 - display line on RC35
|
||||
telegram->read_value(ibaMainDisplay_,
|
||||
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(ibaLanguage_, 1); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
|
||||
telegram->read_value(ibaCalIntTemperature_, 2); // offset int. temperature sensor, by * 0.1 Kelvin
|
||||
telegram->read_value(ibaBuildingType_, 6); // building type: 0 = light, 1 = medium, 2 = heavy
|
||||
telegram->read_value(ibaMinExtTemperature_, 5); // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1
|
||||
telegram->read_value(ibaClockOffset_, 12); // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s
|
||||
changed_ |=
|
||||
telegram->read_value(ibaMainDisplay_,
|
||||
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
|
||||
changed_ |= telegram->read_value(ibaLanguage_, 1); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
|
||||
changed_ |= telegram->read_value(ibaCalIntTemperature_, 2); // offset int. temperature sensor, by * 0.1 Kelvin
|
||||
changed_ |= telegram->read_value(ibaBuildingType_, 6); // building type: 0 = light, 1 = medium, 2 = heavy
|
||||
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
|
||||
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
|
||||
@@ -989,21 +997,21 @@ void Thermostat::process_JunkersMonitor(std::shared_ptr<const Telegram> telegram
|
||||
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
|
||||
|
||||
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->curr_roomTemp, 4); // 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
|
||||
telegram->read_value(hc->mode, 1); // 1 = manual, 2 = auto
|
||||
changed_ |= telegram->read_value(hc->mode_type, 0); // 1 = nofrost, 2 = eco, 3 = heat
|
||||
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
|
||||
void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> 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);
|
||||
telegram->read_bitvalue(hc->mode, 10, 0); // bit 1, mode (auto=1 or manual=0)
|
||||
changed_ |= telegram->read_bitvalue(hc->mode_type, 10, 1);
|
||||
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 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 6 actual setpoint according to programmed changes eco/comfort
|
||||
// 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
|
||||
telegram->read_bitvalue(hc->summer_mode, 2, 4);
|
||||
telegram->read_value(hc->targetflowtemp, 4);
|
||||
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 3, 1); // is * 2, force as single byte
|
||||
changed_ |= telegram->read_bitvalue(hc->summer_mode, 2, 4);
|
||||
changed_ |= telegram->read_value(hc->targetflowtemp, 4);
|
||||
}
|
||||
|
||||
// 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
|
||||
// I think auto is position 8?
|
||||
// actual setpoint taken from RC300Monitor (Michael 12.06.2020)
|
||||
// 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, 8, 1); // single byte conversion, value is * 2 - auto?
|
||||
// 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!
|
||||
// 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
|
||||
telegram->read_value(hc->nighttemp, 4); // is * 2
|
||||
changed_ |= telegram->read_value(hc->daytemp, 2); // is * 2
|
||||
changed_ |= telegram->read_value(hc->nighttemp, 4); // is * 2
|
||||
}
|
||||
|
||||
// types 0x31D and 0x31E
|
||||
void Thermostat::process_RC300WWmode(std::shared_ptr<const Telegram> telegram) {
|
||||
// 0x31D for WW system 1, 0x31E for WW system 2
|
||||
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 2 = current status of DHW setpoint
|
||||
// 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) {
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
|
||||
|
||||
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->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
|
||||
changed_ |= telegram->read_value(hc->curr_roomTemp, 2);
|
||||
}
|
||||
|
||||
// type 0xA7 - for reading the mode from the RC30 thermostat (0x10)
|
||||
void Thermostat::process_RC30Set(std::shared_ptr<const Telegram> 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
|
||||
@@ -1072,14 +1080,14 @@ void Thermostat::process_RC35Monitor(std::shared_ptr<const Telegram> 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
|
||||
telegram->read_value(hc->curr_roomTemp, 3); // is * 10 - or 0x7D00 if thermostat is mounted on boiler
|
||||
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force to single byte, is 0 in summermode
|
||||
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);
|
||||
telegram->read_bitvalue(hc->summer_mode, 1, 0);
|
||||
telegram->read_bitvalue(hc->holiday_mode, 0, 5);
|
||||
changed_ |= telegram->read_bitvalue(hc->mode_type, 1, 1);
|
||||
changed_ |= telegram->read_bitvalue(hc->summer_mode, 1, 0);
|
||||
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)
|
||||
@@ -1091,16 +1099,16 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
|
||||
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
|
||||
|
||||
telegram->read_value(hc->mode, 7); // night, day, auto
|
||||
telegram->read_value(hc->daytemp, 2); // is * 2
|
||||
telegram->read_value(hc->nighttemp, 1); // is * 2
|
||||
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->mode, 7); // night, day, auto
|
||||
changed_ |= telegram->read_value(hc->daytemp, 2); // is * 2
|
||||
changed_ |= telegram->read_value(hc->nighttemp, 1); // is * 2
|
||||
changed_ |= telegram->read_value(hc->holidaytemp, 3); // is * 2
|
||||
changed_ |= telegram->read_value(hc->heatingtype, 0); // 0- off, 1-radiator, 2-convector, 3-floor
|
||||
|
||||
telegram->read_value(hc->summertemp, 22); // is * 1
|
||||
telegram->read_value(hc->nofrosttemp, 23); // is * 1
|
||||
telegram->read_value(hc->designtemp, 17); // is * 1
|
||||
telegram->read_value(hc->offsettemp, 6); // is * 2
|
||||
changed_ |= telegram->read_value(hc->summertemp, 22); // is * 1
|
||||
changed_ |= telegram->read_value(hc->nofrosttemp, 23); // is * 1
|
||||
changed_ |= telegram->read_value(hc->designtemp, 17); // is * 1
|
||||
changed_ |= telegram->read_value(hc->offsettemp, 6); // is * 2
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
datetime_.resize(25, '\0');
|
||||
}
|
||||
auto timeold = datetime_;
|
||||
// render time to HH:MM:SS DD/MM/YYYY
|
||||
// had to create separate buffers because of how printf works
|
||||
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::itoa(buf6, telegram->message_data[0] + 2000, 10) // year
|
||||
);
|
||||
if (timeold != datetime_) {
|
||||
changed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// add console commands
|
||||
@@ -1143,15 +1155,9 @@ void Thermostat::console_commands(Shell & shell, unsigned int context) {
|
||||
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
|
||||
CommandFlags::ADMIN,
|
||||
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) {
|
||||
uint8_t value;
|
||||
if (arguments.empty()) {
|
||||
value = EMSESP_DEFAULT_MASTER_THERMOSTAT;
|
||||
} else {
|
||||
value = Helpers::hextoint(arguments.front().c_str());
|
||||
}
|
||||
|
||||
uint8_t value = Helpers::hextoint(arguments.front().c_str());
|
||||
EMSESP::emsespSettingsService.update(
|
||||
[&](EMSESPSettings & settings) {
|
||||
settings.master_thermostat = value;
|
||||
@@ -1203,7 +1209,7 @@ void Thermostat::set_minexttemp(const char * value, const int8_t id) {
|
||||
return;
|
||||
}
|
||||
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
|
||||
@@ -1213,7 +1219,7 @@ void Thermostat::set_clockoffset(const char * value, const int8_t id) {
|
||||
return;
|
||||
}
|
||||
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
|
||||
@@ -1224,7 +1230,7 @@ void Thermostat::set_calinttemp(const char * value, const int8_t id) {
|
||||
}
|
||||
// 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);
|
||||
write_command(EMS_TYPE_IBASettings, 2, ct);
|
||||
write_command(EMS_TYPE_IBASettings, 2, ct, EMS_TYPE_IBASettings);
|
||||
}
|
||||
|
||||
// 0xA5 - Set the display settings
|
||||
@@ -1234,7 +1240,7 @@ void Thermostat::set_display(const char * value, const int8_t id) {
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
@@ -1271,7 +1277,7 @@ void Thermostat::set_building(const char * value, const int8_t id) {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1281,7 +1287,7 @@ void Thermostat::set_language(const char * value, const int8_t id) {
|
||||
return;
|
||||
}
|
||||
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
|
||||
@@ -1326,7 +1332,7 @@ void Thermostat::set_wwmode(const char * value, const int8_t id) {
|
||||
|
||||
if (set != 0xFF) {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
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
|
||||
@@ -1503,7 +1509,7 @@ void Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (this->flags() & 0x0F) {
|
||||
switch (this->model()) {
|
||||
case EMSdevice::EMS_DEVICE_FLAG_RC20:
|
||||
offset = EMS_OFFSET_RC20Set_mode;
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -1600,7 +1605,7 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
|
||||
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
|
||||
uint8_t factor = 2; // some temperatures only use 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)) {
|
||||
validate_typeid = set_typeids[hc->hc_num() - 1];
|
||||
if (mode == HeatingCircuit::Mode::AUTO) {
|
||||
offset = 0x08; // auto offset
|
||||
} else if (mode == HeatingCircuit::Mode::MANUAL) {
|
||||
switch (mode) {
|
||||
case HeatingCircuit::Mode::MANUAL:
|
||||
offset = 0x0A; // manual offset
|
||||
} else if (mode == HeatingCircuit::Mode::COMFORT) {
|
||||
break;
|
||||
case HeatingCircuit::Mode::COMFORT:
|
||||
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) {
|
||||
@@ -1632,6 +1643,11 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
|
||||
case HeatingCircuit::Mode::DAY: // change the day temp
|
||||
offset = EMS_OFFSET_RC20_2_Set_temp_day;
|
||||
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)) {
|
||||
@@ -1662,7 +1678,8 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
|
||||
factor = 1;
|
||||
break;
|
||||
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) {
|
||||
uint8_t mode_ = hc->get_mode(this->flags());
|
||||
if (mode_ == HeatingCircuit::Mode::NIGHT) {
|
||||
@@ -1699,8 +1716,13 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
|
||||
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 || mode_type == HeatingCircuit::Mode::ECO) ? EMS_OFFSET_JunkersSetMessage_night_temp
|
||||
: EMS_OFFSET_JunkersSetMessage_day_temp;
|
||||
if (mode_type == HeatingCircuit::Mode::NIGHT || mode_type == HeatingCircuit::Mode::ECO) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1714,10 +1736,20 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
|
||||
case HeatingCircuit::Mode::NIGHT:
|
||||
offset = EMS_OFFSET_JunkersSetMessage2_eco_temp;
|
||||
break;
|
||||
default:
|
||||
case HeatingCircuit::Mode::HEAT:
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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("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) {
|
||||
case EMS_DEVICE_FLAG_RC20_2:
|
||||
register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { set_nighttemp(value, id); });
|
||||
|
||||
@@ -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);
|
||||
class HeatingCircuit {
|
||||
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)
|
||||
, monitor_typeid_(monitor_typeid)
|
||||
, set_typeid_(set_typeid) {
|
||||
, ha_registered_(false) {
|
||||
}
|
||||
~HeatingCircuit() = default;
|
||||
|
||||
@@ -60,42 +59,46 @@ class Thermostat : public EMSdevice {
|
||||
uint8_t targetflowtemp = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t summertemp = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t nofrosttemp = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t designtemp = EMS_VALUE_UINT_NOTSET; // heatingcurve design temp at MinExtTemp
|
||||
int8_t offsettemp = EMS_VALUE_INT_NOTSET; // heatingcurve offest temp at roomtemp signed!
|
||||
uint8_t designtemp = EMS_VALUE_UINT_NOTSET; // heating curve design temp at MinExtTemp
|
||||
int8_t offsettemp = EMS_VALUE_INT_NOTSET; // heating curve offest temp at roomtemp signed!
|
||||
|
||||
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_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 };
|
||||
|
||||
// for sorting
|
||||
// for sorting based on hc number
|
||||
friend inline bool operator<(const std::shared_ptr<HeatingCircuit> & lhs, const std::shared_ptr<HeatingCircuit> & rhs) {
|
||||
return (lhs->hc_num_ < rhs->hc_num_);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t hc_num_; // 1..10
|
||||
uint16_t monitor_typeid_;
|
||||
uint16_t set_typeid_;
|
||||
uint8_t hc_num_; // heating circuit number 1..10
|
||||
bool ha_registered_; // whether it has been registered for HA MQTT Discovery
|
||||
};
|
||||
|
||||
static std::string mode_tostring(uint8_t mode);
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual void device_info_web(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
@@ -105,6 +108,11 @@ class Thermostat : public EMSdevice {
|
||||
void console_commands(Shell & shell, unsigned int context);
|
||||
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
|
||||
std::vector<uint16_t> monitor_typeids;
|
||||
std::vector<uint16_t> set_typeids;
|
||||
@@ -113,10 +121,11 @@ class Thermostat : public EMSdevice {
|
||||
std::string datetime_; // date and time stamp
|
||||
|
||||
uint8_t mqtt_format_; // single, nested or ha
|
||||
bool changed_ = false;
|
||||
|
||||
// Installation parameters
|
||||
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
|
||||
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
|
||||
@@ -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_no_frost_temp = 5;
|
||||
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
|
||||
#define DEFAULT_HEATING_CIRCUIT 1
|
||||
static constexpr uint8_t AUTO_HEATING_CIRCUIT = 0;
|
||||
|
||||
// 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_building(const char * value, const int8_t id);
|
||||
void set_language(const char * value, const int8_t id);
|
||||
};
|
||||
}; // namespace emsesp
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ std::string EMSdevice::brand_to_string() const {
|
||||
break;
|
||||
case EMSdevice::Brand::NO_BRAND:
|
||||
default:
|
||||
return read_flash_string(F(""));
|
||||
return read_flash_string(F("---"));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ class EMSdevice {
|
||||
virtual void publish_values() = 0;
|
||||
virtual bool updated_values() = 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);
|
||||
|
||||
|
||||
@@ -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
|
||||
uint8_t EMSESP::watch_ = 0; // trace off
|
||||
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()
|
||||
uint32_t EMSESP::last_fetch_ = 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
|
||||
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
|
||||
if (watch_id <= 0xFF) {
|
||||
watch_id_ = (watch_id & 0x7F);
|
||||
} else {
|
||||
watch_id_ = watch_id;
|
||||
}
|
||||
watch_id_ = watch_id;
|
||||
}
|
||||
|
||||
// change the tx_mode
|
||||
@@ -286,27 +282,63 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
|
||||
char valuestr[8] = {0}; // for formatting temp
|
||||
shell.printfln(F("Dallas temperature sensors:"));
|
||||
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();
|
||||
}
|
||||
|
||||
// publish all values from each EMS device to MQTT
|
||||
// plus the heartbeat and sensor if activated
|
||||
void EMSESP::publish_all_values() {
|
||||
void EMSESP::publish_device_values(uint8_t device_type) {
|
||||
if (Mqtt::connected()) {
|
||||
// Dallas sensors first
|
||||
sensors_.publish_values();
|
||||
|
||||
// all the connected EMS devices we known about
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
if (emsdevice && (emsdevice->device_type() == device_type)) {
|
||||
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
|
||||
std::string EMSESP::device_tostring(const uint8_t device_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 (telegram->type_id == read_id_) {
|
||||
LOG_NOTICE(pretty_telegram(telegram).c_str());
|
||||
publish_response(telegram);
|
||||
read_id_ = WATCH_ID_NONE;
|
||||
} 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());
|
||||
}
|
||||
}
|
||||
@@ -507,7 +541,10 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
found = emsdevice->handle_telegram(telegram);
|
||||
// check to see if we need to follow up after the telegram has been processed
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
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) {
|
||||
if (emsdevice) {
|
||||
if (emsdevice->unique_id() == unique_id) {
|
||||
root["deviceName"] = emsdevice->to_string_short(); // can;t use c_str() because of scope
|
||||
JsonArray data = root.createNestedArray("deviceData");
|
||||
emsdevice->device_info(data);
|
||||
emsdevice->device_info_web(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -709,7 +747,6 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
if (tx_state != Telegram::Operation::NONE) {
|
||||
bool tx_successful = false;
|
||||
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 ((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"));
|
||||
txservice_.increment_telegram_write_count(); // last tx/write was confirmed ok
|
||||
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();
|
||||
tx_successful = true;
|
||||
} 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
|
||||
LOG_TRACE(F("[DEBUG] Reply after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
|
||||
#endif
|
||||
// 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.
|
||||
// in add() the CRC will be checked
|
||||
rxservice_.add(data, length);
|
||||
Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data); // check if there is a message for the roomcontroller
|
||||
|
||||
rxservice_.add(data, length); // add to RxQueue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,9 @@ class EMSESP {
|
||||
static void start();
|
||||
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
|
||||
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 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);
|
||||
|
||||
@@ -174,6 +176,7 @@ class EMSESP {
|
||||
|
||||
static void process_UBADevices(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 uint32_t last_fetch_;
|
||||
@@ -191,6 +194,7 @@ class EMSESP {
|
||||
static uint16_t watch_id_;
|
||||
static uint8_t watch_;
|
||||
static uint16_t read_id_;
|
||||
static uint16_t publish_id_;
|
||||
static bool tap_water_active_;
|
||||
|
||||
static uint8_t unique_id_count_;
|
||||
|
||||
@@ -329,27 +329,27 @@ bool Helpers::check_abs(const int32_t i) {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return (v != EMS_VALUE_BOOL_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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
bool Helpers::hasValue(const uint16_t v) {
|
||||
bool Helpers::hasValue(const uint16_t &v) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,11 +50,11 @@ class Helpers {
|
||||
static char * ultostr(char * ptr, uint32_t value, const uint8_t base);
|
||||
#endif
|
||||
|
||||
static bool hasValue(const uint8_t v, const uint8_t isBool = 0);
|
||||
static bool hasValue(const int8_t v);
|
||||
static bool hasValue(const int16_t v);
|
||||
static bool hasValue(const uint16_t v);
|
||||
static bool hasValue(const uint32_t v);
|
||||
static bool hasValue(const uint8_t &v, const uint8_t isBool = 0);
|
||||
static bool hasValue(const int8_t &v);
|
||||
static bool hasValue(const int16_t &v);
|
||||
static bool hasValue(const uint16_t &v);
|
||||
static bool hasValue(const uint32_t &v);
|
||||
|
||||
static std::string toLower(std::string const & s);
|
||||
|
||||
|
||||
@@ -108,7 +108,6 @@ MAKE_PSTR(gpio_mandatory, "<gpio>")
|
||||
MAKE_PSTR(data_optional, "[data]")
|
||||
MAKE_PSTR(typeid_mandatory, "<type ID>")
|
||||
MAKE_PSTR(deviceid_mandatory, "<device ID>")
|
||||
MAKE_PSTR(deviceid_optional, "[device ID]")
|
||||
MAKE_PSTR(invalid_log_level, "Invalid log level")
|
||||
MAKE_PSTR(log_level_fmt, "Log level = %s")
|
||||
MAKE_PSTR(log_level_optional, "[level]")
|
||||
|
||||
115
src/mqtt.cpp
115
src/mqtt.cpp
@@ -27,8 +27,13 @@ AsyncMqttClient * Mqtt::mqttClient_;
|
||||
// static parameters we make global
|
||||
std::string Mqtt::hostname_;
|
||||
uint8_t Mqtt::mqtt_qos_;
|
||||
uint16_t Mqtt::publish_time_;
|
||||
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::MQTTCmdFunction> Mqtt::mqtt_cmdfunctions_;
|
||||
@@ -111,11 +116,30 @@ void Mqtt::loop() {
|
||||
uint32_t currentMillis = uuid::get_uptime();
|
||||
|
||||
// create publish messages for each of the EMS device values, adding to queue
|
||||
if (publish_time_ && (currentMillis - last_publish_ > publish_time_)) {
|
||||
last_publish_ = currentMillis;
|
||||
EMSESP::publish_all_values();
|
||||
if (publish_time_boiler_ && (currentMillis - last_publish_boiler_ > publish_time_boiler_)) {
|
||||
last_publish_boiler_ = currentMillis;
|
||||
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
|
||||
if ((uint32_t)(currentMillis - last_mqtt_poll_) > MQTT_PUBLISH_WAIT) {
|
||||
last_mqtt_poll_ = currentMillis;
|
||||
@@ -280,7 +304,7 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -342,8 +366,13 @@ void Mqtt::start() {
|
||||
|
||||
// fetch MQTT settings
|
||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) {
|
||||
publish_time_ = mqttSettings.publish_time * 1000; // convert to milliseconds
|
||||
mqtt_qos_ = mqttSettings.mqtt_qos;
|
||||
publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000; // convert to milliseconds
|
||||
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; });
|
||||
@@ -390,8 +419,51 @@ void Mqtt::start() {
|
||||
mqtt_subfunctions_.reserve(10);
|
||||
}
|
||||
|
||||
void Mqtt::set_publish_time(uint16_t publish_time) {
|
||||
publish_time_ = publish_time * 1000; // convert to milliseconds
|
||||
void Mqtt::set_publish_time_boiler(uint16_t publish_time) {
|
||||
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) {
|
||||
@@ -407,9 +479,9 @@ void Mqtt::on_connect() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
doc["ip"] = WiFi.localIP().toString();
|
||||
#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
|
||||
|
||||
@@ -466,6 +538,15 @@ void Mqtt::publish(const std::string & topic, const std::string & payload, bool
|
||||
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) {
|
||||
std::string payload_text;
|
||||
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) {
|
||||
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
|
||||
void Mqtt::publish(const std::string & topic) {
|
||||
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
|
||||
// assumes there is an MQTT connection
|
||||
void Mqtt::process_queue() {
|
||||
|
||||
29
src/mqtt.h
29
src/mqtt.h
@@ -67,8 +67,14 @@ class Mqtt {
|
||||
void loop();
|
||||
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);
|
||||
bool get_publish_onchange(uint8_t device_type);
|
||||
|
||||
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 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 __FlashStringHelper * topi, const bool value);
|
||||
static void publish(const std::string & topic);
|
||||
|
||||
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_message(const char * topic, const char * payload, size_t len);
|
||||
void process_queue();
|
||||
void process_all_queue();
|
||||
|
||||
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<MQTTCmdFunction> mqtt_cmdfunctions_; // list of commands
|
||||
|
||||
uint32_t last_mqtt_poll_ = 0;
|
||||
uint32_t last_publish_ = 0;
|
||||
uint32_t last_mqtt_poll_ = 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
|
||||
static std::string hostname_;
|
||||
static uint8_t mqtt_qos_;
|
||||
static uint16_t publish_time_;
|
||||
static uint32_t publish_time_;
|
||||
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
|
||||
|
||||
100
src/sensors.cpp
100
src/sensors.cpp
@@ -49,7 +49,10 @@ void Sensors::reload() {
|
||||
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) {
|
||||
for (uint8_t i = 0; i < MAX_SENSORS; registered_ha_[i++] = false)
|
||||
@@ -64,10 +67,10 @@ void Sensors::loop() {
|
||||
if (state_ == State::IDLE) {
|
||||
if (time_now - last_activity_ >= READ_INTERVAL_MS) {
|
||||
// LOG_DEBUG(F("Read sensor temperature")); // uncomment for debug
|
||||
if (bus_.reset()) {
|
||||
if (bus_.reset() || parasite_) {
|
||||
YIELD;
|
||||
bus_.skip();
|
||||
bus_.write(CMD_CONVERT_TEMP);
|
||||
bus_.write(CMD_CONVERT_TEMP, parasite_ ? 1 : 0);
|
||||
state_ = State::READING;
|
||||
} else {
|
||||
// no sensors found
|
||||
@@ -94,8 +97,9 @@ void Sensors::loop() {
|
||||
uint8_t addr[ADDR_LEN] = {0};
|
||||
|
||||
if (bus_.search(addr)) {
|
||||
bus_.depower();
|
||||
|
||||
if (!parasite_) {
|
||||
bus_.depower();
|
||||
}
|
||||
if (bus_.crc8(addr, ADDR_LEN - 1) == addr[ADDR_LEN - 1]) {
|
||||
switch (addr[0]) {
|
||||
case TYPE_DS18B20:
|
||||
@@ -122,8 +126,19 @@ void Sensors::loop() {
|
||||
LOG_ERROR(F("Invalid sensor %s"), Device(addr).to_string().c_str());
|
||||
}
|
||||
} else {
|
||||
bus_.depower();
|
||||
if (!parasite_) {
|
||||
bus_.depower();
|
||||
}
|
||||
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_);
|
||||
retrycnt_ = 0;
|
||||
} else {
|
||||
@@ -140,9 +155,12 @@ void Sensors::loop() {
|
||||
|
||||
bool Sensors::temperature_convert_complete() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (parasite_) {
|
||||
return true; // don't care, use the minimum time in loop
|
||||
}
|
||||
return bus_.read_bit() == 1;
|
||||
#else
|
||||
return 1;
|
||||
return true;
|
||||
#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];
|
||||
|
||||
// Adjust based on device resolution
|
||||
int resolution = 9 + ((scratchpad[SCRATCHPAD_CONFIG] >> 5) & 0x3);
|
||||
switch (resolution) {
|
||||
case 9:
|
||||
raw_value &= ~0x1;
|
||||
break;
|
||||
|
||||
case 10:
|
||||
raw_value &= ~0x3;
|
||||
break;
|
||||
|
||||
case 11:
|
||||
raw_value &= ~0x7;
|
||||
break;
|
||||
|
||||
case 12:
|
||||
break;
|
||||
if (addr[0] == TYPE_DS18S20) {
|
||||
raw_value = (raw_value << 3) + 12 - scratchpad[SCRATCHPAD_CNT_REM];
|
||||
} else {
|
||||
// Adjust based on device resolution
|
||||
int resolution = 9 + ((scratchpad[SCRATCHPAD_CONFIG] >> 5) & 0x3);
|
||||
switch (resolution) {
|
||||
case 9:
|
||||
raw_value &= ~0x7;
|
||||
break;
|
||||
case 10:
|
||||
raw_value &= ~0x3;
|
||||
break;
|
||||
case 11:
|
||||
raw_value &= ~0x1;
|
||||
break;
|
||||
case 12:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t raw = (raw_value * 625) / 100; // round to 0.01
|
||||
return (float)raw / 100;
|
||||
uint32_t raw = (raw_value * 625 + 500) / 1000; // round to 0.1
|
||||
return (float)raw / 10;
|
||||
#else
|
||||
return NAN;
|
||||
#endif
|
||||
@@ -237,6 +255,14 @@ std::string Sensors::Device::to_string() const {
|
||||
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
|
||||
// assumes there are devices
|
||||
@@ -254,7 +280,7 @@ void Sensors::publish_values() {
|
||||
StaticJsonDocument<100> doc;
|
||||
for (const auto & device : devices_) {
|
||||
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}
|
||||
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);
|
||||
@@ -264,28 +290,22 @@ void Sensors::publish_values() {
|
||||
return;
|
||||
}
|
||||
|
||||
// const size_t capacity = num_devices * JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(num_devices);
|
||||
DynamicJsonDocument doc(100 * num_devices);
|
||||
uint8_t i = 1; // sensor count
|
||||
for (const auto & device : devices_) {
|
||||
char s[7];
|
||||
|
||||
if (mqtt_format_ == MQTT_format::CUSTOM) {
|
||||
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c, 2);
|
||||
} else if (mqtt_format_ == MQTT_format::SINGLE) {
|
||||
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);
|
||||
// e.g. sensors = {28-EA41-9497-0E03-5F":23.30,"28-233D-9497-0C03-8B":24.0}
|
||||
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c, 1);
|
||||
} 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}
|
||||
strlcpy(sensorID, "sensor", 10);
|
||||
strlcat(sensorID, Helpers::itoa(s, i), 10);
|
||||
JsonObject dataSensor = doc.createNestedObject(sensorID);
|
||||
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
|
||||
@@ -327,9 +347,9 @@ void Sensors::publish_values() {
|
||||
}
|
||||
|
||||
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) {
|
||||
Mqtt::publish("homeassistant/sensor/ems-esp/state", doc);
|
||||
Mqtt::publish(F("homeassistant/sensor/ems-esp/state"), doc);
|
||||
}
|
||||
}
|
||||
} // namespace emsesp
|
||||
@@ -60,6 +60,7 @@ class Sensors {
|
||||
void loop();
|
||||
void publish_values();
|
||||
void reload();
|
||||
bool updated_values();
|
||||
|
||||
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_LSB = 0;
|
||||
static constexpr size_t SCRATCHPAD_CONFIG = 4;
|
||||
static constexpr size_t SCRATCHPAD_CNT_REM = 6;
|
||||
|
||||
// dallas chips
|
||||
static constexpr uint8_t TYPE_DS18B20 = 0x28;
|
||||
static constexpr uint8_t TYPE_DS18S20 = 0x10;
|
||||
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 CONVERSION_MS = 1000; // 1 seconds
|
||||
@@ -109,6 +111,8 @@ class Sensors {
|
||||
uint8_t mqtt_format_;
|
||||
uint8_t retrycnt_ = 0;
|
||||
uint8_t dallas_gpio_ = 0;
|
||||
bool parasite_ = false;
|
||||
bool changed_ = false;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -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
|
||||
if (!shower_on_ && (time_now - timer_start_) > SHOWER_MIN_DURATION) {
|
||||
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"));
|
||||
}
|
||||
// check if the shower has been on too long
|
||||
@@ -74,7 +74,7 @@ void Shower::loop() {
|
||||
if ((timer_pause_ - timer_start_) > SHOWER_OFFSET_TIME) {
|
||||
duration_ = (timer_pause_ - timer_start_ - SHOWER_OFFSET_TIME);
|
||||
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_);
|
||||
publish_values();
|
||||
}
|
||||
@@ -129,7 +129,7 @@ void Shower::publish_values() {
|
||||
doc["duration"] = s;
|
||||
}
|
||||
|
||||
Mqtt::publish("shower_data", doc);
|
||||
Mqtt::publish(F("shower_data"), doc);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
288
src/system.cpp
288
src/system.cpp
@@ -35,6 +35,7 @@ int System::reset_counter_ = 0;
|
||||
bool System::upload_status_ = false;
|
||||
bool System::hide_led_ = false;
|
||||
uint8_t System::led_gpio_ = 0;
|
||||
uint16_t System::analog_ = 0;
|
||||
|
||||
// send on/off to a gpio pin
|
||||
// value: true = HIGH, false = LOW
|
||||
@@ -188,6 +189,7 @@ void System::loop() {
|
||||
#endif
|
||||
led_monitor(); // check status and report back using the LED
|
||||
system_check(); // check system health
|
||||
measure_analog();
|
||||
|
||||
// send out heartbeat
|
||||
uint32_t currentMillis = uuid::get_uptime();
|
||||
@@ -233,8 +235,34 @@ void System::send_heartbeat() {
|
||||
doc["mqttpublishfails"] = Mqtt::publish_fails();
|
||||
doc["txfails"] = EMSESP::txservice_.telegram_fail_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
|
||||
@@ -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
|
||||
// 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() {
|
||||
#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 ignored "-Wdeprecated-declarations"
|
||||
|
||||
@@ -604,120 +646,172 @@ bool System::check_upgrade() {
|
||||
cfg.setAutoFormat(false); // prevent formatting when opening SPIFFS filesystem
|
||||
SPIFFS.setConfig(cfg);
|
||||
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);
|
||||
|
||||
// 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;
|
||||
File file;
|
||||
JsonObject network, general, mqtt, custom_settings;
|
||||
StaticJsonDocument<1024> doc;
|
||||
|
||||
error = deserializeJson(doc1, file1);
|
||||
if (error) {
|
||||
Serial.printf("Error. Failed to deserialize json, doc1, error %s", error.c_str());
|
||||
// open the system settings:
|
||||
// {
|
||||
// "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;
|
||||
} 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);
|
||||
if (error) {
|
||||
Serial.printf("Error. Failed to deserialize json, doc2, error %s", error.c_str());
|
||||
failed = true;
|
||||
file.close();
|
||||
|
||||
if (failed) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
Serial.println(F("Failed to read system config. Quitting."));
|
||||
#endif
|
||||
SPIFFS.end();
|
||||
Serial.end();
|
||||
return false;
|
||||
}
|
||||
|
||||
file1.close();
|
||||
file2.close();
|
||||
// open the custom settings file next:
|
||||
// {
|
||||
// "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();
|
||||
|
||||
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
|
||||
|
||||
LittleFS.begin();
|
||||
EMSESP::esp8266React.begin(); // loads system settings (wifi, mqtt, etc)
|
||||
EMSESP::emsespSettingsService.begin(); // load EMS-ESP specific settings
|
||||
|
||||
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.println(F("Restarting..."));
|
||||
Serial.flush();
|
||||
delay(1000);
|
||||
Serial.end();
|
||||
delay(1000);
|
||||
restart();
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
|
||||
25
src/system.h
25
src/system.h
@@ -68,29 +68,19 @@ class System {
|
||||
static uuid::syslog::SyslogService syslog_;
|
||||
#endif
|
||||
|
||||
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_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_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_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_MEASURE_ANALOG_INTERVAL = 1100;
|
||||
|
||||
// internal LED
|
||||
#ifndef EMSESP_NO_LED
|
||||
#if defined(ESP8266)
|
||||
// internal LED
|
||||
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 set_led_speed(uint32_t speed);
|
||||
void system_check();
|
||||
void measure_analog();
|
||||
|
||||
static void show_system(uuid::console::Shell & shell);
|
||||
static void show_users(uuid::console::Shell & shell);
|
||||
@@ -103,6 +93,7 @@ class System {
|
||||
static int reset_counter_;
|
||||
uint32_t last_heartbeat_ = 0;
|
||||
static bool upload_status_; // true if we're in the middle of a OTA firmware upload
|
||||
static uint16_t analog_;
|
||||
|
||||
// settings
|
||||
bool system_heartbeat_;
|
||||
|
||||
@@ -73,7 +73,7 @@ Telegram::Telegram(const uint8_t operation,
|
||||
, offset(offset)
|
||||
, message_length(message_length) {
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ std::string Telegram::to_string() const {
|
||||
data[2] = this->type_id;
|
||||
length = 5;
|
||||
}
|
||||
} else if (this->operation == Telegram::Operation::TX_WRITE) {
|
||||
} else {
|
||||
data[1] = this->dest;
|
||||
if (this->type_id > 0xFF) {
|
||||
data[2] = 0xFF;
|
||||
@@ -109,10 +109,6 @@ std::string Telegram::to_string() const {
|
||||
for (uint8_t i = 0; i < this->message_length; 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);
|
||||
@@ -189,13 +185,14 @@ void RxService::add(uint8_t * data, uint8_t length) {
|
||||
}
|
||||
type_id = (data[4 + shift] << 8) + data[5 + shift] + 256;
|
||||
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 (EMSESP::watch() == EMSESP::Watch::WATCH_RAW) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
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);
|
||||
|
||||
// check if queue is full, if so remove top item to make space
|
||||
if (rx_telegrams_.size() >= MAX_RX_TELEGRAMS) {
|
||||
rx_telegrams_.pop_front();
|
||||
increment_telegram_error_count();
|
||||
}
|
||||
|
||||
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[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) {
|
||||
// WRITE
|
||||
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;
|
||||
} else {
|
||||
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
|
||||
@@ -535,7 +534,7 @@ void TxService::send_raw(const char * telegram_data) {
|
||||
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
|
||||
@@ -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
|
||||
void TxService::post_send_query() {
|
||||
if (telegram_last_post_send_query_) {
|
||||
uint8_t dest = (telegram_last_->dest & 0x7F);
|
||||
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);
|
||||
}
|
||||
}
|
||||
// unless the post_send_query has a type_id of 0
|
||||
uint16_t TxService::post_send_query() {
|
||||
uint16_t post_typeid = this->get_post_send_query();
|
||||
|
||||
// print out the last Tx that was sent
|
||||
void TxService::print_last_tx() {
|
||||
LOG_DEBUG(F("Last Tx %s operation: %s"),
|
||||
(telegram_last_->operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"),
|
||||
telegram_last_->to_string().c_str());
|
||||
if (post_typeid) {
|
||||
uint8_t dest = (this->telegram_last_->dest & 0x7F);
|
||||
// when set a value with large offset before and validate on same type, we have to add offset 0, 26, 52, ...
|
||||
uint8_t offset = (this->telegram_last_->type_id == post_typeid) ? ((this->telegram_last_->offset / 26) * 26) : 0;
|
||||
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
|
||||
|
||||
134
src/telegram.h
134
src/telegram.h
@@ -83,36 +83,43 @@ class Telegram {
|
||||
std::string to_string() const;
|
||||
|
||||
// reads a bit value from a given telegram position
|
||||
void read_bitvalue(uint8_t & value, const uint8_t index, const uint8_t bit) const {
|
||||
uint8_t abs_index = (index - offset);
|
||||
if (abs_index >= message_length - 1) {
|
||||
return; // out of bounds
|
||||
bool read_bitvalue(uint8_t & value, const uint8_t index, const uint8_t bit) const {
|
||||
uint8_t abs_index = (index - this->offset);
|
||||
if (abs_index >= this->message_length) {
|
||||
return false; // out of bounds
|
||||
}
|
||||
|
||||
value = (uint8_t)(((message_data[abs_index]) >> (bit)) & 0x01);
|
||||
uint8_t val = value;
|
||||
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>
|
||||
// assuming negative numbers are stored as 2's-complement
|
||||
// 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
|
||||
// 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)
|
||||
void read_value(Value & value, const uint8_t index, uint8_t s = 0) const {
|
||||
uint8_t size = (!s) ? sizeof(Value) : s;
|
||||
int8_t abs_index = ((index - offset + size - 1) >= message_length - 1) ? -1 : (index - offset);
|
||||
if (abs_index < 0) {
|
||||
return; // out of bounds, we don't change the value
|
||||
// s is to override number of bytes read (e.g. use 3 to simulate a uint24_t)
|
||||
bool read_value(Value & value, const uint8_t index, uint8_t s = 0) const {
|
||||
uint8_t num_bytes = (!s) ? sizeof(Value) : s;
|
||||
// check for out of bounds, if so don't modify the value
|
||||
if ((index < this->offset) || ((index - this->offset + num_bytes - 1) >= this->message_length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value = 0;
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
value = (value << 8) + message_data[abs_index + i]; // shift
|
||||
auto val = value;
|
||||
value = 0;
|
||||
for (uint8_t i = 0; i < num_bytes; i++) {
|
||||
value = (value << 8) + this->message_data[index - this->offset + i]; // shift by byte
|
||||
}
|
||||
if (val != value) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
int8_t _getDataPosition(const uint8_t index, const uint8_t size) const;
|
||||
};
|
||||
@@ -182,13 +189,12 @@ class EMSbus {
|
||||
|
||||
private:
|
||||
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 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_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_state_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE)
|
||||
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 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 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)
|
||||
};
|
||||
|
||||
class RxService : public EMSbus {
|
||||
@@ -201,7 +207,7 @@ class RxService : public EMSbus {
|
||||
void loop();
|
||||
void add(uint8_t * data, uint8_t length);
|
||||
|
||||
uint16_t telegram_count() const {
|
||||
uint32_t telegram_count() const {
|
||||
return telegram_count_;
|
||||
}
|
||||
|
||||
@@ -209,7 +215,7 @@ class RxService : public EMSbus {
|
||||
telegram_count_++;
|
||||
}
|
||||
|
||||
uint16_t telegram_error_count() const {
|
||||
uint32_t telegram_error_count() const {
|
||||
return telegram_error_count_;
|
||||
}
|
||||
|
||||
@@ -235,44 +241,38 @@ class RxService : public EMSbus {
|
||||
|
||||
private:
|
||||
uint8_t rx_telegram_id_ = 0; // queue counter
|
||||
uint16_t telegram_count_ = 0; // # Rx received
|
||||
uint16_t telegram_error_count_ = 0; // # Rx CRC errors
|
||||
uint32_t telegram_count_ = 0; // # Rx received
|
||||
uint32_t telegram_error_count_ = 0; // # Rx CRC errors
|
||||
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 {
|
||||
public:
|
||||
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_SUCCESS = 1; // EMS return code for success
|
||||
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_SUCCESS = 1; // EMS return code for success
|
||||
|
||||
TxService() = default;
|
||||
~TxService() = default;
|
||||
|
||||
void start();
|
||||
void send();
|
||||
|
||||
void add(const uint8_t operation,
|
||||
const uint8_t dest,
|
||||
const uint16_t type_id,
|
||||
const uint8_t offset,
|
||||
uint8_t * message_data,
|
||||
const uint8_t message_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 send_poll();
|
||||
|
||||
void flush_tx_queue();
|
||||
|
||||
void retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length);
|
||||
void start();
|
||||
void send();
|
||||
void add(const uint8_t operation,
|
||||
const uint8_t dest,
|
||||
const uint16_t type_id,
|
||||
const uint8_t offset,
|
||||
uint8_t * message_data,
|
||||
const uint8_t message_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 send_poll();
|
||||
void flush_tx_queue();
|
||||
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;
|
||||
uint16_t post_send_query();
|
||||
|
||||
uint8_t retry_count() const {
|
||||
return retry_count_;
|
||||
@@ -282,13 +282,15 @@ class TxService : public EMSbus {
|
||||
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) {
|
||||
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_;
|
||||
}
|
||||
|
||||
@@ -300,7 +302,7 @@ class TxService : public EMSbus {
|
||||
telegram_read_count_++;
|
||||
}
|
||||
|
||||
uint16_t telegram_fail_count() const {
|
||||
uint32_t telegram_fail_count() const {
|
||||
return telegram_fail_count_;
|
||||
}
|
||||
|
||||
@@ -312,7 +314,7 @@ class TxService : public EMSbus {
|
||||
telegram_fail_count_++;
|
||||
}
|
||||
|
||||
uint16_t telegram_write_count() const {
|
||||
uint32_t telegram_write_count() const {
|
||||
return telegram_write_count_;
|
||||
}
|
||||
|
||||
@@ -324,10 +326,6 @@ class TxService : public EMSbus {
|
||||
telegram_write_count_++;
|
||||
}
|
||||
|
||||
void post_send_query();
|
||||
|
||||
void print_last_tx();
|
||||
|
||||
class QueuedTxTelegram {
|
||||
public:
|
||||
const uint16_t id_;
|
||||
@@ -355,9 +353,9 @@ class TxService : public EMSbus {
|
||||
private:
|
||||
std::list<QueuedTxTelegram> tx_telegrams_; // the Tx queue
|
||||
|
||||
uint16_t telegram_read_count_ = 0; // # Tx successful reads
|
||||
uint16_t telegram_write_count_ = 0; // # Tx successful writes
|
||||
uint16_t telegram_fail_count_ = 0; // # Tx unsuccessful transmits
|
||||
uint32_t telegram_read_count_ = 0; // # Tx successful reads
|
||||
uint32_t telegram_write_count_ = 0; // # Tx successful writes
|
||||
uint32_t telegram_fail_count_ = 0; // # Tx unsuccessful transmits
|
||||
|
||||
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
|
||||
|
||||
@@ -123,10 +123,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
uint8bitb = EMS_VALUE_UINT_NOTSET;
|
||||
telegram->read_bitvalue(uint8bitb, 0, 0); // value is 0x01 = 0000 0001
|
||||
shell.printfln("uint8 bit read: expecting 1, got:%d", uint8bitb);
|
||||
|
||||
shell.loop_all();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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?
|
||||
std::string version("1.2.3");
|
||||
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
|
||||
|
||||
// UBAuptime
|
||||
uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70});
|
||||
}
|
||||
|
||||
// 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)
|
||||
rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x59, 0x09, 0x0a});
|
||||
|
||||
shell.loop_all();
|
||||
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("system");
|
||||
// shell.invoke_command("show mqtt");
|
||||
|
||||
// shell.loop_all();
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// 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)
|
||||
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
|
||||
// HC1
|
||||
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});
|
||||
|
||||
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") {
|
||||
@@ -272,6 +289,39 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
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") {
|
||||
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});
|
||||
|
||||
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();
|
||||
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("pin");
|
||||
shell.invoke_command("pin 1 true");
|
||||
|
||||
shell.loop_all();
|
||||
}
|
||||
|
||||
if (command == "mqtt") {
|
||||
@@ -539,7 +587,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
// add a thermostat
|
||||
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,
|
||||
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
|
||||
@@ -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\":\"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\":\"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\":\"temp\",\"data\":22.56}");
|
||||
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::show_mqtt(shell); // show queue
|
||||
|
||||
shell.loop_all();
|
||||
}
|
||||
|
||||
if (command == "poll2") {
|
||||
@@ -656,7 +703,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
}
|
||||
|
||||
// finally dump to console
|
||||
shell.loop_all();
|
||||
EMSESP::loop();
|
||||
}
|
||||
|
||||
// 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);
|
||||
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'
|
||||
@@ -720,6 +768,7 @@ void Test::uart_telegram_withCRC(const char * rx_data) {
|
||||
}
|
||||
|
||||
EMSESP::incoming_telegram(data, count + 1);
|
||||
EMSESP::rxservice_.loop();
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
EMSESP::incoming_telegram(data, count + 2);
|
||||
EMSESP::rxservice_.loop();
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
|
||||
@@ -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
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK); // reset tx-brk
|
||||
if (emsTxBufIdx < emsTxBufLen) { // irq tx_mode is interrupted by <brk>
|
||||
emsTxBufIdx = emsTxBufLen + 1; // stop tx
|
||||
// drop_next_rx = true; // we have trash in buffer
|
||||
if (sending_) { // irq tx_mode is interrupted by <brk>, should never happen
|
||||
drop_next_rx = true; // we have trash in buffer
|
||||
}
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
|
||||
length = 0;
|
||||
while ((USS(EMSUART_UART) >> USRXC) & 0x0FF) { // read fifo into buffer
|
||||
uint8_t rx = USF(EMSUART_UART);
|
||||
if (length < EMS_MAXBUFFERSIZE) {
|
||||
uart_buffer[length++] = rx;
|
||||
if (length || rx) { // skip a leading zero
|
||||
uart_buffer[length++] = rx;
|
||||
}
|
||||
} else {
|
||||
drop_next_rx = true;
|
||||
}
|
||||
}
|
||||
if (!drop_next_rx) {
|
||||
if (uart_buffer[length - 1]) { // check if last byte is break
|
||||
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
|
||||
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
|
||||
void ICACHE_RAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() {
|
||||
if (!sending_) {
|
||||
return;
|
||||
}
|
||||
emsTxBufIdx++;
|
||||
if (emsTxBufIdx < emsTxBufLen) {
|
||||
USF(EMSUART_UART) = emsTxBuf[emsTxBufIdx];
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "2.0.0"
|
||||
#define EMSESP_APP_VERSION "2.0.1"
|
||||
|
||||
Reference in New Issue
Block a user