1.5.7b
268
CHANGELOG.md
@@ -1,32 +1,280 @@
|
|||||||
# EMS-ESP-Boiler Changelog
|
# EMS-ESP Changelog
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [1.5.6] 2019-03-09
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Setting the mode and setpoint temperature on a RC35
|
- test_mode option
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- upgraded MyESP library
|
||||||
|
- minor changes
|
||||||
|
|
||||||
|
|
||||||
|
## [1.5.5] 2019-03-07
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Support the latest ArduinoJson v6 and espressif8266 2.0.4 libraries (in PlatformIO do a `pio lib update` and `pio update`)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- MQTT keep alive to 2 minutes (60 seconds was just too short for slower networks)
|
||||||
|
- Improved MQTT startup time
|
||||||
|
- Setting wifi or mqtt settings are immediate, no need to restart the ESP
|
||||||
|
- Text changes in the help
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Show if MQTT is connected
|
||||||
|
- Show version of MyESP (the custom MQTT, Wifi, OTA, MDNS, Telnet library)
|
||||||
|
- EMS-OT OpenTherm connector
|
||||||
|
|
||||||
|
## [1.5.4] 2019-03-03
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- MQTT keep alive changed from 5 minutes to 1 minute
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Callback for OTA. This is used to disable EMS bus during a firmware OTA update, which caused problems with the latest ESP89266 core libraries
|
||||||
|
- Added rough estimate of WiFi signal strength to info page
|
||||||
|
- Added the build time & date to the info page (optional in platformio.ini)
|
||||||
|
|
||||||
|
## [1.5.3] 2019-02-22
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Support for latest esp8266 arduino core version [2.5.0](https://github.com/esp8266/Arduino/releases/tag/2.5.0) and platform espressif8266 version 2.0.0
|
||||||
|
- Added board type to the info screen
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Improved MQTT LWT (Last Will Testament). Uses payload called 'online' and 'offline'. See https://github.com/proddy/EMS-ESP/issues/57
|
||||||
|
- Added ESP32 support to MyESP library
|
||||||
|
- Added Bosch Easy thermostat, Buderus Logamax U122
|
||||||
|
- Support for changing boiler wwtemp via MQTT (merge #58 from egrekov). thanks!
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Custom MDNS support. Now handled much better in the new esp core under OTA
|
||||||
|
|
||||||
|
## [1.5.2] 2019-02-04
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change wifi settings using the `set wifi <ssid> <password>` command
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added incoming MQTT "TOPIC_BOILER_WWACTIVATED" to set the warm water on/off. Payload is 1 or 0. See [issue](https://github.com/proddy/EMS-ESP/issues/46#issuecomment-460375689).
|
||||||
|
- Added the list of all MQTT topics to the README file
|
||||||
|
|
||||||
|
## [1.5.1] 2019-02-03
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- issue with Serial monitoring conflicting with UART when both running
|
||||||
|
- Fixed typo with -D settings in the example platformio.ini
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `thermostat temp` now except floats (e.g. 20.5). Some thermostats may round up or down if they use 0.5 intervals.
|
||||||
|
|
||||||
|
## [1.5.0] 2019-02-03
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for RC10 thermostat
|
||||||
|
- New command `set serial`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improved Tx logic. Retries are more efficient and startup is faster and less error prone.
|
||||||
|
- "# Rx telegrams" and "# Tx telegrams" show number of successful Reads and Writes initiated by the user or automatically by the code. This makes it easy to see if the Tx is working.
|
||||||
|
- Some refactoring in preparation for the EMS+ support coming soon
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed the `poll` and `tx` commands
|
||||||
|
- `DEBUG_SUPPORT`. Now controlled with the 'set serial' command
|
||||||
|
- removed MQTT and WIFI settings from my_config.h. These have to be set either within the application (using set) or hardcoded in platformio.ini You can now check in `my_config.h` without everyone seeing your passwords!
|
||||||
|
- TxCapable removed from `info`
|
||||||
|
|
||||||
|
## [1.4.1] 2019-01-29
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The led pin, dallas pin and both thermostat and boiler type IDs can be set with the application, and stored.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- some minor improvements to autodetect
|
||||||
|
|
||||||
|
## [1.4.0] 2019-01-27
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- last will MQTT topic prefixed with a header like the rest of the topics
|
||||||
|
- All double and float numbers rendered to 2 decimal places (precision = 2)
|
||||||
|
- Default logging set to None when starting a telnet session
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for external Dallas sensors (DS1822, DS18S20, DS18B20, DS1825). See readme
|
||||||
|
- Added UBAParametersMessage type to fetch boiler modulation min & max values
|
||||||
|
- Report shows system load average
|
||||||
|
|
||||||
|
## [1.3.2] 2019-01-23
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Handle thermostats that don't have builtin temperature sensors when showing current temperature (https://github.com/proddy/EMS-ESP/issues/18#issuecomment-451012963)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improved way to identify if the EMS bus is connected
|
||||||
|
- Improved 'types' command to show more details
|
||||||
|
- Improved auto detect of thermostat types
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Some more devices like the Nefit Topline & RC310 thermostat recognition
|
||||||
|
- Added a check to see Tx is possible. See 'Tx Capable' under the 'info' screen
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed `MY_BOILER_MODELID` from `my_config.h`. It's always hardcoded.
|
||||||
|
|
||||||
|
## [1.3.1] 2019-01-12
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- telnet commands with set are no longer forced to lower case
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Custom settings (e.g set led) moved outside MyESP
|
||||||
|
- Moved all MQTT to my_config.h making it independent from Home Assistant
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- MQTT keep alive, last will testament and other settings all configurable in my_config.h
|
||||||
|
- RC35: MQTT day/night/auto mode; sets setpoint temperature in type 0x3D depends on current night/day Mode (@SpaceTeddy) [#33](https://github.com/proddy/EMS-ESP/pull/33)
|
||||||
|
|
||||||
|
## [1.3.0] 2019-01-09
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Renamed project from EMS-ESP-Boiler to EMS-ESP since it's kinda EMS generic now
|
||||||
|
- Support for RC20F and RFM20 (https://github.com/proddy/EMS-ESP/issues/18)
|
||||||
|
- Moved all EMS device information into a separate file `ems_devices.h` so no longer need to touch `ems.h`
|
||||||
|
- Telnet commands can be strings now and output is suspended when typing
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed SHOWER_TEST
|
||||||
|
- Removed WIFI and MQTT credentials from the platformio.ini file
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
## [1.2.4] 2019-01-04
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Scanning known EMS Devices now ignores duplicates (https://github.com/proddy/EMS-ESP/pull/30)
|
||||||
|
- ServiceCode stored as a two byte char
|
||||||
|
- Support for RC20F and RFM20 (https://github.com/proddy/EMS-ESP/issues/18)
|
||||||
|
|
||||||
|
## [1.2.3] 2019-01-03
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Can now hardcode Boiler and Thermostat types in my_config.h to bypass auto-detection
|
||||||
|
- Fixed MQTT subscribing to Heating and Hot Water active topics
|
||||||
|
- Fixed for listening to incoming MQTT topics (https://github.com/proddy/EMS-ESP/issues/27)
|
||||||
|
- Fixed handling of current temperature on an RC35-type thermostat that doesn't have a sensor (https://github.com/proddy/EMS-ESP/issues/18)
|
||||||
|
|
||||||
|
## [1.2.2] 2019-01-02
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Issues in 1.2.1 (see https://github.com/proddy/EMS-ESP/issues/25)
|
||||||
|
- Logic for determining if there is activity on the EMS bus and using the onboard LEDs properly
|
||||||
|
|
||||||
|
## [1.2.1] 2019-01-02
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- 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)
|
||||||
|
|
||||||
|
## [1.2.0] 2019-01-01
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Incorrect indenting in `climate.yaml` (thanks @mrfixit1)
|
||||||
|
- Improved support for slower WiFi connections
|
||||||
|
- Fixed issue with OTA not always giving back a completion response to platformio
|
||||||
|
- Fixed issue with repeating reads after a raw mode send
|
||||||
|
- Fixed handling of long integers (thanks @SpaceTeddy)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- added 'dout' flashmode to platformio.ini so OTA works now when uploading to a Wemos D1 Pro's or any other board with larger flash's
|
||||||
|
- added un tested supporting RC35 type of thermostats
|
||||||
|
- Try and discover and set Boiler and Thermostat types automatically
|
||||||
|
- Fetch UBATotalUptimeMessage from Boiler to get total working minutes
|
||||||
|
- Added check to see if bus is connected. Shown in stats page
|
||||||
|
- If no Wifi connection can be made, start up as a WiFi Access Point (AP)
|
||||||
|
- Report out service codes and water-flow [pull-request](https://github.com/proddy/EMS-ESP/pull/20/files). Thanks @Bonusbartus
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Build option is called `DEBUG_SUPPORT` (was `USE_SERIAL`)
|
||||||
|
- Replaced old **ESPHelper** with my own **MyESP** library to handle Wifi, MQTT, MDNS and Telnet handlers. Supports asynchronous TCP and has smaller memory footprint. And moved to libs directory.
|
||||||
|
- Simplified LED error checking. If enabled (by default), solid means connected and flashing means error. Uses either an external pull-up or the onboard ESP8266 LED.
|
||||||
|
- Improved Telnet debugging which uses TelnetSpy to keep a buffer of previous output
|
||||||
|
- Optimized memory usage & heap conflicts, removing nasty things like strcpy, sprintf where possible
|
||||||
|
- Improved checking for tap water on/off (thanks @Bonusbartus)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Time and TimeLib's. Not used in code.
|
||||||
|
- Removed build option `MQTT_MAX_PACKAGE_SIZE` as not using the PubSubClient library any more
|
||||||
|
- Removed all of Espurna's pre-built firmwares and instructions to build. Keeping it simple.
|
||||||
|
|
||||||
|
## [1.1.1] 2018-12-23
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Espurna build notes and ready made firmware
|
||||||
|
|
||||||
## [1.1.0] 2018-12-22
|
## [1.1.0] 2018-12-22
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed handling of negative flaoting point values (like outdoor temp)
|
- Fixed handling of negative floating point values (like outdoor temp)
|
||||||
- Fixed handling of auto & manual mode on an RC30
|
- Fixed handling of auto & manual mode on an RC30
|
||||||
- [Fixed condition where all telegram types were processed, instead of only broadcasts or our own reads](https://github.com/proddy/EMS-ESP-Boiler/issues/15)
|
- [Fixed condition where all telegram types were processed, instead of only broadcasts or our own reads](https://github.com/proddy/EMS-ESP/issues/15)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Created this CHANGELOG.md file!
|
- Created this CHANGELOG.md file!
|
||||||
- [Added support for the Nefit Easy thermostat, reading of temperature values only](https://github.com/proddy/EMS-ESP-Boiler/issues/9) - note *read only* (big thanks @**kroon040** for lending me an Easy device)
|
- [Added support for the Nefit Easy thermostat, reading of temperature values only](https://github.com/proddy/EMS-ESP/issues/9) - note *read only* (big thanks @**kroon040** for lending me an Easy device)
|
||||||
- [Added support for RC35/Moduline 400](https://github.com/proddy/EMS-ESP-Boiler/issues/14) - *read only*
|
- [Added support for RC35/Moduline 400](https://github.com/proddy/EMS-ESP/issues/14) - *read only*
|
||||||
- [New raw logging mode for logging](https://github.com/proddy/EMS-ESP-Boiler/issues/11)
|
- [New raw logging mode for logging](https://github.com/proddy/EMS-ESP/issues/11)
|
||||||
- [New 'r'command to send raw data to EMS](https://github.com/proddy/EMS-ESP-Boiler/issues/11)
|
- [New 'r'command to send raw data to EMS](https://github.com/proddy/EMS-ESP/issues/11)
|
||||||
- [Added MQTT messages for hot water on and heating on](https://github.com/proddy/EMS-ESP-Boiler/issues/10)
|
- [Added MQTT messages for hot water on and heating on](https://github.com/proddy/EMS-ESP/issues/10)
|
||||||
- Implemented FIFO circular buffer queue for up to 20 Tx messages (Q command to show queue)
|
- Implemented FIFO circular buffer queue for up to 20 Tx messages (Q command to show queue)
|
||||||
- Toggle Tx transmission via telnet (use 'X' command)
|
- Toggle Tx transmission via telnet (use 'X' command)
|
||||||
- Show thermostat type in help stats (use 's' command)
|
- Show thermostat type in help stats (use 's' command)
|
||||||
|
|||||||
13
LICENSE.md
@@ -1,13 +0,0 @@
|
|||||||
#### Copyright 2018 [Paul Derbsyhire](mailto:dev@derbyshire.nl). All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met :
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and / or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
The views and conclusions contained in the software and documentation are those of the
|
|
||||||
authors and should not be interpreted as representing official policies, either expressed
|
|
||||||
or implied, of [Paul Derbyshire](mailto:dev@derbyshire.nl).
|
|
||||||
337
README.md
@@ -1,42 +1,38 @@
|
|||||||
# EMS-ESP-Boiler
|
# EMS-ESP
|
||||||
|
|
||||||
EMS-ESP-Boiler is a project to build a controller circuit running with an ESP8266 to communicate with EMS (Energy Management System) based Boilers and Thermostats from the Bosch range and compatibles such as Buderus, Nefit, Junkers etc.
|
EMS-ESP is a project to build an electronic controller circuit using an Espressif ESP8266 microcontroller to communicate with EMS (Energy Management System) based Boilers and Thermostats from the Bosch range and compatibles such as Buderus, Nefit, Junkers etc.
|
||||||
|
|
||||||
There are 3 parts to this project, first the design of the circuit, second the code for the ESP8266 microcontroller firmware and lastly an example configuration for Home Assistant to monitor the data and issue direct commands via MQTT.
|
There are 3 parts to this project, first the design of the circuit, secondly the code for the ESP8266 microcontroller firmware with telnet and MQTT support, and lastly an example configuration for Home Assistant to monitor the data and issue direct commands via a MQTT broker.
|
||||||
|
|
||||||
[](CHANGELOG.md)
|
[](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.org/xoseperez/espurna/tree/dev/)
|
[](CHANGELOG.md)
|
||||||
[](LICENSE)
|
|
||||||
|
|
||||||
- [EMS-ESP-Boiler](#ems-esp-boiler)
|
- [EMS-ESP](#ems-esp)
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [Supported Boilers Types](#supported-boilers-types)
|
- [Supported EMS Devices](#supported-ems-devices)
|
||||||
- [Supported ESP8266 devices](#supported-esp8266-devices)
|
- [Supported ESP8266 devices](#supported-esp8266-devices)
|
||||||
- [Getting Started](#getting-started)
|
- [Getting Started](#getting-started)
|
||||||
- [Monitoring The Output](#monitoring-the-output)
|
- [Monitoring The Output](#monitoring-the-output)
|
||||||
- [Building The Circuit](#building-the-circuit)
|
- [Building The Circuit](#building-the-circuit)
|
||||||
- [Powering The EMS Circuit](#powering-the-ems-circuit)
|
- [Powering The EMS Circuit](#powering-the-ems-circuit)
|
||||||
|
- [Adding external temperature sensors](#adding-external-temperature-sensors)
|
||||||
- [How The EMS Bus Works](#how-the-ems-bus-works)
|
- [How The EMS Bus Works](#how-the-ems-bus-works)
|
||||||
- [EMS IDs](#ems-ids)
|
- [EMS IDs](#ems-ids)
|
||||||
- [EMS Polling](#ems-polling)
|
- [EMS Polling](#ems-polling)
|
||||||
- [EMS Broadcasting](#ems-broadcasting)
|
- [EMS Broadcasting](#ems-broadcasting)
|
||||||
- [EMS Reading and Writing](#ems-reading-and-writing)
|
- [EMS Reading and Writing](#ems-reading-and-writing)
|
||||||
- [The ESP8266 Source Code](#the-esp8266-source-code)
|
- [The ESP8266 Source Code](#the-esp8266-source-code)
|
||||||
- [Supported EMS Types](#supported-ems-types)
|
- [Special EMS Types](#special-ems-types)
|
||||||
- [Supported Thermostats](#supported-thermostats)
|
- [Which thermostats are supported?](#which-thermostats-are-supported)
|
||||||
- [RC20 (Moduline 300)](#rc20-moduline-300)
|
|
||||||
- [RC30 (Moduline 400)](#rc30-moduline-400)
|
|
||||||
- [RC35](#rc35)
|
|
||||||
- [TC100/TC200 (Nefit Easy)](#tc100tc200-nefit-easy)
|
|
||||||
- [Customizing The Code](#customizing-the-code)
|
- [Customizing The Code](#customizing-the-code)
|
||||||
- [Using MQTT](#using-mqtt)
|
- [Using MQTT](#using-mqtt)
|
||||||
- [The Basic Shower Logic](#the-basic-shower-logic)
|
- [The Basic Shower Logic](#the-basic-shower-logic)
|
||||||
- [Home Assistant Configuration](#home-assistant-configuration)
|
- [Home Assistant Configuration](#home-assistant-configuration)
|
||||||
- [Building The Firmware](#building-the-firmware)
|
- [Building The Firmware](#building-the-firmware)
|
||||||
- [Using PlatformIO Standalone](#using-platformio-standalone)
|
- [Using PlatformIO Standalone](#using-platformio-standalone)
|
||||||
- [Using ESPurna](#using-espurna)
|
|
||||||
- [Using Pre-built Firmware](#using-pre-built-firmware)
|
|
||||||
- [Building Using Arduino IDE](#building-using-arduino-ide)
|
- [Building Using Arduino IDE](#building-using-arduino-ide)
|
||||||
|
- [Using the Pre-built Firmware](#using-the-pre-built-firmware)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
- [Known Issues](#known-issues)
|
- [Known Issues](#known-issues)
|
||||||
- [Wish List](#wish-list)
|
- [Wish List](#wish-list)
|
||||||
- [Your Comments and Feedback](#your-comments-and-feedback)
|
- [Your Comments and Feedback](#your-comments-and-feedback)
|
||||||
@@ -44,82 +40,84 @@ There are 3 parts to this project, first the design of the circuit, second the c
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
My original intention for this home project with to build my own smart thermostat for my Nefit Trendline boiler and then have it controlled automatically via [Home Assistant](https://www.home-assistant.io/) on my mobile phone. I had a few ESP32s and ESP8266s lying around from previous IoT projects and building a specific circuit to decode the EMS messages was a nice challenge into designing complete end-to-end complex electronic circuits. I then began adding new features such as timing how long the shower would be running for and subsequently triggering an alarm (actually a shot of cold water) after a certain period.
|
The original intention for this home project was to build a custom smart thermostat that interfaces with my Nefit Trendline HRC30 boiler and have it controlled via a mobile app using MQTT. I had a few cheap ESP32s and ESP8266s microcontrollers lying around from previous IoT projects and learning how to build a circuit to decode the EMS bus messages seemed like a nice challenge.
|
||||||
|
|
||||||
Acknowledgments and kudos to the following people and their open-sourced projects that have helped me get this far:
|
Acknowledgments and kudos to the following people who have open-sourced their projects:
|
||||||
|
|
||||||
**susisstrolch** - Probably the first working version of the EMS bridge circuit I found designed for the ESP8266. I borrowed Juergen's [schematic](https://github.com/susisstrolch/EMS-ESP12) and parts of his code logic.
|
**susisstrolch** - One of the first working versions of the EMS bridge circuit I found designed for specifically for the ESP8266. I borrowed Juergen's [schematic](https://github.com/susisstrolch/EMS-ESP12) and parts of his code ideas for reading telegrams.
|
||||||
|
|
||||||
**bbqkees** - Kees built a [circuit](https://github.com/bbqkees/Nefit-Buderus-EMS-bus-Arduino-Domoticz) and some sample Arduino code to read from the EMS and push messages to Domoticz. His SMD board is also now available for purchase.
|
**bbqkees** - Kees built a working [circuit](https://shop.hotgoodies.nl/ems/) and his SMD board is available for purchase on his website.
|
||||||
|
|
||||||
**EMS Wiki** - A comprehensive [reference](https://emswiki.thefischer.net/doku.php?id=wiki:ems:telegramme) for decoding the EMS telegrams, which I found not always to be 100% accurate. It's in German so use Google Translate if you need help.
|
**EMS Wiki** - A comprehensive [reference](https://emswiki.thefischer.net/doku.php?id=wiki:ems:telegramme) (in German) for the EMS bus which is a little outdated, not always 100% accurate and sadly no longer maintained.
|
||||||
|
|
||||||
## Supported Boilers Types
|
## Supported EMS Devices
|
||||||
|
|
||||||
Most Bosch branded boilers that support the Logamatic EMS (and EMS+) bus protocols work with this design. Which are Nefit, Buderus, Worcester and Junkers and copyrighted. Please make sure you read the **Disclaimer** carefully before sending ambigious messages to your EMS bus as you cause device damage.
|
Most Bosch branded boilers that support the Logamatic EMS bus protocols work with this design. This includes Nefit, Buderus, Worcester and Junkers (all copyrighted). Please make sure you read the **Disclaimer** carefully before sending ambiguous messages to your EMS bus as you could cause serious damage to your equipment.
|
||||||
|
|
||||||
|
Note support for the later EMS Plus (EMS+ or EMS2) standard hasn't been added yet to the library. If you'd like to help please reach out.
|
||||||
|
|
||||||
## Supported ESP8266 devices
|
## Supported ESP8266 devices
|
||||||
|
|
||||||
I've tested the code and circuit with a few ESP8266 development boards such as the Wemos D1 Mini, Wemos D1 Mini Pro, Nodemcu0.9 and Nodemcu2 boards. It will also work on bare ESP8266 chips such as the E-12s but do make sure you disabled the LED support and wire the UART correctly as the code doesn't use the normal Rx and Tx pins. This is explained below.
|
The code and circuit has been tested with a few ESP8266 development boards such as the Wemos D1 Mini, Wemos D1 Mini Pro, Nodemcu0.9 and Nodemcu2 dev boards. It will also work on bare ESP8266 chips such as the ESP-12E but do make sure you disabled the LED support and wire the UART correctly as the code doesn't use the normal Rx and Tx pins.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
1. Either build the circuit below or purchase a ready built board from bbqkees via his [GitHub](https://github.com/bbqkees/Nefit-Buderus-EMS-bus-Arduino-Domoticz) page or the [Domoticz forum](http://www.domoticz.com/forum/viewtopic.php?f=22&t=22079&start=20).
|
1. Either build the circuit described below or purchase a ready built board from bbqkees.
|
||||||
2. Get an ESP8266 dev board and connect the 2 EMS output lines from the boiler to the circuit and the Rx and Tx out to ESP pins D7 and D8 respectively. The EMS connection can either be the 12-15V AC direct from the thermostat bus line or from the 3.5" Service Jack at the front.
|
2. Grab any ESP8266 dev board. The latest bbqkees boards have a Wemos D1 pre-mounted with a copy of this firmware.
|
||||||
3. Optionally connect the three LEDs to show Rx and Tx traffic and Error codes to pins D1, D2, D3 respectively. I use 220 Ohm pull-down resistors. These pins are configurable in ``boiler.ino``. This is further explained in the **code** section below.
|
3. Optionally add external Dallas temperature sensors and an external LED. The default pins for these are D1 and D5 respectively.
|
||||||
4. Build and upload the firmware to the ESP8266 device. I used Platformio with Visual Studio. Do make sure you set the MQTT and WiFi credentials correctly and if you're not using MQTT leave the MQTT_IP blank. The firmware supports OTA too with the default hostname as 'boiler' (or 'boiler.' depending on your OS and how the mdns resolves hostnames).
|
4. Decide whether to compile and upload the code yourself using PlatformIO or just upload the pre-baked firmware using the esptool (read these [instructions](#using-the-pre-built-firmware)). If you want to build yourself now is the time to customize your settings in `my_custom.h`. Upload the firmware.
|
||||||
5. Power the ESP either via USB or direct into the 5v vin pin from an external power 5V volts supply with min 400mA.
|
5. Connect a USB 5v power supply to the ESP8266 board, either via laptop/PC or external power supply.
|
||||||
6. Attach the 3v3 out on the ESP8266 to the DC power line on the EMS circuit as indicated in the schematics.
|
7. When the ESP8266 starts up for the first time the onboard LED will be flashing. This is because the EMS bus is not yet connected.
|
||||||
7. The WiFi connects via DHCP by default. Find the IP by from your router and then telnet (port 23) to it. Tip: to enable Telnet on Windows run `dism /online /Enable-Feature /FeatureName:TelnetClient` or install something like [putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html). If everything is working you should see the messages appear in the window as shown in the next section. However if you're unable to locate the IP of the ESP then probably the WiFi failed to instantiate. In this case add -DUSE_SERIAL to the build options, connect at USB, build, upload and then use a terminal to connect to the serial port to see the debug messages. A word of warning, do not use both a USB and power from the EMS at the same time.
|
8. If you haven't hardcoded the WiFi credentials in step 4, the ESP8266 will boot up in a WiFi Access Point (AP) mode with the ssid name `ems-esp`. Now you can either use a laptop and connect to this AP using Telnet to `192.168.1.4` or if its powered from a computers USB use a Serial monitor tool to the ESP's COM port. Tip: to enable Telnet on Windows 10 run `dism /online /Enable-Feature /FeatureName:TelnetClient` or install something like [putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html).
|
||||||
|
9. Next is to change some of the settings. Type `set` to list the current stored settings. Use `set wifi` to add your wifi credentials and if you're using MQTT set the host, username and password. There is no need to reboot the device.
|
||||||
|
10. The `led_gpio` will default to the onboard LED (which is probably blinking now). Ignore `thermostat_type` and `boiler_type` as these will be auto-detected hopefully later on.
|
||||||
|
11. **Important**: If `serial` is set to `on` set it to `off` using `set serial off`. The EMS bus is disabled when the serial is on. This mode is only used for setting up a new board or debugging startup issues.
|
||||||
|
12. Hook up the ESP to the EMS board as follows:
|
||||||
|
|
||||||
|
| EMS board | ESP8266 dev board |
|
||||||
|
| ----------|------------------ |
|
||||||
|
| Ground/G/J2| GND/G |
|
||||||
|
| Rx/J2 | D7 |
|
||||||
|
| Tx/J2 | D8 |
|
||||||
|
| VC/J2 | 3v3 or 5v |
|
||||||
|
13. Connect the EMS lines to the ESP. This can be done via the two EMS wires or via the 3.5" service jack if you have an bbqkees board.
|
||||||
|
14. Reboot the ESP, either by the reset switch or pulling the power.
|
||||||
|
15. The ESP will first perform an autodetect to try and discover the EMS devices attached. If your boiler and thermostat are recognized it will set these types and store them for ever and ever. You can trace the output by telnet'ing to the board `telnet ems-esp.local`. Also type `info` to check what happened.
|
||||||
|
16. If your boiler/thermostat is not discovered create a GitHub issue stating the type and product ID. These will be added to the file `ems_devices.h` in a future release.
|
||||||
|
17. If all is well and there is traffic on the EMS bus the onboard LED will stop blinking and be permanently on. If this is annoying you can disable with `set led off`. To see the EMS messages type `set log v` for verbose logging.
|
||||||
|
18. And all is not well, check the wiring, make sure serial is off and look at the telnet session for errors. If in doubt, wipe the ESP with `pio run -t erase` and start again with step #3
|
||||||
|
|
||||||
## Monitoring The Output
|
## Monitoring The Output
|
||||||
|
|
||||||
Use the telnet client to inform you of all activity and errors real-time. This is an example of the telnet output:
|
Use the telnet client to inform you of all activity and errors real-time. This is an example of the telnet output:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
If you type 'l 4' and Enter, it will toggle verbose logging showing you more detailed messages. I use ANSI colors with white text for info messages, green for well formatted telegram packages (which have validated CRC checks), red for corrupt packages and yellow for send responses.
|
Type 'log v' and Enter and you'll be seeing verbose logging messages. ANSI colors with white text for info messages, green are for broadcast telegrams, yellow are the ones sent to us and red are for unknown data or telegrans which have failed the CRC check.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
To see the current values of the Boiler and its parameters type 's' and hit Enter. Watch out for unsuccessful telegram packets in the #CrcErrors line.
|
To see the current stats and collected values type 'info'. Watch out for unsuccessful telegram packets in the #CrcErrors line.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Commands can be issued directly to the EMS bus typing in a letter followed by an optional parameter and pressing Enter. Supported commands are:
|
**Disclaimer: be careful when sending values to the boiler. If in doubt you can always reset the boiler to its original factory settings by following the instructions in the user guide. For example on my Nefit Trendline that is done by holding down the Home and Menu buttons simultaneously for a few seconds, selecting factory settings from the scroll menu followed by pressing the Reset button.**
|
||||||
|
|
||||||
- **b** to send a read command to the boiler. The 2nd parameter is the type. For example 'b 33' will request type UBAParameterWW and bring back the Warm Water temperatures from the Boiler.
|
|
||||||
- **t** is similar, but to send a read command to the thermostat.
|
|
||||||
- **T** set the thermostat temperature to the given celsius value
|
|
||||||
- **w** to adjust the temperature of the warm water from the boiler
|
|
||||||
- **a** to turn the warm tap water on and off
|
|
||||||
- **h** to list all the recognized EMS types
|
|
||||||
- **P** to toggle the Polling response on/off (note it's not necessary to have Polling enabled to work)
|
|
||||||
- **m** to set the thermostat mode to manual or auto
|
|
||||||
- **S** to toggle the Shower Timer functionality on/off
|
|
||||||
- **A** to toggle the Shower Timer Alert functionality on/off
|
|
||||||
|
|
||||||
**Disclaimer: be careful when sending values to the boiler. If in doubt you can always reset the boiler to its original factory settings by following the instructions in the user guide. On my **Nefit Trendline HRC30** that is done by holding down the Home and Menu buttons simultaneously for a few seconds, selecting factory settings from the scroll menu and lastly pressing the Reset button.**
|
|
||||||
|
|
||||||
## Building The Circuit
|
## Building The Circuit
|
||||||
|
|
||||||
The EMS circuit is really all credit to the hard work many people have done before me, noticeably *susisstrolch* with his ESP8266 [version](https://github.com/susisstrolch/EMS-ESP8266_12-PCB/tree/newmaster/Schematics/EMS-ESP8266-12).
|
Included is a prototype boards you can build yourself on a breadboard.
|
||||||
|
|
||||||
I've included a prototype boards you can build yourself on a breadboard. One part for only reading values from the Boiler and an extension with the write logic so you can also send commands.
|
|
||||||
|
|
||||||
We need the Rx/Tx of the ESP8266 for flashing, so the code in ``emsuart.cpp`` switches the UART pins to use RX1 and TX1 (GPIO13/D7 and GPIO15/D8 respectively). This also prevents any bogus stack data being sent to EMS bus when the ESP8266 decides to crash like after a Watch Dog Reset.
|
|
||||||
|
|
||||||
The breadboard layout was done using [DIY Layout Creator](https://github.com/bancika/diy-layout-creator) and sources files are included in this repo.
|
The breadboard layout was done using [DIY Layout Creator](https://github.com/bancika/diy-layout-creator) and sources files are included in this repo.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The schematic used (as designed by [susisstrolch](https://github.com/susisstrolch/EMS-ESP8266_12-PCB)):
|
The schematic used:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*Optionally I've also added 2 0.5A/72V polyfuses between the EMS and the two inductors L1 and L2 for extra protection.*
|
*Optionally I've also added 2 0.5A/72V polyfuses between the EMS and the two inductors L1 and L2 for extra protection.*
|
||||||
|
|
||||||
And lastly if you don't fancy building the circuit, [bbqkees](http://www.domoticz.com/forum/memberlist.php?mode=viewprofile&u=1736) can sell you one complete with SMD components which looks like the photo below when connected to a Wemos D1 Mini:
|
And here's a version using an early prototype board from **bbqkees**:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -134,33 +132,35 @@ The EMS circuit will work with both 3.3V and 5V. It's easiest though to power di
|
|||||||
|
|
||||||
| With Power Circuit |
|
| With Power Circuit |
|
||||||
| ------------------------------------------ |
|
| ------------------------------------------ |
|
||||||
|  |
|
|  |
|
||||||
|
|
||||||
|
## Adding external temperature sensors
|
||||||
|
|
||||||
|
The code supports auto-detection of Dallas type temperature sensors. The default gpio pin used on the ESP8266 is D5 but this can be configured in the setting menu (`set dallas_gpio`). The dallas chips DS1822, DS18S20, DS18B20, DS1825 are supported including their parasite varieties.
|
||||||
|
|
||||||
## How The EMS Bus Works
|
## How The EMS Bus Works
|
||||||
|
|
||||||
Packages are sent to the EMS "bus" from the Boiler and any other compatible connected devices via serial TTL transmission. The protocol is 9600 baud, 8N1 (8 bytes, no parity, 1 stop bit). Each package is terminated with a break signal `<BRK>`, a 11-bit long low signal of zeros.
|
Packages are streamed to the EMS "bus" from any other compatible connected device via serial TTL transmission using protocol 9600 baud, 8N1 (8 bytes, no parity, 1 stop bit). Each package is terminated with a break signal `<BRK>`, a 11-bit long low signal of zeros.
|
||||||
|
|
||||||
A package can be a single byte (see Polling below) or a string of 6 or more bytes making up an actual data telegram. A telegram is always in the format:
|
A package can be a single byte (see Polling below) or a string of 6 or more bytes making up an actual data telegram. A telegram is always in the format:
|
||||||
|
|
||||||
``[src] [dest] [type] [offset] [data] [crc] <BRK>``
|
``[src] [dest] [type] [offset] [data] [crc] <BRK>``
|
||||||
|
|
||||||
I reference the first 4 bytes as the *header* in this document.
|
The first 4 bytes is referenced as the *header* in this document.
|
||||||
|
|
||||||
### EMS IDs
|
### EMS IDs
|
||||||
|
|
||||||
Each device has a unique ID.
|
Each device has a unique ID.
|
||||||
|
|
||||||
The Boiler has an ID of 0x08 (type MC10) and also referred to as the Bus Master or UBA.
|
In this example a UBA boiler has an ID of 0x08 (such as a MC10) and also referred to as the Bus Master.
|
||||||
|
|
||||||
My thermostat, which is a* Moduline 300* uses the RC30 protocol and has an ID 0x17. If you're using a RC35 type thermostat such as the newer Moduline 300s or 400s use 0x10 and make adjustments in the code as appropriate. bbqkees did a nice write-up on his github page [here](https://github.com/bbqkees/Nefit-Buderus-EMS-bus-Arduino-Domoticz/blob/master/README.md).
|
The circuit acts as a service key and thus uses an ID 0x0B. This ID is reserved for special devices intended for service engineers.
|
||||||
|
|
||||||
Our circuit acts as a service key and thus uses an ID 0x0B. This ID is reserved for special devices intended for installation engineers for maintenance work.
|
|
||||||
|
|
||||||
### EMS Polling
|
### EMS Polling
|
||||||
|
|
||||||
The bus master (boiler) sends out a poll request every second by sending out a sequential list of all possible IDs as a single byte followed by the break signal. The ID always has its high 8th bit (MSB) set so in the code we're looking for 1 byte messages matching the format `[dest|0x80] <BRK>`.
|
The bus master (boiler) sends out a poll request every second by sending out a sequential list of all possible IDs as a single byte followed by the break signal. The ID always has its high 8th bit (MSB) set so in the code we're looking for 1 byte messages matching the format `[dest|0x80] <BRK>`.
|
||||||
|
|
||||||
Any connected device can respond to a Polling call with an acknowledging by sending back a single byte with its own ID. In our case we would listen for a `[0x8B] <BRK>` (meaning us) and then send back `[0x0B] <BRK>` to say we're alive and ready. Although I found this is not needed for normal operation so it's disabled as default in the code.
|
Any connected device can respond to a Polling request with an acknowledgement by sending back a single byte with its own ID. In our case we would listen for a `[0x8B] <BRK>` (meaning us) and then send back `[0x0B] <BRK>` to say we're alive and ready.
|
||||||
|
|
||||||
Polling is also the trigger to start transmitting any packages queued for sending. It must be done within 200ms or the bus master will time out.
|
Polling is also the trigger to start transmitting any packages queued for sending. It must be done within 200ms or the bus master will time out.
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ Polling is also the trigger to start transmitting any packages queued for sendin
|
|||||||
|
|
||||||
When a device is broadcasting to everyone there is no specific destination needed. `[dest]` is always 0x00.
|
When a device is broadcasting to everyone there is no specific destination needed. `[dest]` is always 0x00.
|
||||||
|
|
||||||
The tables below shows which types are broadcasted regularly by the boiler (ID 0x08) and thermostat (ID 0x17). The **data length** is excluding the 4 byte header and CRC and the **Name** references those in the [ems wiki](https://emswiki.thefischer.net/doku.php?id=wiki:ems:telegramme).
|
The tables below shows which types are broadcasted regularly by the boiler (in this case ID 0x08) and thermostat (ID 0x17). The **data length** is excluding the 4 byte header and CRC and the **Name** references those in the German [ems wiki](https://emswiki.thefischer.net/doku.php?id=wiki:ems:telegramme).
|
||||||
|
|
||||||
| Source (ID) | Type ID | Name | Description | Data length | Frequency |
|
| Source (ID) | Type ID | Name | Description | Data length | Frequency |
|
||||||
| ------------- | ------- | ------------------- | -------------------------------------- | ----------- | ---------- |
|
| ------------- | ------- | ------------------- | -------------------------------------- | ----------- | ---------- |
|
||||||
@@ -189,106 +189,86 @@ Refer to the code in `ems.cpp` for further explanation on how to parse these mes
|
|||||||
|
|
||||||
### EMS Reading and Writing
|
### EMS Reading and Writing
|
||||||
|
|
||||||
Telegram packets can only be sent after the Boiler sends a poll to the sending device. The response can be a read command to request data or a write command to send data. At the end of the transmission a poll response is sent from the client (`<ID> <BRK>`) to say we're all done and free up the bus for other clients.
|
Telegrams can only be sent after the Master (boiler) sends a poll to the receiving device. The response can be a read command to request data or a write command to send data. At the end of the transmission a poll response is sent from the client (`<ID> <BRK>`) to say we're all done and free up the bus for other clients.
|
||||||
|
|
||||||
When doing a request to read data the `[src]` is our device `(0x0B)` and the `[dest]` must have has it's MSB (8th bit) set. Say we were requesting data from the thermostat we would use `[dest] = 0x97` since RC30 has an ID of 0x17.
|
When executing a request to read data the `[src]` is our device `(0x0B)` and the `[dest]` must have has it's MSB (8th bit) set. Say we were requesting data from the thermostat we would use `[dest] = 0x97` since RC20 has an ID of 0x17.
|
||||||
|
|
||||||
Following a write request, the `[dest]` doesn't have the 8th bit set and after this write request the destination device will send either a single byte 0x01 for success or 0x04 for failure.
|
Following a write request, the `[dest]` doesn't have the 8th bit set and after this write request the destination device will send either a single byte 0x01 for success or 0x04 for failure.
|
||||||
|
|
||||||
Every telegram sent is echo'd back to Rx.
|
Every telegram sent is echo'd back to Rx, along the same Bus used for all Rx/Tx transmissions.
|
||||||
|
|
||||||
## The ESP8266 Source Code
|
## The ESP8266 Source Code
|
||||||
|
|
||||||
*Disclaimer*: This code here is really for reference only, I don't expect anyone to use "as is" since it's highly tailored to my environment and my needs. Most of the code however is self explanatory with comments here and there in the code.
|
`emsuart.cpp` handles the low level UART read and write logic to the bus. You shouldn't need to touch this. All receive commands from the EMS bus are handled asynchronously using a circular buffer via an interrupt. A separate function processes the buffer and extracts the telegrams.
|
||||||
|
|
||||||
The code is built on the Arduino framework and is dependent on these external libraries:
|
`ems.cpp` is the logic to read the EMS data packets (telegrams), validates them and process them based on the type.
|
||||||
|
|
||||||
- Time http://playground.arduino.cc/code/time
|
`ems-esp.ino` is the Arduino code for the ESP8266 that kicks it all off. This is where we have specific logic such as the code to monitor and alert on the Shower timer and light up the LEDs.
|
||||||
- PubSubClient http://pubsubclient.knolleary.net
|
|
||||||
- ArduinoJson https://github.com/bblanchon/ArduinoJson
|
|
||||||
- CRC32 https://github.com/bakercp/CRC32
|
|
||||||
|
|
||||||
`emsuart.cpp` handles the low level UART read and write logic. You shouldn't need to touch this. All receive commands from the EMS bus are handled asynchronously using a circular buffer via an interrupt. A separate function processes the buffer and extracts the telegrams. Since we don't send too many write commands this is done sequentially. I couldn't use the standard Arduino Serial implementation because of the 11-bit break signal causes a frame-error which gets ignored.
|
`my_config.h` has all the custom settings tailored to your environment. Specific values here are also stored in the ESP's SPIFFs (File system).
|
||||||
|
|
||||||
`ems.cpp` is the logic to read the EMS packets (telegrams), validates them and process them based on the type.
|
`ems_devices.h` has all the configuration for the known EMS devices currently supported.
|
||||||
|
|
||||||
`boiler.ino` is the Arduino code for the ESP8266 that kicks it all off. This is where we have specific logic such as the code to monitor and alert on the Shower timer and light up the LEDs. LED support is enabled by setting the -DUSE_LED build flag.
|
`MyESP.cpp` is my custom library to handle WiFi, MQTT and Telnet. Uses a modified version of [TelnetSpy](https://github.com/yasheena/telnetspy)
|
||||||
|
|
||||||
`ESPHelper.cpp` is my customized version of [ESPHelper](https://github.com/ItKindaWorks/ESPHelper) with added Telnet support and some other minor tweaking.
|
### Special EMS Types
|
||||||
|
|
||||||
### Supported EMS Types
|
|
||||||
|
|
||||||
`ems.cpp` defines callback functions that handle all the broadcast types listed above (e.g. 0x34, 0x18, 0x19 etc) plus these extra types:
|
`ems.cpp` defines callback functions that handle all the broadcast types listed above (e.g. 0x34, 0x18, 0x19 etc) plus these extra types:
|
||||||
|
|
||||||
| Source (ID) | Type ID | Name | Description |
|
| Source (ID) | Type ID | Name | Description |
|
||||||
| ----------------- | ---------------- | ----------------------------- | ---------------------------------------- |
|
| ------------- | ------- | ----------------------------- | ---------------------------------------- |
|
||||||
| Boiler (0x08) | 0x33 | UBAParameterWW | reads selected & desired warm water temp |
|
| Boiler (0x08) | 0x33 | UBAParameterWW | reads selected & desired warm water temp |
|
||||||
| Boiler (0x08) | 0x14 | UBATotalUptimeMessage | |
|
| Boiler (0x08) | 0x14 | UBATotalUptimeMessage | |
|
||||||
| Boiler (0x08) | 0x15 | UBAMaintenanceSettingsMessage | |
|
| Boiler (0x08) | 0x15 | UBAMaintenanceSettingsMessage | |
|
||||||
| Boiler (0x08) | 0x16 | UBAParametersMessage | |
|
| Boiler (0x08) | 0x16 | UBAParametersMessage | |
|
||||||
| Thermostat (0x17) | 0xA8 | RC20Set | sets operating modes for an RC20 |
|
|
||||||
| Thermostat (0x10) | 0xA7 | RC30Set | sets operating modes for an RC30 |
|
|
||||||
| Thermostat | 0x02 | Version | reads Version major/minor |
|
|
||||||
| Thermostat | 0x91, 0x41, 0x0A | Status Message | read monitor values |
|
|
||||||
|
|
||||||
In `boiler.ino` you can make calls to automatically send these read commands. See the function *regularUpdates()*
|
In `ems.cpp` you can add scheduled calls to specific EMS types in the functions `ems_getThermostatValues()` and `ems_getBoilerValues()`.
|
||||||
|
|
||||||
### Supported Thermostats
|
### Which thermostats are supported?
|
||||||
|
|
||||||
Modify `EMS_ID_THERMOSTAT` in `myconfig.h` to the thermostat type you want to support.
|
I am still working on adding more support to known thermostats. Any contributions here are welcome. The know types are listed in `ems_devices.h` and include
|
||||||
|
|
||||||
#### RC20 (Moduline 300)
|
- RC20 and RC30, both are fully supported
|
||||||
|
- RC10 support is being added
|
||||||
Read and write of setpoint temp and mode supported.
|
- RC35 with support for the 1st heating circuit (HC1)
|
||||||
|
- TC100/TC200/Easy but only with support for reading the temperatures. There seems to be no way to set settings using EMS bus messages that I know of. One option is to send XMPP messages but a special server is needed and out of scope for this project.
|
||||||
#### RC30 (Moduline 400)
|
|
||||||
|
|
||||||
Read and write of setpoint temp and mode supported.
|
|
||||||
|
|
||||||
Type's 3F, 49, 53, 5D are identical. So are 4B, 55, 5F and mostly zero's. Types 40, 4A, 54 and 5E are also the same.
|
|
||||||
|
|
||||||
#### RC35
|
|
||||||
|
|
||||||
***not implemented yet***!
|
|
||||||
|
|
||||||
An RC35 thermostat can support up to 4 heating circuits each controlled with their own Monitor and Working Mode IDs.
|
|
||||||
|
|
||||||
Fetching the thermostats setpoint temp us by requesting 0x3E and looking at the 3rd byte in the data telegram (data[2]) and dividing by 2.
|
|
||||||
The mode is on type 0x47 (or 0x3D) and the 8th byte (data[7]). 0=off, 1=on, 2=auto
|
|
||||||
|
|
||||||
#### TC100/TC200 (Nefit Easy)
|
|
||||||
|
|
||||||
There is limited support for an Nefit Easy TC100/TC200 type thermostat. The current room temperature and setpoint temperature can be read. What I'm still figuring out is how to read the mode and set the temperature values without sending http post commands to their web server.
|
|
||||||
|
|
||||||
### Customizing The Code
|
### Customizing The Code
|
||||||
|
|
||||||
- To configure for your thermostat and specific boiler settings, modify `my_config.h`. Here you can
|
- To configure for your thermostat and specific boiler settings, modify `my_config.h`. Here you can
|
||||||
- set the thermostat type. The default ID is 0x17 for an RC30 Moduline 300.
|
- set flags for enabled/disabling functionality such as `BOILER_SHOWER_ENABLED` and `BOILER_SHOWER_TIMER`.
|
||||||
- set flags for enabled/disabling functionality such as `BOILER_THERMOSTAT_ENABLED`, `BOILER_SHOWER_ENABLED` and `BOILER_SHOWER_TIMER`.
|
- Set WIFI and MQTT settings. The values can also be set from the telnet command menu using the **set** command.
|
||||||
- Set WIFI and MQTT settings, instead of doing this in `platformio.ini`
|
|
||||||
- To add new handlers for EMS data types, first create a callback function and add to the `EMS_Types` array at the top of the file `ems.cpp` and modify `ems.h`
|
- To add new handlers for EMS data types, first create a callback function and add to the `EMS_Types` array at the top of the file `ems.cpp` and modify `ems.h`
|
||||||
|
- To add new devices modify `ems_devices.h`
|
||||||
|
|
||||||
### Using MQTT
|
### Using MQTT
|
||||||
|
|
||||||
When the ESP8266 boots it will send a start signal via MQTT. This is picked up by Home Assistant and sends a notification informing me that the device has booted. Useful for knowing when the ESP gets reset.
|
The boiler data is collected and sent as a single JSON object to MQTT TOPIC `home/ems-esp/boiler_data`. The `home` preifx is the MQTT topic prefix and can be customized in `my_config.h`. A hash is generated (CRC32 based) to determine if the payload has changed, otherwise it will not be sent. An example payload looks like:
|
||||||
|
|
||||||
I'm using the standard PubSubClient client so make sure you set `-DMQTT_MAX_PACKET_SIZE=400` as the default package size is 128 and our JSON messages are around 300 bytes.
|
`{"wWSelTemp":"60","selFlowTemp":"5.0","outdoorTemp":"?","wWActivated":"on","wWComfort":"Comfort","wWCurTmp":"46.0","wWCurFlow":"0.0","wWHeat":"on","curFlowTemp":"54.2","retTemp":"51.5","burnGas":"off","heatPmp":"off","fanWork":"off","ignWork":"off","wWCirc":"off","selBurnPow":"0","curBurnPow":"0","sysPress":"1.2","boilTemp":"56.7","pumpMod":"0","ServiceCode":"0H"}`
|
||||||
|
|
||||||
I run Mosquitto on my Raspberry PI 3 as the MQTT broker.
|
Similarly the thermostat values are also sent as a JSON package with the topic `home/ems-esp/thermostat_data` along with the current mode, room temperature and set temperature:
|
||||||
|
|
||||||
The boiler data is collected and sent as a single JSON object to MQTT TOPIC `home/boiler/boiler_data` (or `{hostname}/boiler_data` for ESPurna). A hash is generated (CRC32 based) to determine if the payload has changed, otherwise don't send it. An example payload looks like:
|
`{"thermostat_currtemp":"19.8","thermostat_seltemp":"16.0","thermostat_mode":"manual"}`
|
||||||
|
|
||||||
`{"wWCurTmp":"43.0","wWHeat":"on","curFlowTemp":"51.7","retTemp":"48.0","burnGas":"off","heatPmp":"off","fanWork":"off","ignWork":"off","wWCirc":"off","selBurnPow":"0","curBurnPow":"0","sysPress":"1.6","boilTemp":"54.7","pumpMod":"4"}`
|
These incoming MQTT topics are also handled:
|
||||||
|
|
||||||
Similarly the thermostat values are sent as a json package under a topic named `home/boiler/thermostat_data` with the current mode, room temperature and set temperature.
|
| topic | ID in my_config.h | Payload | Description |
|
||||||
|
| ------------------- | ------------------------- | ---------------------- | ---------------------------------------- |
|
||||||
|
| thermostat_cmd_temp | TOPIC_THERMOSTAT_CMD_TEMP | temperature as a float | sets the thermostat current setpoint |
|
||||||
|
| thermostat_cmd_mode | TOPIC_THERMOSTAT_CMD_MODE | auto, day, night | sets the thermostat mode |
|
||||||
|
| wwactivated | TOPIC_BOILER_WWACTIVATED | 0 or 1 | turns boiler warm water on/off (not tap) |
|
||||||
|
| boiler_cmd_wwtemp | TOPIC_BOILER_CMD_WWTEMP | temperature as a float | sets the boiler wwtemp current setpoint |
|
||||||
|
|
||||||
These topics can be configured in the `TOPIC_*` defines in `boiler.ino`. Make sure you change the HA configuration too to match.
|
If MQTT is not used use 'set mqtt_host' to remove it.
|
||||||
|
|
||||||
|
Some home automation systems such as Domoticz and OpenHab have special formats for their MQTT messages so I would advise to use [node-red](https://nodered.org/) as a parser like in [this example](https://www.domoticz.com/forum/download/file.php?id=18977&sid=67d048f1b4c8833822175eac6b55ecff).
|
||||||
|
|
||||||
### The Basic Shower Logic
|
### The Basic Shower Logic
|
||||||
|
|
||||||
Checking whether the shower is running was tricky. We know when the warm water is on and being heated but need to distinguish between the central heating, shower, hot tap and bath. I found via trial and error the Selected Burner Max Power is between 80% and 115% when the shower is running and fixed at 75% if the central heating is on. Furthermore the Selected Flow Impulsion is 80 C for the heating.
|
Checking whether the shower is running is tricky. We know when the warm water is on and being heated but need to distinguish between the central heating, shower, hot tap and even a bath tap. So this code is a little experimental.
|
||||||
|
|
||||||
There is other logic in the code to compensate for ramp up and whether the shower is turned off and back on again quickly within a 10 second window.
|
There is other logic in the code to compensate for water heating up to shower temperature and whether the shower is turned off and back on again quickly within a 10 second window.
|
||||||
|
|
||||||
## Home Assistant Configuration
|
## Home Assistant Configuration
|
||||||
|
|
||||||
@@ -300,122 +280,85 @@ and the alerts on an iOS/Android device using PushBullet, PushOver or any notifi
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
You can find the .yaml configuration files under `doc/ha`. See also https://community.home-assistant.io/t/thermostat-and-boiler-controller-for-ems-based-boilers-nefit-buderus-bosch-using-esp/53382
|
You can find the .yaml configuration files under `doc/ha`. See also this [HA forum post](https://community.home-assistant.io/t/thermostat-and-boiler-controller-for-ems-based-boilers-nefit-buderus-bosch-using-esp/53382).
|
||||||
|
|
||||||
## Building The Firmware
|
## Building The Firmware
|
||||||
|
|
||||||
### Using PlatformIO Standalone
|
### Using PlatformIO Standalone
|
||||||
|
|
||||||
PlatformIO is my preferred way. The code uses a modified version [ESPHelper](https://github.com/ItKindaWorks/ESPHelper) which handles all the basic handling of the WiFi, MQTT, OTA and Telnet server. I switched from Atom to the marvelous Visual Studio Code, works on Windows, OSX and Linux.
|
|
||||||
|
|
||||||
**On Windows:**
|
**On Windows:**
|
||||||
|
|
||||||
- Download [Git](https://git-scm.com/download/win) (install using the default settings)
|
- Download [Git](https://git-scm.com/download/win) (install using the default settings)
|
||||||
- Download and install [Visual Studio Code](https://code.visualstudio.com/docs/?dv=win) (VSC). It's like 40MB so don't confuse with the commercial Microsoft Visual Studio.
|
- Download and install [Visual Studio Code](https://code.visualstudio.com/docs/?dv=win) (VSC). It's like 40MB so don't confuse with the commercial Microsoft Visual Studio.
|
||||||
- Restart the PC (if using Windows) to apply the new PATH settings. It should now detect Git
|
- Restart the PC (if using Windows) to apply the new PATH settings. It should now detect Git
|
||||||
- Install these VSC extensions: PlatformIO IDE & GitLens, and then click reload to activate them
|
- Install the VSC extension "PlatformIO IDE" then click reload to activate it
|
||||||
- Git clone this repo, eith using `git clone` from PlatformIO's terminal or the Git GUI interface
|
- Git clone this repo, eith using `git clone` from PlatformIO's terminal or the Git GUI interface
|
||||||
- Create a `platformio.ini` based on the `platformio.ini-example` making the necessary changes for your WiFi and MQTT credentials in the build flags. If you're not using MQTT leave MQTT_IP empty (`MQTT_IP=""`)
|
- Create a `platformio.ini` based on the `platformio.ini-example` making the necessary changes for your board type
|
||||||
|
|
||||||
**On Linux (e.g. Ubuntu under Windows 10):**
|
**On Linux (e.g. Ubuntu under Windows 10):**
|
||||||
|
|
||||||
- make sure Python 2.7 is installed
|
Make sure Python 2.7 is installed, then...
|
||||||
- make sure you have a Linux distro installed (https://docs.microsoft.com/en-us/windows/wsl/install-win10)
|
|
||||||
- Do:
|
|
||||||
```python
|
```python
|
||||||
% pip install -U platformio
|
% pip install -U platformio
|
||||||
% sudo platformio upgrade
|
% sudo platformio upgrade
|
||||||
% platformio platform update
|
% platformio platform update
|
||||||
|
|
||||||
% git clone https://github.com/proddy/EMS-ESP-Boiler.git
|
% git clone https://github.com/proddy/EMS-ESP.git
|
||||||
% cd EMS-ESP-Boiler
|
% cd EMS-ESP
|
||||||
% cp platformio.ini-example platformio.ini
|
% cp platformio.ini-example platformio.ini
|
||||||
```
|
```
|
||||||
- edit `platformio.ini` to set `env_default` and the flags `WIFI_SSID WIFI_PASSWORD, MQTT_IP, MQTT_USER, MQTT_PASS`. If you're not using MQTT leave MQTT_IP empty (`MQTT_IP=""`)
|
edit `platformio.ini` to set `env_default` to your board type, then
|
||||||
```c
|
```c
|
||||||
% platformio run -t upload
|
% platformio run -t upload
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using ESPurna
|
|
||||||
|
|
||||||
[ESPurna](https://github.com/xoseperez/espurna/wiki) is framework that handles most of the tedious tasks of building IoT devices so you can focus on the functionality you need. This replaces my ESPHelper code in the standalone version above. ESPurna is natively built on PlatformIO and Visual Studio Code too which is nice. The latest version I tested this on is 1.13.3. So if you're brave, follow these steps:
|
|
||||||
|
|
||||||
1. Download and install [NodeJS](https://nodejs.org/en/download). This gives you npm. Choose the LTS version
|
|
||||||
2. Download ESPurna by cloning the ESPurna git repository (command palette, git clone, https://github.com/xoseperez/espurna.git)
|
|
||||||
3. Restart VSC.
|
|
||||||
4. From VSC open the folder `espurna\code`. PlatformIO should detect and set some things up for you automagically
|
|
||||||
5. open a terminal window (*ctrl-`*)
|
|
||||||
6. Install the node modules: `npm install --only=dev`
|
|
||||||
7. Build the web interface: `node node_modules/gulp/bin/gulp.js`. This will create a compressed `code/espurna/static/index.html.gz.h`. If you get warnings about lf during the building edit `gulpfile.js` and change the line `'failOnError': true` to `false` as a temporary workaround.
|
|
||||||
8. Modify the platformio.ini file making sure you add `-DUSE_CUSTOM_H -DUSE_EXTRA` to the `debug_flags`
|
|
||||||
9. Copy the following files from EMS-ESP-Boiler repo to where you installed ESPurna
|
|
||||||
```c
|
|
||||||
espurna/index.html -> code/html/index.html
|
|
||||||
espurna/custom.h -> code/config/custom.h
|
|
||||||
espurna/boiler-espurna.ino -> code/espurna/boiler-espurna.ino
|
|
||||||
ems*.* -> code/espurna/
|
|
||||||
```
|
|
||||||
10. Now build and upload as you usually would with PlatformIO (or ctrl-arl-t and choose the right build). Look at my version of platformio.ini as an example.
|
|
||||||
11. When the firmware loads, use a wifi connected pc/mobile to connect to the Access Point called ESPURNA_XXXXXX. Use 'fibonacci' as the password. Navigate to `http://192.168.4.1` from a browser, set a new username and password when prompted, log off the wifi and reconnect to the AP using these new credentials. Again go to 192.168.4.1
|
|
||||||
12. In the ADMIN page enable Telnet and SAVE
|
|
||||||
13. In the WIFI page add your home wifi details, click SAVE and reboot, and go to the new IP
|
|
||||||
14. Configure MQTT
|
|
||||||
|
|
||||||
The Telnet functions are `BOILER.READ`, `BOILER.INFO` and a few others for reference. `HELP` will list them. Add your own functions to expand the functionality by calling the EMS* functions as in the examples.
|
|
||||||
|
|
||||||
If you run into issues refer to ESPurna's official setup instructions [here](https://github.com/xoseperez/espurna/wiki/Build-and-update-from-Visual-Studio-Code-using-PlatformIO) and [here](https://github.com/xoseperez/espurna/wiki/Configuration).
|
|
||||||
|
|
||||||
This is what ESPurna looks like with the custom boiler code:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
*Note: I didn't bother porting all the EMS Read and Write commands from the Telnet code to the Espurna, but its pretty straight forward if you want to extend the code.*
|
|
||||||
|
|
||||||
### Using Pre-built Firmware
|
|
||||||
|
|
||||||
pre-baked firmwares for some ESP8266 devices based on ESPurna are available in the directory `/firmware` which you can upload yourself using [esptool](https://github.com/espressif/esptool) bootloader. On Windows, follow these instructions:
|
|
||||||
|
|
||||||
1. Check if you have **python 2.7** installed. If not [download it](https://www.python.org/downloads/) and make sure you select the option to add Python to the windows PATH
|
|
||||||
2. Install the ESPTool by running `pip install esptool` from a command prompt
|
|
||||||
3. Connect the ESP via USB, figure out the COM port
|
|
||||||
4. run `esptool.py -p <com> write_flash 0x00000 <firmware>` where firmware is the `.bin` file and \<com\> is the COM port, e.g. `COM3`
|
|
||||||
|
|
||||||
now follow the steps in ESPurna section above from #10 on to configure the device.
|
|
||||||
|
|
||||||
### Building Using Arduino IDE
|
### Building Using Arduino IDE
|
||||||
|
|
||||||
Porting to the Arduino IDE can be a little tricky but it is possible.
|
Porting to the Arduino IDE can be a little tricky but it did it once. Something along these lines:
|
||||||
|
|
||||||
- Add the ESP8266 boards (from Preferences add Additional Board URL `http://arduino.esp8266.com/stable/package_esp8266com_index.json`)
|
- Add the ESP8266 boards (from Preferences add Additional Board URL `http://arduino.esp8266.com/stable/package_esp8266com_index.json`)
|
||||||
- Go to Boards Manager and install ESP8266 2.4.x platform
|
- Go to Boards Manager and install ESP8266 2.4.x platform. Make sure your board supports SPIFFS.
|
||||||
- Select your ESP8266 from Tools->Boards and the correct port with Tools->Port
|
- Select your ESP8266 from Tools->Boards and the correct port with Tools->Port
|
||||||
- From the Library Manager install ArduinoJson 5.13.x, PubSubClient 2.6.x, CRC32 and Time
|
- From the Library Manager install the needed libraries from platformio.ini. Note make sure you pick ArduinoJson v5 (5.13.4 and above) and not v6. See https://arduinojson.org/v5/doc/
|
||||||
- The Arduino IDE doesn't have a common way to set build flags (ugh!) so you'll need to un-comment these lines in `boiler.ino`:
|
- Put all the files in a single sketch folder
|
||||||
|
- cross your fingers and hit CTRL-R to compile
|
||||||
|
|
||||||
```c
|
## Using the Pre-built Firmware
|
||||||
#define WIFI_SSID "<my_ssid>"
|
|
||||||
#define WIFI_PASSWORD "<my_password>"
|
|
||||||
#define MQTT_IP "<broker_ip>"
|
|
||||||
#define MQTT_USER "<broker_username>"
|
|
||||||
#define MQTT_PASS "<broker_password>"
|
|
||||||
```
|
|
||||||
|
|
||||||
- Put all the files in a single sketch folder (`ESPHelper.*, boiler.ino, ems.*, emsuart.*`)
|
pre-baked firmware for the Wemos D1 mini is available in the GitHub [releases](https://github.com/proddy/EMS-ESP/releases) which you can upload yourself using the [esptool](https://github.com/espressif/esptool) bootloader like `esptool.py -p <com port> write_flash 0x00000 <firmware.bin file>`. Here's how to set it up on Windows:
|
||||||
- cross your fingers and hit CTRL-R to compile...
|
|
||||||
|
1. Check if you have **python 2.7** installed. If not [download it](https://www.python.org/downloads/) and make sure you select the option to add Python to the windows PATH
|
||||||
|
2. Then install the ESPTool by running `pip install esptool` from a command prompt
|
||||||
|
|
||||||
|
The ESP8266 will start in Access Point (AP) mode. Connect via WiFi to the SSID **EMS-ESP** and telnet to **192.168.4.1**. Then use the `set wifi` command to configure your own network settings like `set wifi your_ssid your_password`. Alternatively connect the ESP8266 to your PC and open a Serial monitor (with baud 115200) to configure the settings. Make sure you disable Serial support before connecting the EMS lines using `set serial off`.
|
||||||
|
|
||||||
|
`set` wil list all currently stored settings.
|
||||||
|
|
||||||
|
`set erase` will clear all settings.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
When flashing for the first time the Serial port is enabled by default with baud 115200. You can then use a PC with USB to the ESP8266 to set the settings like wifi, mqtt etc and also monitor the boot up procedure. Remember to disable the serial (`set serial off`) when connecting to the EMS lines.
|
||||||
|
|
||||||
|
The onboard LED will flash if there is no connection with the EMS bus. You can disable LED support by the 'set led' command from the telnet client.
|
||||||
|
|
||||||
|
If you want to completely erase the ESP and rebuild the firmware then do a `pio run -t erase` which will wipe the onboard flash including the SPIFFs where all the settings are stored.
|
||||||
|
|
||||||
## Known Issues
|
## Known Issues
|
||||||
|
|
||||||
Some annoying issues that need fixing:
|
Some annoying issues that need fixing:
|
||||||
|
|
||||||
- Very infrequently an EMS write command is not sent, probably due to a collision somewhere in the UART between an incoming Rx and a Poll. The retries in the code fix this for now.
|
- On newer EMS+ Boilers the Tx commands for reading and writing may not always work. I believe there is some handshake that needs to happen before the UBA3/Master is able to send a poll request to our service device.
|
||||||
|
|
||||||
## Wish List
|
## Wish List
|
||||||
|
|
||||||
- Measure amount of gas in m3 per day for the hot water vs the central heating, and convert this into cost in Home Assistant
|
- Measure amount of gas in m3 per day for the hot water vs the central heating, and convert this into cost.
|
||||||
- Support changing temps on an Nefit Easy. To do this you must send XMPP messages directly to the thermostat. See this project: https://github.com/robertklep/nefit-easy-core
|
- Support changing temperatures on an Nefit Easy. To do this we must send XMPP messages directly to the thermostat. There is already a TCP stack and a Wifi and Telnet server running in the code, so the building blocks are there to extend with another XMPP client. Here are a number of Python based projects that show how to do this:
|
||||||
- Store custom params like wifi credentials, mqtt, thermostat type on ESP8266 using SPIFFS
|
- https://github.com/patvdleer/nefit-client-python
|
||||||
- Automatic detection of thermostat type
|
- https://github.com/marconfus/ha-nefit
|
||||||
- Add support for a temperature sensor on the circuit (DS18B20)
|
- https://github.com/robertklep/nefit-easy-core
|
||||||
|
- Improve detection of Heating Off without checking for selFlowTemp (selected flow temperature)
|
||||||
|
- Split MQTT into smaller chunks. Now the messages can be up to 600 bytes which may cause issues. Preferably make the items configurable.
|
||||||
|
|
||||||
## Your Comments and Feedback
|
## Your Comments and Feedback
|
||||||
|
|
||||||
|
|||||||
28
checkcode.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from subprocess import call
|
||||||
|
import os
|
||||||
|
Import("env")
|
||||||
|
|
||||||
|
|
||||||
|
def code_check(source, target, env):
|
||||||
|
print("\n** Starting cppcheck...")
|
||||||
|
call(["cppcheck", os.getcwd()+"/.", "--force", "--enable=all"])
|
||||||
|
print("\n** Finished cppcheck...\n")
|
||||||
|
print("\n** Starting cpplint...")
|
||||||
|
call(["cpplint", "--extensions=ino,cpp,h", "--filter=-legal/copyright,-build/include,-whitespace",
|
||||||
|
"--linelength=120", "--recursive", "src", "lib/myESP"])
|
||||||
|
print("\n** Finished cpplint...")
|
||||||
|
|
||||||
|
#my_flags = env.ParseFlags(env['BUILD_FLAGS'])
|
||||||
|
#defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")}
|
||||||
|
# print defines
|
||||||
|
# print env.Dump()
|
||||||
|
|
||||||
|
|
||||||
|
# built in targets: (buildprog, size, upload, program, buildfs, uploadfs, uploadfsota)
|
||||||
|
env.AddPreAction("buildprog", code_check)
|
||||||
|
# env.AddPostAction(.....)
|
||||||
|
|
||||||
|
# see http://docs.platformio.org/en/latest/projectconf/advanced_scripting.html#before-pre-and-after-post-actions
|
||||||
|
# env.Replace(PROGNAME="firmware_%s" % defines.get("VERSION"))
|
||||||
|
# env.Replace(PROGNAME="firmware_%s" % env['BOARD'])
|
||||||
24
clean_fw.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from subprocess import call
|
||||||
|
import os
|
||||||
|
Import("env")
|
||||||
|
|
||||||
|
def clean(source, target, env):
|
||||||
|
print("\n** Starting clean...")
|
||||||
|
call(["pio", "run", "-t", "erase"])
|
||||||
|
call(["esptool.py", "-p COM6", "write_flash 0x00000", os.getcwd()+"../firmware/*.bin"])
|
||||||
|
print("\n** Finished clean.")
|
||||||
|
|
||||||
|
#my_flags = env.ParseFlags(env['BUILD_FLAGS'])
|
||||||
|
#defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")}
|
||||||
|
# print defines
|
||||||
|
# print env.Dump()
|
||||||
|
|
||||||
|
|
||||||
|
# built in targets: (buildprog, size, upload, program, buildfs, uploadfs, uploadfsota)
|
||||||
|
env.AddPreAction("buildprog", clean)
|
||||||
|
# env.AddPostAction(.....)
|
||||||
|
|
||||||
|
# see http://docs.platformio.org/en/latest/projectconf/advanced_scripting.html#before-pre-and-after-post-actions
|
||||||
|
# env.Replace(PROGNAME="firmware_%s" % defines.get("VERSION"))
|
||||||
|
# env.Replace(PROGNAME="firmware_%s" % env['BOARD'])
|
||||||
113
doc/Domoticz/nefit/mqtt.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# Based on https://github.com/emontnemery/domoticz_mqtt_discovery
|
||||||
|
import Domoticz
|
||||||
|
import time
|
||||||
|
|
||||||
|
class MqttClient:
|
||||||
|
Address = ""
|
||||||
|
Port = ""
|
||||||
|
mqttConn = None
|
||||||
|
isConnected = False
|
||||||
|
mqttConnectedCb = None
|
||||||
|
mqttDisconnectedCb = None
|
||||||
|
mqttPublishCb = None
|
||||||
|
|
||||||
|
def __init__(self, destination, port, mqttConnectedCb, mqttDisconnectedCb, mqttPublishCb, mqttSubackCb):
|
||||||
|
Domoticz.Debug("MqttClient::__init__")
|
||||||
|
self.Address = destination
|
||||||
|
self.Port = port
|
||||||
|
self.mqttConnectedCb = mqttConnectedCb
|
||||||
|
self.mqttDisconnectedCb = mqttDisconnectedCb
|
||||||
|
self.mqttPublishCb = mqttPublishCb
|
||||||
|
self.mqttSubackCb = mqttSubackCb
|
||||||
|
self.Open()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
Domoticz.Debug("MqttClient::__str__")
|
||||||
|
if (self.mqttConn != None):
|
||||||
|
return str(self.mqttConn)
|
||||||
|
else:
|
||||||
|
return "None"
|
||||||
|
|
||||||
|
def Open(self):
|
||||||
|
Domoticz.Debug("MqttClient::Open")
|
||||||
|
if (self.mqttConn != None):
|
||||||
|
self.Close()
|
||||||
|
self.isConnected = False
|
||||||
|
self.mqttConn = Domoticz.Connection(Name=self.Address, Transport="TCP/IP", Protocol="MQTT", Address=self.Address, Port=self.Port)
|
||||||
|
self.mqttConn.Connect()
|
||||||
|
|
||||||
|
def Connect(self):
|
||||||
|
Domoticz.Debug("MqttClient::Connect")
|
||||||
|
if (self.mqttConn == None):
|
||||||
|
self.Open()
|
||||||
|
else:
|
||||||
|
ID = 'Domoticz_'+str(int(time.time()))
|
||||||
|
Domoticz.Log("MQTT CONNECT ID: '" + ID + "'")
|
||||||
|
self.mqttConn.Send({'Verb': 'CONNECT', 'ID': ID})
|
||||||
|
|
||||||
|
def Ping(self):
|
||||||
|
Domoticz.Debug("MqttClient::Ping")
|
||||||
|
if (self.mqttConn == None or not self.isConnected):
|
||||||
|
self.Open()
|
||||||
|
else:
|
||||||
|
self.mqttConn.Send({'Verb': 'PING'})
|
||||||
|
|
||||||
|
def Publish(self, topic, payload, retain = 0):
|
||||||
|
Domoticz.Log("MqttClient::Publish " + topic + " (" + payload + ")")
|
||||||
|
if (self.mqttConn == None or not self.isConnected):
|
||||||
|
self.Open()
|
||||||
|
else:
|
||||||
|
self.mqttConn.Send({'Verb': 'PUBLISH', 'Topic': topic, 'Payload': bytearray(payload, 'utf-8'), 'Retain': retain})
|
||||||
|
|
||||||
|
def Subscribe(self, topics):
|
||||||
|
Domoticz.Debug("MqttClient::Subscribe")
|
||||||
|
subscriptionlist = []
|
||||||
|
for topic in topics:
|
||||||
|
subscriptionlist.append({'Topic':topic, 'QoS':0})
|
||||||
|
if (self.mqttConn == None or not self.isConnected):
|
||||||
|
self.Open()
|
||||||
|
else:
|
||||||
|
self.mqttConn.Send({'Verb': 'SUBSCRIBE', 'Topics': subscriptionlist})
|
||||||
|
|
||||||
|
def Close(self):
|
||||||
|
Domoticz.Log("MqttClient::Close")
|
||||||
|
#TODO: Disconnect from server
|
||||||
|
self.mqttConn = None
|
||||||
|
self.isConnected = False
|
||||||
|
|
||||||
|
def onConnect(self, Connection, Status, Description):
|
||||||
|
Domoticz.Debug("MqttClient::onConnect")
|
||||||
|
if (Status == 0):
|
||||||
|
Domoticz.Log("Successful connect to: "+Connection.Address+":"+Connection.Port)
|
||||||
|
self.Connect()
|
||||||
|
else:
|
||||||
|
Domoticz.Log("Failed to connect to: "+Connection.Address+":"+Connection.Port+", Description: "+Description)
|
||||||
|
|
||||||
|
def onDisconnect(self, Connection):
|
||||||
|
Domoticz.Log("MqttClient::onDisonnect Disconnected from: "+Connection.Address+":"+Connection.Port)
|
||||||
|
self.Close()
|
||||||
|
# TODO: Reconnect?
|
||||||
|
if self.mqttDisconnectedCb != None:
|
||||||
|
self.mqttDisconnectedCb()
|
||||||
|
|
||||||
|
def onMessage(self, Connection, Data):
|
||||||
|
topic = ''
|
||||||
|
if 'Topic' in Data:
|
||||||
|
topic = Data['Topic']
|
||||||
|
payloadStr = ''
|
||||||
|
if 'Payload' in Data:
|
||||||
|
payloadStr = Data['Payload'].decode('utf8','replace')
|
||||||
|
payloadStr = str(payloadStr.encode('unicode_escape'))
|
||||||
|
|
||||||
|
if Data['Verb'] == "CONNACK":
|
||||||
|
self.isConnected = True
|
||||||
|
if self.mqttConnectedCb != None:
|
||||||
|
self.mqttConnectedCb()
|
||||||
|
|
||||||
|
if Data['Verb'] == "SUBACK":
|
||||||
|
if self.mqttSubackCb != None:
|
||||||
|
self.mqttSubackCb()
|
||||||
|
|
||||||
|
if Data['Verb'] == "PUBLISH":
|
||||||
|
if self.mqttPublishCb != None:
|
||||||
|
self.mqttPublishCb(topic, Data['Payload'])
|
||||||
165
doc/Domoticz/nefit/plugin.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
"""
|
||||||
|
<plugin key="nefit" name="Nefit EMS-ESP with Proddy firmware" version="0.0.1">
|
||||||
|
<description>
|
||||||
|
Plugin to control Nefit EMS-ESP with '<a href="https://github.com/proddy/EMS-ESP"> Proddy</a>' firmware<br/>
|
||||||
|
<br/>
|
||||||
|
Automatically creates Domoticz devices for connected device.<br/>
|
||||||
|
Do not forget to "Accept new Hardware Devices" on first run<br/>
|
||||||
|
</description>
|
||||||
|
<params>
|
||||||
|
<param field="Address" label="MQTT Server address" width="300px" required="true" default="127.0.0.1"/>
|
||||||
|
<param field="Port" label="Port" width="300px" required="true" default="1883"/>
|
||||||
|
<param field="Mode6" label="Debug" width="75px">
|
||||||
|
<options>
|
||||||
|
<option label="Extra verbose" value="Verbose+"/>
|
||||||
|
<option label="Verbose" value="Verbose"/>
|
||||||
|
<option label="True" value="Debug"/>
|
||||||
|
<option label="False" value="Normal" default="true" />
|
||||||
|
</options>
|
||||||
|
</param>
|
||||||
|
</params>
|
||||||
|
</plugin>
|
||||||
|
"""
|
||||||
|
|
||||||
|
import Domoticz
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from mqtt import MqttClient
|
||||||
|
|
||||||
|
class Thermostat:
|
||||||
|
def checkDevices(self):
|
||||||
|
if 1 not in Devices:
|
||||||
|
Domoticz.Debug("Create Temperature Device")
|
||||||
|
Domoticz.Device(Name="Woonkamer", Unit=1, Type=80, Subtype=5).Create()
|
||||||
|
if 2 not in Devices:
|
||||||
|
Domoticz.Debug("Create System Pressure Device")
|
||||||
|
Domoticz.Device(Name="System Pressure", Unit=2, Type=243, Subtype=9).Create()
|
||||||
|
if 3 not in Devices:
|
||||||
|
Domoticz.Debug("Create Thermostat Device")
|
||||||
|
Domoticz.Device(Name="Nefit", Unit=3, Type=242, Subtype=1).Create()
|
||||||
|
|
||||||
|
def onMqttMessage(self, topic, payload):
|
||||||
|
if "thermostat_currtemp" in payload:
|
||||||
|
temp=round(float(payload["thermostat_currtemp"]),1)
|
||||||
|
Domoticz.Debug("Current temp: {}".format(temp))
|
||||||
|
if Devices[1].sValue != temp:
|
||||||
|
Devices[1].Update(nValue=1, sValue=str(temp))
|
||||||
|
if "sysPress" in payload:
|
||||||
|
pressure=payload["sysPress"]
|
||||||
|
Domoticz.Debug("System Pressure: {}".format(pressure))
|
||||||
|
if Devices[2].sValue != pressure:
|
||||||
|
Devices[2].Update(nValue=1, sValue=str(pressure))
|
||||||
|
if "thermostat_seltemp" in payload:
|
||||||
|
temp=payload["thermostat_seltemp"]
|
||||||
|
Domoticz.Debug("Temp setting: {}".format(temp))
|
||||||
|
if Devices[3].sValue != temp:
|
||||||
|
Devices[3].Update(nValue=1, sValue=str(temp))
|
||||||
|
|
||||||
|
def onCommand(self, mqttClient, unit, command, level, color):
|
||||||
|
topic = "home/ems-esp/thermostat_cmd_temp"
|
||||||
|
if (command == "Set Level"):
|
||||||
|
mqttClient.Publish(topic, str(level))
|
||||||
|
|
||||||
|
class BasePlugin:
|
||||||
|
mqttClient = None
|
||||||
|
|
||||||
|
def onStart(self):
|
||||||
|
self.debugging = Parameters["Mode6"]
|
||||||
|
|
||||||
|
if self.debugging == "Verbose+":
|
||||||
|
Domoticz.Debugging(2+4+8+16+64)
|
||||||
|
if self.debugging == "Verbose":
|
||||||
|
Domoticz.Debugging(2+4+8+16+64)
|
||||||
|
if self.debugging == "Debug":
|
||||||
|
Domoticz.Debugging(2+4+8)
|
||||||
|
|
||||||
|
self.controller = Thermostat()
|
||||||
|
|
||||||
|
self.controller.checkDevices()
|
||||||
|
|
||||||
|
self.topics = list(["home/ems-esp/thermostat_data", "home/ems-esp/boiler_data", "home/ems-esp/STATE"])
|
||||||
|
self.mqttserveraddress = Parameters["Address"].replace(" ", "")
|
||||||
|
self.mqttserverport = Parameters["Port"].replace(" ", "")
|
||||||
|
self.mqttClient = MqttClient(self.mqttserveraddress, self.mqttserverport, self.onMQTTConnected, self.onMQTTDisconnected, self.onMQTTPublish, self.onMQTTSubscribed)
|
||||||
|
|
||||||
|
def checkDevices(self):
|
||||||
|
Domoticz.Log("checkDevices called")
|
||||||
|
|
||||||
|
def onStop(self):
|
||||||
|
Domoticz.Log("onStop called")
|
||||||
|
|
||||||
|
def onCommand(self, Unit, Command, Level, Color):
|
||||||
|
Domoticz.Debug("Command: " + Command + " (" + str(Level))
|
||||||
|
self.controller.onCommand(self.mqttClient, Unit, Command, Level, Color)
|
||||||
|
|
||||||
|
def onConnect(self, Connection, Status, Description):
|
||||||
|
self.mqttClient.onConnect(Connection, Status, Description)
|
||||||
|
|
||||||
|
def onDisconnect(self, Connection):
|
||||||
|
self.mqttClient.onDisconnect(Connection)
|
||||||
|
|
||||||
|
def onMessage(self, Connection, Data):
|
||||||
|
self.mqttClient.onMessage(Connection, Data)
|
||||||
|
|
||||||
|
def onHeartbeat(self):
|
||||||
|
Domoticz.Debug("Heartbeating...")
|
||||||
|
|
||||||
|
# Reconnect if connection has dropped
|
||||||
|
if self.mqttClient.mqttConn is None or (not self.mqttClient.mqttConn.Connecting() and not self.mqttClient.mqttConn.Connected() or not self.mqttClient.isConnected):
|
||||||
|
Domoticz.Debug("Reconnecting")
|
||||||
|
self.mqttClient.Open()
|
||||||
|
else:
|
||||||
|
self.mqttClient.Ping()
|
||||||
|
|
||||||
|
def onMQTTConnected(self):
|
||||||
|
Domoticz.Debug("onMQTTConnected")
|
||||||
|
self.mqttClient.Subscribe(self.topics)
|
||||||
|
|
||||||
|
def onMQTTDisconnected(self):
|
||||||
|
Domoticz.Debug("onMQTTDisconnected")
|
||||||
|
|
||||||
|
def onMQTTSubscribed(self):
|
||||||
|
Domoticz.Debug("onMQTTSubscribed")
|
||||||
|
|
||||||
|
def onMQTTPublish(self, topic, rawmessage):
|
||||||
|
Domoticz.Debug("MQTT message: " + topic + " " + str(rawmessage))
|
||||||
|
|
||||||
|
message = ""
|
||||||
|
try:
|
||||||
|
message = json.loads(rawmessage.decode('utf8'))
|
||||||
|
except ValueError:
|
||||||
|
message = rawmessage.decode('utf8')
|
||||||
|
|
||||||
|
if (topic in self.topics):
|
||||||
|
self.controller.onMqttMessage(topic, message)
|
||||||
|
|
||||||
|
global _plugin
|
||||||
|
_plugin = BasePlugin()
|
||||||
|
|
||||||
|
def onStart():
|
||||||
|
global _plugin
|
||||||
|
_plugin.onStart()
|
||||||
|
|
||||||
|
def onStop():
|
||||||
|
global _plugin
|
||||||
|
_plugin.onStop()
|
||||||
|
|
||||||
|
def onConnect(Connection, Status, Description):
|
||||||
|
global _plugin
|
||||||
|
_plugin.onConnect(Connection, Status, Description)
|
||||||
|
|
||||||
|
def onDisconnect(Connection):
|
||||||
|
global _plugin
|
||||||
|
_plugin.onDisconnect(Connection)
|
||||||
|
|
||||||
|
def onMessage(Connection, Data):
|
||||||
|
global _plugin
|
||||||
|
_plugin.onMessage(Connection, Data)
|
||||||
|
|
||||||
|
def onCommand(Unit, Command, Level, Color):
|
||||||
|
global _plugin
|
||||||
|
_plugin.onCommand(Unit, Command, Level, Color)
|
||||||
|
|
||||||
|
def onHeartbeat():
|
||||||
|
global _plugin
|
||||||
|
_plugin.onHeartbeat()
|
||||||
10
doc/Domoticz/readme.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
to install the plugin:
|
||||||
|
- copy the directory 'nefit' to the domoticz/plugins directory
|
||||||
|
- make sure that 'Accept new Hardware Devices' is enabeled in settings/sysem
|
||||||
|
- create new hardware with type 'Nefit EMS-ESP with Proddy firmware'
|
||||||
|
- set MQTT server and port
|
||||||
|
|
||||||
|
The plugin crrently creates 3 devices:
|
||||||
|
- a room temperature meter
|
||||||
|
- a system pressure meter
|
||||||
|
- a thermostat setpoint control
|
||||||
|
Before Width: | Height: | Size: 42 KiB |
@@ -2,7 +2,7 @@
|
|||||||
alias: Alert shower time
|
alias: Alert shower time
|
||||||
trigger:
|
trigger:
|
||||||
platform: mqtt
|
platform: mqtt
|
||||||
topic: home/boiler/showertime
|
topic: home/ems-esp/showertime
|
||||||
action:
|
action:
|
||||||
- service: notify.general_notify
|
- service: notify.general_notify
|
||||||
data_template:
|
data_template:
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
alias: Alert shower too long
|
alias: Alert shower too long
|
||||||
trigger:
|
trigger:
|
||||||
platform: mqtt
|
platform: mqtt
|
||||||
topic: home/boiler/command
|
topic: home/ems-esp/command
|
||||||
payload: 'shower_alarm'
|
payload: 'shower_alarm'
|
||||||
action:
|
action:
|
||||||
- service: notify.admin_notify
|
- service: notify.admin_notify
|
||||||
@@ -21,21 +21,21 @@
|
|||||||
title: "Shower Alert!"
|
title: "Shower Alert!"
|
||||||
message: "Shower time exceeded limit"
|
message: "Shower time exceeded limit"
|
||||||
|
|
||||||
# when boiler starts send boottime
|
# when ems-esp starts send boottime
|
||||||
- id: boiler_restart
|
- id: boiler_restart
|
||||||
alias: See if boiler restarts
|
alias: See if ems-esp restarts
|
||||||
trigger:
|
trigger:
|
||||||
platform: mqtt
|
platform: mqtt
|
||||||
topic: home/boiler/start
|
topic: home/ems-esp/start
|
||||||
payload: 'start'
|
payload: 'start'
|
||||||
action:
|
action:
|
||||||
- service: notify.admin_notify
|
- service: notify.admin_notify
|
||||||
data_template:
|
data_template:
|
||||||
title: "boiler has booted"
|
title: "ems-esp has booted"
|
||||||
message: "Boiler"
|
message: "EMS-ESP"
|
||||||
- service: mqtt.publish
|
- service: mqtt.publish
|
||||||
data_template:
|
data_template:
|
||||||
topic: 'home/boiler/start'
|
topic: 'home/ems-esp/start'
|
||||||
payload: >
|
payload: >
|
||||||
{{ now().strftime("%H:%M:%S %-d/%b/%Y") }}
|
{{ now().strftime("%H:%M:%S %-d/%b/%Y") }}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
name: 'Tap Water'
|
name: 'Tap Water'
|
||||||
state_topic: 'home/boiler/tapwater_active'
|
state_topic: 'home/ems-esp/tapwater_active'
|
||||||
payload_on: "1"
|
payload_on: "1"
|
||||||
payload_off: "0"
|
payload_off: "0"
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
name: 'Heating'
|
name: 'Heating'
|
||||||
state_topic: 'home/boiler/heating_active'
|
state_topic: 'home/ems-esp/heating_active'
|
||||||
payload_on: "1"
|
payload_on: "1"
|
||||||
payload_off: "0"
|
payload_off: "0"
|
||||||
|
|
||||||
@@ -5,12 +5,12 @@
|
|||||||
- manual
|
- manual
|
||||||
- auto
|
- auto
|
||||||
|
|
||||||
mode_state_topic: "home/boiler/thermostat_data"
|
mode_state_topic: "home/ems-esp/thermostat_data"
|
||||||
current_temperature_topic: "home/boiler/thermostat_data"
|
current_temperature_topic: "home/ems-esp/thermostat_data"
|
||||||
temperature_state_topic: "home/boiler/thermostat_data"
|
temperature_state_topic: "home/ems-esp/thermostat_data"
|
||||||
|
|
||||||
temperature_command_topic: "home/boiler/thermostat_cmd_temp"
|
temperature_command_topic: "home/ems-esp/thermostat_cmd_temp"
|
||||||
mode_command_topic: "home/boiler/thermostat_cmd_mode"
|
mode_command_topic: "home/ems-esp/thermostat_cmd_mode"
|
||||||
|
|
||||||
mode_state_template: "{{ value_json.thermostat_mode }}"
|
mode_state_template: "{{ value_json.thermostat_mode }}"
|
||||||
current_temperature_template: "{{ value_json.thermostat_currtemp }}"
|
current_temperature_template: "{{ value_json.thermostat_currtemp }}"
|
||||||
@@ -18,3 +18,13 @@
|
|||||||
|
|
||||||
temp_step: 0.5
|
temp_step: 0.5
|
||||||
|
|
||||||
|
- platform: mqtt
|
||||||
|
name: boiler
|
||||||
|
min_temp: 40
|
||||||
|
max_temp: 60
|
||||||
|
temp_step: 1
|
||||||
|
current_temperature_topic: "home/ems-esp/boiler_data"
|
||||||
|
temperature_state_topic: "home/ems-esp/boiler_data"
|
||||||
|
temperature_command_topic: "home/ems-esp/boiler_cmd_wwtemp"
|
||||||
|
current_temperature_template: "{{ value_json.wWCurTmp }}"
|
||||||
|
temperature_state_template: "{{ value_json.wWSelTemp }}"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 107 KiB |
@@ -2,6 +2,6 @@ shower_coldshot:
|
|||||||
sequence:
|
sequence:
|
||||||
- service: mqtt.publish
|
- service: mqtt.publish
|
||||||
data_template:
|
data_template:
|
||||||
topic: 'home/boiler/shower_coldshot'
|
topic: 'home/ems-esp/shower_coldshot'
|
||||||
payload: '1'
|
payload: '1'
|
||||||
|
|
||||||
|
|||||||
@@ -1,132 +1,138 @@
|
|||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/thermostat_data'
|
state_topic: 'home/ems-esp/thermostat_data'
|
||||||
name: 'Current Room Temperature'
|
name: 'Current Room Temperature'
|
||||||
unit_of_measurement: '°C'
|
unit_of_measurement: '°C'
|
||||||
value_template: "{{ value_json.thermostat_currtemp }}"
|
value_template: "{{ value_json.thermostat_currtemp }}"
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/thermostat_data'
|
state_topic: 'home/ems-esp/thermostat_data'
|
||||||
name: 'Current Set Temperature'
|
name: 'Current Set Temperature'
|
||||||
unit_of_measurement: '°C'
|
unit_of_measurement: '°C'
|
||||||
value_template: "{{ value_json.thermostat_seltemp }}"
|
value_template: "{{ value_json.thermostat_seltemp }}"
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/thermostat_data'
|
state_topic: 'home/ems-esp/thermostat_data'
|
||||||
name: 'Current Mode'
|
name: 'Current Mode'
|
||||||
value_template: "{{ value_json.thermostat_mode }}"
|
value_template: "{{ value_json.thermostat_mode }}"
|
||||||
|
|
||||||
# last time boiler was started
|
# last time esp-esp was started
|
||||||
- platform: template
|
- platform: template
|
||||||
sensors:
|
sensors:
|
||||||
boiler_boottime:
|
boiler_boottime:
|
||||||
value_template: '{{ as_timestamp(states.automation.see_if_boiler_restarts.attributes.last_triggered) | timestamp_custom("%H:%M:%S %d/%m/%y") }}'
|
value_template: '{{ as_timestamp(states.automation.see_if_boiler_restarts.attributes.last_triggered) | timestamp_custom("%H:%M:%S %d/%m/%y") }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/showertime'
|
state_topic: 'home/ems-esp/showertime'
|
||||||
name: 'Last shower duration'
|
name: 'Last shower duration'
|
||||||
force_update: true
|
force_update: true
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Tap Water'
|
name: 'Tap Water'
|
||||||
value_template: '{{ value_json.tapwaterActive }}'
|
value_template: '{{ value_json.tapwaterActive }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Heating'
|
name: 'Heating'
|
||||||
value_template: '{{ value_json.heatingActive }}'
|
value_template: '{{ value_json.heatingActive }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Warm Water selected temperature'
|
name: 'Warm Water selected temperature'
|
||||||
unit_of_measurement: '°C'
|
unit_of_measurement: '°C'
|
||||||
value_template: '{{ value_json.wWSelTemp }}'
|
value_template: '{{ value_json.wWSelTemp }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
|
name: 'Warm Water tapwater flow rate'
|
||||||
|
unit_of_measurement: 'l/min'
|
||||||
|
value_template: '{{ value_json.wWCurFlow }}'
|
||||||
|
|
||||||
|
- platform: mqtt
|
||||||
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Warm Water current temperature'
|
name: 'Warm Water current temperature'
|
||||||
unit_of_measurement: '°C'
|
unit_of_measurement: '°C'
|
||||||
value_template: '{{ value_json.wWCurTmp }}'
|
value_template: '{{ value_json.wWCurTmp }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Warm Water activated'
|
name: 'Warm Water activated'
|
||||||
value_template: '{{ value_json.wWActivated }}'
|
value_template: '{{ value_json.wWActivated }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Warm Water 3-way valve'
|
name: 'Warm Water 3-way valve'
|
||||||
value_template: '{{ value_json.wWHeat }}'
|
value_template: '{{ value_json.wWHeat }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Current flow temperature'
|
name: 'Current flow temperature'
|
||||||
unit_of_measurement: '°C'
|
unit_of_measurement: '°C'
|
||||||
value_template: '{{ value_json.curFlowTemp }}'
|
value_template: '{{ value_json.curFlowTemp }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Return temperature'
|
name: 'Return temperature'
|
||||||
unit_of_measurement: '°C'
|
unit_of_measurement: '°C'
|
||||||
value_template: '{{ value_json.retTemp }}'
|
value_template: '{{ value_json.retTemp }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Gas'
|
name: 'Gas'
|
||||||
value_template: '{{ value_json.burnGas }}'
|
value_template: '{{ value_json.burnGas }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Boiler pump'
|
name: 'Boiler pump'
|
||||||
value_template: '{{ value_json.heatPmp }}'
|
value_template: '{{ value_json.heatPmp }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Fan'
|
name: 'Fan'
|
||||||
value_template: '{{ value_json.fanWork }}'
|
value_template: '{{ value_json.fanWork }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Ignition'
|
name: 'Ignition'
|
||||||
value_template: '{{ value_json.ignWork }}'
|
value_template: '{{ value_json.ignWork }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Circulation pump'
|
name: 'Circulation pump'
|
||||||
value_template: '{{ value_json.wWCirc }}'
|
value_template: '{{ value_json.wWCirc }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Burner max power'
|
name: 'Burner max power'
|
||||||
unit_of_measurement: '%'
|
unit_of_measurement: '%'
|
||||||
value_template: '{{ value_json.selBurnPow }}'
|
value_template: '{{ value_json.selBurnPow }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Burner max power'
|
name: 'Burner max power'
|
||||||
unit_of_measurement: '%'
|
unit_of_measurement: '%'
|
||||||
value_template: '{{ value_json.selBurnPow }}'
|
value_template: '{{ value_json.selBurnPow }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Burner current power'
|
name: 'Burner current power'
|
||||||
unit_of_measurement: '%'
|
unit_of_measurement: '%'
|
||||||
value_template: '{{ value_json.curBurnPow }}'
|
value_template: '{{ value_json.curBurnPow }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'System Pressure'
|
name: 'System Pressure'
|
||||||
unit_of_measurement: 'bar'
|
unit_of_measurement: 'bar'
|
||||||
value_template: '{{ value_json.sysPress }}'
|
value_template: '{{ value_json.sysPress }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Boiler temperature'
|
name: 'Boiler temperature'
|
||||||
unit_of_measurement: '°C'
|
unit_of_measurement: '°C'
|
||||||
value_template: '{{ value_json.boilTemp }}'
|
value_template: '{{ value_json.boilTemp }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/ems-esp/boiler_data'
|
||||||
name: 'Pump modulation'
|
name: 'Pump modulation'
|
||||||
unit_of_measurement: '%'
|
unit_of_measurement: '%'
|
||||||
value_template: '{{ value_json.pumpMod }}'
|
value_template: '{{ value_json.pumpMod }}'
|
||||||
@@ -137,7 +143,6 @@
|
|||||||
value_template: '{{ as_timestamp(states.sensor.last_shower_duration.last_updated) | int | timestamp_custom("%-I:%M %P on %a %-d %b") }}'
|
value_template: '{{ as_timestamp(states.sensor.last_shower_duration.last_updated) | int | timestamp_custom("%-I:%M %P on %a %-d %b") }}'
|
||||||
|
|
||||||
boiler_updated:
|
boiler_updated:
|
||||||
# value_template: '{{ (as_timestamp(now()) - as_timestamp(states.sensor.boiler_temperature.last_updated)) | int | timestamp_custom("%-M min %-S seconds ago") }}'
|
|
||||||
value_template: '{{ as_timestamp(states.sensor.boiler_temperature.last_updated) | timestamp_custom("%H:%M on %d/%b") }}'
|
value_template: '{{ as_timestamp(states.sensor.boiler_temperature.last_updated) | timestamp_custom("%H:%M on %d/%b") }}'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
name: "Shower Timer"
|
name: "Shower Timer"
|
||||||
state_topic: "home/boiler/shower_timer"
|
state_topic: "home/ems-esp/shower_timer"
|
||||||
command_topic: "home/boiler/shower_timer"
|
command_topic: "home/ems-esp/shower_timer"
|
||||||
payload_on: "1"
|
payload_on: "1"
|
||||||
payload_off: "0"
|
payload_off: "0"
|
||||||
optimistic: false
|
optimistic: false
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
name: "Long Shower Alert"
|
name: "Long Shower Alert"
|
||||||
state_topic: "home/boiler/shower_alert"
|
state_topic: "home/ems-esp/shower_alert"
|
||||||
command_topic: "home/boiler/shower_alert"
|
command_topic: "home/ems-esp/shower_alert"
|
||||||
payload_on: "1"
|
payload_on: "1"
|
||||||
payload_off: "0"
|
payload_off: "0"
|
||||||
optimistic: false
|
optimistic: false
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ views:
|
|||||||
- sensor.warm_water_selected_temperature
|
- sensor.warm_water_selected_temperature
|
||||||
- sensor.warm_water_current_temperature
|
- sensor.warm_water_current_temperature
|
||||||
- sensor.warm_water_activated
|
- sensor.warm_water_activated
|
||||||
- sensor.warm_water_3way_valve
|
- sensor.warm_water_3_way_valve
|
||||||
|
- sensor.warm_water_tapwater_flow_rate
|
||||||
- type: divider
|
- type: divider
|
||||||
- sensor.boiler_temperature
|
- sensor.boiler_temperature
|
||||||
- sensor.return_temperature
|
- sensor.return_temperature
|
||||||
@@ -38,16 +39,15 @@ views:
|
|||||||
- type: divider
|
- type: divider
|
||||||
- sensor.last_shower_duration
|
- sensor.last_shower_duration
|
||||||
- sensor.showertime_time
|
- sensor.showertime_time
|
||||||
- type: custom:button-card
|
- type: entity-button
|
||||||
color: auto
|
|
||||||
icon: mdi:shower-head
|
icon: mdi:shower-head
|
||||||
name: send a cold shot of shower water
|
name: send a cold shot of shower water
|
||||||
style:
|
|
||||||
- text-transform: none
|
|
||||||
- color: rgb(28, 128, 199)
|
|
||||||
- font-weight: bold
|
|
||||||
entity: script.shower_coldshot
|
entity: script.shower_coldshot
|
||||||
show_state: false
|
tap_action:
|
||||||
|
action: call-service
|
||||||
|
service: script.turn_on
|
||||||
|
service_data:
|
||||||
|
entity_id: script.shower_coldshot
|
||||||
|
|
||||||
- type: vertical-stack
|
- type: vertical-stack
|
||||||
cards:
|
cards:
|
||||||
@@ -57,3 +57,6 @@ views:
|
|||||||
- sensor.dark_sky_temperature
|
- sensor.dark_sky_temperature
|
||||||
- type: thermostat
|
- type: thermostat
|
||||||
entity: climate.thermostat
|
entity: climate.thermostat
|
||||||
|
- type: thermostat
|
||||||
|
name: WarmWater
|
||||||
|
entity: climate.boiler
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 72 KiB |
BIN
doc/telnet/telnet_menu.jpg
Normal file
|
After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 130 KiB |
@@ -1,466 +0,0 @@
|
|||||||
// Boiler
|
|
||||||
// Espurna version
|
|
||||||
// Paul Derbyshire - https://github.com/proddy/EMS-ESP-Boiler
|
|
||||||
|
|
||||||
#include "emsuart.h"
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <ems.h>
|
|
||||||
|
|
||||||
#define myDebug(...) debugSend(__VA_ARGS__)
|
|
||||||
|
|
||||||
#define BOILER_THERMOSTAT_ENABLED 1
|
|
||||||
#define BOILER_SHOWER_ENABLED 1
|
|
||||||
#define BOILER_POLLING_ENABLED 0
|
|
||||||
#define BOILER_LOGGING_NONE 1
|
|
||||||
|
|
||||||
// home/boiler/
|
|
||||||
#define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values
|
|
||||||
#define TOPIC_THERMOSTAT_TEMP "thermostat_temp" // for received thermostat temp changes
|
|
||||||
#define TOPIC_THERMOSTAT_CURRTEMP "thermostat_currtemp" // current temperature
|
|
||||||
#define TOPIC_THERMOSTAT_SELTEMP "thermostat_seltemp" // selected temperature
|
|
||||||
#define TOPIC_THERMOSTAT_MODE "thermostat_mode" // selected temperature
|
|
||||||
#define TOPIC_BOILER_WARM_WATER_SELECTED_TEMPERATURE "boiler_wwtemp" // warm water selected temp
|
|
||||||
|
|
||||||
#define BOILERSEND_INTERVAL 60000 // send every minute to HA
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool wifi_connected;
|
|
||||||
bool boiler_online;
|
|
||||||
bool thermostat_enabled;
|
|
||||||
bool shower_timer; // true if we want to report back on shower times
|
|
||||||
bool shower_alert; // true if we want the cold water reminder
|
|
||||||
} _Boiler_Status;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool showerOn;
|
|
||||||
bool hotWaterOn;
|
|
||||||
unsigned long timerStart; // ms
|
|
||||||
unsigned long timerPause; // ms
|
|
||||||
unsigned long duration; // ms
|
|
||||||
bool doingColdShot; // true if we've just sent a jolt of cold water
|
|
||||||
} _Boiler_Shower;
|
|
||||||
|
|
||||||
// store for overall system status
|
|
||||||
_Boiler_Status Boiler_Status;
|
|
||||||
_Boiler_Shower Boiler_Shower;
|
|
||||||
|
|
||||||
// Config
|
|
||||||
void _boilerConfigure() {
|
|
||||||
Boiler_Status.thermostat_enabled = getSetting("boilerThermostat", BOILER_THERMOSTAT_ENABLED).toInt() == 1;
|
|
||||||
ems_setThermostatEnabled(Boiler_Status.thermostat_enabled);
|
|
||||||
|
|
||||||
bool _boilerPolling = getSetting("boilerPolling", BOILER_POLLING_ENABLED).toInt() == 1;
|
|
||||||
ems_setPoll(_boilerPolling);
|
|
||||||
|
|
||||||
uint8_t _boilerLogging = getSetting("boilerLogging", BOILER_LOGGING_NONE).toInt();
|
|
||||||
ems_setLogging((_EMS_SYS_LOGGING)_boilerLogging);
|
|
||||||
|
|
||||||
Boiler_Status.shower_timer = getSetting("boilerShower", BOILER_SHOWER_ENABLED).toInt() == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// WEB callbacks
|
|
||||||
bool _boilerWebSocketOnReceive(const char * key, JsonVariant & value) {
|
|
||||||
return (strncmp(key, "boiler", 6) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _boilerWebSocketOnSend(JsonObject & root) {
|
|
||||||
root["boilerThermostat"] = getSetting("boilerThermostat", BOILER_THERMOSTAT_ENABLED).toInt() == 1;
|
|
||||||
root["boilerShower"] = getSetting("boilerShower", BOILER_SHOWER_ENABLED).toInt() == 1;
|
|
||||||
root["boilerPolling"] = getSetting("boilerPolling", BOILER_POLLING_ENABLED).toInt() == 1;
|
|
||||||
root["boilerLogging"] = getSetting("boilerLogging", BOILER_LOGGING_NONE).toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
// used if we have a button
|
|
||||||
void _boilerWebSocketOnAction(uint32_t client_id, const char * action, JsonObject & data) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert float to char
|
|
||||||
//char * _float_to_char(char * a, float f, uint8_t precision = 1);
|
|
||||||
char * _float_to_char(char * a, float f, uint8_t precision = 1) {
|
|
||||||
long p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
|
|
||||||
|
|
||||||
char * ret = a;
|
|
||||||
// check for 0x8000 (sensor missing), which has a -1 value
|
|
||||||
if (f == EMS_VALUE_FLOAT_NOTSET) {
|
|
||||||
strcpy(ret, "?");
|
|
||||||
} else {
|
|
||||||
long whole = (long)f;
|
|
||||||
itoa(whole, a, 10);
|
|
||||||
while (*a != '\0')
|
|
||||||
a++;
|
|
||||||
*a++ = '.';
|
|
||||||
long decimal = abs((long)((f - whole) * p[precision]));
|
|
||||||
itoa(decimal, a, 10);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert bool to text
|
|
||||||
char * _bool_to_char(char * s, uint8_t value) {
|
|
||||||
if (value == EMS_VALUE_INT_ON) {
|
|
||||||
strcpy(s, "on");
|
|
||||||
} else if (value == EMS_VALUE_INT_OFF) {
|
|
||||||
strcpy(s, "off");
|
|
||||||
} else {
|
|
||||||
strcpy(s, "?");
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert int to text value
|
|
||||||
char * _int_to_char(char * s, uint8_t value) {
|
|
||||||
if (value == EMS_VALUE_INT_NOTSET) {
|
|
||||||
strcpy(s, "?");
|
|
||||||
} else {
|
|
||||||
itoa(value, s, 10);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// takes a float value at prints it to debug log
|
|
||||||
void _renderFloatValue(const char * prefix, const char * postfix, float value) {
|
|
||||||
myDebug(" %s: ", prefix);
|
|
||||||
char s[20];
|
|
||||||
myDebug("%s", _float_to_char(s, value));
|
|
||||||
|
|
||||||
if (postfix != NULL) {
|
|
||||||
myDebug(" %s", postfix);
|
|
||||||
}
|
|
||||||
|
|
||||||
myDebug("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// takes an int value at prints it to debug log
|
|
||||||
void _renderIntValue(const char * prefix, const char * postfix, uint8_t value) {
|
|
||||||
myDebug(" %s: ", prefix);
|
|
||||||
char s[20];
|
|
||||||
myDebug("%s", _int_to_char(s, value));
|
|
||||||
|
|
||||||
if (postfix != NULL) {
|
|
||||||
myDebug(" %s", postfix);
|
|
||||||
}
|
|
||||||
|
|
||||||
myDebug("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// takes a bool value at prints it to debug log
|
|
||||||
void _renderBoolValue(const char * prefix, uint8_t value) {
|
|
||||||
myDebug(" %s: ", prefix);
|
|
||||||
char s[20];
|
|
||||||
myDebug("%s\n", _bool_to_char(s, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show command - display stats on an 's' command
|
|
||||||
void showInfo() {
|
|
||||||
// General stats from EMS bus
|
|
||||||
myDebug("%sEMS-ESP-Boiler system stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
|
||||||
myDebug(" System Logging is set to ");
|
|
||||||
_EMS_SYS_LOGGING sysLog = ems_getLogging();
|
|
||||||
if (sysLog == EMS_SYS_LOGGING_BASIC) {
|
|
||||||
myDebug("Basic");
|
|
||||||
} else if (sysLog == EMS_SYS_LOGGING_VERBOSE) {
|
|
||||||
myDebug("Verbose");
|
|
||||||
} else {
|
|
||||||
myDebug("None");
|
|
||||||
}
|
|
||||||
|
|
||||||
myDebug("\n # EMS type handlers: %d\n", ems_getEmsTypesCount());
|
|
||||||
|
|
||||||
myDebug(" Thermostat is %s, Poll is %s, Tx is %s, Shower Timer is %s, Shower Alert is %s\n",
|
|
||||||
((Boiler_Status.thermostat_enabled) ? "enabled" : "disabled"),
|
|
||||||
((EMS_Sys_Status.emsPollEnabled) ? "enabled" : "disabled"),
|
|
||||||
((EMS_Sys_Status.emsTxEnabled) ? "enabled" : "disabled"),
|
|
||||||
((Boiler_Status.shower_timer) ? "enabled" : "disabled"),
|
|
||||||
((Boiler_Status.shower_alert) ? "enabled" : "disabled"));
|
|
||||||
|
|
||||||
myDebug(" EMS Bus Stats: Connected=%s, # Rx telegrams=%d, # Tx telegrams=%d, # Crc Errors=%d, ",
|
|
||||||
(Boiler_Status.boiler_online ? "yes" : "no"),
|
|
||||||
EMS_Sys_Status.emsRxPgks,
|
|
||||||
EMS_Sys_Status.emsTxPkgs,
|
|
||||||
EMS_Sys_Status.emxCrcErr);
|
|
||||||
|
|
||||||
myDebug("Rx Status=");
|
|
||||||
switch (EMS_Sys_Status.emsRxStatus) {
|
|
||||||
case EMS_RX_IDLE:
|
|
||||||
myDebug("idle");
|
|
||||||
break;
|
|
||||||
case EMS_RX_ACTIVE:
|
|
||||||
myDebug("active");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
myDebug(", Tx Status=");
|
|
||||||
switch (EMS_Sys_Status.emsTxStatus) {
|
|
||||||
case EMS_TX_IDLE:
|
|
||||||
myDebug("idle");
|
|
||||||
break;
|
|
||||||
case EMS_TX_PENDING:
|
|
||||||
myDebug("pending");
|
|
||||||
break;
|
|
||||||
case EMS_TX_ACTIVE:
|
|
||||||
myDebug("active");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
myDebug(", Last Tx Action=");
|
|
||||||
switch (EMS_TxTelegram.action) {
|
|
||||||
case EMS_TX_READ:
|
|
||||||
myDebug("read");
|
|
||||||
break;
|
|
||||||
case EMS_TX_WRITE:
|
|
||||||
myDebug("write");
|
|
||||||
break;
|
|
||||||
case EMS_TX_VALIDATE:
|
|
||||||
myDebug("validate");
|
|
||||||
break;
|
|
||||||
case EMS_TX_NONE:
|
|
||||||
myDebug("none");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
myDebug("\n\n%sBoiler stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
|
||||||
|
|
||||||
// UBAParameterWW
|
|
||||||
_renderBoolValue("Warm Water activated", EMS_Boiler.wWActivated);
|
|
||||||
_renderBoolValue("Warm Water circulation pump available", EMS_Boiler.wWCircPump);
|
|
||||||
_renderIntValue("Warm Water selected temperature", "C", EMS_Boiler.wWSelTemp);
|
|
||||||
_renderIntValue("Warm Water desired temperature", "C", EMS_Boiler.wWDesiredTemp);
|
|
||||||
|
|
||||||
// UBAMonitorWWMessage
|
|
||||||
_renderFloatValue("Warm Water current temperature", "C", EMS_Boiler.wWCurTmp);
|
|
||||||
_renderIntValue("Warm Water # starts", "times", EMS_Boiler.wWStarts);
|
|
||||||
myDebug(" Warm Water active time: %d days %d hours %d minutes\n",
|
|
||||||
EMS_Boiler.wWWorkM / 1440,
|
|
||||||
(EMS_Boiler.wWWorkM % 1440) / 60,
|
|
||||||
EMS_Boiler.wWWorkM % 60);
|
|
||||||
_renderBoolValue("Warm Water 3-way valve", EMS_Boiler.wWHeat);
|
|
||||||
|
|
||||||
// UBAMonitorFast
|
|
||||||
_renderIntValue("Selected flow temperature", "C", EMS_Boiler.selFlowTemp);
|
|
||||||
_renderFloatValue("Current flow temperature", "C", EMS_Boiler.curFlowTemp);
|
|
||||||
_renderFloatValue("Return temperature", "C", EMS_Boiler.retTemp);
|
|
||||||
_renderBoolValue("Gas", EMS_Boiler.burnGas);
|
|
||||||
_renderBoolValue("Boiler pump", EMS_Boiler.heatPmp);
|
|
||||||
_renderBoolValue("Fan", EMS_Boiler.fanWork);
|
|
||||||
_renderBoolValue("Ignition", EMS_Boiler.ignWork);
|
|
||||||
_renderBoolValue("Circulation pump", EMS_Boiler.wWCirc);
|
|
||||||
_renderIntValue("Burner selected max power", "%", EMS_Boiler.selBurnPow);
|
|
||||||
_renderIntValue("Burner current power", "%", EMS_Boiler.curBurnPow);
|
|
||||||
_renderFloatValue("Flame current", "uA", EMS_Boiler.flameCurr);
|
|
||||||
_renderFloatValue("System pressure", "bar", EMS_Boiler.sysPress);
|
|
||||||
|
|
||||||
// UBAMonitorSlow
|
|
||||||
_renderFloatValue("Outside temperature", "C", EMS_Boiler.extTemp);
|
|
||||||
_renderFloatValue("Boiler temperature", "C", EMS_Boiler.boilTemp);
|
|
||||||
_renderIntValue("Pump modulation", "%", EMS_Boiler.pumpMod);
|
|
||||||
_renderIntValue("Burner # restarts", "times", EMS_Boiler.burnStarts);
|
|
||||||
myDebug(" Total burner operating time: %d days %d hours %d minutes\n",
|
|
||||||
EMS_Boiler.burnWorkMin / 1440,
|
|
||||||
(EMS_Boiler.burnWorkMin % 1440) / 60,
|
|
||||||
EMS_Boiler.burnWorkMin % 60);
|
|
||||||
myDebug(" Total heat operating time: %d days %d hours %d minutes\n",
|
|
||||||
EMS_Boiler.heatWorkMin / 1440,
|
|
||||||
(EMS_Boiler.heatWorkMin % 1440) / 60,
|
|
||||||
EMS_Boiler.heatWorkMin % 60);
|
|
||||||
|
|
||||||
// Thermostat stats
|
|
||||||
if (Boiler_Status.thermostat_enabled) {
|
|
||||||
myDebug("\n%sThermostat stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
|
||||||
myDebug(" Thermostat time is %02d:%02d:%02d %d/%d/%d\n",
|
|
||||||
EMS_Thermostat.hour,
|
|
||||||
EMS_Thermostat.minute,
|
|
||||||
EMS_Thermostat.second,
|
|
||||||
EMS_Thermostat.day,
|
|
||||||
EMS_Thermostat.month,
|
|
||||||
EMS_Thermostat.year + 2000);
|
|
||||||
|
|
||||||
_renderFloatValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp);
|
|
||||||
_renderFloatValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp);
|
|
||||||
myDebug(" Mode is set to ");
|
|
||||||
if (EMS_Thermostat.mode == 0) {
|
|
||||||
myDebug("low\n");
|
|
||||||
} else if (EMS_Thermostat.mode == 1) {
|
|
||||||
myDebug("manual\n");
|
|
||||||
} else if (EMS_Thermostat.mode == 2) {
|
|
||||||
myDebug("auto\n");
|
|
||||||
} else {
|
|
||||||
myDebug("?\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
myDebug("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init telnet commands
|
|
||||||
void _boilerInitCommands() {
|
|
||||||
settingsRegisterCommand(F("BOILER.INFO"), [](Embedis * e) {
|
|
||||||
showInfo();
|
|
||||||
DEBUG_MSG(_boilerGetConfig().c_str());
|
|
||||||
DEBUG_MSG_P(PSTR("+OK\n"));
|
|
||||||
});
|
|
||||||
|
|
||||||
settingsRegisterCommand(F("BOILER.POLLING"), [](Embedis * e) {
|
|
||||||
if (e->argc < 2) {
|
|
||||||
DEBUG_MSG_P(PSTR("-ERROR: arg is 0 or 1\n"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int param = String(e->argv[1]).toInt();
|
|
||||||
|
|
||||||
bool b = setSetting("boilerPolling", param);
|
|
||||||
if (b) {
|
|
||||||
_boilerConfigure();
|
|
||||||
}
|
|
||||||
|
|
||||||
wsSend(_boilerWebSocketOnSend); // update web
|
|
||||||
|
|
||||||
DEBUG_MSG_P(PSTR("+OK\n"));
|
|
||||||
});
|
|
||||||
|
|
||||||
settingsRegisterCommand(F("BOILER.LOGGING"), [](Embedis * e) {
|
|
||||||
if (e->argc < 2) {
|
|
||||||
DEBUG_MSG_P(PSTR("-ERROR: arg is 0, 1 or 2\n"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int param = String(e->argv[1]).toInt();
|
|
||||||
|
|
||||||
bool b = setSetting("boilerLogging", param);
|
|
||||||
if (b) {
|
|
||||||
_boilerConfigure();
|
|
||||||
}
|
|
||||||
|
|
||||||
wsSend(_boilerWebSocketOnSend); // update web
|
|
||||||
|
|
||||||
DEBUG_MSG_P(PSTR("+OK\n"));
|
|
||||||
});
|
|
||||||
|
|
||||||
settingsRegisterCommand(F("BOILER.SEND"), [](Embedis * e) {
|
|
||||||
if (e->argc < 2) {
|
|
||||||
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int cmd = String(e->argv[1]).toInt();
|
|
||||||
DEBUG_MSG_P(PSTR("Sending %d\n"), cmd);
|
|
||||||
DEBUG_MSG_P(PSTR("+OK\n"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// send values to HA via MQTT
|
|
||||||
void publishValues() {
|
|
||||||
char s[20]; // for formatting strings
|
|
||||||
|
|
||||||
// Boiler values as one JSON object
|
|
||||||
StaticJsonBuffer<512> jsonBuffer;
|
|
||||||
char data[512];
|
|
||||||
JsonObject & root = jsonBuffer.createObject();
|
|
||||||
|
|
||||||
root["wWSelTemp"] = _int_to_char(s, EMS_Boiler.wWSelTemp);
|
|
||||||
root["wWActivated"] = _bool_to_char(s, EMS_Boiler.wWActivated);
|
|
||||||
root["wWCurTmp"] = _float_to_char(s, EMS_Boiler.wWCurTmp);
|
|
||||||
root["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat);
|
|
||||||
root["curFlowTemp"] = _float_to_char(s, EMS_Boiler.curFlowTemp);
|
|
||||||
root["retTemp"] = _float_to_char(s, EMS_Boiler.retTemp);
|
|
||||||
root["burnGas"] = _bool_to_char(s, EMS_Boiler.burnGas);
|
|
||||||
root["heatPmp"] = _bool_to_char(s, EMS_Boiler.heatPmp);
|
|
||||||
root["fanWork"] = _bool_to_char(s, EMS_Boiler.fanWork);
|
|
||||||
root["ignWork"] = _bool_to_char(s, EMS_Boiler.ignWork);
|
|
||||||
root["wWCirc"] = _bool_to_char(s, EMS_Boiler.wWCirc);
|
|
||||||
root["selBurnPow"] = _int_to_char(s, EMS_Boiler.selBurnPow);
|
|
||||||
root["curBurnPow"] = _int_to_char(s, EMS_Boiler.curBurnPow);
|
|
||||||
root["sysPress"] = _float_to_char(s, EMS_Boiler.sysPress);
|
|
||||||
root["boilTemp"] = _float_to_char(s, EMS_Boiler.boilTemp);
|
|
||||||
root["pumpMod"] = _int_to_char(s, EMS_Boiler.pumpMod);
|
|
||||||
|
|
||||||
size_t len = root.measureLength();
|
|
||||||
root.printTo(data, len + 1); // form the json string
|
|
||||||
mqttSend(TOPIC_BOILER_DATA, data);
|
|
||||||
|
|
||||||
// handle the thermostat values separately
|
|
||||||
if (EMS_Sys_Status.emsThermostatEnabled) {
|
|
||||||
// only send thermostat values if we actually have them
|
|
||||||
if (((int)EMS_Thermostat.curr_roomTemp == (int)0) || ((int)EMS_Thermostat.setpoint_roomTemp == (int)0)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mqttSend(TOPIC_THERMOSTAT_CURRTEMP, _float_to_char(s, EMS_Thermostat.curr_roomTemp));
|
|
||||||
mqttSend(TOPIC_THERMOSTAT_SELTEMP, _float_to_char(s, EMS_Thermostat.setpoint_roomTemp));
|
|
||||||
|
|
||||||
// send mode 0=low, 1=manual, 2=auto
|
|
||||||
if (EMS_Thermostat.mode == 0) {
|
|
||||||
mqttSend(TOPIC_THERMOSTAT_MODE, "low");
|
|
||||||
} else if (EMS_Thermostat.mode == 1) {
|
|
||||||
mqttSend(TOPIC_THERMOSTAT_MODE, "manual");
|
|
||||||
} else {
|
|
||||||
mqttSend(TOPIC_THERMOSTAT_MODE, "auto");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void _boilerMQTTCallback(unsigned int type, const char * topic, const char * payload) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// TELNET commands
|
|
||||||
String _boilerGetConfig() {
|
|
||||||
String output;
|
|
||||||
|
|
||||||
// get values and print them
|
|
||||||
output = String("Thermostat is ") + getSetting("boilerThermostat") + String(", Polling is ")
|
|
||||||
+ getSetting("boilerPolling") + String(", Logging is ") + getSetting("boilerLogging")
|
|
||||||
+ String(", Shower is ") + getSetting("boilerShower") + String("\n");
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SETUP
|
|
||||||
void extraSetup() {
|
|
||||||
boilerSetup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void boilerSetup() {
|
|
||||||
// configure
|
|
||||||
_boilerConfigure();
|
|
||||||
|
|
||||||
wsOnSendRegister(_boilerWebSocketOnSend);
|
|
||||||
wsOnActionRegister(_boilerWebSocketOnAction);
|
|
||||||
wsOnReceiveRegister(_boilerWebSocketOnReceive);
|
|
||||||
|
|
||||||
mqttRegister(_boilerMQTTCallback);
|
|
||||||
|
|
||||||
_boilerInitCommands(); // telnet
|
|
||||||
|
|
||||||
// init shower
|
|
||||||
Boiler_Shower.timerStart = 0;
|
|
||||||
Boiler_Shower.timerPause = 0;
|
|
||||||
Boiler_Shower.duration = 0;
|
|
||||||
Boiler_Shower.doingColdShot = false;
|
|
||||||
|
|
||||||
// ems init values
|
|
||||||
ems_init();
|
|
||||||
|
|
||||||
// start uart
|
|
||||||
emsuart_init();
|
|
||||||
|
|
||||||
// Register loop
|
|
||||||
espurnaRegisterLoop(_boilerLoop);
|
|
||||||
|
|
||||||
espurnaRegisterReload([]() { _boilerConfigure(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
// LOOP
|
|
||||||
void _boilerLoop() {
|
|
||||||
static unsigned long last_boilersend = 0;
|
|
||||||
if ((last_boilersend == 0) || (millis() - last_boilersend > BOILERSEND_INTERVAL)) {
|
|
||||||
last_boilersend = millis();
|
|
||||||
|
|
||||||
// get 0x33 WW values manually
|
|
||||||
ems_doReadCommand(EMS_TYPE_UBAParameterWW, EMS_ID_BOILER);
|
|
||||||
|
|
||||||
#if MQTT_SUPPORT
|
|
||||||
// send MQTT
|
|
||||||
publishValues();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'"
|
|
||||||
// e.g.
|
|
||||||
// build_flags = -g -DMQTT_MAX_PACKET_SIZE=400 ${env.ESPURNA_FLAGS} -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH -DUSE_CUSTOM_H -DUSE_EXTRA
|
|
||||||
|
|
||||||
#undef EMBEDDED_WEB
|
|
||||||
#define EMBEDDED_WEB 1
|
|
||||||
|
|
||||||
//#undef MQTT_TOPIC
|
|
||||||
//#define MQTT_TOPIC "/{identifier}"
|
|
||||||
// default is "{hostname}"
|
|
||||||
|
|
||||||
#undef ENABLE_DOMOTICZ
|
|
||||||
#define ENABLE_DOMOTICZ 0
|
|
||||||
|
|
||||||
#undef THINGSPEAK_SUPPORT
|
|
||||||
#define THINGSPEAK_SUPPORT 0
|
|
||||||
|
|
||||||
#undef SCHEDULER_SUPPORT
|
|
||||||
#define SCHEDULER_SUPPORT 0
|
|
||||||
|
|
||||||
#undef ENABLE_FAUXMO
|
|
||||||
#define ENABLE_FAUXMO 0
|
|
||||||
|
|
||||||
#undef DEBUG_SERIAL_SUPPORT
|
|
||||||
#define DEBUG_SERIAL_SUPPORT 0
|
|
||||||
1893
espurna/index.html
1178
lib/MyESP/MyESP.cpp
Normal file
228
lib/MyESP/MyESP.h
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* MyESP.h
|
||||||
|
*
|
||||||
|
* Paul Derbyshire - December 2018
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef MyEMS_h
|
||||||
|
#define MyEMS_h
|
||||||
|
|
||||||
|
#define MYESP_VERSION "1.1.5"
|
||||||
|
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <ArduinoOTA.h>
|
||||||
|
#include <AsyncMqttClient.h> // https://github.com/marvinroger/async-mqtt-client and for ESP32 see https://github.com/marvinroger/async-mqtt-client/issues/127
|
||||||
|
#include <DNSServer.h>
|
||||||
|
#include <FS.h>
|
||||||
|
#include <JustWifi.h> // https://github.com/xoseperez/justwifi
|
||||||
|
#include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
//#include <ESPmDNS.h>
|
||||||
|
#include <SPIFFS.h> // added for ESP32
|
||||||
|
#define ets_vsnprintf vsnprintf // added for ESP32
|
||||||
|
#define OTA_PORT 8266
|
||||||
|
#else
|
||||||
|
//#include <ESP8266mDNS.h>
|
||||||
|
#include <ESPAsyncTCP.h>
|
||||||
|
#define OTA_PORT 3232
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MYEMS_CONFIG_FILE "/config.json"
|
||||||
|
|
||||||
|
#define LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms)
|
||||||
|
|
||||||
|
// WIFI
|
||||||
|
#define WIFI_CONNECT_TIMEOUT 10000 // Connecting timeout for WIFI in ms
|
||||||
|
#define WIFI_RECONNECT_INTERVAL 60000 // If could not connect to WIFI, retry after this time in ms
|
||||||
|
|
||||||
|
// MQTT
|
||||||
|
#define MQTT_PORT 1883 // MQTT port
|
||||||
|
#define MQTT_RECONNECT_DELAY_MIN 2000 // Try to reconnect in 3 seconds upon disconnection
|
||||||
|
#define MQTT_RECONNECT_DELAY_STEP 3000 // Increase the reconnect delay in 3 seconds after each failed attempt
|
||||||
|
#define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most
|
||||||
|
#define MQTT_MAX_SIZE 600 // max length of MQTT message
|
||||||
|
#define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT message
|
||||||
|
|
||||||
|
// Internal MQTT events
|
||||||
|
#define MQTT_CONNECT_EVENT 0
|
||||||
|
#define MQTT_DISCONNECT_EVENT 1
|
||||||
|
#define MQTT_MESSAGE_EVENT 2
|
||||||
|
|
||||||
|
// Telnet
|
||||||
|
#define TELNET_SERIAL_BAUD 115200
|
||||||
|
#define TELNET_MAX_COMMAND_LENGTH 80 // length of a command
|
||||||
|
#define TELNET_EVENT_CONNECT 1
|
||||||
|
#define TELNET_EVENT_DISCONNECT 0
|
||||||
|
|
||||||
|
// ANSI Colors
|
||||||
|
#define COLOR_RESET "\x1B[0m"
|
||||||
|
#define COLOR_BLACK "\x1B[0;30m"
|
||||||
|
#define COLOR_RED "\x1B[0;31m"
|
||||||
|
#define COLOR_GREEN "\x1B[0;32m"
|
||||||
|
#define COLOR_YELLOW "\x1B[0;33m"
|
||||||
|
#define COLOR_BLUE "\x1B[0;34m"
|
||||||
|
#define COLOR_MAGENTA "\x1B[0;35m"
|
||||||
|
#define COLOR_CYAN "\x1B[0;36m"
|
||||||
|
#define COLOR_WHITE "\x1B[0;37m"
|
||||||
|
#define COLOR_BOLD_ON "\x1B[1m"
|
||||||
|
#define COLOR_BOLD_OFF "\x1B[22m" // fixed by Scott Arlott
|
||||||
|
|
||||||
|
// SPIFFS
|
||||||
|
#define SPIFFS_MAXSIZE 500 // https://arduinojson.org/v5/assistant/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char key[40];
|
||||||
|
char description[100];
|
||||||
|
} command_t;
|
||||||
|
|
||||||
|
typedef enum { MYESP_FSACTION_SET, MYESP_FSACTION_LIST, MYESP_FSACTION_SAVE, MYESP_FSACTION_LOAD } MYESP_FSACTION;
|
||||||
|
|
||||||
|
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
|
||||||
|
typedef std::function<void()> wifi_callback_f;
|
||||||
|
typedef std::function<void()> ota_callback_f;
|
||||||
|
typedef std::function<void(uint8_t, const char *)> telnetcommand_callback_f;
|
||||||
|
typedef std::function<void(uint8_t)> telnet_callback_f;
|
||||||
|
typedef std::function<bool(MYESP_FSACTION, const JsonObject json)> fs_callback_f;
|
||||||
|
typedef std::function<bool(MYESP_FSACTION, uint8_t, const char *, const char *)> fs_settings_callback_f;
|
||||||
|
|
||||||
|
// calculates size of an 2d array at compile time
|
||||||
|
template <typename T, size_t N>
|
||||||
|
constexpr size_t ArraySize(T (&)[N]) {
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
|
||||||
|
// class definition
|
||||||
|
class MyESP {
|
||||||
|
public:
|
||||||
|
MyESP();
|
||||||
|
~MyESP();
|
||||||
|
|
||||||
|
// wifi
|
||||||
|
void setWIFICallback(void (*callback)());
|
||||||
|
void setWIFI(const char * wifi_ssid, const char * wifi_password, wifi_callback_f callback);
|
||||||
|
bool isWifiConnected();
|
||||||
|
|
||||||
|
// mqtt
|
||||||
|
void mqttSubscribe(const char * topic);
|
||||||
|
void mqttUnsubscribe(const char * topic);
|
||||||
|
void mqttPublish(const char * topic, const char * payload);
|
||||||
|
void setMQTT(const char * mqtt_host,
|
||||||
|
const char * mqtt_username,
|
||||||
|
const char * mqtt_password,
|
||||||
|
const char * mqtt_base,
|
||||||
|
unsigned long mqtt_keepalive,
|
||||||
|
unsigned char mqtt_qos,
|
||||||
|
bool mqtt_retain,
|
||||||
|
const char * mqtt_will_topic,
|
||||||
|
const char * mqtt_will_online_payload,
|
||||||
|
const char * mqtt_will_offline_payload,
|
||||||
|
mqtt_callback_f callback);
|
||||||
|
|
||||||
|
// OTA
|
||||||
|
void setOTA(ota_callback_f OTACallback);
|
||||||
|
|
||||||
|
// debug & telnet
|
||||||
|
void myDebug(const char * format, ...);
|
||||||
|
void myDebug_P(PGM_P format_P, ...);
|
||||||
|
void setTelnet(command_t * cmds, uint8_t count, telnetcommand_callback_f callback_cmd, telnet_callback_f callback);
|
||||||
|
bool getUseSerial();
|
||||||
|
|
||||||
|
// FS
|
||||||
|
void setSettings(fs_callback_f callback, fs_settings_callback_f fs_settings_callback);
|
||||||
|
bool fs_saveConfig();
|
||||||
|
|
||||||
|
// general
|
||||||
|
void end();
|
||||||
|
void loop();
|
||||||
|
void begin(const char * app_hostname, const char * app_name, const char * app_version);
|
||||||
|
void setBoottime(const char * boottime);
|
||||||
|
void resetESP();
|
||||||
|
uint16_t getSystemLoadAverage();
|
||||||
|
int getWifiQuality();
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
// mqtt
|
||||||
|
AsyncMqttClient mqttClient;
|
||||||
|
unsigned long _mqtt_reconnect_delay;
|
||||||
|
void _mqttOnMessage(char * topic, char * payload, size_t len);
|
||||||
|
void _mqttConnect();
|
||||||
|
void _mqtt_setup();
|
||||||
|
mqtt_callback_f _mqtt_callback;
|
||||||
|
void _mqttOnConnect();
|
||||||
|
void _sendStart();
|
||||||
|
char * _mqttTopic(const char * topic);
|
||||||
|
char * _mqtt_host;
|
||||||
|
char * _mqtt_username;
|
||||||
|
char * _mqtt_password;
|
||||||
|
char * _mqtt_base;
|
||||||
|
unsigned long _mqtt_keepalive;
|
||||||
|
unsigned char _mqtt_qos;
|
||||||
|
bool _mqtt_retain;
|
||||||
|
char * _mqtt_will_topic;
|
||||||
|
char * _mqtt_will_online_payload;
|
||||||
|
char * _mqtt_will_offline_payload;
|
||||||
|
char * _mqtt_topic;
|
||||||
|
unsigned long _mqtt_last_connection;
|
||||||
|
bool _mqtt_connecting;
|
||||||
|
|
||||||
|
// wifi
|
||||||
|
DNSServer dnsServer; // For Access Point (AP) support
|
||||||
|
void _wifiCallback(justwifi_messages_t code, char * parameter);
|
||||||
|
void _wifi_setup();
|
||||||
|
wifi_callback_f _wifi_callback;
|
||||||
|
char * _wifi_ssid;
|
||||||
|
char * _wifi_password;
|
||||||
|
bool _wifi_connected;
|
||||||
|
|
||||||
|
// ota
|
||||||
|
ota_callback_f _ota_callback;
|
||||||
|
void _ota_setup();
|
||||||
|
void _OTACallback();
|
||||||
|
|
||||||
|
// telnet & debug
|
||||||
|
TelnetSpy SerialAndTelnet;
|
||||||
|
void _telnetConnected();
|
||||||
|
void _telnetDisconnected();
|
||||||
|
void _telnetHandle();
|
||||||
|
void _telnetCommand(char * commandLine);
|
||||||
|
char * _telnet_readWord();
|
||||||
|
void _telnet_setup();
|
||||||
|
char _command[TELNET_MAX_COMMAND_LENGTH]; // the input command from either Serial or Telnet
|
||||||
|
command_t * _helpProjectCmds; // Help of commands setted by project
|
||||||
|
uint8_t _helpProjectCmds_count; // # available commands
|
||||||
|
void _consoleShowHelp();
|
||||||
|
telnetcommand_callback_f _telnetcommand_callback; // Callable for projects commands
|
||||||
|
telnet_callback_f _telnet_callback; // callback for connect/disconnect
|
||||||
|
void _changeSetting(uint8_t wc, const char * setting, const char * value);
|
||||||
|
void _changeSetting2(const char * setting, const char * value1, const char * value2);
|
||||||
|
|
||||||
|
// fs
|
||||||
|
void _fs_setup();
|
||||||
|
bool _fs_loadConfig();
|
||||||
|
void _fs_printConfig();
|
||||||
|
void _fs_eraseConfig();
|
||||||
|
|
||||||
|
fs_callback_f _fs_callback;
|
||||||
|
fs_settings_callback_f _fs_settings_callback;
|
||||||
|
|
||||||
|
// general
|
||||||
|
char * _app_hostname;
|
||||||
|
char * _app_name;
|
||||||
|
char * _app_version;
|
||||||
|
char * _boottime;
|
||||||
|
bool _suspendOutput;
|
||||||
|
bool _use_serial;
|
||||||
|
void _printBuildTime(unsigned long rawTime);
|
||||||
|
|
||||||
|
// load average (0..100)
|
||||||
|
void _calculateLoad();
|
||||||
|
unsigned short int _load_average;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern MyESP myESP;
|
||||||
|
|
||||||
|
#endif
|
||||||
655
lib/TelnetSpy/TelnetSpy.cpp
Normal file
@@ -0,0 +1,655 @@
|
|||||||
|
/*
|
||||||
|
* TELNET SERVER FOR ESP8266 / ESP32
|
||||||
|
* Cloning the serial port via Telnet.
|
||||||
|
*
|
||||||
|
* Written by Wolfgang Mattis (arduino@yasheena.de).
|
||||||
|
* Version 1.1 / September 7, 2018.
|
||||||
|
* MIT license, all text above must be included in any redistribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
extern "C" {
|
||||||
|
#include "user_interface.h"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "TelnetSpy.h"
|
||||||
|
|
||||||
|
#ifndef min
|
||||||
|
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||||
|
#endif
|
||||||
|
#ifndef max
|
||||||
|
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static TelnetSpy * actualObject = NULL;
|
||||||
|
|
||||||
|
static void TelnetSpy_putc(char c) {
|
||||||
|
if (actualObject) {
|
||||||
|
actualObject->write(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TelnetSpy_ignore_putc(char c) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
TelnetSpy::TelnetSpy() {
|
||||||
|
port = TELNETSPY_PORT;
|
||||||
|
telnetServer = NULL;
|
||||||
|
started = false;
|
||||||
|
listening = false;
|
||||||
|
firstMainLoop = true;
|
||||||
|
usedSer = &Serial;
|
||||||
|
storeOffline = true;
|
||||||
|
connected = false;
|
||||||
|
callbackConnect = NULL;
|
||||||
|
callbackDisconnect = NULL;
|
||||||
|
welcomeMsg = strdup(TELNETSPY_WELCOME_MSG);
|
||||||
|
rejectMsg = strdup(TELNETSPY_REJECT_MSG);
|
||||||
|
minBlockSize = TELNETSPY_MIN_BLOCK_SIZE;
|
||||||
|
collectingTime = TELNETSPY_COLLECTING_TIME;
|
||||||
|
maxBlockSize = TELNETSPY_MAX_BLOCK_SIZE;
|
||||||
|
pingTime = TELNETSPY_PING_TIME;
|
||||||
|
pingRef = 0xFFFFFFFF;
|
||||||
|
waitRef = 0xFFFFFFFF;
|
||||||
|
telnetBuf = NULL;
|
||||||
|
bufLen = 0;
|
||||||
|
uint16_t size = TELNETSPY_BUFFER_LEN;
|
||||||
|
while (!setBufferSize(size)) {
|
||||||
|
size = size >> 1;
|
||||||
|
if (size < minBlockSize) {
|
||||||
|
setBufferSize(minBlockSize);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debugOutput = TELNETSPY_CAPTURE_OS_PRINT;
|
||||||
|
if (debugOutput) {
|
||||||
|
setDebugOutput(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TelnetSpy::~TelnetSpy() {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// added by proddy
|
||||||
|
void TelnetSpy::disconnectClient() {
|
||||||
|
if (client.connected()) {
|
||||||
|
client.flush();
|
||||||
|
client.stop();
|
||||||
|
}
|
||||||
|
if (connected && (callbackDisconnect != NULL)) {
|
||||||
|
callbackDisconnect();
|
||||||
|
}
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::setPort(uint16_t portToUse) {
|
||||||
|
port = portToUse;
|
||||||
|
if (listening) {
|
||||||
|
if (client.connected()) {
|
||||||
|
client.flush();
|
||||||
|
client.stop();
|
||||||
|
}
|
||||||
|
if (connected && (callbackDisconnect != NULL)) {
|
||||||
|
callbackDisconnect();
|
||||||
|
}
|
||||||
|
connected = false;
|
||||||
|
telnetServer->close();
|
||||||
|
delete telnetServer;
|
||||||
|
telnetServer = new WiFiServer(port);
|
||||||
|
if (started) {
|
||||||
|
telnetServer->begin();
|
||||||
|
telnetServer->setNoDelay(bufLen > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::setWelcomeMsg(const char * msg) {
|
||||||
|
if (welcomeMsg) {
|
||||||
|
free(welcomeMsg);
|
||||||
|
}
|
||||||
|
welcomeMsg = strdup(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::setRejectMsg(const char * msg) {
|
||||||
|
if (rejectMsg) {
|
||||||
|
free(rejectMsg);
|
||||||
|
}
|
||||||
|
rejectMsg = strdup(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::setMinBlockSize(uint16_t minSize) {
|
||||||
|
minBlockSize = min(max((uint16_t)1, minSize), maxBlockSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::setCollectingTime(uint16_t colTime) {
|
||||||
|
collectingTime = colTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::setMaxBlockSize(uint16_t maxSize) {
|
||||||
|
maxBlockSize = max(maxSize, minBlockSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelnetSpy::setBufferSize(uint16_t newSize) {
|
||||||
|
if (telnetBuf && (bufLen == newSize)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (newSize == 0) {
|
||||||
|
bufLen = 0;
|
||||||
|
if (telnetBuf) {
|
||||||
|
free(telnetBuf);
|
||||||
|
telnetBuf = NULL;
|
||||||
|
}
|
||||||
|
if (telnetServer) {
|
||||||
|
telnetServer->setNoDelay(false);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
newSize = max(newSize, minBlockSize);
|
||||||
|
uint16_t oldBufLen = bufLen;
|
||||||
|
bufLen = newSize;
|
||||||
|
uint16_t tmp;
|
||||||
|
if (!telnetBuf || (bufUsed == 0)) {
|
||||||
|
bufRdIdx = 0;
|
||||||
|
bufWrIdx = 0;
|
||||||
|
bufUsed = 0;
|
||||||
|
} else {
|
||||||
|
if (bufLen < oldBufLen) {
|
||||||
|
if (bufRdIdx < bufWrIdx) {
|
||||||
|
if (bufWrIdx > bufLen) {
|
||||||
|
tmp = min(bufLen, (uint16_t)(bufWrIdx - max(bufLen, bufRdIdx)));
|
||||||
|
memcpy(telnetBuf, &telnetBuf[bufWrIdx - tmp], tmp);
|
||||||
|
bufWrIdx = tmp;
|
||||||
|
if (bufWrIdx > bufRdIdx) {
|
||||||
|
bufRdIdx = bufWrIdx;
|
||||||
|
} else {
|
||||||
|
if (bufRdIdx > bufLen) {
|
||||||
|
bufRdIdx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bufRdIdx == bufWrIdx) {
|
||||||
|
bufUsed = bufLen;
|
||||||
|
} else {
|
||||||
|
bufUsed = bufWrIdx - bufRdIdx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (bufWrIdx > bufLen) {
|
||||||
|
memcpy(telnetBuf, &telnetBuf[bufWrIdx - bufLen], bufLen);
|
||||||
|
bufRdIdx = 0;
|
||||||
|
bufWrIdx = 0;
|
||||||
|
bufUsed = bufLen;
|
||||||
|
} else {
|
||||||
|
tmp = min(bufLen - bufWrIdx, oldBufLen - bufRdIdx);
|
||||||
|
memcpy(&telnetBuf[bufLen - tmp], &telnetBuf[oldBufLen - tmp], tmp);
|
||||||
|
bufRdIdx = bufLen - tmp;
|
||||||
|
bufUsed = bufWrIdx + tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char * temp = (char *)realloc(telnetBuf, bufLen);
|
||||||
|
if (!temp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
telnetBuf = temp;
|
||||||
|
if (telnetBuf && (bufLen > oldBufLen) && (bufRdIdx > bufWrIdx)) {
|
||||||
|
tmp = bufLen - (oldBufLen - bufRdIdx);
|
||||||
|
memcpy(&telnetBuf[tmp], &telnetBuf[bufRdIdx], oldBufLen - bufRdIdx);
|
||||||
|
bufRdIdx = tmp;
|
||||||
|
}
|
||||||
|
if (telnetServer) {
|
||||||
|
telnetServer->setNoDelay(true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t TelnetSpy::getBufferSize() {
|
||||||
|
if (!telnetBuf) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return bufLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::setStoreOffline(bool store) {
|
||||||
|
storeOffline = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelnetSpy::getStoreOffline() {
|
||||||
|
return storeOffline;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::setPingTime(uint16_t pngTime) {
|
||||||
|
pingTime = pngTime;
|
||||||
|
if (pingTime == 0) {
|
||||||
|
pingRef = 0xFFFFFFFF;
|
||||||
|
} else {
|
||||||
|
pingRef = (millis() & 0x7FFFFFF) + pingTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::setSerial(HardwareSerial * usedSerial) {
|
||||||
|
usedSer = usedSerial;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t TelnetSpy::write(uint8_t data) {
|
||||||
|
if (telnetBuf) {
|
||||||
|
if (storeOffline || client.connected()) {
|
||||||
|
if (bufUsed == bufLen) {
|
||||||
|
if (client.connected()) {
|
||||||
|
sendBlock();
|
||||||
|
}
|
||||||
|
if (bufUsed == bufLen) {
|
||||||
|
char c;
|
||||||
|
while (bufUsed > 0) {
|
||||||
|
c = pullTelnetBuf();
|
||||||
|
if (c == '\n') {
|
||||||
|
addTelnetBuf('\r');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (peekTelnetBuf() == '\r') {
|
||||||
|
pullTelnetBuf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addTelnetBuf(data);
|
||||||
|
/*
|
||||||
|
if (data == '\n') {
|
||||||
|
addTelnetBuf('\r'); // added by proddy, fix for Windows
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (client.connected()) {
|
||||||
|
client.write(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (usedSer) {
|
||||||
|
return usedSer->write(data);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this still needs some work
|
||||||
|
bool TelnetSpy::isSerialAvailable(void) {
|
||||||
|
if (usedSer) {
|
||||||
|
usedSer->write("test");
|
||||||
|
//Wait for four seconds or till data is available on serial, whichever occurs first.
|
||||||
|
while (usedSer->available() == 0 && millis() < 4000)
|
||||||
|
;
|
||||||
|
|
||||||
|
if (usedSer->available() > 0) {
|
||||||
|
(void)usedSer->read(); // We then clear the input buffer
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TelnetSpy::available(void) {
|
||||||
|
if (usedSer) {
|
||||||
|
int avail = usedSer->available();
|
||||||
|
if (avail > 0) {
|
||||||
|
return avail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (client.connected()) {
|
||||||
|
return telnetAvailable();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TelnetSpy::read(void) {
|
||||||
|
int val;
|
||||||
|
if (usedSer) {
|
||||||
|
val = usedSer->read();
|
||||||
|
if (val != -1) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (client.connected()) {
|
||||||
|
if (telnetAvailable()) {
|
||||||
|
val = client.read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TelnetSpy::peek(void) {
|
||||||
|
int val;
|
||||||
|
if (usedSer) {
|
||||||
|
val = usedSer->peek();
|
||||||
|
if (val != -1) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (client.connected()) {
|
||||||
|
if (telnetAvailable()) {
|
||||||
|
val = client.peek();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::flush(void) {
|
||||||
|
if (usedSer) {
|
||||||
|
usedSer->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
|
||||||
|
void TelnetSpy::begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin) {
|
||||||
|
if (usedSer) {
|
||||||
|
usedSer->begin(baud, config, mode, tx_pin);
|
||||||
|
}
|
||||||
|
started = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // ESP32
|
||||||
|
|
||||||
|
void TelnetSpy::begin(unsigned long baud, uint32_t config, int8_t rxPin, int8_t txPin, bool invert) {
|
||||||
|
if (usedSer) {
|
||||||
|
usedSer->begin(baud, config, rxPin, txPin, invert);
|
||||||
|
}
|
||||||
|
started = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void TelnetSpy::end() {
|
||||||
|
if (debugOutput) {
|
||||||
|
setDebugOutput(false);
|
||||||
|
}
|
||||||
|
if (usedSer) {
|
||||||
|
usedSer->end();
|
||||||
|
}
|
||||||
|
if (client.connected()) {
|
||||||
|
client.flush();
|
||||||
|
client.stop();
|
||||||
|
}
|
||||||
|
if (connected && (callbackDisconnect != NULL)) {
|
||||||
|
callbackDisconnect();
|
||||||
|
}
|
||||||
|
connected = false;
|
||||||
|
telnetServer->close();
|
||||||
|
delete telnetServer;
|
||||||
|
telnetServer = NULL;
|
||||||
|
listening = false;
|
||||||
|
started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
|
||||||
|
void TelnetSpy::swap(uint8_t tx_pin) {
|
||||||
|
if (usedSer) {
|
||||||
|
usedSer->swap(tx_pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::set_tx(uint8_t tx_pin) {
|
||||||
|
if (usedSer) {
|
||||||
|
usedSer->set_tx(tx_pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::pins(uint8_t tx, uint8_t rx) {
|
||||||
|
if (usedSer) {
|
||||||
|
usedSer->pins(tx, rx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelnetSpy::isTxEnabled(void) {
|
||||||
|
if (usedSer) {
|
||||||
|
return usedSer->isTxEnabled();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelnetSpy::isRxEnabled(void) {
|
||||||
|
if (usedSer) {
|
||||||
|
return usedSer->isRxEnabled();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int TelnetSpy::availableForWrite(void) {
|
||||||
|
if (usedSer) {
|
||||||
|
return min(usedSer->availableForWrite(), bufLen - bufUsed);
|
||||||
|
}
|
||||||
|
return bufLen - bufUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
TelnetSpy::operator bool() const {
|
||||||
|
if (usedSer) {
|
||||||
|
return (bool)*usedSer;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::setDebugOutput(bool en) {
|
||||||
|
debugOutput = en;
|
||||||
|
|
||||||
|
// TODO: figure out how to disable system printing for the ESP32
|
||||||
|
if (debugOutput) {
|
||||||
|
actualObject = this;
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
os_install_putc1((void *)TelnetSpy_putc); // Set system printing (os_printf) to TelnetSpy
|
||||||
|
system_set_os_print(true);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (actualObject == this) {
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
system_set_os_print(false);
|
||||||
|
os_install_putc1((void *)TelnetSpy_ignore_putc); // Ignore system printing
|
||||||
|
#endif
|
||||||
|
|
||||||
|
actualObject = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t TelnetSpy::baudRate(void) {
|
||||||
|
if (usedSer) {
|
||||||
|
return usedSer->baudRate();
|
||||||
|
}
|
||||||
|
return 115200;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::sendBlock() {
|
||||||
|
uint16_t len = bufUsed;
|
||||||
|
if (len > maxBlockSize) {
|
||||||
|
len = maxBlockSize;
|
||||||
|
}
|
||||||
|
len = min(len, (uint16_t)(bufLen - bufRdIdx));
|
||||||
|
client.write(&telnetBuf[bufRdIdx], len);
|
||||||
|
bufRdIdx += len;
|
||||||
|
if (bufRdIdx >= bufLen) {
|
||||||
|
bufRdIdx = 0;
|
||||||
|
}
|
||||||
|
bufUsed -= len;
|
||||||
|
if (bufUsed == 0) {
|
||||||
|
bufRdIdx = 0;
|
||||||
|
bufWrIdx = 0;
|
||||||
|
}
|
||||||
|
waitRef = 0xFFFFFFFF;
|
||||||
|
if (pingRef != 0xFFFFFFFF) {
|
||||||
|
pingRef = (millis() & 0x7FFFFFF) + pingTime;
|
||||||
|
if (pingRef > 0x7FFFFFFF) {
|
||||||
|
pingRef -= 0x80000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::addTelnetBuf(char c) {
|
||||||
|
telnetBuf[bufWrIdx] = c;
|
||||||
|
if (bufUsed == bufLen) {
|
||||||
|
bufRdIdx++;
|
||||||
|
if (bufRdIdx >= bufLen) {
|
||||||
|
bufRdIdx = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bufUsed++;
|
||||||
|
}
|
||||||
|
bufWrIdx++;
|
||||||
|
if (bufWrIdx >= bufLen) {
|
||||||
|
bufWrIdx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char TelnetSpy::pullTelnetBuf() {
|
||||||
|
if (bufUsed == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
char c = telnetBuf[bufRdIdx++];
|
||||||
|
if (bufRdIdx >= bufLen) {
|
||||||
|
bufRdIdx = 0;
|
||||||
|
}
|
||||||
|
bufUsed--;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
char TelnetSpy::peekTelnetBuf() {
|
||||||
|
if (bufUsed == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return telnetBuf[bufRdIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
int TelnetSpy::telnetAvailable() {
|
||||||
|
int n = client.available();
|
||||||
|
while (n > 0) {
|
||||||
|
if (0xff == client.peek()) { // If esc char for telnet NVT protocol data remove that telegram:
|
||||||
|
client.read(); // Remove esc char
|
||||||
|
n--;
|
||||||
|
if (0xff == client.peek()) { // If esc sequence for 0xFF data byte...
|
||||||
|
return n; // ...return info about available data (just this 0xFF data byte)
|
||||||
|
}
|
||||||
|
client.read(); // Skip the rest of the telegram of the telnet NVT protocol data
|
||||||
|
client.read();
|
||||||
|
n--;
|
||||||
|
n--;
|
||||||
|
} else { // If next char is a normal data byte...
|
||||||
|
return n; // ...return info about available data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelnetSpy::isClientConnected() {
|
||||||
|
return connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::setCallbackOnConnect(telnetSpyCallback callback) {
|
||||||
|
callbackConnect = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::setCallbackOnDisconnect(telnetSpyCallback callback) {
|
||||||
|
callbackDisconnect = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::serialPrint(char c) {
|
||||||
|
if (usedSer) {
|
||||||
|
usedSer->print(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TelnetSpy::handle() {
|
||||||
|
if (firstMainLoop) {
|
||||||
|
firstMainLoop = false;
|
||||||
|
// Between setup() and loop() the configuration for os_print may be changed so it must be renewed
|
||||||
|
if (debugOutput && (actualObject == this)) {
|
||||||
|
setDebugOutput(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!listening) {
|
||||||
|
if ((WiFi.status() == WL_DISCONNECTED) && (WiFi.getMode() & WIFI_AP)) {
|
||||||
|
if (usedSer) {
|
||||||
|
usedSer->println("[TELNET] in AP mode"); // added by Proddy
|
||||||
|
}
|
||||||
|
} else if (WiFi.status() != WL_CONNECTED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
telnetServer = new WiFiServer(port);
|
||||||
|
telnetServer->begin();
|
||||||
|
telnetServer->setNoDelay(bufLen > 0);
|
||||||
|
listening = true;
|
||||||
|
if (usedSer) {
|
||||||
|
usedSer->println("[TELNET] Telnet server started"); // added by Proddy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (telnetServer->hasClient()) {
|
||||||
|
if (client.connected()) {
|
||||||
|
WiFiClient rejectClient = telnetServer->available();
|
||||||
|
if (strlen(rejectMsg) > 0) {
|
||||||
|
rejectClient.write((const uint8_t *)rejectMsg, strlen(rejectMsg));
|
||||||
|
}
|
||||||
|
rejectClient.flush();
|
||||||
|
rejectClient.stop();
|
||||||
|
} else {
|
||||||
|
client = telnetServer->available();
|
||||||
|
if (strlen(welcomeMsg) > 0) {
|
||||||
|
client.write((const uint8_t *)welcomeMsg, strlen(welcomeMsg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (client.connected()) {
|
||||||
|
if (!connected) {
|
||||||
|
connected = true;
|
||||||
|
if (pingTime != 0) {
|
||||||
|
pingRef = (millis() & 0x7FFFFFF) + pingTime;
|
||||||
|
}
|
||||||
|
if (callbackConnect != NULL) {
|
||||||
|
callbackConnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (connected) {
|
||||||
|
connected = false;
|
||||||
|
client.flush();
|
||||||
|
client.stop();
|
||||||
|
pingRef = 0xFFFFFFFF;
|
||||||
|
waitRef = 0xFFFFFFFF;
|
||||||
|
if (callbackDisconnect != NULL) {
|
||||||
|
callbackDisconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.connected() && (bufUsed > 0)) {
|
||||||
|
if (bufUsed >= minBlockSize) {
|
||||||
|
sendBlock();
|
||||||
|
} else {
|
||||||
|
unsigned long m = millis() & 0x7FFFFFF;
|
||||||
|
if (waitRef == 0xFFFFFFFF) {
|
||||||
|
waitRef = m + collectingTime;
|
||||||
|
if (waitRef > 0x7FFFFFFF) {
|
||||||
|
waitRef -= 0x80000000;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!((waitRef < 0x20000000) && (m > 0x60000000)) && (m >= waitRef)) {
|
||||||
|
sendBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (client.connected() && (pingRef != 0xFFFFFFFF)) {
|
||||||
|
unsigned long m = millis() & 0x7FFFFFF;
|
||||||
|
if (!((pingRef < 0x20000000) && (m > 0x60000000)) && (m >= pingRef)) {
|
||||||
|
addTelnetBuf(0);
|
||||||
|
sendBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
281
lib/TelnetSpy/TelnetSpy.h
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
/*
|
||||||
|
* TELNET SERVER FOR ESP8266 / ESP32
|
||||||
|
* Cloning the serial port via Telnet.
|
||||||
|
*
|
||||||
|
* Written by Wolfgang Mattis (arduino@yasheena.de).
|
||||||
|
* Version 1.1 / September 7, 2018.
|
||||||
|
* MIT license, all text above must be included in any redistribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DESCRIPTION
|
||||||
|
*
|
||||||
|
* This module allows you "Debugging over the air". So if you already use
|
||||||
|
* ArduinoOTA this is a helpful extension for wireless development. Use
|
||||||
|
* "TelnetSpy" instead of "Serial" to send data to the serial port and a copy
|
||||||
|
* to a telnet connection. There is a circular buffer which allows to store the
|
||||||
|
* data while the telnet connection is not established. So its possible to
|
||||||
|
* collect data even when the Wifi and Telnet connections are still not
|
||||||
|
* established. Its also possible to create a telnet session only if it is
|
||||||
|
* neccessary: then you will get the already collected data as far as it is
|
||||||
|
* still stored in the circular buffer. Data send from telnet terminal to
|
||||||
|
* ESP8266 / ESP32 will be handled as data received by serial port. It is also
|
||||||
|
* possible to use more than one instance of TelnetSpy, for example to send
|
||||||
|
* control information on the first instance and data dumps on the second
|
||||||
|
* instance.
|
||||||
|
*
|
||||||
|
* USAGE
|
||||||
|
*
|
||||||
|
* Add the following line to your sketch:
|
||||||
|
* #include <TelnetSpy.h>
|
||||||
|
* TelnetSpy LOG;
|
||||||
|
*
|
||||||
|
* Add the following line to your initialisation block ( void setup() ):
|
||||||
|
* LOG.begin();
|
||||||
|
*
|
||||||
|
* Add the following line at the beginning of your main loop ( void loop() ):
|
||||||
|
* LOG.handle();
|
||||||
|
*
|
||||||
|
* Use the following functions of the TelnetSpy object to modify behavior
|
||||||
|
*
|
||||||
|
* Change the port number of this telnet server. If a client is already
|
||||||
|
* connected it will be disconnected.
|
||||||
|
* Default: 23
|
||||||
|
* void setPort(uint16_t portToUse);
|
||||||
|
*
|
||||||
|
* Change the message which will be send to the telnet client after a session
|
||||||
|
* is established.
|
||||||
|
* Default: "Connection established via TelnetSpy.\n"
|
||||||
|
* void setWelcomeMsg(char* msg);
|
||||||
|
*
|
||||||
|
* Change the message which will be send to the telnet client if another
|
||||||
|
* session is already established.
|
||||||
|
* Default: "TelnetSpy: Only one connection possible.\n"
|
||||||
|
* void setRejectMsg(char* msg);
|
||||||
|
*
|
||||||
|
* Change the amount of characters to collect before sending a telnet block.
|
||||||
|
* Default: 64
|
||||||
|
* void setMinBlockSize(uint16_t minSize);
|
||||||
|
*
|
||||||
|
* Change the time (in ms) to wait before sending a telnet block if its size is
|
||||||
|
* less than <minSize> (defined by setMinBlockSize).
|
||||||
|
* Default: 100
|
||||||
|
* void setCollectingTime(uint16_t colTime);
|
||||||
|
*
|
||||||
|
* Change the maximum size of the telnet packets to send.
|
||||||
|
* Default: 512
|
||||||
|
* void setMaxBlockSize(uint16_t maxSize);
|
||||||
|
*
|
||||||
|
* Change the size of the ring buffer. Set it to 0 to disable buffering.
|
||||||
|
* Changing size tries to preserve the already collected data. If the new
|
||||||
|
* buffer size is too small the youngest data will be preserved only. Returns
|
||||||
|
* false if the requested buffer size cannot be set.
|
||||||
|
* Default: 3000
|
||||||
|
* bool setBufferSize(uint16_t newSize);
|
||||||
|
*
|
||||||
|
* This function returns the actual size of the ring buffer.
|
||||||
|
* uint16_t getBufferSize();
|
||||||
|
*
|
||||||
|
* Enable / disable storing new data in the ring buffer if no telnet connection
|
||||||
|
* is established. This function allows you to store important data only. You
|
||||||
|
* can do this by disabling "storeOffline" for sending less important data.
|
||||||
|
* Default: true
|
||||||
|
* void setStoreOffline(bool store);
|
||||||
|
*
|
||||||
|
* Get actual state of storing data when offline.
|
||||||
|
* bool getStoreOffline();
|
||||||
|
*
|
||||||
|
* If no data is sent via TelnetSpy the detection of a disconnected client has
|
||||||
|
* a long timeout. Use setPingTime to define the time (in ms) without traffic
|
||||||
|
* after which a ping (chr(0)) is sent to the telnet client to detect a
|
||||||
|
* disconnect earlier. Use 0 as parameter to disable pings.
|
||||||
|
* Default: 1500
|
||||||
|
* void setPingTime(uint16_t pngTime);
|
||||||
|
*
|
||||||
|
* Set the serial port you want to use with this object (especially for ESP32)
|
||||||
|
* or NULL if no serial port should be used (telnet only).
|
||||||
|
* Default: Serial
|
||||||
|
* void setSerial(HardwareSerial* usedSerial);
|
||||||
|
*
|
||||||
|
* This function returns true, if a telnet client is connected.
|
||||||
|
* bool isClientConnected();
|
||||||
|
*
|
||||||
|
* This function installs a callback function which will be called on every
|
||||||
|
* telnet connect of this object (except rejected connect tries). Use NULL to
|
||||||
|
* remove the callback.
|
||||||
|
* Default: NULL
|
||||||
|
* void setCallbackOnConnect(void (*callback)());
|
||||||
|
*
|
||||||
|
* This function installs a callback function which will be called on every
|
||||||
|
* telnet disconnect of this object (except rejected connect tries). Use NULL
|
||||||
|
* to remove the callback.
|
||||||
|
* Default: NULL
|
||||||
|
* void setCallbackOnDisconnect(void (*callback)());
|
||||||
|
*
|
||||||
|
* HINT
|
||||||
|
*
|
||||||
|
* Add the following lines to your sketch:
|
||||||
|
* #define SERIAL TelnetSpy
|
||||||
|
* //#define SERIAL Serial
|
||||||
|
*
|
||||||
|
* Replace "Serial" with "SERIAL" in your sketch. Now you can switch between
|
||||||
|
* serial only and serial with telnet by changing the comments of the defines
|
||||||
|
* only.
|
||||||
|
*
|
||||||
|
* IMPORTANT
|
||||||
|
*
|
||||||
|
* To connect to the telnet server you have to:
|
||||||
|
* - establish the Wifi connection
|
||||||
|
* - execute "TelnetSpy.begin(WhatEverYouWant);"
|
||||||
|
*
|
||||||
|
* The order is not important.
|
||||||
|
*
|
||||||
|
* All you do with "Serial" you can also do with "TelnetSpy", but remember:
|
||||||
|
* Transfering data also via telnet will need more performance than the serial
|
||||||
|
* port only. So time critical things may be influenced.
|
||||||
|
*
|
||||||
|
* It is not possible to establish more than one telnet connection at the same
|
||||||
|
* time. But its possible to use more than one instance of TelnetSpy.
|
||||||
|
*
|
||||||
|
* If you have problems with low memory you may reduce the value of the define
|
||||||
|
* TELNETSPY_BUFFER_LEN for a smaller ring buffer on initialisation.
|
||||||
|
*
|
||||||
|
* Usage of void setDebugOutput(bool) to enable / disable of capturing of
|
||||||
|
* os_print calls when you have more than one TelnetSpy instance: That
|
||||||
|
* TelnetSpy object will handle this functionality where you used
|
||||||
|
* setDebugOutput at last. On default TelnetSpy has the capturing of OS_print
|
||||||
|
* calls enabled. So if you have more instances the last created instance will
|
||||||
|
* handle the capturing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TelnetSpy_h
|
||||||
|
#define TelnetSpy_h
|
||||||
|
|
||||||
|
#define TELNETSPY_BUFFER_LEN 3000
|
||||||
|
#define TELNETSPY_MIN_BLOCK_SIZE 64
|
||||||
|
#define TELNETSPY_COLLECTING_TIME 100
|
||||||
|
#define TELNETSPY_MAX_BLOCK_SIZE 512
|
||||||
|
#define TELNETSPY_PING_TIME 1500
|
||||||
|
#define TELNETSPY_PORT 23
|
||||||
|
#define TELNETSPY_CAPTURE_OS_PRINT true
|
||||||
|
#define TELNETSPY_WELCOME_MSG "Connection established via Telnet.\n"
|
||||||
|
#define TELNETSPY_REJECT_MSG "Telnet: Only one connection possible.\n"
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#else // ESP32
|
||||||
|
#include <WiFi.h>
|
||||||
|
#endif
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
|
||||||
|
class TelnetSpy : public Stream {
|
||||||
|
public:
|
||||||
|
TelnetSpy();
|
||||||
|
~TelnetSpy();
|
||||||
|
void handle(void);
|
||||||
|
void setPort(uint16_t portToUse);
|
||||||
|
void setWelcomeMsg(const char * msg);
|
||||||
|
void setRejectMsg(const char * msg);
|
||||||
|
void setMinBlockSize(uint16_t minSize);
|
||||||
|
void setCollectingTime(uint16_t colTime);
|
||||||
|
void setMaxBlockSize(uint16_t maxSize);
|
||||||
|
bool setBufferSize(uint16_t newSize);
|
||||||
|
uint16_t getBufferSize();
|
||||||
|
void setStoreOffline(bool store);
|
||||||
|
bool getStoreOffline();
|
||||||
|
void setPingTime(uint16_t pngTime);
|
||||||
|
void setSerial(HardwareSerial * usedSerial);
|
||||||
|
bool isClientConnected();
|
||||||
|
void serialPrint(char c);
|
||||||
|
|
||||||
|
void disconnectClient(); // added by Proddy
|
||||||
|
typedef std::function<void()> telnetSpyCallback; // added by Proddy
|
||||||
|
void setCallbackOnConnect(telnetSpyCallback callback); // changed by proddy
|
||||||
|
void setCallbackOnDisconnect(telnetSpyCallback callback); // changed by proddy
|
||||||
|
|
||||||
|
// Functions offered by HardwareSerial class:
|
||||||
|
#ifdef ESP8266
|
||||||
|
void begin(unsigned long baud) {
|
||||||
|
begin(baud, SERIAL_8N1, SERIAL_FULL, 1);
|
||||||
|
}
|
||||||
|
void begin(unsigned long baud, SerialConfig config) {
|
||||||
|
begin(baud, config, SERIAL_FULL, 1);
|
||||||
|
}
|
||||||
|
void begin(unsigned long baud, SerialConfig config, SerialMode mode) {
|
||||||
|
begin(baud, config, mode, 1);
|
||||||
|
}
|
||||||
|
void begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin);
|
||||||
|
#else // ESP32
|
||||||
|
void begin(unsigned long baud, uint32_t config = SERIAL_8N1, int8_t rxPin = -1, int8_t txPin = -1, bool invert = false);
|
||||||
|
#endif
|
||||||
|
void end();
|
||||||
|
#ifdef ESP8266
|
||||||
|
void swap() {
|
||||||
|
swap(1);
|
||||||
|
}
|
||||||
|
void swap(uint8_t tx_pin);
|
||||||
|
void set_tx(uint8_t tx_pin);
|
||||||
|
void pins(uint8_t tx, uint8_t rx);
|
||||||
|
bool isTxEnabled(void);
|
||||||
|
bool isRxEnabled(void);
|
||||||
|
#endif
|
||||||
|
int available(void) override;
|
||||||
|
int peek(void) override;
|
||||||
|
int read(void) override;
|
||||||
|
int availableForWrite(void);
|
||||||
|
void flush(void) override;
|
||||||
|
size_t write(uint8_t) override;
|
||||||
|
inline size_t write(unsigned long n) {
|
||||||
|
return write((uint8_t)n);
|
||||||
|
}
|
||||||
|
inline size_t write(long n) {
|
||||||
|
return write((uint8_t)n);
|
||||||
|
}
|
||||||
|
inline size_t write(unsigned int n) {
|
||||||
|
return write((uint8_t)n);
|
||||||
|
}
|
||||||
|
inline size_t write(int n) {
|
||||||
|
return write((uint8_t)n);
|
||||||
|
}
|
||||||
|
using Print::write;
|
||||||
|
operator bool() const;
|
||||||
|
void setDebugOutput(bool);
|
||||||
|
uint32_t baudRate(void);
|
||||||
|
|
||||||
|
bool isSerialAvailable(void);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void sendBlock(void);
|
||||||
|
void addTelnetBuf(char c);
|
||||||
|
char pullTelnetBuf();
|
||||||
|
char peekTelnetBuf();
|
||||||
|
int telnetAvailable();
|
||||||
|
WiFiServer * telnetServer;
|
||||||
|
WiFiClient client;
|
||||||
|
uint16_t port;
|
||||||
|
HardwareSerial * usedSer;
|
||||||
|
bool storeOffline;
|
||||||
|
bool started;
|
||||||
|
bool listening;
|
||||||
|
bool firstMainLoop;
|
||||||
|
unsigned long waitRef;
|
||||||
|
unsigned long pingRef;
|
||||||
|
uint16_t pingTime;
|
||||||
|
char * welcomeMsg;
|
||||||
|
char * rejectMsg;
|
||||||
|
uint16_t minBlockSize;
|
||||||
|
uint16_t collectingTime;
|
||||||
|
uint16_t maxBlockSize;
|
||||||
|
bool debugOutput;
|
||||||
|
char * telnetBuf;
|
||||||
|
uint16_t bufLen;
|
||||||
|
uint16_t bufUsed;
|
||||||
|
uint16_t bufRdIdx;
|
||||||
|
uint16_t bufWrIdx;
|
||||||
|
bool connected;
|
||||||
|
|
||||||
|
telnetSpyCallback callbackConnect; // added by proddy
|
||||||
|
telnetSpyCallback callbackDisconnect; // added by proddy
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,45 +1,37 @@
|
|||||||
[platformio]
|
[platformio]
|
||||||
; change this for your ESP8266 device
|
env_default = d1_mini
|
||||||
env_default = nodemcuv2
|
|
||||||
; env_default = d1_mini
|
|
||||||
|
|
||||||
[common]
|
[common]
|
||||||
platform = espressif8266
|
platform = espressif8266
|
||||||
; optional flags are -DUSE_LED -DSHOWER_TEST -DUSE_SERIAL
|
flash_mode = dout
|
||||||
build_flags = -g -w -DMQTT_MAX_PACKET_SIZE=400
|
build_flags = -g -w
|
||||||
build_flags_custom = '-DWIFI_SSID="my_ssid"' '-DWIFI_PASSWORD="my_password"' '-DMQTT_IP="my_broker_ip"' '-DMQTT_USER="my_broker_username"' '-DMQTT_PASS="my_broker_password"'
|
;build_flags = -g -w -DBUILD_TIME=$UNIX_TIME
|
||||||
|
|
||||||
|
wifi_settings =
|
||||||
|
; hard code if you prefer. Recommendation is to set from within the app when in Serial or AP mode
|
||||||
|
;wifi_settings = '-DWIFI_SSID="XXXX"' '-DWIFI_PASSWORD="XXXX"'
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
Time
|
|
||||||
PubSubClient
|
|
||||||
ArduinoJson
|
|
||||||
CRC32
|
CRC32
|
||||||
CircularBuffer
|
CircularBuffer
|
||||||
|
JustWifi
|
||||||
[env:nodemcuv2]
|
AsyncMqttClient
|
||||||
board = nodemcuv2
|
ArduinoJson
|
||||||
platform = ${common.platform}
|
; https://github.com/bblanchon/ArduinoJson#v5.13.5
|
||||||
framework = arduino
|
OneWire
|
||||||
lib_deps = ${common.lib_deps}
|
|
||||||
build_flags = ${common.build_flags} ${common.build_flags_custom}
|
|
||||||
upload_speed = 921600
|
|
||||||
; comment out next line if using USB and not OTA
|
|
||||||
upload_port = "boiler."
|
|
||||||
; examples....
|
|
||||||
;upload_port = "boiler"
|
|
||||||
;upload_port = "boiler.local"
|
|
||||||
;upload_port = 10.10.10.6
|
|
||||||
|
|
||||||
[env:d1_mini]
|
[env:d1_mini]
|
||||||
board = d1_mini
|
board = d1_mini
|
||||||
platform = ${common.platform}
|
platform = ${common.platform}
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${common.lib_deps}
|
lib_deps = ${common.lib_deps}
|
||||||
build_flags = ${common.build_flags} ${common.build_flags_custom}
|
build_flags = ${common.build_flags} ${common.wifi_settings}
|
||||||
|
board_build.flash_mode = ${common.flash_mode}
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
; comment out next line if using USB and not OTA
|
monitor_speed = 115200
|
||||||
upload_port = "boiler."
|
|
||||||
; examples....
|
; for OTA comment out these sections
|
||||||
;upload_port = "boiler"
|
;upload_protocol = espota
|
||||||
;upload_port = "boiler.local"
|
;upload_port = ems-esp.local
|
||||||
;upload_port = 10.10.10.6
|
|
||||||
|
|
||||||
|
|||||||
13
rename_fw.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from subprocess import call
|
||||||
|
import os
|
||||||
|
Import("env")
|
||||||
|
|
||||||
|
#my_flags = env.ParseFlags(env['BUILD_FLAGS'])
|
||||||
|
#defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")}
|
||||||
|
# print defines
|
||||||
|
# print env.Dump()
|
||||||
|
|
||||||
|
# see http://docs.platformio.org/en/latest/projectconf/advanced_scripting.html#before-pre-and-after-post-actions
|
||||||
|
# env.Replace(PROGNAME="firmware_%s" % defines.get("VERSION"))
|
||||||
|
env.Replace(PROGNAME="firmware_%s" % env['BOARD'])
|
||||||
@@ -1,895 +0,0 @@
|
|||||||
/*
|
|
||||||
Based off :
|
|
||||||
1) ESPHelper.cpp - Copyright (c) 2017 ItKindaWorks Inc All right reserved. github.com/ItKindaWorks
|
|
||||||
2) https://github.com/JoaoLopesF/ESP8266-RemoteDebug-Telnet
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ESPHelper.h"
|
|
||||||
|
|
||||||
WiFiServer telnetServer(TELNET_PORT);
|
|
||||||
|
|
||||||
//initializer with single netInfo network
|
|
||||||
ESPHelper::ESPHelper(netInfo * startingNet) {
|
|
||||||
//disconnect from and previous wifi networks
|
|
||||||
WiFi.softAPdisconnect();
|
|
||||||
WiFi.disconnect();
|
|
||||||
|
|
||||||
//setup current network information
|
|
||||||
_currentNet = *startingNet;
|
|
||||||
|
|
||||||
//validate various bits of network/MQTT info
|
|
||||||
|
|
||||||
//network pass
|
|
||||||
if (_currentNet.pass[0] == '\0') {
|
|
||||||
_passSet = false;
|
|
||||||
} else {
|
|
||||||
_passSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//ssid
|
|
||||||
if (_currentNet.ssid[0] == '\0') {
|
|
||||||
_ssidSet = false;
|
|
||||||
} else {
|
|
||||||
_ssidSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt host
|
|
||||||
if (_currentNet.mqttHost[0] == '\0') {
|
|
||||||
_mqttSet = false;
|
|
||||||
} else {
|
|
||||||
_mqttSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt port
|
|
||||||
if (_currentNet.mqttPort == 0) {
|
|
||||||
_currentNet.mqttPort = 1883;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt username
|
|
||||||
if (_currentNet.mqttUser[0] == '\0') {
|
|
||||||
_mqttUserSet = false;
|
|
||||||
} else {
|
|
||||||
_mqttUserSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt password
|
|
||||||
if (_currentNet.mqttPass[0] == '\0') {
|
|
||||||
_mqttPassSet = false;
|
|
||||||
} else {
|
|
||||||
_mqttPassSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//disable hopping on single network
|
|
||||||
_hoppingAllowed = false;
|
|
||||||
|
|
||||||
//disable ota by default
|
|
||||||
_useOTA = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//start the wifi & mqtt systems and attempt connection (currently blocking)
|
|
||||||
//true on: parameter check validated
|
|
||||||
//false on: parameter check failed
|
|
||||||
bool ESPHelper::begin(const char * hostname, const char * app_name, const char * app_version) {
|
|
||||||
#ifdef USE_SERIAL1
|
|
||||||
Serial1.begin(115200);
|
|
||||||
Serial1.setDebugOutput(true);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL
|
|
||||||
Serial.begin(115200);
|
|
||||||
Serial.setDebugOutput(true);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// set hostname first
|
|
||||||
strcpy(_hostname, hostname);
|
|
||||||
OTA_enable();
|
|
||||||
|
|
||||||
strcpy(_app_name, app_name); // app name
|
|
||||||
strcpy(_app_version, app_version); // app version
|
|
||||||
|
|
||||||
|
|
||||||
setBoottime("<unknown>");
|
|
||||||
|
|
||||||
if (_ssidSet) {
|
|
||||||
strcpy(_clientName, hostname);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Generate client name based on MAC address and last 8 bits of microsecond counter
|
|
||||||
|
|
||||||
_clientName += "esp-";
|
|
||||||
uint8_t mac[6];
|
|
||||||
WiFi.macAddress(mac);
|
|
||||||
_clientName += macToStr(mac);
|
|
||||||
*/
|
|
||||||
|
|
||||||
// set hostname
|
|
||||||
// can ping by <hostname> or on Windows10 it's <hostname>.
|
|
||||||
#if defined(ESP8266)
|
|
||||||
WiFi.hostname(_hostname);
|
|
||||||
#elif defined(ESP32)
|
|
||||||
WiFi.setHostname(_hostname);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//set the wifi mode to station and begin the wifi (connect using either ssid or ssid/pass)
|
|
||||||
WiFi.mode(WIFI_STA);
|
|
||||||
if (_passSet) {
|
|
||||||
WiFi.begin(_currentNet.ssid, _currentNet.pass);
|
|
||||||
} else {
|
|
||||||
WiFi.begin(_currentNet.ssid);
|
|
||||||
}
|
|
||||||
|
|
||||||
//as long as an mqtt ip has been set create an instance of PubSub for client
|
|
||||||
if (_mqttSet) {
|
|
||||||
//make mqtt client use either the secure or non-secure wifi client depending on the setting
|
|
||||||
if (_useSecureClient) {
|
|
||||||
client = PubSubClient(_currentNet.mqttHost, _currentNet.mqttPort, wifiClientSecure);
|
|
||||||
} else {
|
|
||||||
client = PubSubClient(_currentNet.mqttHost, _currentNet.mqttPort, wifiClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
//set the mqtt message callback if needed
|
|
||||||
if (_mqttCallbackSet) {
|
|
||||||
client.setCallback(_mqttCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//define a dummy instance of mqtt so that it is instantiated if no mqtt ip is set
|
|
||||||
else {
|
|
||||||
//make mqtt client use either the secure or non-secure wifi client depending on the setting
|
|
||||||
//(this shouldnt be needed if making a dummy connection since the idea would be that there wont be mqtt in this case)
|
|
||||||
if (_useSecureClient) {
|
|
||||||
client = PubSubClient("192.168.1.255", _currentNet.mqttPort, wifiClientSecure);
|
|
||||||
} else {
|
|
||||||
client = PubSubClient("192.168.1.255", _currentNet.mqttPort, wifiClient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//ota event handlers
|
|
||||||
ArduinoOTA.onStart([]() { /* ota start code */ });
|
|
||||||
ArduinoOTA.onEnd([]() {
|
|
||||||
//on ota end we disconnect from wifi cleanly before restarting.
|
|
||||||
WiFi.softAPdisconnect();
|
|
||||||
WiFi.disconnect();
|
|
||||||
uint8_t timeout = 0;
|
|
||||||
//max timeout of 2seconds before just dropping out and restarting
|
|
||||||
while (WiFi.status() != WL_DISCONNECTED && timeout < 200) {
|
|
||||||
timeout++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { /* ota progress code */ });
|
|
||||||
ArduinoOTA.onError([](ota_error_t error) { /* ota error code */ });
|
|
||||||
|
|
||||||
//initially attempt to connect to wifi when we begin (but only block for 2 seconds before timing out)
|
|
||||||
uint8_t timeout = 0; //counter for begin connection attempts
|
|
||||||
while (((!client.connected() && _mqttSet) || WiFi.status() != WL_CONNECTED)
|
|
||||||
&& timeout < 200) { //max 2 sec before timeout
|
|
||||||
reconnect();
|
|
||||||
timeout++;
|
|
||||||
}
|
|
||||||
|
|
||||||
//attempt to start ota if needed
|
|
||||||
OTA_begin();
|
|
||||||
|
|
||||||
// Initialize the telnet server
|
|
||||||
telnetServer.begin();
|
|
||||||
telnetServer.setNoDelay(true);
|
|
||||||
|
|
||||||
// init command buffer for console commands
|
|
||||||
memset(_command, 0, sizeof(_command));
|
|
||||||
|
|
||||||
consoleShowHelp(); // show this at bootup
|
|
||||||
|
|
||||||
// mark the system as started and return
|
|
||||||
_hasBegun = true;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//if no ssid was set even then dont try to begin and return false
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//end the instance of ESPHelper (shutdown wifi, ota, mqtt)
|
|
||||||
void ESPHelper::end() {
|
|
||||||
// Stop telnet Client & Server
|
|
||||||
if (telnetClient && telnetClient.connected()) {
|
|
||||||
telnetClient.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop server
|
|
||||||
telnetServer.stop();
|
|
||||||
|
|
||||||
OTA_disable();
|
|
||||||
WiFi.softAPdisconnect();
|
|
||||||
WiFi.disconnect();
|
|
||||||
|
|
||||||
uint8_t timeout = 0;
|
|
||||||
while (WiFi.status() != WL_DISCONNECTED && timeout < 200) {
|
|
||||||
timeout++;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL
|
|
||||||
Serial.flush();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL1
|
|
||||||
Serial1.flush();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
//main loop - should be called as often as possible - handles wifi/mqtt connection and mqtt handler
|
|
||||||
//true on: network/server connected
|
|
||||||
//false on: network or server disconnected
|
|
||||||
int ESPHelper::loop() {
|
|
||||||
if (_ssidSet) {
|
|
||||||
//check for good connections and attempt a reconnect if needed
|
|
||||||
if (((_mqttSet && !client.connected()) || setConnectionStatus() < WIFI_ONLY) && _connectionStatus != BROADCAST) {
|
|
||||||
reconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
//run the wifi loop as long as the connection status is at a minimum of BROADCAST
|
|
||||||
if (_connectionStatus >= BROADCAST) {
|
|
||||||
//run the MQTT loop if we have a full connection
|
|
||||||
if (_connectionStatus == FULL_CONNECTION) {
|
|
||||||
client.loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
//check for whether we want to use OTA and whether the system is running
|
|
||||||
if (_useOTA && _OTArunning) {
|
|
||||||
ArduinoOTA.handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
//if we want to use OTA but its not running yet, start it up.
|
|
||||||
else if (_useOTA && !_OTArunning) {
|
|
||||||
OTA_begin();
|
|
||||||
ArduinoOTA.handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
// do the telnet stuff
|
|
||||||
consoleHandle();
|
|
||||||
|
|
||||||
return _connectionStatus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//return -1 for no connection because of bad network info
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//subscribe to a specific topic (does not add to topic list)
|
|
||||||
//true on: subscription success
|
|
||||||
//false on: subscription failed (either from PubSub lib or network is disconnected)
|
|
||||||
bool ESPHelper::subscribe(const char * topic, uint8_t qos) {
|
|
||||||
if (_connectionStatus == FULL_CONNECTION) {
|
|
||||||
//set the return value to the output of subscribe
|
|
||||||
bool returnVal = client.subscribe(topic, qos);
|
|
||||||
|
|
||||||
//loop mqtt client
|
|
||||||
client.loop();
|
|
||||||
return returnVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
//if not fully connected return false
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//add a topic to the list of subscriptions and attempt to subscribe to the topic on the spot
|
|
||||||
//true on: subscription added to list (does not guarantee that the topic was subscribed to, only that it was added to the list)
|
|
||||||
//false on: subscription not added to list
|
|
||||||
bool ESPHelper::addSubscription(const char * topic) {
|
|
||||||
//default return value is false
|
|
||||||
bool subscribed = false;
|
|
||||||
|
|
||||||
//loop through finding the next available slot for a subscription and add it
|
|
||||||
for (uint8_t i = 0; i < MAX_SUBSCRIPTIONS; i++) {
|
|
||||||
if (_subscriptions[i].isUsed == false) {
|
|
||||||
_subscriptions[i].topic = topic;
|
|
||||||
_subscriptions[i].isUsed = true;
|
|
||||||
subscribed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//if added to the list, subscribe to the topic
|
|
||||||
if (subscribed) {
|
|
||||||
subscribe(topic, _qos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return subscribed;
|
|
||||||
}
|
|
||||||
|
|
||||||
//loops through list of subscriptions and attempts to subscribe to all topics
|
|
||||||
void ESPHelper::resubscribe() {
|
|
||||||
for (uint8_t i = 0; i < MAX_SUBSCRIPTIONS; i++) {
|
|
||||||
if (_subscriptions[i].isUsed) {
|
|
||||||
subscribe(_subscriptions[i].topic, _qos);
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//manually unsubscribes from a topic (This is basically just a wrapper for the pubsubclient function)
|
|
||||||
bool ESPHelper::unsubscribe(const char * topic) {
|
|
||||||
return client.unsubscribe(topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
//publish to a specified topic
|
|
||||||
void ESPHelper::publish(const char * topic, const char * payload) {
|
|
||||||
if (_mqttSet) {
|
|
||||||
publish(topic, payload, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//publish to a specified topic with a given retain level
|
|
||||||
void ESPHelper::publish(const char * topic, const char * payload, bool retain) {
|
|
||||||
client.publish(topic, payload, retain);
|
|
||||||
}
|
|
||||||
|
|
||||||
//set the callback function for MQTT
|
|
||||||
void ESPHelper::setMQTTCallback(MQTT_CALLBACK_SIGNATURE) {
|
|
||||||
_mqttCallback = callback;
|
|
||||||
|
|
||||||
//only set the callback if using mqtt AND the system has already been started. Otherwise just save it for later
|
|
||||||
if (_hasBegun && _mqttSet) {
|
|
||||||
client.setCallback(_mqttCallback);
|
|
||||||
}
|
|
||||||
_mqttCallbackSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//sets a custom function to run when connection to wifi is established
|
|
||||||
void ESPHelper::setWifiCallback(void (*callback)()) {
|
|
||||||
_wifiCallback = callback;
|
|
||||||
_wifiCallbackSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//sets a custom function to run when telnet is started
|
|
||||||
void ESPHelper::setInitCallback(void (*callback)()) {
|
|
||||||
_initCallback = callback;
|
|
||||||
_initCallbackSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//attempts to connect to wifi & mqtt server if not connected
|
|
||||||
void ESPHelper::reconnect() {
|
|
||||||
static uint8_t tryCount = 0;
|
|
||||||
|
|
||||||
if (_connectionStatus != BROADCAST && setConnectionStatus() != FULL_CONNECTION) {
|
|
||||||
logger(LOG_CONSOLE, "Attempting WiFi Connection...");
|
|
||||||
//attempt to connect to the wifi if connection is lost
|
|
||||||
if (WiFi.status() != WL_CONNECTED) {
|
|
||||||
_connectionStatus = NO_CONNECTION;
|
|
||||||
|
|
||||||
//increment try count each time it cannot connect (this is used to determine when to hop to a new network)
|
|
||||||
tryCount++;
|
|
||||||
if (tryCount == 20) {
|
|
||||||
//change networks (if possible) when we have tried to connect 20 times and failed
|
|
||||||
changeNetwork();
|
|
||||||
tryCount = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure we are connected to WIFI before attempting to reconnect to MQTT
|
|
||||||
//----note---- maybe want to reset tryCount whenever we succeed at getting wifi connection?
|
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
|
||||||
//if the wifi previously wasnt connected but now is, run the callback
|
|
||||||
if (_connectionStatus < WIFI_ONLY && _wifiCallbackSet) {
|
|
||||||
_wifiCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
logger(LOG_CONSOLE, "---WiFi Connected!---");
|
|
||||||
_connectionStatus = WIFI_ONLY;
|
|
||||||
|
|
||||||
//attempt to connect to mqtt when we finally get connected to WiFi
|
|
||||||
if (_mqttSet) {
|
|
||||||
static uint8_t timeout = 0; //allow a max of 5 mqtt connection attempts before timing out
|
|
||||||
if (!client.connected() && timeout < 5) {
|
|
||||||
logger(LOG_CONSOLE, "Attempting MQTT connection...");
|
|
||||||
|
|
||||||
uint8_t connected = 0;
|
|
||||||
|
|
||||||
//connect to mqtt with user/pass
|
|
||||||
if (_mqttUserSet) {
|
|
||||||
connected = client.connect(_clientName, _currentNet.mqttUser, _currentNet.mqttPass);
|
|
||||||
}
|
|
||||||
|
|
||||||
//connect to mqtt without credentials
|
|
||||||
else {
|
|
||||||
connected = client.connect(_clientName);
|
|
||||||
}
|
|
||||||
|
|
||||||
//if connected, subscribe to the topic(s) we want to be notified about
|
|
||||||
if (connected) {
|
|
||||||
logger(LOG_CONSOLE, " -- Connected");
|
|
||||||
|
|
||||||
//if using https, verify the fingerprint of the server before setting full connection (return on fail)
|
|
||||||
// removing this as not supported with ESP32, see https://github.com/espressif/arduino-esp32/issues/278
|
|
||||||
/*
|
|
||||||
if (wifiClientSecure.verify(_fingerprint,
|
|
||||||
_currentNet.mqttHost)) {
|
|
||||||
logger(LOG_CONSOLE,
|
|
||||||
"Certificate Matches - SUCCESS\n");
|
|
||||||
} else {
|
|
||||||
logger(LOG_CONSOLE,
|
|
||||||
"Certificate Doesn't Match - FAIL\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
_connectionStatus = FULL_CONNECTION;
|
|
||||||
resubscribe();
|
|
||||||
timeout = 0;
|
|
||||||
} else {
|
|
||||||
logger(LOG_CONSOLE, " -- Failed\n");
|
|
||||||
}
|
|
||||||
timeout++;
|
|
||||||
}
|
|
||||||
|
|
||||||
//if we still cant connect to mqtt after 10 attempts increment the try count
|
|
||||||
if (timeout >= 5 && !client.connected()) {
|
|
||||||
timeout = 0;
|
|
||||||
tryCount++;
|
|
||||||
if (tryCount == 20) {
|
|
||||||
changeNetwork();
|
|
||||||
tryCount = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//reset the reconnect metro
|
|
||||||
//reconnectMetro.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t ESPHelper::setConnectionStatus() {
|
|
||||||
//assume no connection
|
|
||||||
uint8_t returnVal = NO_CONNECTION;
|
|
||||||
|
|
||||||
//make sure were not in broadcast mode
|
|
||||||
if (_connectionStatus != BROADCAST) {
|
|
||||||
//if connected to wifi set the mode to wifi only and run the callback if needed
|
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
|
||||||
if (_connectionStatus < WIFI_ONLY
|
|
||||||
&& _wifiCallbackSet) { //if the wifi previously wasn't connected but now is, run the callback
|
|
||||||
_wifiCallback();
|
|
||||||
}
|
|
||||||
returnVal = WIFI_ONLY;
|
|
||||||
|
|
||||||
//if mqtt is connected as well then set the status to full connection
|
|
||||||
if (client.connected()) {
|
|
||||||
returnVal = FULL_CONNECTION;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
returnVal = BROADCAST;
|
|
||||||
}
|
|
||||||
|
|
||||||
//set the connection status and return
|
|
||||||
_connectionStatus = returnVal;
|
|
||||||
return returnVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
//changes the current network settings to the next listed network if network hopping is allowed
|
|
||||||
void ESPHelper::changeNetwork() {
|
|
||||||
//only attempt to change networks if hopping is allowed
|
|
||||||
if (_hoppingAllowed) {
|
|
||||||
//change the index/reset to 0 if we've hit the last network setting
|
|
||||||
_currentIndex++;
|
|
||||||
if (_currentIndex >= _netCount) {
|
|
||||||
_currentIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//set the current netlist to the new network
|
|
||||||
_currentNet = *_netList[_currentIndex];
|
|
||||||
|
|
||||||
//verify various bits of network info
|
|
||||||
|
|
||||||
//network password
|
|
||||||
if (_currentNet.pass[0] == '\0') {
|
|
||||||
_passSet = false;
|
|
||||||
} else {
|
|
||||||
_passSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//ssid
|
|
||||||
if (_currentNet.ssid[0] == '\0') {
|
|
||||||
_ssidSet = false;
|
|
||||||
} else {
|
|
||||||
_ssidSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt host
|
|
||||||
if (_currentNet.mqttHost[0] == '\0') {
|
|
||||||
_mqttSet = false;
|
|
||||||
} else {
|
|
||||||
_mqttSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt username
|
|
||||||
if (_currentNet.mqttUser[0] == '\0') {
|
|
||||||
_mqttUserSet = false;
|
|
||||||
} else {
|
|
||||||
_mqttUserSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt password
|
|
||||||
if (_currentNet.mqttPass[0] == '\0') {
|
|
||||||
_mqttPassSet = false;
|
|
||||||
} else {
|
|
||||||
_mqttPassSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Trying next network: %s\n", _currentNet.ssid);
|
|
||||||
|
|
||||||
//update the network connection
|
|
||||||
updateNetwork();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESPHelper::updateNetwork() {
|
|
||||||
logger(LOG_CONSOLE, "\tDisconnecting from WiFi");
|
|
||||||
WiFi.disconnect();
|
|
||||||
logger(LOG_CONSOLE, "\tAttempting to begin on new network...");
|
|
||||||
|
|
||||||
//set the wifi mode
|
|
||||||
WiFi.mode(WIFI_STA);
|
|
||||||
|
|
||||||
//connect to the network
|
|
||||||
if (_passSet && _ssidSet) {
|
|
||||||
WiFi.begin(_currentNet.ssid, _currentNet.pass);
|
|
||||||
} else if (_ssidSet) {
|
|
||||||
WiFi.begin(_currentNet.ssid);
|
|
||||||
} else {
|
|
||||||
WiFi.begin("NO_SSID_SET");
|
|
||||||
}
|
|
||||||
|
|
||||||
logger(LOG_CONSOLE, "\tSetting new MQTT server");
|
|
||||||
//setup the mqtt broker info
|
|
||||||
if (_mqttSet) {
|
|
||||||
client.setServer(_currentNet.mqttHost, _currentNet.mqttPort);
|
|
||||||
} else {
|
|
||||||
client.setServer("192.168.1.3", 1883);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger(LOG_CONSOLE, "\tDone - Ready for next reconnect attempt");
|
|
||||||
}
|
|
||||||
|
|
||||||
//enable use of OTA updates
|
|
||||||
void ESPHelper::OTA_enable() {
|
|
||||||
_useOTA = true;
|
|
||||||
ArduinoOTA.setHostname(_hostname);
|
|
||||||
OTA_begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
//begin the OTA subsystem but with a check for connectivity and enabled use of OTA
|
|
||||||
void ESPHelper::OTA_begin() {
|
|
||||||
if (_connectionStatus >= BROADCAST && _useOTA) {
|
|
||||||
ArduinoOTA.begin();
|
|
||||||
_OTArunning = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//disable use of OTA updates
|
|
||||||
void ESPHelper::OTA_disable() {
|
|
||||||
_useOTA = false;
|
|
||||||
_OTArunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is CR or LF ?
|
|
||||||
bool ESPHelper::isCRLF(char character) {
|
|
||||||
return (character == '\r' || character == '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
// handler for Telnet
|
|
||||||
void ESPHelper::consoleHandle() {
|
|
||||||
// look for Client
|
|
||||||
if (telnetServer.hasClient()) {
|
|
||||||
if (telnetClient && telnetClient.connected()) {
|
|
||||||
// Verify if the IP is same than actual connection
|
|
||||||
WiFiClient newClient;
|
|
||||||
newClient = telnetServer.available();
|
|
||||||
String ip = newClient.remoteIP().toString();
|
|
||||||
|
|
||||||
if (ip == telnetClient.remoteIP().toString()) {
|
|
||||||
// Reconnect
|
|
||||||
telnetClient.stop();
|
|
||||||
telnetClient = newClient;
|
|
||||||
} else {
|
|
||||||
// Desconnect (not allow more than one connection)
|
|
||||||
newClient.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// New TCP client
|
|
||||||
telnetClient = telnetServer.available();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!telnetClient) { // No client yet ???
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set client
|
|
||||||
telnetClient.setNoDelay(true); // faster
|
|
||||||
telnetClient.flush(); // clear input buffer, to prevent strange characters
|
|
||||||
|
|
||||||
_lastTimeCommand = millis(); // To mark time for inactivity
|
|
||||||
|
|
||||||
// Show the initial message
|
|
||||||
consoleShowHelp();
|
|
||||||
|
|
||||||
_initCallback(); // call callback to set any custom things
|
|
||||||
|
|
||||||
// Empty buffer
|
|
||||||
while (telnetClient.available()) {
|
|
||||||
telnetClient.read();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is client connected ? (to reduce overhead in active)
|
|
||||||
_telnetConnected = (telnetClient && telnetClient.connected());
|
|
||||||
|
|
||||||
// Get command over telnet
|
|
||||||
if (_telnetConnected) {
|
|
||||||
char last = ' '; // To avoid processing double "\r\n"
|
|
||||||
|
|
||||||
while (telnetClient.available()) { // get data from Client
|
|
||||||
|
|
||||||
// Get character
|
|
||||||
char character = telnetClient.read();
|
|
||||||
|
|
||||||
// Newline (CR or LF) - once one time if (\r\n)
|
|
||||||
if (isCRLF(character) == true) {
|
|
||||||
if (isCRLF(last) == false) {
|
|
||||||
// Process the command
|
|
||||||
if (strlen(_command) > 0) {
|
|
||||||
consoleProcessCommand();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// reset for next command
|
|
||||||
memset(_command, 0, sizeof(_command));
|
|
||||||
} else if (isPrintable(character)) {
|
|
||||||
// Concat char to end of buffer
|
|
||||||
uint16_t len = strlen(_command);
|
|
||||||
_command[len] = character;
|
|
||||||
_command[len + 1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Last char
|
|
||||||
last = character;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inactivity - close connection if not received commands from user in telnet to reduce overheads
|
|
||||||
if ((millis() - _lastTimeCommand) > MAX_TIME_INACTIVE) {
|
|
||||||
telnetClient.println("* Closing telnet session due to inactivity");
|
|
||||||
telnetClient.flush();
|
|
||||||
telnetClient.stop();
|
|
||||||
_telnetConnected = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set callback of sketch function to process project messages
|
|
||||||
void ESPHelper::consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)()) {
|
|
||||||
_helpProjectCmds = cmds; // command list
|
|
||||||
_helpProjectCmds_count = count; // number of commands
|
|
||||||
_consoleCallbackProjectCmds = callback; // external function to handle commands
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set bootime received as a string from HA
|
|
||||||
void ESPHelper::setBoottime(const char * boottime) {
|
|
||||||
strcpy(_boottime, boottime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// overrides the write call to print to the telnet connection
|
|
||||||
size_t ESPHelper::write(uint8_t character) {
|
|
||||||
if (!_verboseMessages)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
//static uint32_t elapsed = 0;
|
|
||||||
|
|
||||||
// If start of a new line, initiate a new string buffer with time counter as a prefix
|
|
||||||
if (_newLine) {
|
|
||||||
unsigned long upt = millis();
|
|
||||||
sprintf(bufferPrint,
|
|
||||||
"(%s%02d:%02d:%02d%s) ",
|
|
||||||
COLOR_CYAN,
|
|
||||||
(uint8_t)((upt / (1000 * 60 * 60)) % 24),
|
|
||||||
(uint8_t)((upt / (1000 * 60)) % 60),
|
|
||||||
(uint8_t)((upt / 1000) % 60),
|
|
||||||
COLOR_RESET);
|
|
||||||
_newLine = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print ?
|
|
||||||
bool doPrint = false;
|
|
||||||
|
|
||||||
// New line ?
|
|
||||||
if (character == '\n') {
|
|
||||||
_newLine = true;
|
|
||||||
doPrint = true;
|
|
||||||
} else if (strlen(bufferPrint) == BUFFER_PRINT - 1) { // Limit of buffer
|
|
||||||
doPrint = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add character to telnet buffer
|
|
||||||
uint16_t len = strlen(bufferPrint);
|
|
||||||
bufferPrint[len] = character;
|
|
||||||
|
|
||||||
if (_newLine) {
|
|
||||||
// add additional \r for windows
|
|
||||||
bufferPrint[++len] = '\r';
|
|
||||||
}
|
|
||||||
|
|
||||||
// terminate string
|
|
||||||
bufferPrint[++len] = '\0';
|
|
||||||
|
|
||||||
// Send the characters buffered by print.h
|
|
||||||
if (doPrint) {
|
|
||||||
if (_telnetConnected) {
|
|
||||||
telnetClient.print(bufferPrint);
|
|
||||||
}
|
|
||||||
|
|
||||||
// echo to Serial if enabled
|
|
||||||
#ifdef USE_SERIAL
|
|
||||||
Serial.print(bufferPrint);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL1
|
|
||||||
Serial1.print(bufferPrint);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Empty the buffer
|
|
||||||
bufferPrint[0] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
return len + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show help of commands
|
|
||||||
void ESPHelper::consoleShowHelp() {
|
|
||||||
String help = "**********************************************\n\r* Remote Telnet Command Center & Log Monitor "
|
|
||||||
"*\n\r**********************************************\n\r";
|
|
||||||
help += "* Device hostname: " + WiFi.hostname() + "\tIP: " + WiFi.localIP().toString()
|
|
||||||
+ "\tMAC address: " + WiFi.macAddress() + "\n\r";
|
|
||||||
help += "* Connected to WiFi AP: " + WiFi.SSID() + "\n\r";
|
|
||||||
help += "* Boot time: ";
|
|
||||||
help.concat(_boottime);
|
|
||||||
help += "\n\r* ";
|
|
||||||
help.concat(_app_name);
|
|
||||||
help += " Version ";
|
|
||||||
help.concat(_app_version);
|
|
||||||
help += "\n\r* Free RAM: ";
|
|
||||||
help.concat(ESP.getFreeHeap());
|
|
||||||
help += " bytes\n\r";
|
|
||||||
help += "*\n\r* Commands:\n\r* ?=this help, q=quit telnet, $=show free memory, !=reboot, &=suspend all "
|
|
||||||
"notifications\n\r";
|
|
||||||
|
|
||||||
char s[100];
|
|
||||||
|
|
||||||
// print custom commands if available
|
|
||||||
if (_consoleCallbackProjectCmds) {
|
|
||||||
for (uint8_t i = 0; i < _helpProjectCmds_count; i++) {
|
|
||||||
help += FPSTR("* ");
|
|
||||||
help += FPSTR(_helpProjectCmds[i].key);
|
|
||||||
for (int j = 0; j < (8 - strlen(_helpProjectCmds[i].key)); j++) { // padding
|
|
||||||
help += FPSTR(" ");
|
|
||||||
}
|
|
||||||
help += FPSTR(_helpProjectCmds[i].description);
|
|
||||||
help += FPSTR("\n\r");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
telnetClient.print(help);
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL
|
|
||||||
Serial.print(help);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL1
|
|
||||||
Serial1.print(help);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset / restart
|
|
||||||
void ESPHelper::resetESP() {
|
|
||||||
telnetClient.println("* Reboot ESP...");
|
|
||||||
telnetClient.flush();
|
|
||||||
telnetClient.stop();
|
|
||||||
// end();
|
|
||||||
|
|
||||||
// Reset
|
|
||||||
ESP.restart();
|
|
||||||
// ESP.reset(); // for ESP8266 only
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get last command received
|
|
||||||
char * ESPHelper::consoleGetLastCommand() {
|
|
||||||
return _command;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process user command over telnet
|
|
||||||
void ESPHelper::consoleProcessCommand() {
|
|
||||||
// Set time of last command received
|
|
||||||
_lastTimeCommand = millis();
|
|
||||||
uint8_t cmd = _command[0];
|
|
||||||
|
|
||||||
if (!_verboseMessages) {
|
|
||||||
telnetClient.println("Warning, all messages are supsended. Use & to enable.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the command
|
|
||||||
if (cmd == '?') {
|
|
||||||
consoleShowHelp(); // Show help
|
|
||||||
} else if (cmd == 'q') { // quit
|
|
||||||
telnetClient.println("* Closing telnet connection...");
|
|
||||||
telnetClient.stop();
|
|
||||||
} else if (cmd == '$') {
|
|
||||||
telnetClient.print("* Free RAM (bytes): ");
|
|
||||||
telnetClient.println(ESP.getFreeHeap());
|
|
||||||
} else if (cmd == '!') {
|
|
||||||
resetESP();
|
|
||||||
} else if (cmd == '&') {
|
|
||||||
_verboseMessages = !_verboseMessages; // toggle
|
|
||||||
telnetClient.printf("Suspend all messages is %s\n\r", _verboseMessages ? "disabled" : "enabled");
|
|
||||||
} else {
|
|
||||||
// custom Project commands
|
|
||||||
if (_consoleCallbackProjectCmds) {
|
|
||||||
_consoleCallbackProjectCmds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logger
|
|
||||||
// LOG_CONSOLE sends to the Telnet session
|
|
||||||
// LOG_HA sends to Telnet session plus a MQTT for Home Assistant
|
|
||||||
// LOG_NONE turns off all logging
|
|
||||||
void ESPHelper::logger(log_level_t level, const char * message) {
|
|
||||||
// do we log to the telnet window?
|
|
||||||
if ((level == LOG_CONSOLE) && (telnetClient && telnetClient.connected())) {
|
|
||||||
telnetClient.println(message);
|
|
||||||
telnetClient.flush();
|
|
||||||
} else if (level == LOG_HA) {
|
|
||||||
char s[100];
|
|
||||||
sprintf(s, "%s: %s\n", _hostname, message); // add new line, for the debug telnet printer
|
|
||||||
publish(MQTT_NOTIFICATION, s, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// print to Serial if set in platform.io (requires recompile)
|
|
||||||
#ifdef USE_SERIAL
|
|
||||||
Serial.println(message);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL1
|
|
||||||
Serial.println(message);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// send specific command to HA via MQTT
|
|
||||||
// format is: home/<hostname>/command/<cmd>
|
|
||||||
void ESPHelper::sendHACommand(const char * cmd) {
|
|
||||||
//logger(LOG_CONSOLE, "Sending command to HA...");
|
|
||||||
|
|
||||||
char s[100];
|
|
||||||
sprintf(s, "%s%s/%s", MQTT_BASE, _hostname, MQTT_TOPIC_COMMAND);
|
|
||||||
|
|
||||||
publish(s, cmd, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// send specific start command to HA via MQTT, which returns the boottime
|
|
||||||
// format is: home/<hostname>/start
|
|
||||||
void ESPHelper::sendStart() {
|
|
||||||
//logger(LOG_CONSOLE, "Sending Start command to HA...");
|
|
||||||
|
|
||||||
char s[100];
|
|
||||||
sprintf(s, "%s%s/%s", MQTT_BASE, _hostname, MQTT_TOPIC_START);
|
|
||||||
|
|
||||||
// send initial payload of "start" to kick things off
|
|
||||||
publish(s, MQTT_TOPIC_START, false);
|
|
||||||
}
|
|
||||||
219
src/ESPHelper.h
@@ -1,219 +0,0 @@
|
|||||||
/*
|
|
||||||
ESPHelper.h
|
|
||||||
Copyright (c) 2017 ItKindaWorks Inc All right reserved.
|
|
||||||
github.com/ItKindaWorks
|
|
||||||
|
|
||||||
This file is part of ESPHelper
|
|
||||||
|
|
||||||
ESPHelper is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
ESPHelper is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with ESPHelper. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <ArduinoOTA.h>
|
|
||||||
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
|
|
||||||
#include <ESP8266mDNS.h>
|
|
||||||
#include <Print.h>
|
|
||||||
#include <PubSubClient.h>
|
|
||||||
#include <WiFiClientSecure.h>
|
|
||||||
#include <WiFiUdp.h>
|
|
||||||
#include <pgmspace.h>
|
|
||||||
|
|
||||||
// MQTT stuff
|
|
||||||
#define DEFAULT_QOS 1 //at least once - devices are guarantee to get a message.
|
|
||||||
#define MQTT_BASE "home/"
|
|
||||||
#define MQTT_NOTIFICATION MQTT_BASE "notification"
|
|
||||||
#define MQTT_TOPIC_COMMAND "command"
|
|
||||||
#define MQTT_TOPIC_START "start"
|
|
||||||
#define MQTT_HA MQTT_BASE "ha"
|
|
||||||
|
|
||||||
#define MAX_SUBSCRIPTIONS 25 // max # of subscriptions
|
|
||||||
#define MAX_TIME_INACTIVE 600000 // Max time for inactivity (ms) - 10 mins
|
|
||||||
#define TELNET_PORT 23 // telnet port
|
|
||||||
#define BUFFER_PRINT 500 // length of telnet buffer (default was 150)
|
|
||||||
#define COMMAND_LENGTH 20 // length of a command
|
|
||||||
|
|
||||||
// ANSI Colors
|
|
||||||
#define COLOR_RESET "\x1B[0m"
|
|
||||||
#define COLOR_BLACK "\x1B[0;30m"
|
|
||||||
#define COLOR_RED "\x1B[0;31m"
|
|
||||||
#define COLOR_GREEN "\x1B[0;32m"
|
|
||||||
#define COLOR_YELLOW "\x1B[0;33m"
|
|
||||||
#define COLOR_BLUE "\x1B[0;34m"
|
|
||||||
#define COLOR_MAGENTA "\x1B[0;35m"
|
|
||||||
#define COLOR_CYAN "\x1B[0;36m"
|
|
||||||
#define COLOR_WHITE "\x1B[0;37m"
|
|
||||||
|
|
||||||
// Logger
|
|
||||||
typedef enum { LOG_NONE, LOG_CONSOLE, LOG_HA } log_level_t;
|
|
||||||
|
|
||||||
enum connStatus { NO_CONNECTION, BROADCAST, WIFI_ONLY, FULL_CONNECTION };
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const char * mqttHost;
|
|
||||||
const char * mqttUser;
|
|
||||||
const char * mqttPass;
|
|
||||||
uint16_t mqttPort;
|
|
||||||
const char * ssid;
|
|
||||||
const char * pass;
|
|
||||||
} netInfo;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool isUsed = false;
|
|
||||||
const char * topic;
|
|
||||||
} subscription;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char key[10];
|
|
||||||
char description[400];
|
|
||||||
} command_t;
|
|
||||||
|
|
||||||
// class ESPHelper {
|
|
||||||
class ESPHelper : public Print {
|
|
||||||
public:
|
|
||||||
void consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)());
|
|
||||||
char * consoleGetLastCommand();
|
|
||||||
void resetESP();
|
|
||||||
void logger(log_level_t level, const char * message);
|
|
||||||
|
|
||||||
virtual size_t write(uint8_t);
|
|
||||||
|
|
||||||
ESPHelper(netInfo * startingNet);
|
|
||||||
|
|
||||||
bool begin(const char * hostname, const char * app_name, const char * app_version);
|
|
||||||
|
|
||||||
void end();
|
|
||||||
|
|
||||||
void useSecureClient(const char * fingerprint);
|
|
||||||
|
|
||||||
int loop();
|
|
||||||
|
|
||||||
bool subscribe(const char * topic, uint8_t qos);
|
|
||||||
bool addSubscription(const char * topic);
|
|
||||||
bool removeSubscription(const char * topic);
|
|
||||||
bool unsubscribe(const char * topic);
|
|
||||||
bool addHASubscription(const char * topic);
|
|
||||||
|
|
||||||
void publish(const char * topic, const char * payload);
|
|
||||||
void publish(const char * topic, const char * payload, bool retain);
|
|
||||||
|
|
||||||
bool setCallback(MQTT_CALLBACK_SIGNATURE);
|
|
||||||
void setMQTTCallback(MQTT_CALLBACK_SIGNATURE);
|
|
||||||
|
|
||||||
void setWifiCallback(void (*callback)());
|
|
||||||
void setInitCallback(void (*callback)());
|
|
||||||
|
|
||||||
void sendHACommand(const char * s);
|
|
||||||
void sendStart();
|
|
||||||
|
|
||||||
void reconnect();
|
|
||||||
|
|
||||||
void updateNetwork();
|
|
||||||
|
|
||||||
const char * getSSID();
|
|
||||||
void setSSID(const char * ssid);
|
|
||||||
|
|
||||||
const char * getPASS();
|
|
||||||
void setPASS(const char * pass);
|
|
||||||
|
|
||||||
const char * getMQTTIP();
|
|
||||||
void setMQTTIP(const char * mqttIP);
|
|
||||||
void setMQTTIP(const char * mqttIP, const char * mqttUser, const char * mqttPass);
|
|
||||||
|
|
||||||
uint8_t getMQTTQOS();
|
|
||||||
void setMQTTQOS(uint8_t qos);
|
|
||||||
|
|
||||||
String getIP();
|
|
||||||
IPAddress getIPAddress();
|
|
||||||
|
|
||||||
uint8_t getStatus();
|
|
||||||
|
|
||||||
void setNetInfo(netInfo newNetwork);
|
|
||||||
void setNetInfo(netInfo * newNetwork);
|
|
||||||
netInfo * getNetInfo();
|
|
||||||
|
|
||||||
void setHopping(bool canHop);
|
|
||||||
|
|
||||||
void listSubscriptions();
|
|
||||||
|
|
||||||
void OTA_enable();
|
|
||||||
void OTA_disable();
|
|
||||||
void OTA_begin();
|
|
||||||
|
|
||||||
void setBoottime(const char * boottime);
|
|
||||||
|
|
||||||
void consoleHandle();
|
|
||||||
|
|
||||||
private:
|
|
||||||
netInfo _currentNet;
|
|
||||||
PubSubClient client;
|
|
||||||
WiFiClient wifiClient;
|
|
||||||
WiFiClientSecure wifiClientSecure;
|
|
||||||
const char * _fingerprint;
|
|
||||||
bool _useSecureClient = false;
|
|
||||||
char _clientName[40];
|
|
||||||
void (*_wifiCallback)();
|
|
||||||
bool _wifiCallbackSet = false;
|
|
||||||
void (*_initCallback)();
|
|
||||||
bool _initCallbackSet = false;
|
|
||||||
|
|
||||||
std::function<void(char *, uint8_t *, uint8_t)> _mqttCallback;
|
|
||||||
|
|
||||||
bool _mqttCallbackSet = false;
|
|
||||||
uint8_t _connectionStatus = NO_CONNECTION;
|
|
||||||
uint8_t _netCount = 0;
|
|
||||||
uint8_t _currentIndex = 0;
|
|
||||||
bool _ssidSet = false;
|
|
||||||
bool _passSet = false;
|
|
||||||
bool _mqttSet = false;
|
|
||||||
bool _mqttUserSet = false;
|
|
||||||
bool _mqttPassSet = false;
|
|
||||||
bool _useOTA = false;
|
|
||||||
bool _OTArunning = false;
|
|
||||||
bool _hoppingAllowed = false;
|
|
||||||
bool _hasBegun = false;
|
|
||||||
netInfo ** _netList;
|
|
||||||
bool _verboseMessages = true;
|
|
||||||
subscription _subscriptions[MAX_SUBSCRIPTIONS];
|
|
||||||
char _hostname[24];
|
|
||||||
uint8_t _qos = DEFAULT_QOS;
|
|
||||||
IPAddress _apIP = IPAddress(192, 168, 1, 254);
|
|
||||||
void changeNetwork();
|
|
||||||
String macToStr(const uint8_t * mac);
|
|
||||||
bool checkParams();
|
|
||||||
void resubscribe();
|
|
||||||
uint8_t setConnectionStatus();
|
|
||||||
|
|
||||||
char _boottime[24];
|
|
||||||
char _app_name[24];
|
|
||||||
char _app_version[10];
|
|
||||||
|
|
||||||
// console/telnet specific
|
|
||||||
WiFiClient telnetClient;
|
|
||||||
|
|
||||||
bool _telnetConnected = false; // Client is connected ?
|
|
||||||
bool _newLine = true; // New line write ?
|
|
||||||
|
|
||||||
char _command[COMMAND_LENGTH]; // Command received, includes options seperated by a space
|
|
||||||
uint32_t _lastTimeCommand = millis(); // Last time command received
|
|
||||||
|
|
||||||
command_t * _helpProjectCmds; // Help of commands setted by project
|
|
||||||
uint8_t _helpProjectCmds_count; // # available commands
|
|
||||||
|
|
||||||
void (*_consoleCallbackProjectCmds)(); // Callable for projects commands
|
|
||||||
void consoleShowHelp();
|
|
||||||
void consoleProcessCommand();
|
|
||||||
bool isCRLF(char character);
|
|
||||||
|
|
||||||
char bufferPrint[BUFFER_PRINT];
|
|
||||||
};
|
|
||||||
956
src/boiler.ino
@@ -1,956 +0,0 @@
|
|||||||
/*
|
|
||||||
* EMS-ESP-Boiler
|
|
||||||
* Paul Derbyshire - May 2018 - https://github.com/proddy/EMS-ESP-Boiler
|
|
||||||
* https://community.home-assistant.io/t/thermostat-and-boiler-controller-for-ems-based-boilers-nefit-buderus-bosch-using-esp/53382
|
|
||||||
*
|
|
||||||
* See README for Acknowledgments
|
|
||||||
*/
|
|
||||||
|
|
||||||
// local libraries
|
|
||||||
#include "ESPHelper.h"
|
|
||||||
#include "ems.h"
|
|
||||||
#include "emsuart.h"
|
|
||||||
#include "my_config.h"
|
|
||||||
#include "version.h"
|
|
||||||
|
|
||||||
// public libraries
|
|
||||||
#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson
|
|
||||||
#include <CRC32.h> // https://github.com/bakercp/CRC32
|
|
||||||
|
|
||||||
// standard arduino libs
|
|
||||||
#include <Ticker.h> // https://github.com/esp8266/Arduino/tree/master/libraries/Ticker
|
|
||||||
|
|
||||||
// timers, all values are in seconds
|
|
||||||
#define PUBLISHVALUES_TIME 120 // every 2 minutes post HA values
|
|
||||||
Ticker publishValuesTimer;
|
|
||||||
|
|
||||||
#define SYSTEMCHECK_TIME 10 // every 10 seconds check if Boiler is online and execute other requests
|
|
||||||
Ticker systemCheckTimer;
|
|
||||||
|
|
||||||
#define REGULARUPDATES_TIME 60 // every minute a call is made
|
|
||||||
Ticker regularUpdatesTimer;
|
|
||||||
|
|
||||||
#define HEARTBEAT_TIME 1 // every second blink heartbeat LED
|
|
||||||
Ticker heartbeatTimer;
|
|
||||||
|
|
||||||
// thermostat scan - for debugging
|
|
||||||
Ticker scanThermostat;
|
|
||||||
#define SCANTHERMOSTAT_TIME 4
|
|
||||||
uint8_t scanThermostat_count;
|
|
||||||
|
|
||||||
Ticker showerColdShotStopTimer;
|
|
||||||
|
|
||||||
// GPIOs
|
|
||||||
#define LED_HEARTBEAT LED_BUILTIN // onboard LED
|
|
||||||
|
|
||||||
// hostname is also used as the MQTT topic identifier (home/<hostname>)
|
|
||||||
#define HOSTNAME "boiler"
|
|
||||||
|
|
||||||
// app specific - do not change
|
|
||||||
#define MQTT_BOILER MQTT_BASE HOSTNAME "/"
|
|
||||||
#define TOPIC_START MQTT_BOILER MQTT_TOPIC_START
|
|
||||||
|
|
||||||
// thermostat
|
|
||||||
#define TOPIC_THERMOSTAT_DATA MQTT_BOILER "thermostat_data" // for sending thermostat values
|
|
||||||
#define TOPIC_THERMOSTAT_CMD_TEMP MQTT_BOILER "thermostat_cmd_temp" // for received thermostat temp changes
|
|
||||||
#define TOPIC_THERMOSTAT_CMD_MODE MQTT_BOILER "thermostat_cmd_mode" // for received thermostat mode changes
|
|
||||||
#define TOPIC_THERMOSTAT_CURRTEMP "thermostat_currtemp" // current temperature
|
|
||||||
#define TOPIC_THERMOSTAT_SELTEMP "thermostat_seltemp" // selected temperature
|
|
||||||
#define TOPIC_THERMOSTAT_MODE "thermostat_mode" // mode
|
|
||||||
|
|
||||||
// boiler
|
|
||||||
#define TOPIC_BOILER_DATA MQTT_BOILER "boiler_data" // for sending boiler values
|
|
||||||
#define TOPIC_BOILER_TAPWATER_ACTIVE MQTT_BOILER "tapwater_active" // if hot tap water is running
|
|
||||||
#define TOPIC_BOILER_HEATING_ACTIVE MQTT_BOILER "heating_active" // if heating is on
|
|
||||||
|
|
||||||
// shower time
|
|
||||||
#define TOPIC_SHOWERTIME MQTT_BOILER "showertime" // for sending shower time results
|
|
||||||
#define TOPIC_SHOWER_ALARM "shower_alarm" // for notifying HA that shower time has reached its limit
|
|
||||||
#define TOPIC_SHOWER_TIMER MQTT_BOILER "shower_timer" // toggle switch for enabling the shower logic
|
|
||||||
#define TOPIC_SHOWER_ALERT MQTT_BOILER "shower_alert" // toggle switch for enabling the shower alarm logic
|
|
||||||
#define TOPIC_SHOWER_COLDSHOT MQTT_BOILER "shower_coldshot" // used to trigger a coldshot from HA publish
|
|
||||||
|
|
||||||
// logging - EMS_SYS_LOGGING_VERBOSE, EMS_SYS_LOGGING_NONE, EMS_SYS_LOGGING_BASIC (see ems.h)
|
|
||||||
#define BOILER_DEFAULT_LOGGING EMS_SYS_LOGGING_NONE
|
|
||||||
|
|
||||||
// shower settings for DEBUGGING only
|
|
||||||
#ifdef SHOWER_TEST
|
|
||||||
#undef SHOWER_PAUSE_TIME
|
|
||||||
#undef SHOWER_MIN_DURATION
|
|
||||||
#undef SHOWER_MAX_DURATION
|
|
||||||
#undef SHOWER_COLDSHOT_DURATION
|
|
||||||
#undef SHOWER_OFFSET_TIME
|
|
||||||
const unsigned long SHOWER_PAUSE_TIME = 15000; // 15 seconds, max time if water is switched off & on during a shower
|
|
||||||
const unsigned long SHOWER_MIN_DURATION = 20000; // 20 secs, before recognizing its a shower
|
|
||||||
const unsigned long SHOWER_MAX_DURATION = 25000; // 25 secs, before trigger a shot of cold water
|
|
||||||
const unsigned long SHOWER_COLDSHOT_DURATION = 5; // in seconds! how long for cold water shot
|
|
||||||
const unsigned long SHOWER_OFFSET_TIME = 0; // 0 seconds grace time, to calibrate actual time under the shower
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool wifi_connected;
|
|
||||||
bool shower_timer; // true if we want to report back on shower times
|
|
||||||
bool shower_alert; // true if we want the cold water reminder
|
|
||||||
} _Boiler_Status;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool showerOn;
|
|
||||||
unsigned long timerStart; // ms
|
|
||||||
unsigned long timerPause; // ms
|
|
||||||
unsigned long duration; // ms
|
|
||||||
bool doingColdShot; // true if we've just sent a jolt of cold water
|
|
||||||
} _Boiler_Shower;
|
|
||||||
|
|
||||||
// ESPHelper
|
|
||||||
netInfo homeNet = {.mqttHost = MQTT_IP,
|
|
||||||
.mqttUser = MQTT_USER,
|
|
||||||
.mqttPass = MQTT_PASS,
|
|
||||||
.mqttPort = 1883, // this is the default, change if using another port
|
|
||||||
.ssid = WIFI_SSID,
|
|
||||||
.pass = WIFI_PASSWORD};
|
|
||||||
ESPHelper myESP(&homeNet);
|
|
||||||
|
|
||||||
command_t PROGMEM project_cmds[] = {
|
|
||||||
|
|
||||||
{"l [n]", "set logging (0=none, 1=raw, 2=basic, 3=thermostat only, 4=verbose)"},
|
|
||||||
{"s", "show statistics"},
|
|
||||||
{"h", "list supported EMS telegram type IDs"},
|
|
||||||
{"M", "publish to MQTT"},
|
|
||||||
{"Q", "print Tx Queue"},
|
|
||||||
{"P", "toggle EMS Poll response on/off"},
|
|
||||||
{"X", "toggle EMS Tx transmission on/off"},
|
|
||||||
{"S", "toggle Shower timer on/off"},
|
|
||||||
{"A", "toggle shower Alert on/off"},
|
|
||||||
{"r [s]", "send raw telegram to EMS (s=XX XX XX...)"},
|
|
||||||
{"b [xx]", "send boiler read request (xx=telegram type ID in hex)"},
|
|
||||||
{"t [xx]", "send thermostat read request (xx=telegram type ID in hex)"},
|
|
||||||
{"w [nn]", "set boiler warm water temperature (min 30)"},
|
|
||||||
{"a [n]", "set boiler warm tap water (0=off, 1=on)"},
|
|
||||||
{"T [xx]", "set thermostat temperature"},
|
|
||||||
{"m [n]", "set thermostat mode (1=manual, 2=auto)"}
|
|
||||||
//{"U [c]", "do a thermostat scan on all ids (c=start id) for debugging only"}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// calculates size of an 2d array at compile time
|
|
||||||
template <typename T, size_t N>
|
|
||||||
constexpr size_t ArraySize(T (&)[N]) {
|
|
||||||
return N;
|
|
||||||
}
|
|
||||||
|
|
||||||
// store for overall system status
|
|
||||||
_Boiler_Status Boiler_Status;
|
|
||||||
_Boiler_Shower Boiler_Shower;
|
|
||||||
|
|
||||||
// Debugger to telnet
|
|
||||||
#define myDebug(x, ...) myESP.printf(x, ##__VA_ARGS__);
|
|
||||||
|
|
||||||
// CRC checks
|
|
||||||
uint32_t previousBoilerPublishCRC = 0;
|
|
||||||
uint32_t previousThermostatPublishCRC = 0;
|
|
||||||
|
|
||||||
// Times
|
|
||||||
const unsigned long POLL_TIMEOUT_ERR = 10000; // if no signal from boiler for last 10 seconds, assume its offline
|
|
||||||
const unsigned long TX_HOLD_LED_TIME = 2000; // how long to hold the Tx LED because its so quick
|
|
||||||
|
|
||||||
unsigned long timestamp; // for internal timings, via millis()
|
|
||||||
static int connectionStatus = NO_CONNECTION;
|
|
||||||
int boilerStatus = false;
|
|
||||||
bool startMQTTsent = false;
|
|
||||||
|
|
||||||
uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off
|
|
||||||
|
|
||||||
// toggle for heartbeat LED
|
|
||||||
bool heartbeatEnabled;
|
|
||||||
|
|
||||||
// logging messages with fixed strings (newline done automatically)
|
|
||||||
void myDebugLog(const char * s) {
|
|
||||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
|
||||||
myDebug("%s\n", s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert float to char
|
|
||||||
char * _float_to_char(char * a, float f, uint8_t precision = 1) {
|
|
||||||
long p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
|
|
||||||
|
|
||||||
char * ret = a;
|
|
||||||
// check for 0x8000 (sensor missing)
|
|
||||||
if (f == EMS_VALUE_FLOAT_NOTSET) {
|
|
||||||
strcpy(ret, "?");
|
|
||||||
} else {
|
|
||||||
long whole = (long)f;
|
|
||||||
itoa(whole, a, 10);
|
|
||||||
while (*a != '\0')
|
|
||||||
a++;
|
|
||||||
*a++ = '.';
|
|
||||||
long decimal = abs((long)((f - whole) * p[precision]));
|
|
||||||
itoa(decimal, a, 10);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert bool to text
|
|
||||||
char * _bool_to_char(char * s, uint8_t value) {
|
|
||||||
if (value == EMS_VALUE_INT_ON) {
|
|
||||||
strcpy(s, "on");
|
|
||||||
} else if (value == EMS_VALUE_INT_OFF) {
|
|
||||||
strcpy(s, "off");
|
|
||||||
} else {
|
|
||||||
strcpy(s, "?");
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert int to text value
|
|
||||||
char * _int_to_char(char * s, uint8_t value) {
|
|
||||||
if (value == EMS_VALUE_INT_NOTSET) {
|
|
||||||
strcpy(s, "?");
|
|
||||||
} else {
|
|
||||||
itoa(value, s, 10);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// takes a float value at prints it to debug log
|
|
||||||
void _renderFloatValue(const char * prefix, const char * postfix, float value) {
|
|
||||||
myDebug(" %s: ", prefix);
|
|
||||||
char s[20];
|
|
||||||
myDebug("%s", _float_to_char(s, value));
|
|
||||||
|
|
||||||
if (postfix != NULL) {
|
|
||||||
myDebug(" %s", postfix);
|
|
||||||
}
|
|
||||||
|
|
||||||
myDebug("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// takes an int value at prints it to debug log
|
|
||||||
void _renderIntValue(const char * prefix, const char * postfix, uint8_t value) {
|
|
||||||
myDebug(" %s: ", prefix);
|
|
||||||
char s[20];
|
|
||||||
myDebug("%s", _int_to_char(s, value));
|
|
||||||
|
|
||||||
if (postfix != NULL) {
|
|
||||||
myDebug(" %s", postfix);
|
|
||||||
}
|
|
||||||
|
|
||||||
myDebug("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// takes a bool value at prints it to debug log
|
|
||||||
void _renderBoolValue(const char * prefix, uint8_t value) {
|
|
||||||
myDebug(" %s: ", prefix);
|
|
||||||
char s[20];
|
|
||||||
myDebug("%s\n", _bool_to_char(s, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show command - display stats on an 's' command
|
|
||||||
void showInfo() {
|
|
||||||
// General stats from EMS bus
|
|
||||||
|
|
||||||
myDebug("%sEMS-ESP-Boiler system stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
|
||||||
myDebug(" System logging is set to ");
|
|
||||||
_EMS_SYS_LOGGING sysLog = ems_getLogging();
|
|
||||||
if (sysLog == EMS_SYS_LOGGING_BASIC) {
|
|
||||||
myDebug("Basic");
|
|
||||||
} else if (sysLog == EMS_SYS_LOGGING_VERBOSE) {
|
|
||||||
myDebug("Verbose");
|
|
||||||
} else if (sysLog == EMS_SYS_LOGGING_THERMOSTAT) {
|
|
||||||
myDebug("Thermostat only");
|
|
||||||
} else {
|
|
||||||
myDebug("None");
|
|
||||||
}
|
|
||||||
|
|
||||||
myDebug("\n # EMS type handlers: %d\n", ems_getEmsTypesCount());
|
|
||||||
|
|
||||||
myDebug(" Thermostat is %s, Poll is %s, Tx is %s, Shower Timer is %s, Shower Alert is %s\n",
|
|
||||||
(ems_getThermostatEnabled() ? "enabled" : "disabled"),
|
|
||||||
((EMS_Sys_Status.emsPollEnabled) ? "enabled" : "disabled"),
|
|
||||||
((EMS_Sys_Status.emsTxEnabled) ? "enabled" : "disabled"),
|
|
||||||
((Boiler_Status.shower_timer) ? "enabled" : "disabled"),
|
|
||||||
((Boiler_Status.shower_alert) ? "enabled" : "disabled"));
|
|
||||||
|
|
||||||
myDebug(" EMS Bus Stats: Connected=%s, # Rx telegrams=%d, # Tx telegrams=%d, # Crc Errors=%d\n",
|
|
||||||
(ems_getBoilerEnabled() ? "yes" : "no"),
|
|
||||||
EMS_Sys_Status.emsRxPgks,
|
|
||||||
EMS_Sys_Status.emsTxPkgs,
|
|
||||||
EMS_Sys_Status.emxCrcErr);
|
|
||||||
|
|
||||||
myDebug("\n%sBoiler stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
|
||||||
|
|
||||||
// active stats
|
|
||||||
myDebug(" Hot tap water is %s\n", (EMS_Boiler.tapwaterActive ? "running" : "off"));
|
|
||||||
myDebug(" Central Heating is %s\n", (EMS_Boiler.heatingActive ? "active" : "off"));
|
|
||||||
|
|
||||||
// UBAParameterWW
|
|
||||||
_renderBoolValue("Warm Water activated", EMS_Boiler.wWActivated);
|
|
||||||
_renderBoolValue("Warm Water circulation pump available", EMS_Boiler.wWCircPump);
|
|
||||||
_renderIntValue("Warm Water selected temperature", "C", EMS_Boiler.wWSelTemp);
|
|
||||||
_renderIntValue("Warm Water desired temperature", "C", EMS_Boiler.wWDesiredTemp);
|
|
||||||
|
|
||||||
// UBAMonitorWWMessage
|
|
||||||
_renderFloatValue("Warm Water current temperature", "C", EMS_Boiler.wWCurTmp);
|
|
||||||
_renderIntValue("Warm Water # starts", "times", EMS_Boiler.wWStarts);
|
|
||||||
myDebug(" Warm Water active time: %d days %d hours %d minutes\n",
|
|
||||||
EMS_Boiler.wWWorkM / 1440,
|
|
||||||
(EMS_Boiler.wWWorkM % 1440) / 60,
|
|
||||||
EMS_Boiler.wWWorkM % 60);
|
|
||||||
_renderBoolValue("Warm Water 3-way valve", EMS_Boiler.wWHeat);
|
|
||||||
|
|
||||||
// UBAMonitorFast
|
|
||||||
_renderIntValue("Selected flow temperature", "C", EMS_Boiler.selFlowTemp);
|
|
||||||
_renderFloatValue("Current flow temperature", "C", EMS_Boiler.curFlowTemp);
|
|
||||||
_renderFloatValue("Return temperature", "C", EMS_Boiler.retTemp);
|
|
||||||
_renderBoolValue("Gas", EMS_Boiler.burnGas);
|
|
||||||
_renderBoolValue("Boiler pump", EMS_Boiler.heatPmp);
|
|
||||||
_renderBoolValue("Fan", EMS_Boiler.fanWork);
|
|
||||||
_renderBoolValue("Ignition", EMS_Boiler.ignWork);
|
|
||||||
_renderBoolValue("Circulation pump", EMS_Boiler.wWCirc);
|
|
||||||
_renderIntValue("Burner selected max power", "%", EMS_Boiler.selBurnPow);
|
|
||||||
_renderIntValue("Burner current power", "%", EMS_Boiler.curBurnPow);
|
|
||||||
_renderFloatValue("Flame current", "uA", EMS_Boiler.flameCurr);
|
|
||||||
_renderFloatValue("System pressure", "bar", EMS_Boiler.sysPress);
|
|
||||||
|
|
||||||
// UBAMonitorSlow
|
|
||||||
_renderFloatValue("Outside temperature", "C", EMS_Boiler.extTemp);
|
|
||||||
_renderFloatValue("Boiler temperature", "C", EMS_Boiler.boilTemp);
|
|
||||||
_renderIntValue("Pump modulation", "%", EMS_Boiler.pumpMod);
|
|
||||||
_renderIntValue("Burner # restarts", "times", EMS_Boiler.burnStarts);
|
|
||||||
myDebug(" Total burner operating time: %d days %d hours %d minutes\n",
|
|
||||||
EMS_Boiler.burnWorkMin / 1440,
|
|
||||||
(EMS_Boiler.burnWorkMin % 1440) / 60,
|
|
||||||
EMS_Boiler.burnWorkMin % 60);
|
|
||||||
myDebug(" Total heat operating time: %d days %d hours %d minutes\n",
|
|
||||||
EMS_Boiler.heatWorkMin / 1440,
|
|
||||||
(EMS_Boiler.heatWorkMin % 1440) / 60,
|
|
||||||
EMS_Boiler.heatWorkMin % 60);
|
|
||||||
|
|
||||||
// Thermostat stats
|
|
||||||
if (ems_getThermostatEnabled()) {
|
|
||||||
myDebug("\n%sThermostat stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
|
||||||
myDebug(" Thermostat type: ");
|
|
||||||
ems_printThermostatType();
|
|
||||||
myDebug("\n Thermostat time is ");
|
|
||||||
if (EMS_ID_THERMOSTAT != EMS_ID_THERMOSTAT_EASY) {
|
|
||||||
myDebug("%02d:%02d:%02d %d/%d/%d\n",
|
|
||||||
EMS_Thermostat.hour,
|
|
||||||
EMS_Thermostat.minute,
|
|
||||||
EMS_Thermostat.second,
|
|
||||||
EMS_Thermostat.day,
|
|
||||||
EMS_Thermostat.month,
|
|
||||||
EMS_Thermostat.year + 2000);
|
|
||||||
} else {
|
|
||||||
myDebug("<not supported>\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderFloatValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp);
|
|
||||||
_renderFloatValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp);
|
|
||||||
myDebug(" Mode is set to ");
|
|
||||||
if (EMS_Thermostat.mode == 0) {
|
|
||||||
myDebug("low\n");
|
|
||||||
} else if (EMS_Thermostat.mode == 1) {
|
|
||||||
myDebug("manual\n");
|
|
||||||
} else if (EMS_Thermostat.mode == 2) {
|
|
||||||
myDebug("auto\n");
|
|
||||||
} else {
|
|
||||||
myDebug("?\n");
|
|
||||||
// myDebug("? (value is %d)\n", EMS_Thermostat.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// show the Shower Info
|
|
||||||
if (Boiler_Status.shower_timer) {
|
|
||||||
myDebug("\n%s Shower stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
|
||||||
myDebug(" Shower Timer is %s\n", (Boiler_Shower.showerOn ? "active" : "off"));
|
|
||||||
}
|
|
||||||
|
|
||||||
myDebug("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// send values to HA via MQTT
|
|
||||||
// a json object is created for the boiler and one for the thermostat
|
|
||||||
// CRC check is done to see if there are changes in the values since the last send to avoid too much wifi traffic
|
|
||||||
void publishValues(bool force) {
|
|
||||||
char s[20]; // for formatting strings
|
|
||||||
|
|
||||||
// Boiler values as one JSON object
|
|
||||||
StaticJsonBuffer<512> jsonBuffer;
|
|
||||||
char data[512];
|
|
||||||
JsonObject & rootBoiler = jsonBuffer.createObject();
|
|
||||||
|
|
||||||
rootBoiler["wWSelTemp"] = _int_to_char(s, EMS_Boiler.wWSelTemp);
|
|
||||||
rootBoiler["wWActivated"] = _bool_to_char(s, EMS_Boiler.wWActivated);
|
|
||||||
rootBoiler["wWCurTmp"] = _float_to_char(s, EMS_Boiler.wWCurTmp);
|
|
||||||
rootBoiler["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat);
|
|
||||||
rootBoiler["curFlowTemp"] = _float_to_char(s, EMS_Boiler.curFlowTemp);
|
|
||||||
rootBoiler["retTemp"] = _float_to_char(s, EMS_Boiler.retTemp);
|
|
||||||
rootBoiler["burnGas"] = _bool_to_char(s, EMS_Boiler.burnGas);
|
|
||||||
rootBoiler["heatPmp"] = _bool_to_char(s, EMS_Boiler.heatPmp);
|
|
||||||
rootBoiler["fanWork"] = _bool_to_char(s, EMS_Boiler.fanWork);
|
|
||||||
rootBoiler["ignWork"] = _bool_to_char(s, EMS_Boiler.ignWork);
|
|
||||||
rootBoiler["wWCirc"] = _bool_to_char(s, EMS_Boiler.wWCirc);
|
|
||||||
rootBoiler["selBurnPow"] = _int_to_char(s, EMS_Boiler.selBurnPow);
|
|
||||||
rootBoiler["curBurnPow"] = _int_to_char(s, EMS_Boiler.curBurnPow);
|
|
||||||
rootBoiler["sysPress"] = _float_to_char(s, EMS_Boiler.sysPress);
|
|
||||||
rootBoiler["boilTemp"] = _float_to_char(s, EMS_Boiler.boilTemp);
|
|
||||||
rootBoiler["pumpMod"] = _int_to_char(s, EMS_Boiler.pumpMod);
|
|
||||||
|
|
||||||
size_t len = rootBoiler.measureLength();
|
|
||||||
rootBoiler.printTo(data, len + 1); // form the json string
|
|
||||||
|
|
||||||
// calculate hash and send values if something has changed, to save unnecessary wifi traffic
|
|
||||||
CRC32 crc;
|
|
||||||
for (size_t i = 0; i < len - 1; i++) {
|
|
||||||
crc.update(data[i]);
|
|
||||||
}
|
|
||||||
uint32_t checksum = crc.finalize();
|
|
||||||
if ((previousBoilerPublishCRC != checksum) || force) {
|
|
||||||
previousBoilerPublishCRC = checksum;
|
|
||||||
if (ems_getLogging() >= EMS_SYS_LOGGING_BASIC) {
|
|
||||||
myDebug("Publishing boiler data via MQTT\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// send values via MQTT
|
|
||||||
myESP.publish(TOPIC_BOILER_DATA, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// see if the heating or hot tap water has changed, if so send
|
|
||||||
// last_boilerActive stores heating in bit 1 and tap water in bit 2
|
|
||||||
if ((last_boilerActive != ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive)) || force) {
|
|
||||||
if (ems_getLogging() >= EMS_SYS_LOGGING_BASIC) {
|
|
||||||
myDebug("Publishing hot water and heating state via MQTT\n");
|
|
||||||
}
|
|
||||||
myESP.publish(TOPIC_BOILER_TAPWATER_ACTIVE, EMS_Boiler.tapwaterActive == 1 ? "1" : "0");
|
|
||||||
myESP.publish(TOPIC_BOILER_HEATING_ACTIVE, EMS_Boiler.heatingActive == 1 ? "1" : "0");
|
|
||||||
|
|
||||||
last_boilerActive = ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive); // remember last state
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle the thermostat values separately
|
|
||||||
if (ems_getThermostatEnabled()) {
|
|
||||||
// only send thermostat values if we actually have them
|
|
||||||
if (((int)EMS_Thermostat.curr_roomTemp == (int)0) || ((int)EMS_Thermostat.setpoint_roomTemp == (int)0))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// build json object
|
|
||||||
JsonObject & rootThermostat = jsonBuffer.createObject();
|
|
||||||
rootThermostat[TOPIC_THERMOSTAT_CURRTEMP] = _float_to_char(s, EMS_Thermostat.curr_roomTemp);
|
|
||||||
rootThermostat[TOPIC_THERMOSTAT_SELTEMP] = _float_to_char(s, EMS_Thermostat.setpoint_roomTemp);
|
|
||||||
|
|
||||||
// send mode 0=low, 1=manual, 2=auto
|
|
||||||
if (EMS_Thermostat.mode == 0) {
|
|
||||||
rootThermostat[TOPIC_THERMOSTAT_MODE] = "low";
|
|
||||||
} else if (EMS_Thermostat.mode == 1) {
|
|
||||||
rootThermostat[TOPIC_THERMOSTAT_MODE] = "manual";
|
|
||||||
} else {
|
|
||||||
rootThermostat[TOPIC_THERMOSTAT_MODE] = "auto";
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t len = rootThermostat.measureLength();
|
|
||||||
rootThermostat.printTo(data, len + 1); // form the json string
|
|
||||||
|
|
||||||
// calculate new CRC
|
|
||||||
crc.reset();
|
|
||||||
for (size_t i = 0; i < len - 1; i++) {
|
|
||||||
crc.update(data[i]);
|
|
||||||
}
|
|
||||||
uint32_t checksum = crc.finalize();
|
|
||||||
if ((previousThermostatPublishCRC != checksum) || force) {
|
|
||||||
previousThermostatPublishCRC = checksum;
|
|
||||||
if (ems_getLogging() >= EMS_SYS_LOGGING_BASIC) {
|
|
||||||
myDebug("Publishing thermostat data via MQTT\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// send values via MQTT
|
|
||||||
myESP.publish(TOPIC_THERMOSTAT_DATA, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sets the shower timer on/off
|
|
||||||
void set_showerTimer() {
|
|
||||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
|
||||||
myDebug("Shower timer is %s\n", Boiler_Status.shower_timer ? "enabled" : "disabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sets the shower alert on/off
|
|
||||||
void set_showerAlert() {
|
|
||||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
|
||||||
myDebug("Shower alert is %s\n", Boiler_Status.shower_alert ? "enabled" : "disabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// extra commands options for telnet debug window
|
|
||||||
void myDebugCallback() {
|
|
||||||
char * cmd = myESP.consoleGetLastCommand();
|
|
||||||
uint8_t len = strlen(cmd);
|
|
||||||
bool b;
|
|
||||||
|
|
||||||
// look for single letter commands
|
|
||||||
if (len == 1) {
|
|
||||||
switch (cmd[0]) {
|
|
||||||
case 's':
|
|
||||||
showInfo();
|
|
||||||
break;
|
|
||||||
case 'P': // toggle Poll
|
|
||||||
b = !ems_getPoll();
|
|
||||||
ems_setPoll(b);
|
|
||||||
break;
|
|
||||||
case 'X': // toggle Tx
|
|
||||||
b = !ems_getTxEnabled();
|
|
||||||
ems_setTxEnabled(b);
|
|
||||||
break;
|
|
||||||
case 'M':
|
|
||||||
//myESP.logger(LOG_HA, "Force publish values");
|
|
||||||
publishValues(true);
|
|
||||||
break;
|
|
||||||
case 'h': // show type handlers
|
|
||||||
ems_printAllTypes();
|
|
||||||
break;
|
|
||||||
case 'S': // toggle Shower timer support
|
|
||||||
Boiler_Status.shower_timer = !Boiler_Status.shower_timer;
|
|
||||||
myESP.publish(TOPIC_SHOWER_TIMER, Boiler_Status.shower_timer ? "1" : "0");
|
|
||||||
break;
|
|
||||||
case 'A': // toggle Shower alert
|
|
||||||
Boiler_Status.shower_alert = !Boiler_Status.shower_alert;
|
|
||||||
myESP.publish(TOPIC_SHOWER_ALERT, Boiler_Status.shower_alert ? "1" : "0");
|
|
||||||
break;
|
|
||||||
case 'Q': //print Tx Queue
|
|
||||||
ems_printTxQueue();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
myDebug("Unknown command. Use ? for help.\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// for commands with parameters, assume command is just one letter
|
|
||||||
switch (cmd[0]) {
|
|
||||||
case 'T': // set thermostat temp
|
|
||||||
ems_setThermostatTemp(strtof(&cmd[2], 0));
|
|
||||||
break;
|
|
||||||
case 'm': // set thermostat mode
|
|
||||||
if ((cmd[2] - '0') == 1)
|
|
||||||
ems_setThermostatMode(1);
|
|
||||||
else if ((cmd[2] - '0') == 2)
|
|
||||||
ems_setThermostatMode(2);
|
|
||||||
break;
|
|
||||||
case 'w': // set warm water temp
|
|
||||||
ems_setWarmWaterTemp((uint8_t)strtol(&cmd[2], 0, 10));
|
|
||||||
break;
|
|
||||||
case 'l': // logging
|
|
||||||
ems_setLogging((_EMS_SYS_LOGGING)(cmd[2] - '0'));
|
|
||||||
updateHeartbeat();
|
|
||||||
break;
|
|
||||||
case 'a': // set ww activate on or off
|
|
||||||
if ((cmd[2] - '0') == 1)
|
|
||||||
ems_setWarmTapWaterActivated(true);
|
|
||||||
else if ((cmd[2] - '0') == 0)
|
|
||||||
ems_setWarmTapWaterActivated(false);
|
|
||||||
break;
|
|
||||||
case 'b': // boiler read command
|
|
||||||
ems_doReadCommand((uint8_t)strtol(&cmd[2], 0, 16), EMS_ID_BOILER);
|
|
||||||
break;
|
|
||||||
case 't': // thermostat command
|
|
||||||
ems_doReadCommand((uint8_t)strtol(&cmd[2], 0, 16), EMS_ID_THERMOSTAT);
|
|
||||||
break;
|
|
||||||
case 'r': // send raw data
|
|
||||||
ems_sendRawTelegram(&cmd[2]);
|
|
||||||
break;
|
|
||||||
case 'x': // experimental, not displayed!
|
|
||||||
myDebug("Calling experimental...\n");
|
|
||||||
ems_setLogging(EMS_SYS_LOGGING_VERBOSE);
|
|
||||||
ems_setExperimental((uint8_t)strtol(&cmd[2], 0, 16)); // takes HEX param
|
|
||||||
break;
|
|
||||||
case 'U': // thermostat scan
|
|
||||||
myDebug("Doing a type ID scan on thermostat...\n");
|
|
||||||
ems_setLogging(EMS_SYS_LOGGING_THERMOSTAT);
|
|
||||||
publishValuesTimer.detach();
|
|
||||||
systemCheckTimer.detach();
|
|
||||||
regularUpdatesTimer.detach();
|
|
||||||
scanThermostat_count = (uint8_t)strtol(&cmd[2], 0, 16);
|
|
||||||
scanThermostat.attach(SCANTHERMOSTAT_TIME, do_scanThermostat);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
myDebug("Unknown command. Use ? for help.\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// MQTT Callback to handle incoming/outgoing changes
|
|
||||||
void MQTTcallback(char * topic, byte * payload, uint8_t length) {
|
|
||||||
// check if start is received, if so return boottime - defined in ESPHelper.h
|
|
||||||
if (strcmp(topic, TOPIC_START) == 0) {
|
|
||||||
payload[length] = '\0'; // add null terminator
|
|
||||||
//myDebug("MQTT topic boottime: %s\n", payload);
|
|
||||||
myESP.setBoottime((char *)payload);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// thermostat temp changes
|
|
||||||
if (strcmp(topic, TOPIC_THERMOSTAT_CMD_TEMP) == 0) {
|
|
||||||
float f = strtof((char *)payload, 0);
|
|
||||||
char s[10];
|
|
||||||
myDebug("MQTT topic: thermostat temp value %s\n", _float_to_char(s, f));
|
|
||||||
ems_setThermostatTemp(f);
|
|
||||||
// publish back so HA is immediately updated
|
|
||||||
publishValues(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// thermostat mode changes
|
|
||||||
if (strcmp(topic, TOPIC_THERMOSTAT_CMD_MODE) == 0) {
|
|
||||||
payload[length] = '\0'; // add null terminator
|
|
||||||
myDebug("MQTT topic: thermostat mode value %s\n", payload);
|
|
||||||
if (strcmp((char *)payload, "auto") == 0) {
|
|
||||||
ems_setThermostatMode(2);
|
|
||||||
} else if (strcmp((char *)payload, "manual") == 0) {
|
|
||||||
ems_setThermostatMode(1);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// shower timer
|
|
||||||
if (strcmp(topic, TOPIC_SHOWER_TIMER) == 0) {
|
|
||||||
if (payload[0] == '1') {
|
|
||||||
Boiler_Status.shower_timer = true;
|
|
||||||
} else if (payload[0] == '0') {
|
|
||||||
Boiler_Status.shower_timer = false;
|
|
||||||
}
|
|
||||||
set_showerTimer();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// shower alert
|
|
||||||
if (strcmp(topic, TOPIC_SHOWER_ALERT) == 0) {
|
|
||||||
if (payload[0] == '1') {
|
|
||||||
Boiler_Status.shower_alert = true;
|
|
||||||
} else if (payload[0] == '0') {
|
|
||||||
Boiler_Status.shower_alert = false;
|
|
||||||
}
|
|
||||||
set_showerAlert();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// shower cold shot
|
|
||||||
if (strcmp(topic, TOPIC_SHOWER_COLDSHOT) == 0) {
|
|
||||||
_showerColdShotStart();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if HA is booted, restart device too
|
|
||||||
if (strcmp(topic, MQTT_HA) == 0) {
|
|
||||||
payload[length] = '\0'; // add null terminator
|
|
||||||
if (strcmp((char *)payload, "start") == 0) {
|
|
||||||
myDebug("HA rebooted - restarting device\n");
|
|
||||||
myESP.resetESP();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init callback, which is used to set functions and call methods when telnet has started
|
|
||||||
void InitCallback() {
|
|
||||||
ems_setLogging(BOILER_DEFAULT_LOGGING); // turn off logging as default startup
|
|
||||||
}
|
|
||||||
|
|
||||||
// WifiCallback, called when a WiFi connect has successfully been established
|
|
||||||
void WIFIcallback() {
|
|
||||||
Boiler_Status.wifi_connected = true;
|
|
||||||
|
|
||||||
#ifdef USE_LED
|
|
||||||
digitalWrite(LED_HEARTBEAT, HIGH);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// when finally we're all set up, we can fire up the uart (this will enable the UART interrupts)
|
|
||||||
emsuart_init();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets the LED heartbeat depending on the logging setting
|
|
||||||
void updateHeartbeat() {
|
|
||||||
_EMS_SYS_LOGGING logSetting = ems_getLogging();
|
|
||||||
if (logSetting == EMS_SYS_LOGGING_VERBOSE) {
|
|
||||||
heartbeatEnabled = true;
|
|
||||||
} else {
|
|
||||||
heartbeatEnabled = false;
|
|
||||||
#ifdef USE_LED
|
|
||||||
digitalWrite(LED_HEARTBEAT, HIGH); // ...and turn off LED
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the boiler settings
|
|
||||||
void initBoiler() {
|
|
||||||
// default settings
|
|
||||||
Boiler_Status.shower_timer = BOILER_SHOWER_TIMER;
|
|
||||||
Boiler_Status.shower_alert = BOILER_SHOWER_ALERT;
|
|
||||||
ems_setThermostatEnabled(BOILER_THERMOSTAT_ENABLED);
|
|
||||||
|
|
||||||
// init boiler
|
|
||||||
Boiler_Status.wifi_connected = false;
|
|
||||||
|
|
||||||
// init shower
|
|
||||||
Boiler_Shower.timerStart = 0;
|
|
||||||
Boiler_Shower.timerPause = 0;
|
|
||||||
Boiler_Shower.duration = 0;
|
|
||||||
Boiler_Shower.doingColdShot = false;
|
|
||||||
|
|
||||||
// heartbeat only if verbose logging
|
|
||||||
ems_setLogging(BOILER_DEFAULT_LOGGING);
|
|
||||||
updateHeartbeat();
|
|
||||||
}
|
|
||||||
|
|
||||||
// call PublishValues without forcing, so using CRC to see if we really need to publish
|
|
||||||
void do_publishValues() {
|
|
||||||
publishValues(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// SETUP
|
|
||||||
// Note: we don't init the UART here as we should wait until everything is loaded first. It's done in loop()
|
|
||||||
//
|
|
||||||
void setup() {
|
|
||||||
#ifdef USE_LED
|
|
||||||
// set pin for LEDs - start up with all lit up while we sort stuff out
|
|
||||||
pinMode(LED_HEARTBEAT, OUTPUT);
|
|
||||||
digitalWrite(LED_HEARTBEAT, LOW); // onboard LED is on
|
|
||||||
heartbeatTimer.attach(HEARTBEAT_TIME, heartbeat); // blink heartbeat LED
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Timers using Ticker library
|
|
||||||
publishValuesTimer.attach(PUBLISHVALUES_TIME, do_publishValues); // post HA values
|
|
||||||
systemCheckTimer.attach(SYSTEMCHECK_TIME, do_systemCheck); // check if Boiler is online
|
|
||||||
regularUpdatesTimer.attach(REGULARUPDATES_TIME, regularUpdates); // regular reads from the EMS
|
|
||||||
|
|
||||||
// set up WiFi
|
|
||||||
myESP.setWifiCallback(WIFIcallback);
|
|
||||||
|
|
||||||
// set up MQTT
|
|
||||||
myESP.setMQTTCallback(MQTTcallback);
|
|
||||||
myESP.addSubscription(MQTT_HA);
|
|
||||||
myESP.addSubscription(TOPIC_START);
|
|
||||||
myESP.addSubscription(TOPIC_THERMOSTAT_CMD_TEMP);
|
|
||||||
myESP.addSubscription(TOPIC_THERMOSTAT_CMD_MODE);
|
|
||||||
myESP.addSubscription(TOPIC_SHOWER_TIMER);
|
|
||||||
myESP.addSubscription(TOPIC_SHOWER_ALERT);
|
|
||||||
myESP.addSubscription(TOPIC_BOILER_TAPWATER_ACTIVE);
|
|
||||||
myESP.addSubscription(TOPIC_BOILER_HEATING_ACTIVE);
|
|
||||||
myESP.addSubscription(TOPIC_SHOWER_COLDSHOT);
|
|
||||||
|
|
||||||
myESP.setInitCallback(InitCallback);
|
|
||||||
|
|
||||||
myESP.consoleSetCallBackProjectCmds(project_cmds, ArraySize(project_cmds), myDebugCallback); // set up Telnet commands
|
|
||||||
myESP.begin(HOSTNAME, APP_NAME, APP_VERSION); // start wifi and mqtt services
|
|
||||||
|
|
||||||
// init ems statisitcs
|
|
||||||
ems_init();
|
|
||||||
|
|
||||||
// init Boiler specific parameters
|
|
||||||
initBoiler();
|
|
||||||
}
|
|
||||||
|
|
||||||
// heartbeat callback to light up the LED, called via Ticker
|
|
||||||
void heartbeat() {
|
|
||||||
if (heartbeatEnabled) {
|
|
||||||
#ifdef USE_LED
|
|
||||||
int state = digitalRead(LED_HEARTBEAT);
|
|
||||||
digitalWrite(LED_HEARTBEAT, !state);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thermostat scan
|
|
||||||
void do_scanThermostat() {
|
|
||||||
//myDebug("Scanning %d..\n", scanThermostat_count);
|
|
||||||
ems_doReadCommand(scanThermostat_count, EMS_ID_THERMOSTAT);
|
|
||||||
scanThermostat_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// do a healthcheck every now and then to see if we connections
|
|
||||||
void do_systemCheck() {
|
|
||||||
// first do a system check to see if there is still a connection to the EMS
|
|
||||||
if (!ems_getBoilerEnabled()) {
|
|
||||||
myDebug("Error! Unable to connect to EMS bus. Please check connections. Retry in %d seconds...\n",
|
|
||||||
SYSTEMCHECK_TIME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EMS telegrams to send after startup
|
|
||||||
void firstTimeFetch() {
|
|
||||||
ems_doReadCommand(EMS_TYPE_UBAMonitorFast, EMS_ID_BOILER); // get boiler stats which usually comes every 10 sec
|
|
||||||
ems_doReadCommand(EMS_TYPE_UBAMonitorSlow, EMS_ID_BOILER); // get boiler stats which usually comes every 60 sec
|
|
||||||
ems_doReadCommand(EMS_TYPE_UBAParameterWW, EMS_ID_BOILER); // get Warm Water values
|
|
||||||
|
|
||||||
if (ems_getThermostatEnabled()) {
|
|
||||||
ems_getThermostatValues(); // get Thermostat temps (if supported)
|
|
||||||
ems_doReadCommand(EMS_TYPE_RCTime, EMS_ID_THERMOSTAT); // get Thermostat time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// force calls to get data from EMS for the types that aren't sent as broadcasts
|
|
||||||
void regularUpdates() {
|
|
||||||
ems_doReadCommand(EMS_TYPE_UBAParameterWW, EMS_ID_BOILER); // get Warm Water values
|
|
||||||
|
|
||||||
if (ems_getThermostatEnabled()) {
|
|
||||||
ems_getThermostatValues(); // get Thermostat temps (if supported)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// turn off hot water to send a shot of cold
|
|
||||||
void _showerColdShotStart() {
|
|
||||||
myDebugLog("Shower: doing a shot of cold");
|
|
||||||
ems_setWarmTapWaterActivated(false);
|
|
||||||
Boiler_Shower.doingColdShot = true;
|
|
||||||
// start the timer for n seconds which will reset the water back to hot
|
|
||||||
showerColdShotStopTimer.attach(SHOWER_COLDSHOT_DURATION, _showerColdShotStop);
|
|
||||||
}
|
|
||||||
|
|
||||||
// turn back on the hot water for the shower
|
|
||||||
void _showerColdShotStop() {
|
|
||||||
if (Boiler_Shower.doingColdShot) {
|
|
||||||
myDebugLog("Shower: finished shot of cold. hot water back on");
|
|
||||||
ems_setWarmTapWaterActivated(true);
|
|
||||||
Boiler_Shower.doingColdShot = false;
|
|
||||||
showerColdShotStopTimer.detach();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Shower Logic
|
|
||||||
*/
|
|
||||||
void showerCheck() {
|
|
||||||
// if already in cold mode, ignore all this logic until we're out of the cold blast
|
|
||||||
if (!Boiler_Shower.doingColdShot) {
|
|
||||||
// is the hot water running?
|
|
||||||
if (EMS_Boiler.tapwaterActive) {
|
|
||||||
// if heater was previously off, start the timer
|
|
||||||
if (Boiler_Shower.timerStart == 0) {
|
|
||||||
// hot water just started...
|
|
||||||
Boiler_Shower.timerStart = timestamp;
|
|
||||||
Boiler_Shower.timerPause = 0; // remove any last pauses
|
|
||||||
Boiler_Shower.doingColdShot = false;
|
|
||||||
Boiler_Shower.duration = 0;
|
|
||||||
Boiler_Shower.showerOn = false;
|
|
||||||
#ifdef SHOWER_TEST
|
|
||||||
myDebugLog("Shower: hot water on...");
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
// hot water has been on for a while
|
|
||||||
// first check to see if hot water has been on long enough to be recognized as a Shower/Bath
|
|
||||||
if (!Boiler_Shower.showerOn && (timestamp - Boiler_Shower.timerStart) > SHOWER_MIN_DURATION) {
|
|
||||||
Boiler_Shower.showerOn = true;
|
|
||||||
#ifdef SHOWER_TEST
|
|
||||||
|
|
||||||
myDebugLog("Shower: hot water still running, starting shower timer");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
// check if the shower has been on too long
|
|
||||||
else if ((((timestamp - Boiler_Shower.timerStart) > SHOWER_MAX_DURATION) && !Boiler_Shower.doingColdShot)
|
|
||||||
&& Boiler_Status.shower_alert) {
|
|
||||||
myESP.sendHACommand(TOPIC_SHOWER_ALARM);
|
|
||||||
#ifdef SHOWER_TEST
|
|
||||||
myDebugLog("Shower: exceeded max shower time");
|
|
||||||
#endif
|
|
||||||
_showerColdShotStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // hot water is off
|
|
||||||
// if it just turned off, record the time as it could be a short pause
|
|
||||||
if ((Boiler_Shower.timerStart != 0) && (Boiler_Shower.timerPause == 0)) {
|
|
||||||
Boiler_Shower.timerPause = timestamp;
|
|
||||||
#ifdef SHOWER_TEST
|
|
||||||
myDebugLog("Shower: hot water turned off");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// if shower has been off for longer than the wait time
|
|
||||||
if ((Boiler_Shower.timerPause != 0) && ((timestamp - Boiler_Shower.timerPause) > SHOWER_PAUSE_TIME)) {
|
|
||||||
/*
|
|
||||||
sprintf(s,
|
|
||||||
"Shower: duration %d offset %d",
|
|
||||||
(Boiler_Shower.timerPause - Boiler_Shower.timerStart),
|
|
||||||
SHOWER_OFFSET_TIME);
|
|
||||||
myDebugLog("s");
|
|
||||||
*/
|
|
||||||
|
|
||||||
// it is over the wait period, so assume that the shower has finished and calculate the total time and publish
|
|
||||||
// because its unsigned long, can't have negative so check if length is less than OFFSET_TIME
|
|
||||||
if ((Boiler_Shower.timerPause - Boiler_Shower.timerStart) > SHOWER_OFFSET_TIME) {
|
|
||||||
Boiler_Shower.duration = (Boiler_Shower.timerPause - Boiler_Shower.timerStart - SHOWER_OFFSET_TIME);
|
|
||||||
if (Boiler_Shower.duration > SHOWER_MIN_DURATION) {
|
|
||||||
char s[50];
|
|
||||||
sprintf(s,
|
|
||||||
"%d minutes and %d seconds",
|
|
||||||
(uint8_t)((Boiler_Shower.duration / (1000 * 60)) % 60),
|
|
||||||
(uint8_t)((Boiler_Shower.duration / 1000) % 60));
|
|
||||||
|
|
||||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
|
||||||
myDebug("Shower: finished with duration %s\n", s);
|
|
||||||
}
|
|
||||||
myESP.publish(TOPIC_SHOWERTIME, s); // publish to HA
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef SHOWER_TEST
|
|
||||||
// reset everything
|
|
||||||
myDebugLog("Shower: resetting timers");
|
|
||||||
#endif
|
|
||||||
Boiler_Shower.timerStart = 0;
|
|
||||||
Boiler_Shower.timerPause = 0;
|
|
||||||
Boiler_Shower.showerOn = false;
|
|
||||||
_showerColdShotStop(); // turn hot water back on in case its off
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Main loop
|
|
||||||
//
|
|
||||||
void loop() {
|
|
||||||
connectionStatus = myESP.loop();
|
|
||||||
timestamp = millis();
|
|
||||||
|
|
||||||
// update the Rx Tx and ERR LEDs
|
|
||||||
#ifdef USE_LED
|
|
||||||
showLEDs();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// do not continue unless we have a wifi connection
|
|
||||||
if (connectionStatus < WIFI_ONLY) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this is the first time we've connected to MQTT, send a welcome start message
|
|
||||||
// which will send all the state values from HA back to the clock via MQTT and return the boottime
|
|
||||||
if ((!startMQTTsent) && (connectionStatus == FULL_CONNECTION)) {
|
|
||||||
myESP.sendStart();
|
|
||||||
startMQTTsent = true;
|
|
||||||
|
|
||||||
// publish to HA the status of the Shower parameters
|
|
||||||
myESP.publish(TOPIC_SHOWER_TIMER, Boiler_Status.shower_timer ? "1" : "0");
|
|
||||||
myESP.publish(TOPIC_SHOWER_ALERT, Boiler_Status.shower_alert ? "1" : "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the EMS bus has just connected, send a request to fetch some initial values
|
|
||||||
if (ems_getBoilerEnabled() && boilerStatus == false) {
|
|
||||||
boilerStatus = true;
|
|
||||||
firstTimeFetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
// publish the values to MQTT, regardless if the values haven't changed
|
|
||||||
if (ems_getEmsRefreshed()) {
|
|
||||||
publishValues(true);
|
|
||||||
ems_setEmsRefreshed(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// do shower logic if its enabled
|
|
||||||
if (Boiler_Status.shower_timer) {
|
|
||||||
showerCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
yield(); // yield to prevent watchdog from timing out
|
|
||||||
}
|
|
||||||
221
src/ds18.cpp
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* Dallas support for external settings
|
||||||
|
* Copied from Espurna - Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||||
|
*
|
||||||
|
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||||
|
*
|
||||||
|
* See ChangeLog.md for history
|
||||||
|
* See README.md for Acknowledgments
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ds18.h"
|
||||||
|
|
||||||
|
std::vector<ds_device_t> _devices;
|
||||||
|
|
||||||
|
DS18::DS18() {
|
||||||
|
_wire = NULL;
|
||||||
|
_count = 0;
|
||||||
|
_gpio = GPIO_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
DS18::~DS18() {
|
||||||
|
if (_wire)
|
||||||
|
delete _wire;
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
uint8_t DS18::setup(uint8_t gpio) {
|
||||||
|
uint8_t count;
|
||||||
|
|
||||||
|
_gpio = gpio;
|
||||||
|
|
||||||
|
// OneWire
|
||||||
|
if (_wire)
|
||||||
|
delete _wire;
|
||||||
|
_wire = new OneWire(_gpio);
|
||||||
|
|
||||||
|
// Search devices
|
||||||
|
count = loadDevices();
|
||||||
|
|
||||||
|
// If no devices found check again pulling up the line
|
||||||
|
if (count == 0) {
|
||||||
|
pinMode(_gpio, INPUT_PULLUP);
|
||||||
|
count = loadDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
_count = count;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan every 2 seconds
|
||||||
|
void DS18::loop() {
|
||||||
|
static unsigned long last = 0;
|
||||||
|
if (millis() - last < DS18_READ_INTERVAL)
|
||||||
|
return;
|
||||||
|
last = millis();
|
||||||
|
|
||||||
|
// Every second we either start a conversion or read the scratchpad
|
||||||
|
static bool conversion = true;
|
||||||
|
if (conversion) {
|
||||||
|
// Start conversion
|
||||||
|
_wire->reset();
|
||||||
|
_wire->skip();
|
||||||
|
_wire->write(DS18_CMD_START_CONVERSION, DS18_PARASITE);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Read scratchpads
|
||||||
|
for (unsigned char index = 0; index < _devices.size(); index++) {
|
||||||
|
// Read scratchpad
|
||||||
|
if (_wire->reset() == 0) {
|
||||||
|
// Force a CRC check error
|
||||||
|
_devices[index].data[0] = _devices[index].data[0] + 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_wire->select(_devices[index].address);
|
||||||
|
_wire->write(DS18_CMD_READ_SCRATCHPAD);
|
||||||
|
|
||||||
|
uint8_t data[DS18_DATA_SIZE];
|
||||||
|
for (unsigned char i = 0; i < DS18_DATA_SIZE; i++) {
|
||||||
|
data[i] = _wire->read();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_wire->reset() != 1) {
|
||||||
|
// Force a CRC check error
|
||||||
|
_devices[index].data[0] = _devices[index].data[0] + 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(_devices[index].data, data, DS18_DATA_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conversion = !conversion;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return string of the device, with name and address
|
||||||
|
char * DS18::getDeviceString(char * buffer, unsigned char index) {
|
||||||
|
uint8_t size = 128;
|
||||||
|
if (index < _count) {
|
||||||
|
uint8_t * address = _devices[index].address;
|
||||||
|
|
||||||
|
unsigned char chip_id = chip(index);
|
||||||
|
if (chip_id == DS18_CHIP_DS18S20) {
|
||||||
|
strlcpy(buffer, "DS18S20", size);
|
||||||
|
} else if (chip_id == DS18_CHIP_DS18B20) {
|
||||||
|
strlcpy(buffer, "DS18B20", size);
|
||||||
|
} else if (chip_id == DS18_CHIP_DS1822) {
|
||||||
|
strlcpy(buffer, "DS1822", size);
|
||||||
|
} else if (chip_id == DS18_CHIP_DS1825) {
|
||||||
|
strlcpy(buffer, "DS1825", size);
|
||||||
|
} else {
|
||||||
|
strlcpy(buffer, "Unknown", size);
|
||||||
|
}
|
||||||
|
|
||||||
|
char a[30] = {0};
|
||||||
|
snprintf(a,
|
||||||
|
sizeof(a),
|
||||||
|
"(%02X%02X%02X%02X%02X%02X%02X%02X) @ GPIO%d",
|
||||||
|
address[0],
|
||||||
|
address[1],
|
||||||
|
address[2],
|
||||||
|
address[3],
|
||||||
|
address[4],
|
||||||
|
address[5],
|
||||||
|
address[6],
|
||||||
|
address[7],
|
||||||
|
_gpio);
|
||||||
|
|
||||||
|
strlcat(buffer, a, size);
|
||||||
|
} else {
|
||||||
|
strlcpy(buffer, "invalid", size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read sensor values
|
||||||
|
*
|
||||||
|
* Registers:
|
||||||
|
byte 0: temperature LSB
|
||||||
|
byte 1: temperature MSB
|
||||||
|
byte 2: high alarm temp
|
||||||
|
byte 3: low alarm temp
|
||||||
|
byte 4: DS18S20: store for crc
|
||||||
|
DS18B20 & DS1822: configuration register
|
||||||
|
byte 5: internal use & crc
|
||||||
|
byte 6: DS18S20: COUNT_REMAIN
|
||||||
|
DS18B20 & DS1822: store for crc
|
||||||
|
byte 7: DS18S20: COUNT_PER_C
|
||||||
|
DS18B20 & DS1822: store for crc
|
||||||
|
byte 8: SCRATCHPAD_CRC
|
||||||
|
*/
|
||||||
|
double DS18::getValue(unsigned char index) {
|
||||||
|
if (index >= _count)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
uint8_t * data = _devices[index].data;
|
||||||
|
|
||||||
|
if (OneWire::crc8(data, DS18_DATA_SIZE - 1) != data[DS18_DATA_SIZE - 1]) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t raw = (data[1] << 8) | data[0];
|
||||||
|
if (chip(index) == DS18_CHIP_DS18S20) {
|
||||||
|
raw = raw << 3; // 9 bit resolution default
|
||||||
|
if (data[7] == 0x10) {
|
||||||
|
raw = (raw & 0xFFF0) + 12 - data[6]; // "count remain" gives full 12 bit resolution
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
byte cfg = (data[4] & 0x60);
|
||||||
|
if (cfg == 0x00)
|
||||||
|
raw = raw & ~7; // 9 bit res, 93.75 ms
|
||||||
|
else if (cfg == 0x20)
|
||||||
|
raw = raw & ~3; // 10 bit res, 187.5 ms
|
||||||
|
else if (cfg == 0x40)
|
||||||
|
raw = raw & ~1; // 11 bit res, 375 ms
|
||||||
|
// 12 bit res, 750 ms
|
||||||
|
}
|
||||||
|
|
||||||
|
double value = (float)raw / 16.0;
|
||||||
|
if (value == DS18_DISCONNECTED) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for a supported DS chip version
|
||||||
|
bool DS18::validateID(unsigned char id) {
|
||||||
|
return (id == DS18_CHIP_DS18S20) || (id == DS18_CHIP_DS18B20) || (id == DS18_CHIP_DS1822) || (id == DS18_CHIP_DS1825);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the type
|
||||||
|
unsigned char DS18::chip(unsigned char index) {
|
||||||
|
if (index < _count)
|
||||||
|
return _devices[index].address[0];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan for DS sensors and load into the vector
|
||||||
|
uint8_t DS18::loadDevices() {
|
||||||
|
uint8_t address[8];
|
||||||
|
_wire->reset();
|
||||||
|
_wire->reset_search();
|
||||||
|
while (_wire->search(address)) {
|
||||||
|
// Check CRC
|
||||||
|
if (_wire->crc8(address, 7) == address[7]) {
|
||||||
|
// Check ID
|
||||||
|
if (validateID(address[0])) {
|
||||||
|
ds_device_t device;
|
||||||
|
memcpy(device.address, address, 8);
|
||||||
|
_devices.push_back(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (_devices.size());
|
||||||
|
}
|
||||||
55
src/ds18.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Dallas support for external temperature sensors
|
||||||
|
* Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||||
|
*
|
||||||
|
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||||
|
*
|
||||||
|
* See ChangeLog.md for history
|
||||||
|
* See README.md for Acknowledgments
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <OneWire.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define DS18_CHIP_DS18S20 0x10
|
||||||
|
#define DS18_CHIP_DS1822 0x22
|
||||||
|
#define DS18_CHIP_DS18B20 0x28
|
||||||
|
#define DS18_CHIP_DS1825 0x3B
|
||||||
|
|
||||||
|
#define DS18_DATA_SIZE 9
|
||||||
|
#define DS18_PARASITE 1
|
||||||
|
#define DS18_DISCONNECTED -127
|
||||||
|
|
||||||
|
#define GPIO_NONE 0x99
|
||||||
|
#define DS18_READ_INTERVAL 2000 // Force sensor read & cache every 2 seconds
|
||||||
|
|
||||||
|
#define DS18_CMD_START_CONVERSION 0x44
|
||||||
|
#define DS18_CMD_READ_SCRATCHPAD 0xBE
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t address[8];
|
||||||
|
uint8_t data[DS18_DATA_SIZE];
|
||||||
|
} ds_device_t;
|
||||||
|
|
||||||
|
class DS18 {
|
||||||
|
public:
|
||||||
|
DS18();
|
||||||
|
~DS18();
|
||||||
|
|
||||||
|
uint8_t setup(uint8_t gpio);
|
||||||
|
void loop();
|
||||||
|
char * getDeviceString(char * s, unsigned char index);
|
||||||
|
double getValue(unsigned char index);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool validateID(unsigned char id);
|
||||||
|
unsigned char chip(unsigned char index);
|
||||||
|
uint8_t loadDevices();
|
||||||
|
|
||||||
|
OneWire * _wire;
|
||||||
|
uint8_t _count; // # devices
|
||||||
|
uint8_t _gpio; // the sensor pin
|
||||||
|
};
|
||||||
1211
src/ems-esp.ino
Normal file
1544
src/ems.cpp
209
src/ems.h
@@ -1,7 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* Header file for EMS.cpp
|
* Header file for ems.cpp
|
||||||
|
*
|
||||||
|
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||||
|
*
|
||||||
|
* See ChangeLog.md for history
|
||||||
|
* See README.md for Acknowledgments
|
||||||
*
|
*
|
||||||
* You shouldn't need to change much in this file
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -10,82 +14,52 @@
|
|||||||
|
|
||||||
// EMS IDs
|
// EMS IDs
|
||||||
#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs
|
#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs
|
||||||
#define EMS_ID_BOILER 0x08 // Fixed - also known as MC10.
|
#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as the "Service Key"
|
||||||
#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as "Service Key"
|
#define EMS_ID_DEFAULT_BOILER 0x08
|
||||||
|
|
||||||
#define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC
|
#define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC
|
||||||
#define EMS_MAX_TELEGRAM_LENGTH 99 // max length of a telegram, including CRC
|
|
||||||
|
|
||||||
#define EMS_TX_MAXBUFFERSIZE 128 // max size of the buffer. packets are 32 bits
|
// max length of a telegram, including CRC, for Rx and Tx.
|
||||||
|
#define EMS_MAX_TELEGRAM_LENGTH 99
|
||||||
#define EMS_ID_THERMOSTAT_RC20 0x17 // RC20 (e.g. Moduline 300)
|
|
||||||
#define EMS_ID_THERMOSTAT_RC30 0x10 // RC30 (e.g. Moduline 400)
|
|
||||||
#define EMS_ID_THERMOSTAT_EASY 0x18 // TC100 (Nefit Easy)
|
|
||||||
|
|
||||||
// define here the EMS telegram types you need
|
|
||||||
|
|
||||||
// Common for all EMS devices
|
|
||||||
#define EMS_TYPE_Version 0x02 // version of the UBA controller (boiler)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Boiler...
|
|
||||||
*/
|
|
||||||
#define EMS_TYPE_UBAMonitorFast 0x18 // is an automatic monitor broadcast
|
|
||||||
#define EMS_TYPE_UBAMonitorSlow 0x19 // is an automatic monitor broadcast
|
|
||||||
#define EMS_TYPE_UBAMonitorWWMessage 0x34 // is an automatic monitor broadcast
|
|
||||||
#define EMS_TYPE_UBAMaintenanceStatusMessage 0x1C // is an automatic monitor broadcast
|
|
||||||
#define EMS_TYPE_UBAParameterWW 0x33
|
|
||||||
#define EMS_TYPE_UBATotalUptimeMessage 0x14
|
|
||||||
#define EMS_TYPE_UBAMaintenanceSettingsMessage 0x15
|
|
||||||
#define EMS_TYPE_UBAParametersMessage 0x16
|
|
||||||
#define EMS_TYPE_UBASetPoints 0x1A
|
|
||||||
#define EMS_TYPE_UBAFunctionTest 0x1D
|
|
||||||
|
|
||||||
#define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature
|
|
||||||
#define EMS_OFFSET_UBAParameterWW_wwactivated 1 // WW Activated
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Thermostat...
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Common for all thermostats
|
|
||||||
#define EMS_TYPE_RCTime 0x06 // is an automatic thermostat broadcast
|
|
||||||
#define EMS_TYPE_RCOutdoorTempMessage 0xA3 // is an automatic thermostat broadcast, outdoor external temp
|
|
||||||
|
|
||||||
// RC20 specific
|
|
||||||
#define EMS_TYPE_RC20StatusMessage 0x91 // is an automatic thermostat broadcast giving us temps
|
|
||||||
#define EMS_TYPE_RC20Set 0xA8 // for setting values like temp and mode
|
|
||||||
#define EMS_OFFSET_RC20Set_mode 23 // position of thermostat mode
|
|
||||||
#define EMS_OFFSET_RC20Set_temp 28 // position of thermostat setpoint temperature
|
|
||||||
|
|
||||||
// RC30 specific
|
|
||||||
#define EMS_TYPE_RC30StatusMessage 0x41 // is an automatic thermostat broadcast giving us temps
|
|
||||||
#define EMS_TYPE_RC30Set 0xA7 // for setting values like temp and mode
|
|
||||||
#define EMS_OFFSET_RC30Set_mode 23 // position of thermostat mode
|
|
||||||
#define EMS_OFFSET_RC30Set_temp 28 // position of thermostat setpoint temperature
|
|
||||||
|
|
||||||
// Easy specific
|
|
||||||
#define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat
|
|
||||||
|
|
||||||
// default values
|
// default values
|
||||||
#define EMS_VALUE_INT_ON 1 // boolean true
|
#define EMS_VALUE_INT_ON 1 // boolean true
|
||||||
#define EMS_VALUE_INT_OFF 0 // boolean false
|
#define EMS_VALUE_INT_OFF 0 // boolean false
|
||||||
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit ints
|
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit ints
|
||||||
#define EMS_VALUE_FLOAT_NOTSET -255 // float unset
|
#define EMS_VALUE_LONG_NOTSET 0xFFFFFF // for 3-byte longs
|
||||||
|
#define EMS_VALUE_FLOAT_NOTSET -255 // float
|
||||||
|
|
||||||
|
#define EMS_THERMOSTAT_READ_YES true
|
||||||
|
#define EMS_THERMOSTAT_READ_NO false
|
||||||
|
#define EMS_THERMOSTAT_WRITE_YES true
|
||||||
|
#define EMS_THERMOSTAT_WRITE_NO false
|
||||||
|
|
||||||
|
// trigger settings to determine if hot tap water or the heating is active
|
||||||
|
#define EMS_BOILER_BURNPOWER_TAPWATER 100
|
||||||
|
#define EMS_BOILER_SELFLOWTEMP_HEATING 70
|
||||||
|
|
||||||
|
//define maximum settable tapwater temperature, not every installation supports 90 degrees
|
||||||
|
#define EMS_BOILER_TAPWATER_TEMPERATURE_MAX 60
|
||||||
|
|
||||||
|
#define EMS_TX_TELEGRAM_QUEUE_MAX 50 // max size of Tx FIFO queue
|
||||||
|
|
||||||
|
//#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_VERBOSE
|
||||||
|
#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_NONE
|
||||||
|
|
||||||
/* EMS UART transfer status */
|
/* EMS UART transfer status */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
EMS_RX_IDLE,
|
EMS_RX_STATUS_IDLE,
|
||||||
EMS_RX_ACTIVE // Rx package is being sent
|
EMS_RX_STATUS_BUSY // Rx package is being received
|
||||||
} _EMS_RX_STATUS;
|
} _EMS_RX_STATUS;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
EMS_TX_IDLE,
|
EMS_TX_STATUS_IDLE, // ready
|
||||||
EMS_TX_ACTIVE, // Tx package being sent, no break sent
|
EMS_TX_STATUS_WAIT // waiting for response from last Tx
|
||||||
EMS_TX_SUCCESS,
|
|
||||||
EMS_TX_ERROR
|
|
||||||
} _EMS_TX_STATUS;
|
} _EMS_TX_STATUS;
|
||||||
|
|
||||||
|
#define EMS_TX_SUCCESS 0x01 // EMS single byte after a Tx Write indicating a success
|
||||||
|
#define EMS_TX_ERROR 0x04 // EMS single byte after a Tx Write indicating an error
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
EMS_TX_TELEGRAM_INIT, // just initialized
|
EMS_TX_TELEGRAM_INIT, // just initialized
|
||||||
EMS_TX_TELEGRAM_READ, // doing a read request
|
EMS_TX_TELEGRAM_READ, // doing a read request
|
||||||
@@ -111,16 +85,18 @@ typedef struct {
|
|||||||
uint16_t emsTxPkgs; // sent
|
uint16_t emsTxPkgs; // sent
|
||||||
uint16_t emxCrcErr; // CRC errors
|
uint16_t emxCrcErr; // CRC errors
|
||||||
bool emsPollEnabled; // flag enable the response to poll messages
|
bool emsPollEnabled; // flag enable the response to poll messages
|
||||||
bool emsTxEnabled; // flag if we're allowing sending of Tx packages
|
|
||||||
bool emsThermostatEnabled; // if there is a RCxx thermostat active
|
|
||||||
bool emsBoilerEnabled; // is the boiler online
|
|
||||||
_EMS_SYS_LOGGING emsLogging; // logging
|
_EMS_SYS_LOGGING emsLogging; // logging
|
||||||
bool emsRefreshed; // fresh data, needs to be pushed out to MQTT
|
bool emsRefreshed; // fresh data, needs to be pushed out to MQTT
|
||||||
|
bool emsBusConnected; // is there an active bus
|
||||||
|
unsigned long emsRxTimestamp; // timestamp of last EMS message received
|
||||||
|
unsigned long emsPollTimestamp; // timestamp of last EMS poll sent to us
|
||||||
|
bool emsTxCapable; // able to send via Tx
|
||||||
|
uint8_t txRetryCount; // # times the last Tx was re-sent
|
||||||
} _EMS_Sys_Status;
|
} _EMS_Sys_Status;
|
||||||
|
|
||||||
// The Tx send package
|
// The Tx send package
|
||||||
typedef struct {
|
typedef struct {
|
||||||
_EMS_TX_TELEGRAM_ACTION action; // read or write
|
_EMS_TX_TELEGRAM_ACTION action; // read, write, validate, init
|
||||||
uint8_t dest;
|
uint8_t dest;
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
uint8_t offset;
|
uint8_t offset;
|
||||||
@@ -130,11 +106,13 @@ typedef struct {
|
|||||||
uint8_t comparisonValue; // value to compare against during a validate
|
uint8_t comparisonValue; // value to compare against during a validate
|
||||||
uint8_t comparisonOffset; // offset of where the byte is we want to compare too later
|
uint8_t comparisonOffset; // offset of where the byte is we want to compare too later
|
||||||
uint8_t comparisonPostRead; // after a successful write call this to read
|
uint8_t comparisonPostRead; // after a successful write call this to read
|
||||||
bool hasSent; // has been sent, just pending ack
|
|
||||||
bool forceRefresh; // should we send to MQTT after a successful Tx?
|
bool forceRefresh; // should we send to MQTT after a successful Tx?
|
||||||
uint8_t data[EMS_TX_MAXBUFFERSIZE];
|
unsigned long timestamp; // when created
|
||||||
|
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
|
||||||
} _EMS_TxTelegram;
|
} _EMS_TxTelegram;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// default empty Tx
|
// default empty Tx
|
||||||
const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
|
const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
|
||||||
EMS_TX_TELEGRAM_INIT, // action
|
EMS_TX_TELEGRAM_INIT, // action
|
||||||
@@ -147,11 +125,28 @@ const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
|
|||||||
0, // comparisonValue
|
0, // comparisonValue
|
||||||
0, // comparisonOffset
|
0, // comparisonOffset
|
||||||
EMS_ID_NONE, // comparisonPostRead
|
EMS_ID_NONE, // comparisonPostRead
|
||||||
false, // hasSent
|
|
||||||
false, // forceRefresh
|
false, // forceRefresh
|
||||||
|
0, // timestamp
|
||||||
{0x00} // data
|
{0x00} // data
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t model_id;
|
||||||
|
uint8_t product_id;
|
||||||
|
uint8_t type_id;
|
||||||
|
char model_string[50];
|
||||||
|
} _Boiler_Type;
|
||||||
|
|
||||||
|
// Definition for thermostat type
|
||||||
|
typedef struct {
|
||||||
|
uint8_t model_id;
|
||||||
|
uint8_t product_id;
|
||||||
|
uint8_t type_id;
|
||||||
|
char model_string[50];
|
||||||
|
bool read_supported;
|
||||||
|
bool write_supported;
|
||||||
|
} _Thermostat_Type;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Telegram package defintions
|
* Telegram package defintions
|
||||||
*/
|
*/
|
||||||
@@ -160,6 +155,7 @@ typedef struct { // UBAParameterWW
|
|||||||
uint8_t wWSelTemp; // Warm Water selected temperature
|
uint8_t wWSelTemp; // Warm Water selected temperature
|
||||||
uint8_t wWCircPump; // Warm Water circulation pump Available
|
uint8_t wWCircPump; // Warm Water circulation pump Available
|
||||||
uint8_t wWDesiredTemp; // Warm Water desired temperature
|
uint8_t wWDesiredTemp; // Warm Water desired temperature
|
||||||
|
uint8_t wWComfort; // Warm water comfort or ECO mode
|
||||||
|
|
||||||
// UBAMonitorFast
|
// UBAMonitorFast
|
||||||
uint8_t selFlowTemp; // Selected flow temperature
|
uint8_t selFlowTemp; // Selected flow temperature
|
||||||
@@ -175,33 +171,53 @@ typedef struct { // UBAParameterWW
|
|||||||
uint8_t curBurnPow; // Burner current power
|
uint8_t curBurnPow; // Burner current power
|
||||||
float flameCurr; // Flame current in micro amps
|
float flameCurr; // Flame current in micro amps
|
||||||
float sysPress; // System pressure
|
float sysPress; // System pressure
|
||||||
|
char serviceCodeChar[2]; // 2 character status/service code
|
||||||
|
|
||||||
// UBAMonitorSlow
|
// UBAMonitorSlow
|
||||||
float extTemp; // Outside temperature
|
float extTemp; // Outside temperature
|
||||||
float boilTemp; // Boiler temperature
|
float boilTemp; // Boiler temperature
|
||||||
uint8_t pumpMod; // Pump modulation
|
uint8_t pumpMod; // Pump modulation
|
||||||
uint16_t burnStarts; // # burner restarts
|
uint32_t burnStarts; // # burner restarts
|
||||||
uint16_t burnWorkMin; // Total burner operating time
|
uint32_t burnWorkMin; // Total burner operating time
|
||||||
uint16_t heatWorkMin; // Total heat operating time
|
uint32_t heatWorkMin; // Total heat operating time
|
||||||
|
|
||||||
// UBAMonitorWWMessage
|
// UBAMonitorWWMessage
|
||||||
float wWCurTmp; // Warm Water current temperature:
|
float wWCurTmp; // Warm Water current temperature:
|
||||||
uint32_t wWStarts; // Warm Water # starts
|
uint32_t wWStarts; // Warm Water # starts
|
||||||
uint32_t wWWorkM; // Warm Water # minutes
|
uint32_t wWWorkM; // Warm Water # minutes
|
||||||
uint8_t wWOneTime; // Warm Water one time function on/off
|
uint8_t wWOneTime; // Warm Water one time function on/off
|
||||||
|
uint8_t wWCurFlow; // Warm Water current flow in l/min
|
||||||
|
|
||||||
|
// UBATotalUptimeMessage
|
||||||
|
uint32_t UBAuptime; // Total UBA working hours
|
||||||
|
|
||||||
|
// UBAParametersMessage
|
||||||
|
uint8_t heating_temp; // Heating temperature setting on the boiler
|
||||||
|
uint8_t pump_mod_max; // Boiler circuit pump modulation max. power
|
||||||
|
uint8_t pump_mod_min; // Boiler circuit pump modulation min. power
|
||||||
|
|
||||||
// calculated values
|
// calculated values
|
||||||
uint8_t tapwaterActive; // Hot tap water is on/off
|
uint8_t tapwaterActive; // Hot tap water is on/off
|
||||||
uint8_t heatingActive; // Central heating is on/off
|
uint8_t heatingActive; // Central heating is on/off
|
||||||
|
|
||||||
|
// settings
|
||||||
|
char version[10];
|
||||||
|
uint8_t type_id; // this is typically always 0x08
|
||||||
|
uint8_t product_id;
|
||||||
} _EMS_Boiler;
|
} _EMS_Boiler;
|
||||||
|
|
||||||
// Thermostat data
|
// Thermostat data
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t type; // thermostat type (RC30, Easy etc)
|
uint8_t type_id; // the type ID of the thermostat
|
||||||
|
uint8_t model_id; // which Thermostat type
|
||||||
|
uint8_t product_id;
|
||||||
|
bool read_supported;
|
||||||
|
bool write_supported;
|
||||||
|
char version[10];
|
||||||
float setpoint_roomTemp; // current set temp
|
float setpoint_roomTemp; // current set temp
|
||||||
float curr_roomTemp; // current room temp
|
float curr_roomTemp; // current room temp
|
||||||
uint8_t mode; // 0=low, 1=manual, 2=auto
|
uint8_t mode; // 0=low, 1=manual, 2=auto
|
||||||
|
bool day_mode; // 0=night, 1=day
|
||||||
uint8_t hour;
|
uint8_t hour;
|
||||||
uint8_t minute;
|
uint8_t minute;
|
||||||
uint8_t second;
|
uint8_t second;
|
||||||
@@ -210,35 +226,16 @@ typedef struct {
|
|||||||
uint8_t year;
|
uint8_t year;
|
||||||
} _EMS_Thermostat;
|
} _EMS_Thermostat;
|
||||||
|
|
||||||
// call back function signature
|
// call back function signature for processing telegram types
|
||||||
typedef void (*EMS_processType_cb)(uint8_t * data, uint8_t length);
|
typedef void (*EMS_processType_cb)(uint8_t type, uint8_t * data, uint8_t length);
|
||||||
|
|
||||||
// Definition for each EMS type, including the relative callback function
|
// Definition for each EMS type, including the relative callback function
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t src;
|
uint8_t model_id;
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
const char typeString[50];
|
const char typeString[50];
|
||||||
EMS_processType_cb processType_cb;
|
EMS_processType_cb processType_cb;
|
||||||
} _EMS_Types;
|
} _EMS_Type;
|
||||||
|
|
||||||
// Definition for thermostat type
|
|
||||||
typedef struct {
|
|
||||||
uint8_t id;
|
|
||||||
const char typeString[50];
|
|
||||||
} _Thermostat_Types;
|
|
||||||
|
|
||||||
// ANSI Colors
|
|
||||||
#define COLOR_RESET "\x1B[0m"
|
|
||||||
#define COLOR_BLACK "\x1B[0;30m"
|
|
||||||
#define COLOR_RED "\x1B[0;31m"
|
|
||||||
#define COLOR_GREEN "\x1B[0;32m"
|
|
||||||
#define COLOR_YELLOW "\x1B[0;33m"
|
|
||||||
#define COLOR_BLUE "\x1B[0;34m"
|
|
||||||
#define COLOR_MAGENTA "\x1B[0;35m"
|
|
||||||
#define COLOR_CYAN "\x1B[0;36m"
|
|
||||||
#define COLOR_WHITE "\x1B[0;37m"
|
|
||||||
#define COLOR_BOLD_ON "\x1B[1m"
|
|
||||||
#define COLOR_BOLD_OFF "\x1B[21m"
|
|
||||||
|
|
||||||
// function definitions
|
// function definitions
|
||||||
extern void ems_parseTelegram(uint8_t * telegram, uint8_t len);
|
extern void ems_parseTelegram(uint8_t * telegram, uint8_t len);
|
||||||
@@ -251,32 +248,42 @@ void ems_setThermostatMode(uint8_t mode);
|
|||||||
void ems_setWarmWaterTemp(uint8_t temperature);
|
void ems_setWarmWaterTemp(uint8_t temperature);
|
||||||
void ems_setWarmWaterActivated(bool activated);
|
void ems_setWarmWaterActivated(bool activated);
|
||||||
void ems_setWarmTapWaterActivated(bool activated);
|
void ems_setWarmTapWaterActivated(bool activated);
|
||||||
void ems_setExperimental(uint8_t value);
|
|
||||||
void ems_setPoll(bool b);
|
void ems_setPoll(bool b);
|
||||||
void ems_setTxEnabled(bool b);
|
void ems_setTxEnabled(bool b);
|
||||||
void ems_setThermostatEnabled(bool b);
|
|
||||||
void ems_setLogging(_EMS_SYS_LOGGING loglevel);
|
void ems_setLogging(_EMS_SYS_LOGGING loglevel);
|
||||||
void ems_setEmsRefreshed(bool b);
|
void ems_setEmsRefreshed(bool b);
|
||||||
|
void ems_setWarmWaterModeComfort(bool comfort);
|
||||||
|
bool ems_checkEMSBUSAlive();
|
||||||
|
void ems_setModels();
|
||||||
|
|
||||||
void ems_getThermostatValues();
|
void ems_getThermostatValues();
|
||||||
|
void ems_getBoilerValues();
|
||||||
bool ems_getPoll();
|
bool ems_getPoll();
|
||||||
bool ems_getTxEnabled();
|
bool ems_getTxEnabled();
|
||||||
bool ems_getThermostatEnabled();
|
bool ems_getThermostatEnabled();
|
||||||
bool ems_getBoilerEnabled();
|
bool ems_getBoilerEnabled();
|
||||||
|
bool ems_getBusConnected();
|
||||||
_EMS_SYS_LOGGING ems_getLogging();
|
_EMS_SYS_LOGGING ems_getLogging();
|
||||||
uint8_t ems_getEmsTypesCount();
|
|
||||||
uint8_t ems_getThermostatTypesCount();
|
|
||||||
bool ems_getEmsRefreshed();
|
bool ems_getEmsRefreshed();
|
||||||
|
uint8_t ems_getThermostatModel();
|
||||||
|
void ems_discoverModels();
|
||||||
|
bool ems_getTxCapable();
|
||||||
|
|
||||||
|
void ems_scanDevices();
|
||||||
void ems_printAllTypes();
|
void ems_printAllTypes();
|
||||||
void ems_printThermostatType();
|
char * ems_getThermostatDescription(char * buffer);
|
||||||
void ems_printTxQueue();
|
void ems_printTxQueue();
|
||||||
|
char * ems_getBoilerDescription(char * buffer);
|
||||||
|
|
||||||
// private functions
|
// private functions
|
||||||
uint8_t _crcCalculator(uint8_t * data, uint8_t len);
|
uint8_t _crcCalculator(uint8_t * data, uint8_t len);
|
||||||
void _processType(uint8_t * telegram, uint8_t length);
|
void _processType(uint8_t * telegram, uint8_t length);
|
||||||
void _debugPrintPackage(const char * prefix, uint8_t * data, uint8_t len, const char * color);
|
void _debugPrintPackage(const char * prefix, uint8_t * data, uint8_t len, const char * color);
|
||||||
void _ems_clearTxData();
|
void _ems_clearTxData();
|
||||||
|
int _ems_findBoilerModel(uint8_t model_id);
|
||||||
|
bool _ems_setModel(uint8_t model_id);
|
||||||
|
void _ems_setThermostatModel(uint8_t thermostat_modelid);
|
||||||
|
void _removeTxQueue();
|
||||||
|
|
||||||
// global so can referenced in other classes
|
// global so can referenced in other classes
|
||||||
extern _EMS_Sys_Status EMS_Sys_Status;
|
extern _EMS_Sys_Status EMS_Sys_Status;
|
||||||
|
|||||||
146
src/ems_devices.h
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* General information about known EMS devices
|
||||||
|
*
|
||||||
|
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||||
|
*
|
||||||
|
* See ChangeLog.md for History
|
||||||
|
* See README.md for Acknowledgments
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ems.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Common
|
||||||
|
*/
|
||||||
|
#define EMS_TYPE_Version 0x02
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Boiler...
|
||||||
|
*/
|
||||||
|
#define EMS_TYPE_UBAMonitorFast 0x18 // is an automatic monitor broadcast
|
||||||
|
#define EMS_TYPE_UBAMonitorSlow 0x19 // is an automatic monitor broadcast
|
||||||
|
#define EMS_TYPE_UBAMonitorWWMessage 0x34 // is an automatic monitor broadcast
|
||||||
|
#define EMS_TYPE_UBAMaintenanceStatusMessage 0x1C // is an automatic monitor broadcast
|
||||||
|
#define EMS_TYPE_UBAParameterWW 0x33
|
||||||
|
#define EMS_TYPE_UBATotalUptimeMessage 0x14
|
||||||
|
#define EMS_TYPE_UBAMaintenanceSettingsMessage 0x15
|
||||||
|
#define EMS_TYPE_UBAParametersMessage 0x16
|
||||||
|
#define EMS_TYPE_UBASetPoints 0x1A
|
||||||
|
#define EMS_TYPE_UBAFunctionTest 0x1D
|
||||||
|
|
||||||
|
#define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature
|
||||||
|
#define EMS_OFFSET_UBAParameterWW_wwactivated 1 // WW Activated
|
||||||
|
#define EMS_OFFSET_UBAParameterWW_wwComfort 9 // WW is in comfort or eco mode
|
||||||
|
#define EMS_VALUE_UBAParameterWW_wwComfort_Comfort 0x00 // the value for comfort
|
||||||
|
#define EMS_VALUE_UBAParameterWW_wwComfort_Eco 0xD8 // the value for eco
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Thermostats...
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Common for all thermostats
|
||||||
|
#define EMS_TYPE_RCTime 0x06 // is an automatic thermostat broadcast
|
||||||
|
#define EMS_TYPE_RCOutdoorTempMessage 0xA3 // is an automatic thermostat broadcast, outdoor external temp
|
||||||
|
|
||||||
|
// RC10 specific
|
||||||
|
#define EMS_TYPE_RC10StatusMessage 0xB1 // is an automatic thermostat broadcast giving us temps
|
||||||
|
#define EMS_TYPE_RC10Set 0xB0 // for setting values like temp and mode
|
||||||
|
#define EMS_OFFSET_RC10Set_temp 4 // position of thermostat setpoint temperature
|
||||||
|
#define EMS_TYPE_RC10StatusMessage_setpoint 1 // setpoint temp
|
||||||
|
#define EMS_TYPE_RC10StatusMessage_curr 3 // current temp
|
||||||
|
|
||||||
|
// RC20 specific
|
||||||
|
#define EMS_TYPE_RC20StatusMessage 0x91 // is an automatic thermostat broadcast giving us temps
|
||||||
|
#define EMS_TYPE_RC20Set 0xA8 // for setting values like temp and mode
|
||||||
|
#define EMS_OFFSET_RC20Set_mode 23 // position of thermostat mode
|
||||||
|
#define EMS_OFFSET_RC20Set_temp 28 // position of thermostat setpoint temperature
|
||||||
|
#define EMS_TYPE_RC20StatusMessage_setpoint 1 // setpoint temp
|
||||||
|
#define EMS_TYPE_RC20StatusMessage_curr 2 // current temp
|
||||||
|
|
||||||
|
// RC30 specific
|
||||||
|
#define EMS_TYPE_RC30StatusMessage 0x41 // is an automatic thermostat broadcast giving us temps
|
||||||
|
#define EMS_TYPE_RC30Set 0xA7 // for setting values like temp and mode
|
||||||
|
#define EMS_OFFSET_RC30Set_mode 23 // position of thermostat mode
|
||||||
|
#define EMS_OFFSET_RC30Set_temp 28 // position of thermostat setpoint temperature
|
||||||
|
#define EMS_TYPE_RC30StatusMessage_setpoint 1 // setpoint temp
|
||||||
|
#define EMS_TYPE_RC30StatusMessage_curr 2 // current temp
|
||||||
|
|
||||||
|
// RC35 specific
|
||||||
|
#define EMS_TYPE_RC35StatusMessage 0x3E // is an automatic thermostat broadcast giving us temps
|
||||||
|
#define EMS_TYPE_RC35StatusMessage_setpoint 2 // desired temp
|
||||||
|
#define EMS_TYPE_RC35StatusMessage_curr 3 // current temp
|
||||||
|
#define EMS_TYPE_RC35Set 0x3D // for setting values like temp and mode (Working mode HC1)
|
||||||
|
#define EMS_OFFSET_RC35Set_mode 7 // position of thermostat mode
|
||||||
|
#define EMS_OFFSET_RC35Set_temp_day 2 // position of thermostat setpoint temperature for day time
|
||||||
|
#define EMS_OFFSET_RC35Set_temp_night 1 // position of thermostat setpoint temperature for night time
|
||||||
|
#define EMS_OFFSET_RC35Get_mode_day 1 // position of thermostat day mode
|
||||||
|
|
||||||
|
// Easy specific
|
||||||
|
#define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat
|
||||||
|
#define EMS_TYPE_EasyStatusMessage_setpoint 10 // setpoint temp
|
||||||
|
#define EMS_TYPE_EasyStatusMessage_curr 8 // current temp
|
||||||
|
|
||||||
|
// Known EMS types
|
||||||
|
typedef enum {
|
||||||
|
EMS_MODEL_NONE,
|
||||||
|
EMS_MODEL_ALL, // common for all devices
|
||||||
|
|
||||||
|
// generic ID for the boiler
|
||||||
|
EMS_MODEL_UBA,
|
||||||
|
|
||||||
|
// thermostats
|
||||||
|
EMS_MODEL_ES73,
|
||||||
|
EMS_MODEL_RC10,
|
||||||
|
EMS_MODEL_RC20,
|
||||||
|
EMS_MODEL_RC20F,
|
||||||
|
EMS_MODEL_RC30,
|
||||||
|
EMS_MODEL_RC35,
|
||||||
|
EMS_MODEL_EASY,
|
||||||
|
EMS_MODEL_BOSCHEASY,
|
||||||
|
EMS_MODEL_RC310,
|
||||||
|
EMS_MODEL_CW100,
|
||||||
|
EMS_MODEL_OT
|
||||||
|
|
||||||
|
} _EMS_MODEL_ID;
|
||||||
|
|
||||||
|
// EMS types for known Buderus/Bosch devices. This list will be extended when new devices are recognized.
|
||||||
|
// format is MODEL_ID, PRODUCT ID, TYPE_ID, DESCRIPTION
|
||||||
|
const _Boiler_Type Boiler_Types[] = {
|
||||||
|
|
||||||
|
{EMS_MODEL_UBA, 72, 0x08, "MC10"},
|
||||||
|
{EMS_MODEL_UBA, 123, 0x08, "Buderus GB172/Nefit Trendline"},
|
||||||
|
{EMS_MODEL_UBA, 115, 0x08, "Nefit Topline Compact"},
|
||||||
|
{EMS_MODEL_UBA, 203, 0x08, "Buderus Logamax U122"},
|
||||||
|
{EMS_MODEL_UBA, 64, 0x08, "Sieger BK15 Boiler/Nefit Smartline"},
|
||||||
|
{EMS_MODEL_UBA, 190, 0x09, "BC10 Base Controller"},
|
||||||
|
{EMS_MODEL_UBA, 114, 0x09, "BC10 Base Controller"},
|
||||||
|
{EMS_MODEL_UBA, 125, 0x09, "BC25 Base Controller"},
|
||||||
|
{EMS_MODEL_UBA, 68, 0x09, "RFM20 Receiver"},
|
||||||
|
{EMS_MODEL_UBA, 95, 0x08, "Bosch Condens 2500"},
|
||||||
|
{EMS_MODEL_UBA, 251, 0x21, "MM10 Mixer Module"}, // warning, fake product id!
|
||||||
|
{EMS_MODEL_UBA, 250, 0x11, "WM10 Switch Module"}, // warning, fake product id!
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Known thermostat types and their capabilities
|
||||||
|
*/
|
||||||
|
const _Thermostat_Type Thermostat_Types[] = {
|
||||||
|
|
||||||
|
{EMS_MODEL_ES73, 76, 0x10, "Sieger ES73", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
|
||||||
|
{EMS_MODEL_RC10, 79, 0x17, "RC10/Nefit Moduline 100)", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
|
||||||
|
{EMS_MODEL_RC20, 77, 0x17, "RC20/Nefit Moduline 300)", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
|
||||||
|
{EMS_MODEL_RC20F, 93, 0x18, "RC20F", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
|
||||||
|
{EMS_MODEL_RC30, 78, 0x10, "RC30/Nefit Moduline 400)", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
|
||||||
|
{EMS_MODEL_RC35, 86, 0x10, "RC35", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
|
||||||
|
{EMS_MODEL_EASY, 202, 0x18, "TC100/Nefit Easy", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_NO},
|
||||||
|
{EMS_MODEL_BOSCHEASY, 206, 0x02, "Bosch Easy", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_NO},
|
||||||
|
{EMS_MODEL_RC310, 158, 0x10, "RC310", EMS_THERMOSTAT_READ_NO, EMS_THERMOSTAT_WRITE_NO},
|
||||||
|
{EMS_MODEL_CW100, 255, 0x18, "Bosch CW100", EMS_THERMOSTAT_READ_NO, EMS_THERMOSTAT_WRITE_NO},
|
||||||
|
{EMS_MODEL_OT, 171, 0x02, "EMS-OT OpenTherm converter", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}
|
||||||
|
|
||||||
|
};
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* emsuart.cpp
|
* emsuart.cpp
|
||||||
*
|
*
|
||||||
* The low level UART code for ESP8266 to read and write to the EMS bus via uart
|
* The low level UART code for ESP8266 to read and write to the EMS bus via uart
|
||||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP-Boiler
|
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "emsuart.h"
|
#include "emsuart.h"
|
||||||
@@ -28,8 +28,8 @@ static void emsuart_rx_intr_handler(void * para) {
|
|||||||
static uint8_t uart_buffer[EMS_MAXBUFFERSIZE];
|
static uint8_t uart_buffer[EMS_MAXBUFFERSIZE];
|
||||||
|
|
||||||
// is a new buffer? if so init the thing for a new telegram
|
// is a new buffer? if so init the thing for a new telegram
|
||||||
if (EMS_Sys_Status.emsRxStatus == EMS_RX_IDLE) {
|
if (EMS_Sys_Status.emsRxStatus == EMS_RX_STATUS_IDLE) {
|
||||||
EMS_Sys_Status.emsRxStatus = EMS_RX_ACTIVE; // status set to active
|
EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_BUSY; // status set to busy
|
||||||
length = 0;
|
length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ static void emsuart_rx_intr_handler(void * para) {
|
|||||||
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, length);
|
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, length);
|
||||||
|
|
||||||
// set the status flag stating BRK has been received and we can start a new package
|
// set the status flag stating BRK has been received and we can start a new package
|
||||||
EMS_Sys_Status.emsRxStatus = EMS_RX_IDLE;
|
EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_IDLE;
|
||||||
|
|
||||||
// call emsuart_recvTask() at next opportunity
|
// call emsuart_recvTask() at next opportunity
|
||||||
system_os_post(EMSUART_recvTaskPrio, 0, 0);
|
system_os_post(EMSUART_recvTaskPrio, 0, 0);
|
||||||
@@ -68,6 +68,7 @@ static void emsuart_rx_intr_handler(void * para) {
|
|||||||
/*
|
/*
|
||||||
* system task triggered on BRK interrupt
|
* system task triggered on BRK interrupt
|
||||||
* Read commands are all asynchronous
|
* Read commands are all asynchronous
|
||||||
|
* When a buffer is full it is sent to the ems_parseTelegram() function in ems.cpp. This is the hook
|
||||||
*/
|
*/
|
||||||
static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events) {
|
static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events) {
|
||||||
// get next free EMS Receive buffer
|
// get next free EMS Receive buffer
|
||||||
@@ -137,6 +138,14 @@ void ICACHE_FLASH_ATTR emsuart_init() {
|
|||||||
system_uart_swap();
|
system_uart_swap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* stop UART0 driver
|
||||||
|
*/
|
||||||
|
void ICACHE_FLASH_ATTR emsuart_stop() {
|
||||||
|
ETS_UART_INTR_DISABLE();
|
||||||
|
ETS_UART_INTR_ATTACH(NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Send a BRK signal
|
* Send a BRK signal
|
||||||
* Which is a 11-bit set of zero's (11 cycles)
|
* Which is a 11-bit set of zero's (11 cycles)
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
/*
|
/*
|
||||||
* emsuart.h
|
* emsuart.h
|
||||||
|
*
|
||||||
* Header file for emsuart.cpp
|
* Header file for emsuart.cpp
|
||||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP-Boiler
|
*
|
||||||
|
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#define EMSUART_UART 0 // UART 0 - there is only one on the esp8266
|
#define EMSUART_UART 0 // UART 0
|
||||||
#define EMSUART_CONFIG 0x1c // 8N1 (8 bits, no stop bits, 1 parity)
|
#define EMSUART_CONFIG 0x1c // 8N1 (8 bits, no stop bits, 1 parity)
|
||||||
#define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit
|
#define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit
|
||||||
|
|
||||||
@@ -23,11 +25,12 @@
|
|||||||
#define EMSUART_recvTaskQueueLen 64
|
#define EMSUART_recvTaskQueueLen 64
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int16_t writePtr;
|
uint8_t writePtr;
|
||||||
uint8_t buffer[EMS_MAXBUFFERSIZE];
|
uint8_t buffer[EMS_MAXBUFFERSIZE];
|
||||||
} _EMSRxBuf;
|
} _EMSRxBuf;
|
||||||
|
|
||||||
void ICACHE_FLASH_ATTR emsuart_init();
|
void ICACHE_FLASH_ATTR emsuart_init();
|
||||||
|
void ICACHE_FLASH_ATTR emsuart_stop();
|
||||||
void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len);
|
void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len);
|
||||||
void ICACHE_FLASH_ATTR emsaurt_tx_poll();
|
void ICACHE_FLASH_ATTR emsaurt_tx_poll();
|
||||||
void ICACHE_FLASH_ATTR emsuart_tx_brk();
|
void ICACHE_FLASH_ATTR emsuart_tx_brk();
|
||||||
|
|||||||
@@ -1,48 +1,71 @@
|
|||||||
/*
|
/*
|
||||||
* my_config.h
|
* my_config.h
|
||||||
|
*
|
||||||
* All configurations and customization's go here
|
* All configurations and customization's go here
|
||||||
*
|
*
|
||||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP-Boiler
|
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// these are set as -D build flags during compilation
|
#include "ems.h"
|
||||||
// they can be set in platformio.ini or alternatively hard coded here
|
|
||||||
|
|
||||||
// WIFI settings
|
// MQTT base name
|
||||||
//#define WIFI_SSID "<my_ssid>"
|
#define MQTT_BASE "home" // all MQTT topics are prefix with this string, in the format <MQTT_BASE>/<app name>/<topic>
|
||||||
//#define WIFI_PASSWORD "<my_password>"
|
|
||||||
|
|
||||||
// MQTT settings
|
// MQTT general settings
|
||||||
// Note port is the default 1883
|
#define MQTT_TOPIC_START "start"
|
||||||
//#define MQTT_IP "<broker_ip>"
|
#define MQTT_TOPIC_START_PAYLOAD "start"
|
||||||
//#define MQTT_USER "<broker_username>"
|
#define MQTT_WILL_TOPIC "status" // for last will & testament topic name
|
||||||
//#define MQTT_PASS "<broker_password>"
|
#define MQTT_WILL_ONLINE_PAYLOAD "online" // for last will & testament payload
|
||||||
|
#define MQTT_WILL_OFFLINE_PAYLOAD "offline" // for last will & testament payload
|
||||||
|
#define MQTT_RETAIN false
|
||||||
|
#define MQTT_KEEPALIVE 120 // 2 minutes
|
||||||
|
#define MQTT_QOS 1
|
||||||
|
|
||||||
// default values
|
// MQTT for thermostat
|
||||||
#define BOILER_THERMOSTAT_ENABLED 1 // thermostat support is enabled (1)
|
#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values to MQTT
|
||||||
#define BOILER_SHOWER_TIMER 1 // monitors how long a shower has taken
|
#define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // for received thermostat temp changes via MQTT
|
||||||
#define BOILER_SHOWER_ALERT 0 // send alert if showetime exceeded
|
#define TOPIC_THERMOSTAT_CMD_MODE "thermostat_cmd_mode" // for received thermostat mode changes via MQTT
|
||||||
|
#define THERMOSTAT_CURRTEMP "thermostat_currtemp" // current temperature
|
||||||
|
#define THERMOSTAT_SELTEMP "thermostat_seltemp" // selected temperature
|
||||||
|
#define THERMOSTAT_MODE "thermostat_mode" // mode
|
||||||
|
|
||||||
// define here the Thermostat type. see ems.h for the supported types
|
// MQTT for boiler
|
||||||
#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_RC20
|
#define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values to MQTT
|
||||||
//#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_RC30
|
#define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running
|
||||||
//#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_EASY
|
#define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on
|
||||||
|
#define TOPIC_BOILER_WWACTIVATED "wwactivated" // for receiving MQTT message to change water on/off
|
||||||
|
#define TOPIC_BOILER_CMD_WWTEMP "boiler_cmd_wwtemp" // for received boiler wwtemp changes via MQTT
|
||||||
|
|
||||||
// trigger settings to determine if hot tap water or the heating is active
|
// shower time
|
||||||
#define EMS_BOILER_BURNPOWER_TAPWATER 100
|
#define TOPIC_SHOWERTIME "showertime" // for sending shower time results
|
||||||
#define EMS_BOILER_SELFLOWTEMP_HEATING 70
|
#define TOPIC_SHOWER_TIMER "shower_timer" // toggle switch for enabling the shower logic
|
||||||
|
#define TOPIC_SHOWER_ALERT "shower_alert" // toggle switch for enabling the shower alarm logic
|
||||||
|
#define TOPIC_SHOWER_COLDSHOT "shower_coldshot" // used to trigger a coldshot from an MQTT command
|
||||||
|
|
||||||
// if using the shower timer, change these settings
|
// default values for shower logic on/off
|
||||||
#define SHOWER_PAUSE_TIME 15000 // in ms. 15 seconds, max time if water is switched off & on during a shower
|
#define BOILER_SHOWER_TIMER 1 // enable (1) to monitor shower time
|
||||||
#define SHOWER_MIN_DURATION 120000 // in ms. 2 minutes, before recognizing its a shower
|
#define BOILER_SHOWER_ALERT 0 // enable (1) to send alert of cold water when shower time limit has exceeded
|
||||||
#define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water
|
#define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water
|
||||||
#define SHOWER_OFFSET_TIME 5000 // in ms. 5 seconds grace time, to calibrate actual time under the shower
|
|
||||||
#define SHOWER_COLDSHOT_DURATION 10 // in seconds. 10 seconds for cold water before turning back hot water
|
|
||||||
|
|
||||||
// if using LEDs to show traffic, configure the GPIOs here
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// only works if -DUSE_LED is set in platformio.ini
|
// THESE DEFAULT VALUES CAN ALSO BE SET AND STORED WITHTIN THE APPLICATION (see 'set' command) //
|
||||||
#define LED_RX D1 // GPIO5
|
// ALTHOUGH YOU MAY ALSO HARDCODE THEM HERE BUT THEY WILL BE OVERWRITTEN WITH NEW RELEASE UPDATES //
|
||||||
#define LED_TX D2 // GPIO4
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
#define LED_ERR D3 // GPIO0
|
|
||||||
|
// Set LED pin used for showing ems bus connection status. Solid is connected, Flashing is error
|
||||||
|
// can be either the onboard LED on the ESP8266 (LED_BULLETIN) or external via an external pull-up LED
|
||||||
|
// (e.g. D1 on a bbqkees' board
|
||||||
|
// can be enabled and disabled via the 'set led'
|
||||||
|
// pin can be set by 'set led_gpio'
|
||||||
|
#define EMSESP_LED_GPIO LED_BUILTIN
|
||||||
|
|
||||||
|
// set this if using an external temperature sensor like a DS18B20
|
||||||
|
// D5 is the default on bbqkees' board
|
||||||
|
#define EMSESP_DALLAS_GPIO D5
|
||||||
|
|
||||||
|
// By default the EMS bus will be scanned for known devices based on product ids in ems_devices.h
|
||||||
|
// You can override the Thermostat and Boiler types here
|
||||||
|
#define EMSESP_BOILER_TYPE EMS_ID_NONE
|
||||||
|
#define EMSESP_THERMOSTAT_TYPE EMS_ID_NONE
|
||||||
|
|||||||
@@ -1,2 +1,10 @@
|
|||||||
#define APP_NAME "EMS-ESP-Boiler"
|
/**
|
||||||
#define APP_VERSION "1.1.0"
|
*
|
||||||
|
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define APP_NAME "EMS-ESP"
|
||||||
|
#define APP_VERSION "1.5.7b"
|
||||||
|
#define APP_HOSTNAME "ems-esp"
|
||||||
|
|||||||