version 1.3.0

This commit is contained in:
proddy
2019-01-09 23:41:41 +01:00
parent 2d2ee1927d
commit 22823545da
28 changed files with 1730 additions and 758 deletions

View File

@@ -9,8 +9,8 @@ assignees: ''
*Before creating a new issue please check that you have:* *Before creating a new issue please check that you have:*
* *searched the existing [issues](https://github.com/proddy/EMS-ESP-Boiler/issues) (both open and closed)* * *searched the existing [issues](https://github.com/proddy/EMS-ESP/issues) (both open and closed)*
* *searched the [doc](https://github.com/proddy/EMS-ESP-Boiler/blob/master/README.md)* * *searched the [doc](https://github.com/proddy/EMS-ESP/blob/master/README.md)*
*Fulfilling this template will help developers and contributors to address the issue. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.* *Fulfilling this template will help developers and contributors to address the issue. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.*

View File

@@ -7,7 +7,7 @@ assignees: ''
--- ---
*Before creating a new feature request please check that you have searched the existing [issues](https://github.com/proddy/EMS-ESP-Boiler/issues) (both open and closed)* *Before creating a new feature request please check that you have searched the existing [issues](https://github.com/proddy/EMS-ESP/issues) (both open and closed)*
*Fulfilling this template will help developers and contributors evaluating the feature. If the information provided is not enough the issue will likely be closed.* *Fulfilling this template will help developers and contributors evaluating the feature. If the information provided is not enough the issue will likely be closed.*

View File

@@ -9,8 +9,8 @@ assignees: ''
*Before creating a new issue please check that you have:* *Before creating a new issue please check that you have:*
* *searched the existing [issues](https://github.com/proddy/EMS-ESP-Boiler/issues) (both open and closed)* * *searched the existing [issues](https://github.com/proddy/EMS-ESP/issues) (both open and closed)*
* *searched the [doc](https://github.com/proddy/EMS-ESP-Boiler/blob/master/README.md)* * *searched the [doc](https://github.com/proddy/EMS-ESP/blob/master/README.md)*
*Fulfilling this template will help developers and contributors help you. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.* *Fulfilling this template will help developers and contributors help you. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.*

View File

@@ -1,16 +1,37 @@
# 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).
## [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 ## [1.2.4] 2019-01-04
### Changed ### Changed
- Scanning known EMS Devices now ignores duplicates (https://github.com/proddy/EMS-ESP-Boiler/pull/30) - Scanning known EMS Devices now ignores duplicates (https://github.com/proddy/EMS-ESP/pull/30)
- ServiceCode stored as a two byte char - 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 ## [1.2.3] 2019-01-03
@@ -18,23 +39,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Can now hardcode Boiler and Thermostat types in my_config.h to bypass auto-detection - 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 MQTT subscribing to Heating and Hot Water active topics
- Fixed for listening to incoming MQTT topics (https://github.com/proddy/EMS-ESP-Boiler/issues/27) - 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-Boiler/issues/18) - 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 ## [1.2.2] 2019-01-02
### Fixed ### Fixed
- Issues in 1.2.1 (see https://github.com/proddy/EMS-ESP-Boiler/issues/25) - 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 - Logic for determining if there is activity on the EMS bus and using the onboard LEDs properly
## [1.2.1] 2019-01-02 ## [1.2.1] 2019-01-02
### Fixed ### Fixed
- Only process broadcast messages if the offset (byte 4) is 0. (https://github.com/proddy/EMS-ESP-Boiler/issues/23) - Only process broadcast messages if the offset (byte 4) is 0. (https://github.com/proddy/EMS-ESP/issues/23)
- Improved checking for duplicate sent Tx telegrams by comparing CRCs - Improved checking for duplicate sent Tx telegrams by comparing CRCs
- Removed distiquishing between noise on the line and corrupt telegrams (https://github.com/proddy/EMS-ESP-Boiler/issues/24) - Removed distiquishing between noise on the line and corrupt telegrams (https://github.com/proddy/EMS-ESP/issues/24)
## [1.2.0] 2019-01-01 ## [1.2.0] 2019-01-01
@@ -54,7 +75,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fetch UBATotalUptimeMessage from Boiler to get total working minutes - Fetch UBATotalUptimeMessage from Boiler to get total working minutes
- Added check to see if bus is connected. Shown in stats page - 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) - 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-Boiler/pull/20/files). Thanks @Bonusbartus - Report out service codes and water-flow [pull-request](https://github.com/proddy/EMS-ESP/pull/20/files). Thanks @Bonusbartus
### Changed ### Changed
@@ -83,16 +104,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed handling of negative floating 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)

108
README.md
View File

@@ -1,13 +1,13 @@
# 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 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.
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, 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.
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/b8880625bdf841d4adb2829732030887)](https://app.codacy.com/app/proddy/EMS-ESP-Boiler?utm_source=github.com&utm_medium=referral&utm_content=proddy/EMS-ESP-Boiler&utm_campaign=Badge_Grade_Settings) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/b8880625bdf841d4adb2829732030887)](https://app.codacy.com/app/proddy/EMS-ESP?utm_source=github.com&utm_medium=referral&utm_content=proddy/EMS-ESP&utm_campaign=Badge_Grade_Settings)
[![version](https://img.shields.io/badge/version-1.2.3-brightgreen.svg)](CHANGELOG.md) [![version](https://img.shields.io/badge/version-1.2.3-brightgreen.svg)](CHANGELOG.md)
- [EMS-ESP-Boiler](#ems-esp-boiler) - [EMS-ESP](#ems-esp)
- [Introduction](#introduction) - [Introduction](#introduction)
- [Supported Boilers Types](#supported-boilers-types) - [Supported Boilers Types](#supported-boilers-types)
- [Supported ESP8266 devices](#supported-esp8266-devices) - [Supported ESP8266 devices](#supported-esp8266-devices)
@@ -22,11 +22,7 @@ There are 3 parts to this project, first the design of the circuit, second the c
- [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) - [Supported EMS Types](#supported-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)
@@ -34,6 +30,7 @@ There are 3 parts to this project, first the design of the circuit, second the c
- [Building The Firmware](#building-the-firmware) - [Building The Firmware](#building-the-firmware)
- [Using PlatformIO Standalone](#using-platformio-standalone) - [Using PlatformIO Standalone](#using-platformio-standalone)
- [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) - [Troubleshooting](#troubleshooting)
- [Known Issues](#known-issues) - [Known Issues](#known-issues)
- [Wish List](#wish-list) - [Wish List](#wish-list)
@@ -48,7 +45,7 @@ Firstly, some acknowledgments and kudos to the following people who have open-so
**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 for reading telegrams. **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 for reading telegrams.
**bbqkees** - Kees built a working [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 some sample Arduino code to read from the EMS and push messages to Domoticz. His SMD board is also available for purchase.
**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 100% accurate and unfortunately no longer maintained. **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 100% accurate and unfortunately no longer maintained.
@@ -62,7 +59,7 @@ I've tested the code and circuit with a few ESP8266 development boards such as t
## 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 via [website](https://shop.hotgoodies.nl/ems/).
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. 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.
3. Optionally connect an external LED or decide to use the onboard ESP8266 LED. This will flash when there is an error on the EMS bus line or stay solid when it's connected. 3. Optionally connect an external LED or decide to use the onboard ESP8266 LED. This will flash when there is an error on the EMS bus line or stay solid when it's connected.
4. Modify `my_custom.h` 4. Modify `my_custom.h`
@@ -133,7 +130,7 @@ The EMS circuit will work with both 3.3V and 5V. It's easiest though to power di
| With Power Circuit | | With Power Circuit |
| ------------------------------------------ | | ------------------------------------------ |
| ![Power circuit](doc/schematics/Schematic_EMS-ESP-Boiler-supercap.png) | | ![Power circuit](doc/schematics/Schematic_EMS-ESP-supercap.png) |
## How The EMS Bus Works ## How The EMS Bus Works
@@ -211,10 +208,12 @@ The code is built on the Arduino framework and is dependent on these external li
`ems.cpp` is the logic to read the EMS packets (telegrams), validates them and process them based on the type. `ems.cpp` is the logic to read the EMS packets (telegrams), validates them and process them based on the type.
`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 default and can be switched off at compile time using the -DNO_LED build flag. `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. LED support is enabled by default and can be switched off at compile time using the -DNO_LED build flag.
`my_config.h` all the custom settings `my_config.h` all the custom settings
`ems_devices.h` has all the configuration for the known EMS devices this supports
`MyESP.cpp` is my custom library to handle WiFi, MQTT, MDNS and Telnet. Uses a modified version of TelnetSpy (https://github.com/yasheena/telnetspy) `MyESP.cpp` is my custom library to handle WiFi, MQTT, MDNS and Telnet. Uses a modified version of TelnetSpy (https://github.com/yasheena/telnetspy)
### Supported EMS Types ### Supported EMS Types
@@ -232,44 +231,17 @@ The code is built on the Arduino framework and is dependent on these external li
| Thermostat | 0x02 | Version | reads Version major/minor | | Thermostat | 0x02 | Version | reads Version major/minor |
| Thermostat | 0x91, 0x41, 0x0A | Status Message | read monitor values | | Thermostat | 0x91, 0x41, 0x0A | Status Message | read monitor values |
In `boiler.ino` you can make calls to automatically request these types in the function *regularUpdates()*. In `ems-esp.ino` you can make calls to automatically request these types in the function *regularUpdates()*.
### Supported Thermostats ### Which thermostats are supported?
I am still working on adding more support to known thermostats. I am still working on adding more support to known thermostats. Any contributions here are welcome. Please use to
Currently known types and collected versions: The know types are listed in `ems_devices.h`. Some special notes
Moduline 300 = Type 77 Version 03.03 - RC20 and RC30 are fully supported
Moduline 400 = Type 78 Version 03.03 - RC35 only supports the 1st heating circuit (HC1)
Buderus RC35 = Type 86 Version 01.15 - TC100/TC200/Easy has only support for reading the temperatures. There seems to be no way to set settngs using EMS bus messages. The device only listens to XMPP requests.
Nefit Easy = Type 202 Version 02.19
Nefit Trendline HRC30 = Type 123 Version 06.01
BC10 = Type 123 Version 04.05
#### RC20 (Moduline 300)
Read and write of setpoint temperature and mode are supported.
#### RC30 (Moduline 400)
Read and write of setpoint temperature and mode are supported.
Note I found 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
An RC35 thermostat can support up to 4 heating circuits each controlled with their own Monitor and Working Mode IDs.
Fetching the thermostats setpoint temperature 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
This is roughly supported but not fully tested.
#### 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
@@ -277,20 +249,17 @@ There is limited support for an Nefit Easy TC100/TC200 type thermostat. The curr
- set flags for enabled/disabling functionality such as `BOILER_SHOWER_ENABLED` and `BOILER_SHOWER_TIMER`. - set flags for enabled/disabling functionality such as `BOILER_SHOWER_ENABLED` and `BOILER_SHOWER_TIMER`.
- Set WIFI and MQTT settings, instead of doing this in `platformio.ini` - 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`. A hash is generated (CRC32 based) to determine if the payload has changed, otherwise don't send it. An example payload looks roughly like:
I run Mosquitto on my Raspberry PI 3 as the MQTT broker.
The boiler data is collected and sent as a single JSON object to MQTT TOPIC `home/boiler/boiler_data`. A hash is generated (CRC32 based) to determine if the payload has changed, otherwise don't send it. An example payload looks roughly like:
`{"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"}` `{"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"}`
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. Similarly the thermostat values are sent as a json package under a topic named `home/ems-esp/thermostat_data` with the current mode, room temperature and set temperature.
These topics can be configured in the `TOPIC_*` defines in `boiler.ino`. Make sure you change the HA configuration too to match. the `home` is the MQTT topic prefix and can be customized in my_config.h
### The Basic Shower Logic ### The Basic Shower Logic
@@ -333,11 +302,11 @@ You can find the .yaml configuration files under `doc/ha`. See also https://comm
% 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 like `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
```c ```c
% platformio run -t upload % platformio run -t upload
``` ```
@@ -350,19 +319,20 @@ Porting to the Arduino IDE can be a little tricky but it is possible.
- Go to Boards Manager and install ESP8266 2.4.x platform - Go to Boards Manager and install ESP8266 2.4.x platform
- 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 the needed libraries from platformio.ini - From the Library Manager install the needed libraries from platformio.ini
- 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
```c
#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.*`)
- cross your fingers and hit CTRL-R to compile... - cross your fingers and hit CTRL-R to compile...
## Using the Pre-built Firmware
pre-baked firmwares for some ESP8266 devices 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`
The ESP8266 will start in Access Point (AP) mode, so connect via WiFi to the SSID **EMS-ESP** and telnet to 192.168.4.1. Then use the set command to configure your own network settings.
## Troubleshooting ## Troubleshooting
If the WiFi, MQTT, MDNS or something else fails to connect, re-build the firmware using the `-DDEBUG_SUPPORT` option, connect the ESP8266 to a USB in your computer and monitor the Serial output. A lot of detailed logging will be printed to help you pinpoint the cause of the error. If the WiFi, MQTT, MDNS or something else fails to connect, re-build the firmware using the `-DDEBUG_SUPPORT` option, connect the ESP8266 to a USB in your computer and monitor the Serial output. A lot of detailed logging will be printed to help you pinpoint the cause of the error.
@@ -384,6 +354,8 @@ Some annoying issues that need fixing:
- https://github.com/robertklep/nefit-easy-core - https://github.com/robertklep/nefit-easy-core
- Store custom params like wifi credentials, mqtt, thermostat type on ESP8266 using SPIFFS - Store custom params like wifi credentials, mqtt, thermostat type on ESP8266 using SPIFFS
- Add support for a temperature sensor on the circuit (DS18B20) - Add support for a temperature sensor on the circuit (DS18B20)
- 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.
## Your Comments and Feedback ## Your Comments and Feedback

View File

@@ -25,4 +25,4 @@ env.AddPreAction("buildprog", code_check)
# see http://docs.platformio.org/en/latest/projectconf/advanced_scripting.html#before-pre-and-after-post-actions # 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" % defines.get("VERSION"))
# env.Replace(PROGNAME="firmware_%s" % env['BOARD']) env.Replace(PROGNAME="firmware_%s" % env['BOARD'])

View File

@@ -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") }}

View File

@@ -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"

View File

@@ -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 }}"

View File

@@ -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'

View File

@@ -1,138 +1,138 @@
- platform: mqtt - platform: mqtt
state_topic: 'home/boiler/thermostat_data' state_topic: 'home/esp-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/esp-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/esp-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/esp-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/esp-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/esp-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/esp-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/esp-esp/boiler_data'
name: 'Warm Water tapwater flow rate' name: 'Warm Water tapwater flow rate'
unit_of_measurement: 'l/min' unit_of_measurement: 'l/min'
value_template: '{{ value_json.wWCurFlow }}' value_template: '{{ value_json.wWCurFlow }}'
- platform: mqtt - platform: mqtt
state_topic: 'home/boiler/boiler_data' state_topic: 'home/esp-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/esp-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/esp-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/esp-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/esp-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/esp-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/esp-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/esp-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/esp-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/esp-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/esp-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/esp-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/esp-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/esp-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/esp-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/esp-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 }}'
@@ -143,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") }}'

View File

@@ -1,7 +1,7 @@
- platform: mqtt - platform: mqtt
name: "Shower Timer" name: "Shower Timer"
state_topic: "home/boiler/shower_timer" state_topic: "home/esp-esp/shower_timer"
command_topic: "home/boiler/shower_timer" command_topic: "home/esp-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/esp-esp/shower_alert"
command_topic: "home/boiler/shower_alert" command_topic: "home/esp-esp/shower_alert"
payload_on: "1" payload_on: "1"
payload_off: "0" payload_off: "0"
optimistic: false optimistic: false

View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -550,9 +550,14 @@ void TelnetSpy::handle() {
return; return;
} }
if (!listening) { if (!listening) {
if (WiFi.status() != WL_CONNECTED) { 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; return;
} }
telnetServer = new WiFiServer(port); telnetServer = new WiFiServer(port);
telnetServer->begin(); telnetServer->begin();
telnetServer->setNoDelay(bufLen > 0); telnetServer->setNoDelay(bufLen > 0);

View File

@@ -158,8 +158,8 @@
#define TELNETSPY_PING_TIME 1500 #define TELNETSPY_PING_TIME 1500
#define TELNETSPY_PORT 23 #define TELNETSPY_PORT 23
#define TELNETSPY_CAPTURE_OS_PRINT true #define TELNETSPY_CAPTURE_OS_PRINT true
#define TELNETSPY_WELCOME_MSG "Connection established via TelnetSpy2.\n" #define TELNETSPY_WELCOME_MSG "Connection established via Telnet.\n"
#define TELNETSPY_REJECT_MSG "TelnetSpy: Only one connection possible.\n" #define TELNETSPY_REJECT_MSG "Telnet: Only one connection possible.\n"
#ifdef ESP8266 #ifdef ESP8266
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>

632
lib/TelnetSpy/TelnetSpy.xxx Normal file
View File

@@ -0,0 +1,632 @@
/*
* 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(char * msg) {
if (welcomeMsg) {
free(welcomeMsg);
}
welcomeMsg = strdup(msg);
}
void TelnetSpy::setRejectMsg(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;
}
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;
if (debugOutput) {
actualObject = this;
#ifdef ESP8266
os_install_putc1((void *)TelnetSpy_putc); // Set system printing (os_printf) to TelnetSpy
system_set_os_print(true);
#else // ESP32 \
// ToDo: How can be done this for ESP32 ?
#endif
} else {
if (actualObject == this) {
#ifdef ESP8266
system_set_os_print(false);
os_install_putc1((void *)TelnetSpy_ignore_putc); // Ignore system printing
#else // ESP32 \
// ToDo: How can be done this for ESP32 ?
#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::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_CONNECTED) {
// unless AP
//if (!(WiFi.getMode() & WIFI_AP)) { // proddy
// 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();
}
}
}
}

View File

@@ -3,31 +3,32 @@
* *
* Paul Derbyshire - December 2018 * Paul Derbyshire - December 2018
* *
* Some ideas from https://github.com/JoaoLopesF/ESP8266-RemoteDebug-Telnet * Ideas borrowed from Espurna https://github.com/xoseperez/espurna
* Ideas from Espurna https://github.com/xoseperez/espurna
*/ */
#include "MyESP.h" #include "MyESP.h"
// constructor // constructor
MyESP::MyESP() { MyESP::MyESP() {
_app_hostname = strdup("MyESP"); _app_hostname = strdup("MyESP");
_app_name = strdup("MyESP"); _app_name = strdup("MyESP");
_app_version = strdup("1.0.0"); _app_version = strdup("1.0.0");
_boottime = strdup("unknown"); _boottime = strdup("unknown");
_extern_WIFICallback = NULL; _extern_WIFICallback = NULL;
_extern_WIFICallbackSet = false; _extern_WIFICallbackSet = false;
_consoleCallbackProjectCmds = NULL; _telnetcommand_callback = NULL;
_helpProjectCmds = NULL; _telnet_callback = NULL;
_helpProjectCmds_count = 0; _helpProjectCmds = NULL;
_mqtt_host = NULL; _helpProjectCmds_count = 0;
_mqtt_password = NULL; _mqtt_host = NULL;
_mqtt_username = NULL; _mqtt_password = NULL;
_wifi_password = NULL; _mqtt_username = NULL;
_wifi_ssid = NULL; _wifi_password = NULL;
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN; _wifi_ssid = NULL;
_suspendMessages = true; _mqttbase = NULL;
_command = (char *)malloc(TELNET_MAX_COMMAND_LENGTH); // reserve buffer for Serial/Telnet commands _suspendOutput = false;
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
_command = (char *)malloc(TELNET_MAX_COMMAND_LENGTH); // reserve buffer for Serial/Telnet commands
} }
MyESP::~MyESP() { MyESP::~MyESP() {
@@ -41,10 +42,9 @@ void MyESP::end() {
jw.disconnect(); jw.disconnect();
} }
// general debug to the telnet or serial channels // general debug to the telnet or serial channels
void MyESP::myDebug(const char * format, ...) { void MyESP::myDebug(const char * format, ...) {
if (!_suspendMessages) if (_suspendOutput)
return; return;
va_list args; va_list args;
@@ -62,6 +62,9 @@ void MyESP::myDebug(const char * format, ...) {
// for flashmemory. Must use PSTR() // for flashmemory. Must use PSTR()
void MyESP::myDebug_P(PGM_P format_P, ...) { void MyESP::myDebug_P(PGM_P format_P, ...) {
if (_suspendOutput)
return;
char format[strlen_P(format_P) + 1]; char format[strlen_P(format_P) + 1];
memcpy_P(format, format_P, sizeof(format)); memcpy_P(format, format_P, sizeof(format));
@@ -85,7 +88,7 @@ void MyESP::myDebug_P(PGM_P format_P, ...) {
// called when WiFi is connected, and used to start MDNS // called when WiFi is connected, and used to start MDNS
void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) { void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
if ((code == MESSAGE_CONNECTED) || (code == MESSAGE_ACCESSPOINT_CREATED)) { if ((code == MESSAGE_CONNECTED)) {
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
String hostname = String(WiFi.getHostname()); String hostname = String(WiFi.getHostname());
#else #else
@@ -102,13 +105,6 @@ void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
myDebug_P(PSTR("[WIFI] DNS %s"), WiFi.dnsIP().toString().c_str()); myDebug_P(PSTR("[WIFI] DNS %s"), WiFi.dnsIP().toString().c_str());
myDebug_P(PSTR("[WIFI] HOST %s"), hostname.c_str()); myDebug_P(PSTR("[WIFI] HOST %s"), hostname.c_str());
if (WiFi.getMode() & WIFI_AP) {
myDebug_P(PSTR("[WIFI] MODE AP --------------------------------------"));
myDebug_P(PSTR("[WIFI] SSID %s"), jw.getAPSSID().c_str());
myDebug_P(PSTR("[WIFI] IP %s"), WiFi.softAPIP().toString().c_str());
myDebug_P(PSTR("[WIFI] MAC %s"), WiFi.softAPmacAddress().c_str());
}
// start MDNS // start MDNS
if (MDNS.begin((char *)hostname.c_str())) { if (MDNS.begin((char *)hostname.c_str())) {
myDebug_P(PSTR("[MDNS] OK")); myDebug_P(PSTR("[MDNS] OK"));
@@ -118,7 +114,18 @@ void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
// call any final custom settings // call any final custom settings
if (_extern_WIFICallbackSet) { if (_extern_WIFICallbackSet) {
// myDebug_P(PSTR("[WIFI] calling custom wifi settings function")); _extern_WIFICallback(); // call callback to set any custom things
}
}
if (code == MESSAGE_ACCESSPOINT_CREATED) {
myDebug_P(PSTR("[WIFI] MODE AP --------------------------------------"));
myDebug_P(PSTR("[WIFI] SSID %s"), jw.getAPSSID().c_str());
myDebug_P(PSTR("[WIFI] IP %s"), WiFi.softAPIP().toString().c_str());
myDebug_P(PSTR("[WIFI] MAC %s"), WiFi.softAPmacAddress().c_str());
// call any final custom settings
if (_extern_WIFICallbackSet) {
_extern_WIFICallback(); // call callback to set any custom things _extern_WIFICallback(); // call callback to set any custom things
} }
} }
@@ -160,9 +167,9 @@ void MyESP::_mqttOnMessage(char * topic, char * payload, size_t len) {
topic = topic_magnitude + 1; topic = topic_magnitude + 1;
} }
// check for bootime, something specific I fetch as an acknolwegdemtn from Home Assistant // check for bootime, something specific I fetch as an acknowledgement from Home Assistant
if (strcmp(topic, MQTT_TOPIC_START) == 0) { if (strcmp(topic, MQTT_TOPIC_START) == 0) {
myDebug_P(PSTR("[MQTT] boottime: %s"), message); myDebug_P(PSTR("[MQTT] received boottime: %s"), message);
setBoottime(message); setBoottime(message);
return; return;
} }
@@ -172,20 +179,22 @@ void MyESP::_mqttOnMessage(char * topic, char * payload, size_t len) {
} }
// MQTT subscribe // MQTT subscribe
// to MQTT_BASE/app_hostname/topic
void MyESP::mqttSubscribe(const char * topic) { void MyESP::mqttSubscribe(const char * topic) {
if (mqttClient.connected() && (strlen(topic) > 0)) { if (mqttClient.connected() && (strlen(topic) > 0)) {
char s[100]; char s[100];
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, topic); snprintf(s, sizeof(s), "%s/%s/%s", _mqttbase, _app_hostname, topic);
unsigned int packetId = mqttClient.subscribe(s, MQTT_QOS); unsigned int packetId = mqttClient.subscribe(s, MQTT_QOS);
myDebug_P(PSTR("[MQTT] Subscribing to %s (PID %d)"), s, packetId); myDebug_P(PSTR("[MQTT] Subscribing to %s (PID %d)"), s, packetId);
} }
} }
// MQTT unsubscribe // MQTT unsubscribe
// to MQTT_BASE/app_hostname/topic
void MyESP::mqttUnsubscribe(const char * topic) { void MyESP::mqttUnsubscribe(const char * topic) {
if (mqttClient.connected() && (strlen(topic) > 0)) { if (mqttClient.connected() && (strlen(topic) > 0)) {
char s[100]; char s[100];
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, topic); snprintf(s, sizeof(s), "%s/%s/%s", _mqttbase, _app_hostname, topic);
unsigned int packetId = mqttClient.unsubscribe(s); unsigned int packetId = mqttClient.unsubscribe(s);
myDebug_P(PSTR("[MQTT] Unsubscribing to %s (PID %d)"), s, packetId); myDebug_P(PSTR("[MQTT] Unsubscribing to %s (PID %d)"), s, packetId);
} }
@@ -194,7 +203,7 @@ void MyESP::mqttUnsubscribe(const char * topic) {
// MQTT Publish // MQTT Publish
void MyESP::mqttPublish(const char * topic, const char * payload) { void MyESP::mqttPublish(const char * topic, const char * payload) {
char s[MQTT_MAX_SIZE]; char s[MQTT_MAX_SIZE];
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, topic); snprintf(s, sizeof(s), "%s/%s/%s", _mqttbase, _app_hostname, topic);
// myDebug_P(PSTR("[MQTT] Sending pubish to %s with payload %s"), s, payload); // myDebug_P(PSTR("[MQTT] Sending pubish to %s with payload %s"), s, payload);
mqttClient.publish(s, MQTT_QOS, false, payload); mqttClient.publish(s, MQTT_QOS, false, payload);
} }
@@ -211,11 +220,11 @@ void MyESP::_mqttOnConnect() {
// send specific start command to HA via MQTT, which returns the boottime // send specific start command to HA via MQTT, which returns the boottime
char s[48]; char s[48];
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, MQTT_TOPIC_START); snprintf(s, sizeof(s), "%s/%s/%s", _mqttbase, _app_hostname, MQTT_TOPIC_START);
mqttClient.publish(s, MQTT_QOS, false, MQTT_TOPIC_START_PAYLOAD); mqttClient.publish(s, MQTT_QOS, false, MQTT_TOPIC_START_PAYLOAD);
#endif #endif
// call custom // call custom function to handle mqtt receives
(_mqtt_callback)(MQTT_CONNECT_EVENT, NULL, NULL); (_mqtt_callback)(MQTT_CONNECT_EVENT, NULL, NULL);
} }
@@ -265,7 +274,7 @@ void MyESP::_wifi_setup() {
jw.enableAP(false); jw.enableAP(false);
jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT); jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
jw.setReconnectTimeout(WIFI_RECONNECT_INTERVAL); jw.setReconnectTimeout(WIFI_RECONNECT_INTERVAL);
jw.enableAPFallback(true); // AP mode only as fallback, but disabled jw.enableAPFallback(true); // AP mode only as fallback
jw.enableSTA(true); // Enable STA mode (connecting to a router) jw.enableSTA(true); // Enable STA mode (connecting to a router)
jw.enableScan(false); // Configure it to scan available networks and connect in order of dBm jw.enableScan(false); // Configure it to scan available networks and connect in order of dBm
jw.cleanNetworks(); // Clean existing network configuration jw.cleanNetworks(); // Clean existing network configuration
@@ -299,6 +308,10 @@ void MyESP::_mdns_setup() {
// OTA Setup // OTA Setup
void MyESP::_ota_setup() { void MyESP::_ota_setup() {
if (!_wifi_ssid) {
return;
}
ArduinoOTA.setPort(OTA_PORT); ArduinoOTA.setPort(OTA_PORT);
ArduinoOTA.setHostname(_app_hostname); ArduinoOTA.setHostname(_app_hostname);
ArduinoOTA.onStart([this]() { myDebug_P(PSTR("[OTA] Start")); }); ArduinoOTA.onStart([this]() { myDebug_P(PSTR("[OTA] Start")); });
@@ -336,25 +349,38 @@ void MyESP::setBoottime(char * boottime) {
_boottime = strdup(boottime); _boottime = strdup(boottime);
} }
// returns boottime // sets boottime
char * MyESP::getBoottime() { void MyESP::setMQTTbase(char * mqttbase) {
return _boottime; if (_mqttbase) {
free(_mqttbase);
}
_mqttbase = strdup(mqttbase);
} }
// Set callback of sketch function to process project messages // Set callback of sketch function to process project messages
void MyESP::consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)()) { void MyESP::setTelnetCommands(command_t * cmds, uint8_t count, telnetcommand_callback_f callback) {
_helpProjectCmds = cmds; // command list _helpProjectCmds = cmds; // command list
_helpProjectCmds_count = count; // number of commands _helpProjectCmds_count = count; // number of commands
_consoleCallbackProjectCmds = callback; // external function to handle commands _telnetcommand_callback = callback; // external function to handle commands
}
void MyESP::setTelnetCallback(telnet_callback_f callback) {
_telnet_callback = callback;
} }
void MyESP::_telnetConnected() { void MyESP::_telnetConnected() {
myDebug_P(PSTR("[TELNET] Telnet connection established")); myDebug_P(PSTR("[TELNET] Telnet connection established"));
_consoleShowHelp(); // Show the initial message _consoleShowHelp(); // Show the initial message
if (_telnet_callback) {
(_telnet_callback)(TELNET_EVENT_CONNECT); // call callback
}
} }
void MyESP::_telnetDisconnected() { void MyESP::_telnetDisconnected() {
myDebug_P(PSTR("[TELNET] Telnet connection closed")); myDebug_P(PSTR("[TELNET] Telnet connection closed"));
if (_telnet_callback) {
(_telnet_callback)(TELNET_EVENT_DISCONNECT); // call callback
}
} }
// Initialize the telnet server // Initialize the telnet server
@@ -375,37 +401,48 @@ void MyESP::_telnet_setup() {
// Show help of commands // Show help of commands
void MyESP::_consoleShowHelp() { void MyESP::_consoleShowHelp() {
SerialAndTelnet.printf("\n\r* Connected to: %s version %s\n\r", _app_name, _app_version);
if (WiFi.getMode() & WIFI_AP) {
SerialAndTelnet.printf("* ESP8266 is in AP mode with SSID %s\n\r", jw.getAPSSID().c_str());
} else {
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
String hostname = String(WiFi.getHostname()); String hostname = String(WiFi.getHostname());
#else #else
String hostname = WiFi.hostname(); String hostname = WiFi.hostname();
#endif #endif
SerialAndTelnet.printf("* Hostname: %s IP: %s MAC: %s\n\r",
hostname.c_str(),
WiFi.localIP().toString().c_str(),
WiFi.macAddress().c_str());
SerialAndTelnet.printf("* Connected to WiFi SSID: %s\n\r", WiFi.SSID().c_str());
SerialAndTelnet.printf("* Boot time: %s\n\r", _boottime);
}
SerialAndTelnet.println("*********************************");
SerialAndTelnet.println("* Console and Log Monitoring *");
SerialAndTelnet.println("*********************************");
SerialAndTelnet.printf("* %s version %s\n\r", _app_name, _app_version);
SerialAndTelnet.printf("* Hostname: %s IP: %s MAC: %s\n\r",
hostname.c_str(),
WiFi.localIP().toString().c_str(),
WiFi.macAddress().c_str());
SerialAndTelnet.printf("* Connected to WiFi AP: %s\n\r", WiFi.SSID().c_str());
SerialAndTelnet.printf("* Boot time: %s\n\r", _boottime);
SerialAndTelnet.printf("* Free RAM: %d bytes\n\r", ESP.getFreeHeap()); SerialAndTelnet.printf("* Free RAM: %d bytes\n\r", ESP.getFreeHeap());
#ifdef DEBUG_SUPPORT #ifdef DEBUG_SUPPORT
SerialAndTelnet.println("* !! in DEBUG_SUPPORT mode !!\n\r"); SerialAndTelnet.println("* Warning: in DEBUG_SUPPORT mode!");
#endif #endif
SerialAndTelnet.println("*\n\r* Commands:\n\r* ?=this help, CTRL-D=quit, $=show free memory, !=reboot ESP, &=suspend all messages"); SerialAndTelnet.println("*\n\r* Commands:\n\r* ?=help, CTRL-D=quit, !=reboot");
SerialAndTelnet.println(FPSTR("* set <wifi_ssid | wifi_password | mqtt_host | mqtt_username | mqtt_password> [value]"));
SerialAndTelnet.println(FPSTR("* set erase"));
SerialAndTelnet.println(FPSTR("*"));
// print custom commands if available. Take from progmem // print custom commands if available. Take from progmem
if (_consoleCallbackProjectCmds) { if (_telnetcommand_callback) {
// find the longest key length so we can right align it
uint8_t max_len = 0;
for (uint8_t i = 0; i < _helpProjectCmds_count; i++) {
if (strlen(_helpProjectCmds[i].key) > max_len)
max_len = strlen(_helpProjectCmds[i].key);
}
for (uint8_t i = 0; i < _helpProjectCmds_count; i++) { for (uint8_t i = 0; i < _helpProjectCmds_count; i++) {
SerialAndTelnet.print(FPSTR("* ")); SerialAndTelnet.print(FPSTR("* "));
SerialAndTelnet.print(FPSTR(_helpProjectCmds[i].key)); SerialAndTelnet.print(FPSTR(_helpProjectCmds[i].key));
for (uint8_t j = 0; j < (8 - strlen(_helpProjectCmds[i].key)); j++) { for (uint8_t j = 0; j < ((max_len + 5) - strlen(_helpProjectCmds[i].key)); j++) { // account for longest string length
SerialAndTelnet.print(FPSTR(" ")); // padding SerialAndTelnet.print(FPSTR(" ")); // padding
} }
SerialAndTelnet.println(FPSTR(_helpProjectCmds[i].description)); SerialAndTelnet.println(FPSTR(_helpProjectCmds[i].description));
} }
@@ -424,90 +461,207 @@ void MyESP::resetESP() {
#endif #endif
} }
// Get last command received // sends a MQTT notification message to Home Assistant (HA)
char * MyESP::consoleGetLastCommand() {
return _command;
}
// Process user command over telnet
void MyESP::consoleProcessCommand() {
uint8_t cmd = _command[0];
// Process the command
if (cmd == '?') {
_consoleShowHelp(); // Show help
} else if (cmd == '$') {
myDebug("* Free RAM (bytes): %d", ESP.getFreeHeap());
} else if (cmd == '!') {
resetESP();
} else if (cmd == '&') {
myDebug("Suspend all messages is %s", !_suspendMessages ? "disabled" : "enabled");
_suspendMessages = !_suspendMessages; // toggle
} else {
// custom Project commands
if (_consoleCallbackProjectCmds) {
_consoleCallbackProjectCmds();
}
}
if (!_suspendMessages) {
myDebug("Warning, all log messages have been supsended. Use & to re-enable.");
}
}
// sends a MQTT notification message to Home Assistant
void MyESP::sendHANotification(const char * message) { void MyESP::sendHANotification(const char * message) {
char payload[48]; char payload[48];
snprintf(payload, sizeof(payload), "%s : %s", _app_hostname, message); snprintf(payload, sizeof(payload), "%s : %s", _app_hostname, message);
myDebug_P(PSTR("[MQTT] Sending HA notification %s"), payload); myDebug_P(PSTR("[MQTT] Sending HA notification %s"), payload);
mqttClient.publish(MQTT_NOTIFICATION, MQTT_QOS, false, payload); mqttClient.publish(MQTT_HA_NOTIFICATION, MQTT_QOS, false, payload);
} }
// send specific command to HA via MQTT // send specific command to Home Assistant (HA) via MQTT
// format is: home/<hostname>/command with payload <cmd> // format is: home/<hostname>/command with payload <cmd>
void MyESP::sendHACommand(const char * cmd) { void MyESP::sendHACommand(const char * cmd) {
myDebug_P(PSTR("[MQTT] Sending HA command %s"), cmd); myDebug_P(PSTR("[MQTT] Sending HA command %s"), cmd);
char topic[48]; char topic[48];
snprintf(topic, sizeof(topic), "%s%s/%s", MQTT_BASE, _app_hostname, MQTT_TOPIC_COMMAND); snprintf(topic, sizeof(topic), "%s/%s/%s", _mqttbase, _app_hostname, MQTT_TOPIC_COMMAND);
mqttClient.publish(topic, MQTT_QOS, false, cmd); mqttClient.publish(topic, MQTT_QOS, false, cmd);
} }
char * MyESP::_telnet_readWord() {
char * word = strtok(NULL, ", \n");
return word;
}
// change settings - always as strings
// messy code but effective since we don't have too many settings
void MyESP::_changeSetting(const char * setting, const char * value) {
bool ok = false;
// validate 2nd argument
if (strcmp(setting, "erase") == 0) {
_fs_eraseConfig();
return;
}
if (strcmp(setting, "wifi_ssid") == 0) {
if (_wifi_ssid)
free(_wifi_ssid);
_wifi_ssid = NULL; // just to be sure
if (value) {
_wifi_ssid = strdup(value);
}
ok = true;
}
if (strcmp(setting, "wifi_password") == 0) {
if (_wifi_password)
free(_wifi_password);
_wifi_password = NULL; // just to be sure
if (value) {
_wifi_password = strdup(value);
}
ok = true;
}
if (strcmp(setting, "mqtt_host") == 0) {
if (_mqtt_host)
free(_mqtt_host);
_mqtt_host = NULL; // just to be sure
if (value) {
_mqtt_host = strdup(value);
}
ok = true;
}
if (strcmp(setting, "mqtt_username") == 0) {
if (_mqtt_username)
free(_mqtt_username);
_mqtt_username = NULL; // just to be sure
if (value) {
_mqtt_username = strdup(value);
}
ok = true;
}
if (strcmp(setting, "mqtt_password") == 0) {
if (_mqtt_password)
free(_mqtt_password);
_mqtt_password = NULL; // just to be sure
if (value) {
_mqtt_password = strdup(value);
}
ok = true;
}
if (!ok) {
SerialAndTelnet.println("\nInvalid parameter for set command.");
return;
}
// check for 2 params
if (value == nullptr) {
SerialAndTelnet.printf("%s setting deleted\n\r", setting);
} else {
// 3 params
SerialAndTelnet.printf("%s changed to %s\n\r", setting, value);
}
if (_fs_saveConfig()) {
SerialAndTelnet.println("Changes will have effect after the next restart. Please reboot using ! command");
}
}
void MyESP::_telnetCommand(char * commandLine) {
// count the number of arguments
char * str = commandLine;
bool state = false;
unsigned wc = 0;
while (*str) {
if (*str == ' ' || *str == '\n' || *str == '\t') {
state = false;
} else if (state == false) {
state = true;
++wc;
}
++str;
}
// check first for reserved commands
char * temp = strdup(commandLine); // because strotok kills original string buffer
char * ptrToCommandName = strtok((char *)temp, ", \n");
if (strcmp(ptrToCommandName, "set") == 0) {
if (wc == 1) {
SerialAndTelnet.println("\n\Stored settings:");
SerialAndTelnet.printf(" wifi_ssid=%s\n\r", (!_wifi_ssid) ? "<not set>" : _wifi_ssid);
SerialAndTelnet.printf(" wifi_password=");
if (!_wifi_password) {
SerialAndTelnet.print("<not set>");
} else {
for (uint8_t i = 0; i < strlen(_wifi_password); i++)
SerialAndTelnet.print("*");
}
SerialAndTelnet.printf("\n\r mqtt_host=%s\n\r", (!_mqtt_host) ? "<not set>" : _mqtt_host);
SerialAndTelnet.printf(" mqtt_username=%s\n\r", (!_mqtt_username) ? "<not set>" : _mqtt_username);
SerialAndTelnet.printf(" mqtt_password=");
if (!_mqtt_password) {
SerialAndTelnet.print("<not set>");
} else {
for (uint8_t i = 0; i < strlen(_mqtt_password); i++)
SerialAndTelnet.print("*");
}
SerialAndTelnet.println("\n\r\n\rUsage: set <setting> <value>");
} else if (wc == 2) {
char * setting = _telnet_readWord();
_changeSetting(setting, NULL);
} else if (wc == 3) {
char * setting = _telnet_readWord();
char * value = _telnet_readWord();
_changeSetting(setting, value);
}
return;
}
// call callback function
(_telnetcommand_callback)(wc, commandLine);
}
// handler for Telnet // handler for Telnet
void MyESP::_telnetHandle() { void MyESP::_telnetHandle() {
SerialAndTelnet.handle(); SerialAndTelnet.handle();
char last = ' '; // To avoid processing double "\r\n" static uint8_t charsRead = 0;
// read asynchronously until full command input
while (SerialAndTelnet.available()) { while (SerialAndTelnet.available()) {
char character = SerialAndTelnet.read(); // Get character char c = SerialAndTelnet.read();
#ifdef DEBUG_SUPPORT
// check for ctrl-D (EOF) or EOT Serial.print(c);
if ((character == 0xEC) || (character == 0x04)) { #endif
SerialAndTelnet.disconnectClient(); switch (c) {
} case '\r': // likely have full command in buffer now, commands are terminated by CR and/or LF
case '\n':
// if we reached our buffer limit, send what we have _command[charsRead] = '\0'; // null terminate our command char array
if (strlen(_command) >= TELNET_MAX_COMMAND_LENGTH) { if (charsRead > 0) {
consoleProcessCommand(); // Process the command charsRead = 0; // is static, so have to reset
memset(_command, 0, TELNET_MAX_COMMAND_LENGTH); // reset for next command _suspendOutput = false;
} _telnetCommand(_command);
// Check for newline (CR or LF)
if (_isCRLF(character) == true) {
if (_isCRLF(last) == false) {
if (strlen(_command) > 0) {
consoleProcessCommand(); // Process the command
}
} }
memset(_command, 0, TELNET_MAX_COMMAND_LENGTH); // reset for next command break;
case '\b': // handle backspace in input: put a space in last char
} else if (isPrintable(character)) { if (charsRead > 0) { // and adjust commandLine and charsRead
// Concat char to end of buffer _command[--charsRead] = '\0';
uint16_t len = strlen(_command); SerialAndTelnet << byte('\b') << byte(' ') << byte('\b'); //no idea how this works, found it on the Internet
_command[len] = character; }
_command[len + 1] = '\0'; break;
case '?':
_consoleShowHelp();
break;
case '!':
resetESP();
break;
case 0x04: // EOT
myDebug_P(PSTR("* exiting telnet session"));
SerialAndTelnet.disconnectClient();
break;
default:
_suspendOutput = true;
c = tolower(c);
if (charsRead < TELNET_MAX_COMMAND_LENGTH) {
_command[charsRead++] = c;
}
_command[charsRead] = '\0'; // just in case
break;
} }
last = character; // remember last char
} }
} }
@@ -522,11 +676,6 @@ void MyESP::setMQTTCallback(mqtt_callback_f callback) {
_mqtt_callback = callback; _mqtt_callback = callback;
} }
// Is CR or LF ?
bool MyESP::_isCRLF(char character) {
return (character == '\r' || character == '\n');
}
// ensure we have a connection to MQTT broker // ensure we have a connection to MQTT broker
void MyESP::_mqttConnect() { void MyESP::_mqttConnect() {
if (!_mqtt_host || mqttClient.connected() || (WiFi.status() != WL_CONNECTED)) { if (!_mqtt_host || mqttClient.connected() || (WiFi.status() != WL_CONNECTED)) {
@@ -560,19 +709,7 @@ void MyESP::_mqttConnect() {
} }
// Setup everything we need // Setup everything we need
void MyESP::setup(char * app_hostname, void MyESP::setConnection(char * wifi_ssid, char * wifi_password, char * mqtt_host, char * mqtt_username, char * mqtt_password) {
char * app_name,
char * app_version,
char * wifi_ssid,
char * wifi_password,
char * mqtt_host,
char * mqtt_username,
char * mqtt_password) {
// get general params first
_app_hostname = strdup(app_hostname);
_app_name = strdup(app_name);
_app_version = strdup(app_version);
// Check SSID too long or missing // Check SSID too long or missing
if (!wifi_ssid || *wifi_ssid == 0x00 || strlen(wifi_ssid) > 31) { if (!wifi_ssid || *wifi_ssid == 0x00 || strlen(wifi_ssid) > 31) {
_wifi_ssid = NULL; _wifi_ssid = NULL;
@@ -581,7 +718,7 @@ void MyESP::setup(char * app_hostname,
} }
// Check PASS too long // Check PASS too long
if (wifi_password && strlen(wifi_password) > 63) { if (!wifi_password || *wifi_ssid == 0x00 || strlen(wifi_password) > 31) {
_wifi_password = NULL; _wifi_password = NULL;
} else { } else {
_wifi_password = strdup(wifi_password); _wifi_password = strdup(wifi_password);
@@ -607,9 +744,120 @@ void MyESP::setup(char * app_hostname,
} else { } else {
_mqtt_password = strdup(mqtt_password); _mqtt_password = strdup(mqtt_password);
} }
}
// print contents of file
void MyESP::_fs_printConfig() {
File configFile = SPIFFS.open("/config.json", "r");
myDebug_P(PSTR("[FS] Contents...."));
while (configFile.available()) {
SerialAndTelnet.print((char)configFile.read());
}
SerialAndTelnet.println();
configFile.close();
}
// format File System
void MyESP::_fs_eraseConfig() {
myDebug_P(PSTR("[FS] Erasing settings. Please wait. ESP will automatically restart when finished."));
if (SPIFFS.format()) {
resetESP();
}
}
// load from spiffs
bool MyESP::_fs_loadConfig() {
File configFile = SPIFFS.open("/config.json", "r");
if (!configFile) {
myDebug_P(PSTR("[FS] Failed to open config file"));
return false;
}
size_t size = configFile.size();
if (size > 1024) {
myDebug_P(PSTR("[FS] Config file size is too large"));
return false;
}
// assign buffer
std::unique_ptr<char[]> buf(new char[size]);
// use configFile.readString
configFile.readBytes(buf.get(), size);
StaticJsonBuffer<300> jsonBuffer; // https://arduinojson.org/v5/assistant/
JsonObject & json = jsonBuffer.parseObject(buf.get());
const char * value;
value = json["wifi_ssid"];
_wifi_ssid = (value) ? strdup(value) : NULL;
value = json["wifi_password"];
_wifi_password = (value) ? strdup(value) : NULL;
value = json["mqtt_host"];
_mqtt_host = (value) ? strdup(value) : NULL;
value = json["mqtt_username"];
_mqtt_username = (value) ? strdup(value) : NULL;
value = json["mqtt_password"];
_mqtt_password = (value) ? strdup(value) : NULL;
configFile.close();
return true;
}
// save settings to spiffs
bool MyESP::_fs_saveConfig() {
StaticJsonBuffer<200> jsonBuffer;
JsonObject & json = jsonBuffer.createObject();
json["wifi_ssid"] = _wifi_ssid;
json["wifi_password"] = _wifi_password;
json["mqtt_host"] = _mqtt_host;
json["mqtt_username"] = _mqtt_username;
json["mqtt_password"] = _mqtt_password;
File configFile = SPIFFS.open("/config.json", "w");
if (!configFile) {
myDebug_P(PSTR("[FS] Failed to open config file for writing"));
return false;
}
json.printTo(configFile);
return true;
}
// init the SPIFF file system and load the config
// if it doesn't exist try and create it
void MyESP::_fs_setup() {
if (!SPIFFS.begin()) {
myDebug_P(PSTR("[FS] Failed to mount the file system"));
return;
}
// load the config file. if it doesn't exist create it with anything that was specified
if (!_fs_loadConfig()) {
_fs_saveConfig();
}
}
// register new instance
void MyESP::begin(char * app_hostname, char * app_name, char * app_version) {
_app_hostname = strdup(app_hostname);
_app_name = strdup(app_name);
_app_version = strdup(app_version);
// call setup of the services... // call setup of the services...
_telnet_setup(); // Telnet setup _telnet_setup(); // Telnet setup
_fs_setup(); // SPIFFS setup
_wifi_setup(); // WIFI setup _wifi_setup(); // WIFI setup
_mqtt_setup(); // MQTT Setup _mqtt_setup(); // MQTT Setup
_mdns_setup(); // MDNS setup _mdns_setup(); // MDNS setup
@@ -620,8 +868,13 @@ void MyESP::setup(char * app_hostname,
* Loop. This is called as often as possible and it handles wifi, telnet, mqtt etc * Loop. This is called as often as possible and it handles wifi, telnet, mqtt etc
*/ */
void MyESP::loop() { void MyESP::loop() {
jw.loop(); // WiFi jw.loop(); // WiFi
_telnetHandle(); // Telnet/Debugger _telnetHandle(); // Telnet/Debugger
if (WiFi.getMode() & WIFI_AP) {
return;
}
ArduinoOTA.handle(); // OTA ArduinoOTA.handle(); // OTA
_mqttConnect(); // MQTT _mqttConnect(); // MQTT

View File

@@ -14,8 +14,10 @@
#include <AsyncMqttClient.h> // https://github.com/marvinroger/async-mqtt-client #include <AsyncMqttClient.h> // https://github.com/marvinroger/async-mqtt-client
#include <DNSServer.h> #include <DNSServer.h>
#include <ESPAsyncTCP.h> // https://github.com/me-no-dev/ESPAsyncTCP #include <ESPAsyncTCP.h> // https://github.com/me-no-dev/ESPAsyncTCP
#include <JustWifi.h> // https://github.com/xoseperez/justwifi #include <FS.h>
#include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy #include <JustWifi.h> // https://github.com/xoseperez/justwifi
#include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
#include <ESPmDNS.h> #include <ESPmDNS.h>
@@ -31,12 +33,12 @@
#define OTA_PORT 8266 // OTA port #define OTA_PORT 8266 // OTA port
// MQTT // MQTT
#define MQTT_BASE "home/" #define MQTT_HA "/home/ha" // HA specific
#define MQTT_NOTIFICATION MQTT_BASE "notification" #define MQTT_HA_NOTIFICATION "home/notification" // HA specific
#define MQTT_TOPIC_COMMAND "command" #define MQTT_TOPIC_COMMAND "command" // HA specific
#define MQTT_TOPIC_START "start" #define MQTT_TOPIC_START "start" // HA specific
#define MQTT_TOPIC_START_PAYLOAD "start" #define MQTT_TOPIC_START_PAYLOAD "start" // HA specific
#define MQTT_HA MQTT_BASE "ha"
#define MQTT_PORT 1883 // MQTT port #define MQTT_PORT 1883 // MQTT port
#define MQTT_QOS 1 #define MQTT_QOS 1
#define MQTT_RECONNECT_DELAY_MIN 5000 // Try to reconnect in 5 seconds upon disconnection #define MQTT_RECONNECT_DELAY_MIN 5000 // Try to reconnect in 5 seconds upon disconnection
@@ -50,6 +52,8 @@
// Telnet // Telnet
#define TELNET_MAX_COMMAND_LENGTH 80 // length of a command #define TELNET_MAX_COMMAND_LENGTH 80 // length of a command
#define TELNET_EVENT_CONNECT 1
#define TELNET_EVENT_DISCONNECT 0
#define COLOR_RESET "\x1B[0m" #define COLOR_RESET "\x1B[0m"
#define COLOR_BLACK "\x1B[0;30m" #define COLOR_BLACK "\x1B[0;30m"
#define COLOR_RED "\x1B[0;31m" #define COLOR_RED "\x1B[0;31m"
@@ -61,12 +65,16 @@
#define COLOR_WHITE "\x1B[0;37m" #define COLOR_WHITE "\x1B[0;37m"
typedef struct { typedef struct {
char key[10]; char key[30];
char description[400]; char description[100];
} command_t; } command_t;
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f; typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
typedef std::function<void(uint8_t, const char *)> telnetcommand_callback_f;
typedef std::function<void(uint8_t)> telnet_callback_f;
// calculates size of an 2d array at compile time // calculates size of an 2d array at compile time
template <typename T, size_t N> template <typename T, size_t N>
constexpr size_t ArraySize(T (&)[N]) { constexpr size_t ArraySize(T (&)[N]) {
@@ -81,7 +89,6 @@ class MyESP {
// wifi // wifi
void setWIFICallback(void (*callback)()); void setWIFICallback(void (*callback)());
void setMQTTCallback(mqtt_callback_f callback);
// ha // ha
void sendHACommand(const char * cmd); void sendHACommand(const char * cmd);
@@ -91,28 +98,22 @@ class MyESP {
void mqttSubscribe(const char * topic); void mqttSubscribe(const char * topic);
void mqttUnsubscribe(const char * topic); void mqttUnsubscribe(const char * topic);
void mqttPublish(const char * topic, const char * payload); void mqttPublish(const char * topic, const char * payload);
void setMQTTbase(char * mqttbase);
void setMQTTCallback(mqtt_callback_f callback);
// debug & telnet // debug & telnet
void myDebug(const char * format, ...); void myDebug(const char * format, ...);
void myDebug_P(PGM_P format_P, ...); void myDebug_P(PGM_P format_P, ...);
void consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)()); void setTelnetCommands(command_t * cmds, uint8_t count, telnetcommand_callback_f callback);
char * consoleGetLastCommand(); void setTelnetCallback(telnet_callback_f callback);
void consoleProcessCommand();
// general
void end(); void end();
void loop(); void loop();
void setup(char * app_hostname, void begin(char * app_hostname, char * app_name, char * app_version);
char * app_name, void setConnection(char * wifi_ssid, char * wifi_password, char * mqtt_host, char * mqtt_username, char * mqtt_password);
char * app_version, void setBoottime(char * boottime);
char * wifi_ssid, void resetESP();
char * wifi_password,
char * mqtt_host,
char * mqtt_username,
char * mqtt_password);
char * getBoottime();
void setBoottime(char * boottime);
void resetESP();
private: private:
// mqtt // mqtt
@@ -128,6 +129,8 @@ class MyESP {
char * _mqtt_username; char * _mqtt_username;
char * _mqtt_password; char * _mqtt_password;
char * _boottime; char * _boottime;
bool _suspendOutput;
char * _mqttbase;
// wifi // wifi
DNSServer dnsServer; // For Access Point (AP) support DNSServer dnsServer; // For Access Point (AP) support
@@ -145,19 +148,27 @@ class MyESP {
void _ota_setup(); void _ota_setup();
// telnet & debug // telnet & debug
TelnetSpy SerialAndTelnet; TelnetSpy SerialAndTelnet;
void _telnetConnected(); void _telnetConnected();
void _telnetDisconnected(); void _telnetDisconnected();
void _telnetHandle(); void _telnetHandle();
void _telnet_setup(); void _telnetCommand(char * commandLine);
char * _command; // the input command from either Serial or Telnet char * _telnet_readWord();
command_t * _helpProjectCmds; // Help of commands setted by project void _telnet_setup();
uint8_t _helpProjectCmds_count; // # available commands char * _command; // the input command from either Serial or Telnet
void _consoleShowHelp(); command_t * _helpProjectCmds; // Help of commands setted by project
void (*_consoleCallbackProjectCmds)(); // Callable for projects commands uint8_t _helpProjectCmds_count; // # available commands
void _consoleProcessCommand(); void _consoleShowHelp();
bool _isCRLF(char character); telnetcommand_callback_f _telnetcommand_callback; // Callable for projects commands
bool _suspendMessages; telnet_callback_f _telnet_callback; // callback for connect/disconnect
void _changeSetting(const char * setting, const char * value);
// fs
void _fs_setup();
bool _fs_saveConfig();
bool _fs_loadConfig();
void _fs_printConfig();
void _fs_eraseConfig();
// general // general
char * _app_hostname; char * _app_hostname;

View File

@@ -8,7 +8,6 @@ platform = espressif8266
flash_mode = dout flash_mode = dout
; optional flags are -DNO_LED -DDEBUG_SUPPORT ; optional flags are -DNO_LED -DDEBUG_SUPPORT
build_flags = -g -w 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"'
lib_deps = lib_deps =
CRC32 CRC32
CircularBuffer CircularBuffer
@@ -21,15 +20,11 @@ board = nodemcuv2
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}
board_build.flash_mode = ${common.flash_mode} board_build.flash_mode = ${common.flash_mode}
upload_speed = 921600 upload_speed = 921600
monitor_speed = 115200 monitor_speed = 115200
; comment out next line if using USB and not OTA upload_port = ems-esp.local
upload_port = "boiler"
; examples....
;upload_port = "boiler."
;upload_port = "boiler.local"
;upload_port = 10.10.10.6 ;upload_port = 10.10.10.6
[env:d1_mini] [env:d1_mini]
@@ -37,14 +32,10 @@ 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}
board_build.flash_mode = ${common.flash_mode} board_build.flash_mode = ${common.flash_mode}
upload_speed = 921600 upload_speed = 921600
monitor_speed = 115200 monitor_speed = 115200
; comment out next line if using USB and not OTA upload_port = ems-esp.local
upload_port = "boiler"
; examples....
;upload_port = "boiler."
;upload_port = "boiler.local"
;upload_port = 10.10.10.6 ;upload_port = 10.10.10.6

13
rename_fw.py Normal file
View 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'])

View File

@@ -1,13 +1,15 @@
/* /*
* EMS-ESP-Boiler * EMS-ESP
* 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 * Paul Derbyshire - https://github.com/proddy/EMS-ESP
*
* See ChangeLog.md for history
* See README.md for Acknowledgments
*/ */
// local libraries // local libraries
#include "ems.h" #include "ems.h"
#include "ems_devices.h"
#include "emsuart.h" #include "emsuart.h"
#include "my_config.h" #include "my_config.h"
#include "version.h" #include "version.h"
@@ -45,6 +47,8 @@ uint8_t scanThermostat_count = 0;
Ticker showerColdShotStopTimer; Ticker showerColdShotStopTimer;
static unsigned long timestamp; // for internal timings, via millis()
// thermostat // thermostat
#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values #define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values
#define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // for received thermostat temp changes #define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // for received thermostat temp changes
@@ -65,19 +69,11 @@ Ticker showerColdShotStopTimer;
#define TOPIC_SHOWER_COLDSHOT "shower_coldshot" // used to trigger a coldshot from HA publish #define TOPIC_SHOWER_COLDSHOT "shower_coldshot" // used to trigger a coldshot from HA publish
#define SHOWER_ALARM "shower_alarm" // for notifying HA that shower time has reached its limit #define SHOWER_ALARM "shower_alarm" // for notifying HA that shower time has reached its limit
// shower settings for DEBUGGING only // if using the shower timer, change these settings
#ifdef SHOWER_TEST #define SHOWER_PAUSE_TIME 15000 // in ms. 15 seconds, max time if water is switched off & on during a shower
#undef SHOWER_PAUSE_TIME #define SHOWER_MIN_DURATION 120000 // in ms. 2 minutes, before recognizing its a shower
#undef SHOWER_MIN_DURATION #define SHOWER_OFFSET_TIME 5000 // in ms. 5 seconds grace time, to calibrate actual time under the shower
#undef SHOWER_MAX_DURATION #define SHOWER_COLDSHOT_DURATION 10 // in seconds. 10 seconds for cold water before turning back hot water
#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 { typedef struct {
bool shower_timer; // true if we want to report back on shower times bool shower_timer; // true if we want to report back on shower times
@@ -94,24 +90,23 @@ typedef struct {
command_t PROGMEM project_cmds[] = { command_t PROGMEM project_cmds[] = {
{"l [n]", "set logging (0=none, 1=raw, 2=basic, 3=thermostat only, 4=verbose)"}, {"info", "show the values"},
{"s", "show statistics"}, {"log <n | b | t | r | v>", "set logging mode to none, basic, thermostat only, raw or verbose"},
{"D", "scan EMS connected Devices"}, {"poll", "toggle EMS poll request on/off"},
{"h", "list supported EMS telegram type IDs"}, {"tx", "toggle EMX Tx on/off"},
{"M", "publish to MQTT"}, {"publish", "publish values to MQTT"},
{"Q", "print Tx Queue"}, {"types", "list supported EMS telegram type IDs"},
{"U [n]", "do a deep scan of all thermostat message types, starting at n"}, {"queue", "print the Tx queue"},
{"P", "toggle EMS Poll response on/off"}, {"autodetect", "discover EMS devices and set boiler and thermostat automatically"},
{"X", "toggle EMS Tx transmission on/off"}, {"shower <timer | alert>", "toggle either timer or alert on/off"},
{"S", "toggle Shower timer on/off"}, {"send XX...", "send raw telegram data in hex to EMS bus"},
{"A", "toggle shower Alert on/off"}, {"thermostat read <hex type ID>", "send read request to thermostat"},
{"r [s]", "send raw telegram in hex to EMS (s=XX XX XX...)"}, {"thermostat temp <degrees>", "set current thermostat temperature"},
{"b [xx]", "send boiler read request (xx=telegram type ID in hex)"}, {"thermostat mode <mode>", "set mode (0=low/night, 1=manual/day, 2=auto)"},
{"t [xx]", "send thermostat read request (xx=telegram type ID in hex)"}, {"thermostat scan <hex type ID>", "do a deep scan of all thermostat message types, starting at n"},
{"w [n]", "set boiler warm water temperature (min 30)"}, {"boiler read <hex type ID>", "send read request to boiler"},
{"a [n]", "set boiler warm tap water (0=off, 1=on)"}, {"boiler wwtemp <degrees>", "set warm water temperature"},
{"T [n]", "set thermostat temperature"}, {"boiler tapwater <on | off>", "set warm tap water on or off"}
{"m [n]", "set thermostat mode (0=low/night, 1=manual/day, 2=auto)"}
}; };
@@ -127,8 +122,6 @@ uint32_t previousThermostatPublishCRC = 0;
const unsigned long POLL_TIMEOUT_ERR = 10000; // if no signal from boiler for last 10 seconds, assume its offline 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 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()
uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off
// logging messages with fixed strings // logging messages with fixed strings
@@ -269,7 +262,7 @@ void _renderBoolValue(const char * prefix, uint8_t value) {
void showInfo() { void showInfo() {
// General stats from EMS bus // General stats from EMS bus
myDebug("%sEMS-ESP-Boiler system stats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug("%sEMS-ESP System setstats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF);
_EMS_SYS_LOGGING sysLog = ems_getLogging(); _EMS_SYS_LOGGING sysLog = ems_getLogging();
if (sysLog == EMS_SYS_LOGGING_BASIC) { if (sysLog == EMS_SYS_LOGGING_BASIC) {
myDebug(" System logging set to Basic"); myDebug(" System logging set to Basic");
@@ -519,116 +512,203 @@ void publishValues(bool force) {
// sets the shower timer on/off // sets the shower timer on/off
void set_showerTimer() { void set_showerTimer() {
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
myDebug("Shower timer is %s", Boiler_Status.shower_timer ? "enabled" : "disabled"); myDebug("Shower timer has been set to %s", Boiler_Status.shower_timer ? "enabled" : "disabled");
} }
} }
// sets the shower alert on/off // sets the shower alert on/off
void set_showerAlert() { void set_showerAlert() {
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
myDebug("Shower alert is %s", Boiler_Status.shower_alert ? "enabled" : "disabled"); myDebug("Shower alert has been set to %s", Boiler_Status.shower_alert ? "enabled" : "disabled");
}
}
// used to read the next string from an input buffer and convert to an 8 bit int
uint8_t _readIntNumber() {
char * numTextPtr = strtok(NULL, ", \n");
if (numTextPtr == nullptr) {
return 0;
}
return atoi(numTextPtr);
}
// used to read the next string from an input buffer as a hex value and convert to an 8 bit int
uint8_t _readHexNumber() {
char * numTextPtr = strtok(NULL, ", \n");
if (numTextPtr == nullptr) {
return 0;
}
return (uint8_t)strtol(numTextPtr, 0, 16);
}
// used to read the next string from an input buffer
char * _readWord() {
char * word = strtok(NULL, ", \n");
return word;
}
// initiate a force scan by sending type read requests from 0 to FF to the thermostat
// used to analyze repsonses for debugging
void startThermostatScan(uint8_t start) {
ems_setLogging(EMS_SYS_LOGGING_THERMOSTAT);
publishValuesTimer.detach();
systemCheckTimer.detach();
regularUpdatesTimer.detach();
scanThermostat_count = start;
myDebug("Starting a deep message scan on thermostat");
scanThermostat.attach(SCANTHERMOSTAT_TIME, do_scanThermostat);
}
// call back when a telnet client connects or disconnects
// we set the logging here
void TelnetCallback(uint8_t event) {
if (event == TELNET_EVENT_CONNECT) {
ems_setLogging(EMS_SYS_LOGGING_BASIC);
} else if (event == TELNET_EVENT_DISCONNECT) {
ems_setLogging(EMS_SYS_LOGGING_NONE);
} }
} }
// extra commands options for telnet debug window // extra commands options for telnet debug window
void myDebugCallback() { // wc is the word count, i.e. number of arguments. Everything is in lower case.
char * cmd = myESP.consoleGetLastCommand(); void TelnetCommandCallback(uint8_t wc, const char * commandLine) {
uint8_t len = strlen(cmd); bool ok = false;
bool b; // get first command argument
char * first_cmd = strtok((char *)commandLine, ", \n");
// look for single letter commands if (strcmp(first_cmd, "info") == 0) {
if (len == 1) { showInfo();
switch (cmd[0]) { ok = true;
case 's': }
showInfo();
break; if (strcmp(first_cmd, "poll") == 0) {
case 'P': // toggle Poll bool b = !ems_getPoll();
b = !ems_getPoll(); ems_setPoll(b);
ems_setPoll(b); ok = true;
break; }
case 'X': // toggle Tx
b = !ems_getTxEnabled(); if (strcmp(first_cmd, "tx") == 0) {
ems_setTxEnabled(b); bool b = !ems_getTxEnabled();
break; ems_setTxEnabled(b);
case 'M': ok = true;
publishValues(true); }
break;
case 'h': // show type handlers if (strcmp(first_cmd, "publish") == 0) {
ems_printAllTypes(); publishValues(true);
break; ok = true;
case 'S': // toggle Shower timer support }
Boiler_Status.shower_timer = !Boiler_Status.shower_timer;
myESP.mqttPublish(TOPIC_SHOWER_TIMER, Boiler_Status.shower_timer ? "1" : "0"); if (strcmp(first_cmd, "types") == 0) {
break; ems_printAllTypes();
case 'A': // toggle Shower alert ok = true;
Boiler_Status.shower_alert = !Boiler_Status.shower_alert; }
myESP.mqttPublish(TOPIC_SHOWER_ALERT, Boiler_Status.shower_alert ? "1" : "0");
break; if (strcmp(first_cmd, "queue") == 0) {
case 'Q': // print Tx Queue ems_printTxQueue();
ems_printTxQueue(); ok = true;
break; }
case 'D': // Auto detect EMS devices
ems_scanDevices(); if (strcmp(first_cmd, "autodetect") == 0) {
break; ems_scanDevices();
default: ok = true;
myDebug("Unknown command. Use ? for help."); }
break;
// shower settings
if (strcmp(first_cmd, "shower") == 0) {
if (wc == 2) {
char * second_cmd = _readWord();
if (strcmp(second_cmd, "timer") == 0) {
Boiler_Status.shower_timer = !Boiler_Status.shower_timer;
myESP.mqttPublish(TOPIC_SHOWER_TIMER, Boiler_Status.shower_timer ? "1" : "0");
ok = true;
} else if (strcmp(second_cmd, "alert") == 0) {
Boiler_Status.shower_alert = !Boiler_Status.shower_alert;
myESP.mqttPublish(TOPIC_SHOWER_ALERT, Boiler_Status.shower_alert ? "1" : "0");
ok = true;
}
} }
return;
} }
// for commands with parameters, assume command is just a single letter followed by a space // logging
switch (cmd[0]) { if (strcmp(first_cmd, "log") == 0) {
case 'T': // set thermostat temp if (wc == 2) {
ems_setThermostatTemp(strtof(&cmd[2], 0)); char * second_cmd = _readWord();
break; if (strcmp(second_cmd, "v") == 0) {
case 'm': // set thermostat mode ems_setLogging(EMS_SYS_LOGGING_VERBOSE);
ems_setThermostatMode(cmd[2] - '0'); ok = true;
break; } else if (strcmp(second_cmd, "b") == 0) {
case 'w': // set warm water temp ems_setLogging(EMS_SYS_LOGGING_BASIC);
ems_setWarmWaterTemp((uint8_t)strtol(&cmd[2], 0, 10)); ok = true;
break; } else if (strcmp(second_cmd, "t") == 0) {
case 'l': // logging ems_setLogging(EMS_SYS_LOGGING_THERMOSTAT);
ems_setLogging((_EMS_SYS_LOGGING)(cmd[2] - '0')); ok = true;
break; } else if (strcmp(second_cmd, "r") == 0) {
case 'a': // set ww activate on or off ems_setLogging(EMS_SYS_LOGGING_RAW);
if ((cmd[2] - '0') == 1) ok = true;
ems_setWarmTapWaterActivated(true); } else if (strcmp(second_cmd, "n") == 0) {
else if ((cmd[2] - '0') == 0) ems_setLogging(EMS_SYS_LOGGING_NONE);
ems_setWarmTapWaterActivated(false); ok = true;
break; }
case 'b': // boiler read command }
ems_doReadCommand((uint8_t)strtol(&cmd[2], 0, 16), EMS_Boiler.type_id); }
break;
case 't': // thermostat command // thermostat commands
ems_doReadCommand((uint8_t)strtol(&cmd[2], 0, 16), EMS_Thermostat.type_id); if (strcmp(first_cmd, "thermostat") == 0) {
break; if (wc == 3) {
case 'r': // send raw data char * second_cmd = _readWord();
ems_sendRawTelegram(&cmd[2]); if (strcmp(second_cmd, "temp") == 0) {
break; ems_setThermostatTemp(_readIntNumber());
case 'x': // experimental, not displayed! ok = true;
myDebug("Calling experimental..."); } else if (strcmp(second_cmd, "mode") == 0) {
ems_setLogging(EMS_SYS_LOGGING_VERBOSE); ems_setThermostatMode(_readIntNumber());
ems_setExperimental((uint8_t)strtol(&cmd[2], 0, 16)); // takes HEX param ok = true;
break; } else if (strcmp(second_cmd, "read") == 0) {
case 'U': // thermostat scan ems_doReadCommand(_readHexNumber(), EMS_Thermostat.type_id);
ems_setLogging(EMS_SYS_LOGGING_THERMOSTAT); ok = true;
publishValuesTimer.detach(); } else if (strcmp(second_cmd, "scan") == 0) {
systemCheckTimer.detach(); startThermostatScan(_readIntNumber());
regularUpdatesTimer.detach(); ok = true;
scanThermostat_count = (uint8_t)strtol(&cmd[2], 0, 16); }
myDebug("Starting a deep message scan on thermometer"); }
scanThermostat.attach(SCANTHERMOSTAT_TIME, do_scanThermostat); }
break;
default: // boiler commands
myDebug("Unknown command. Use ? for help."); if (strcmp(first_cmd, "boiler") == 0) {
break; if (wc == 3) {
char * second_cmd = _readWord();
if (strcmp(second_cmd, "wwtemp") == 0) {
ems_setWarmWaterTemp(_readIntNumber());
ok = true;
} else if (strcmp(second_cmd, "read") == 0) {
ems_doReadCommand(_readHexNumber(), EMS_Boiler.type_id);
ok = true;
} else if (strcmp(second_cmd, "tapwater") == 0) {
char * third_cmd = _readWord();
if (strcmp(third_cmd, "on") == 0) {
ems_setWarmTapWaterActivated(true);
ok = true;
} else if (strcmp(third_cmd, "off") == 0) {
ems_setWarmTapWaterActivated(false);
ok = true;
}
}
}
}
// send raw
if (strcmp(first_cmd, "send") == 0) {
ems_sendRawTelegram((char *)&commandLine[5]);
ok = true;
}
// check for invalid command
if (!ok) {
myDebug("Unknown command. Use ? for help.");
} }
return;
} }
// MQTT Callback to handle incoming/outgoing changes // MQTT Callback to handle incoming/outgoing changes
void MQTTcallback(unsigned int type, const char * topic, const char * message) { void MQTTCallback(unsigned int type, const char * topic, const char * message) {
// we're connected. lets subscribe to some topics // we're connected. lets subscribe to some topics
if (type == MQTT_CONNECT_EVENT) { if (type == MQTT_CONNECT_EVENT) {
myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_TEMP); myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_TEMP);
@@ -708,7 +788,7 @@ void WIFICallback() {
// Initialize the boiler settings // Initialize the boiler settings
void initShower() { void initShower() {
// default showr settings // default shower settings
Boiler_Status.shower_timer = BOILER_SHOWER_TIMER; Boiler_Status.shower_timer = BOILER_SHOWER_TIMER;
Boiler_Status.shower_alert = BOILER_SHOWER_ALERT; Boiler_Status.shower_alert = BOILER_SHOWER_ALERT;
Boiler_Shower.timerStart = 0; Boiler_Shower.timerStart = 0;
@@ -719,7 +799,10 @@ void initShower() {
// call PublishValues without forcing, so using CRC to see if we really need to publish // call PublishValues without forcing, so using CRC to see if we really need to publish
void do_publishValues() { void do_publishValues() {
publishValues(false); // don't publish if we're not connected to the EMS bus
if (ems_getBusConnected()) {
publishValues(false);
}
} }
// callback to light up the LED, called via Ticker every second // callback to light up the LED, called via Ticker every second
@@ -745,16 +828,20 @@ void do_scanThermostat() {
// do a system health check every now and then to see if we all connections // do a system health check every now and then to see if we all connections
void do_systemCheck() { void do_systemCheck() {
if (!ems_getBusConnected()) { if (!ems_getBusConnected()) {
myDebug("Error! Unable to connect to EMS bus. Please make sure you're not in DEBUG_SUPPORT mode. Retrying in %d seconds...", myDebug("Error! Unable to connect to EMS bus. Check connection and make sure you're not in DEBUG_SUPPORT mode. Retrying in %d "
"seconds...",
SYSTEMCHECK_TIME); SYSTEMCHECK_TIME);
} }
} }
// force calls to get data from EMS for the types that aren't sent as broadcasts // force calls to get data from EMS for the types that aren't sent as broadcasts
// only if we have a EMS connection
void do_regularUpdates() { void do_regularUpdates() {
myDebugLog("Calling scheduled data refresh from EMS devices.."); if (ems_getBusConnected()) {
ems_getThermostatValues(); // get Thermostat temps (if supported) myDebugLog("Calling scheduled data refresh from EMS devices..");
ems_getBoilerValues(); ems_getThermostatValues(); // get Thermostat temps (if supported)
ems_getBoilerValues();
}
} }
// turn off hot water to send a shot of cold // turn off hot water to send a shot of cold
@@ -860,16 +947,20 @@ void setup() {
regularUpdatesTimer.attach(REGULARUPDATES_TIME, do_regularUpdates); // regular reads from the EMS regularUpdatesTimer.attach(REGULARUPDATES_TIME, do_regularUpdates); // regular reads from the EMS
// set up myESP for Wifi, MQTT, MDNS and Telnet // set up myESP for Wifi, MQTT, MDNS and Telnet
// with callbacks myESP.setTelnetCommands(project_cmds, ArraySize(project_cmds), TelnetCommandCallback); // set up Telnet commands
myESP.setup(APP_HOSTNAME, APP_NAME, APP_VERSION, WIFI_SSID, WIFI_PASSWORD, MQTT_IP, MQTT_USER, MQTT_PASS); myESP.setConnection(WIFI_SSID, WIFI_PASSWORD, MQTT_IP, MQTT_USER, MQTT_PASS); // optional
myESP.consoleSetCallBackProjectCmds(project_cmds, ArraySize(project_cmds), myDebugCallback); // set up Telnet commands myESP.begin(APP_HOSTNAME, APP_NAME, APP_VERSION);
myESP.setMQTTbase(MQTT_BASE);
// callbacks
myESP.setWIFICallback(WIFICallback); myESP.setWIFICallback(WIFICallback);
myESP.setMQTTCallback(MQTTcallback); myESP.setMQTTCallback(MQTTCallback);
myESP.setTelnetCallback(TelnetCallback);
// init Shower specific parameters // init Shower specific parameters
initShower(); initShower();
ems_setLogging(BOILER_DEFAULT_LOGGING); // set default logging. see my_config.h ems_setLogging(EMS_SYS_LOGGING_NONE); // set default logging to none
// init the EMS bus // init the EMS bus
// call ems.cpp's init function to set all the internal params // call ems.cpp's init function to set all the internal params
@@ -892,7 +983,7 @@ void loop() {
ems_setEmsRefreshed(false); ems_setEmsRefreshed(false);
} }
// do shower logic if it is enabled // do shower logic, if enabled
if (Boiler_Status.shower_timer) { if (Boiler_Status.shower_timer) {
showerCheck(); showerCheck();
} }

View File

@@ -1,10 +1,18 @@
/** /**
* ems.cpp * ems.cpp
*
* handles all the processing of the EMS messages * handles all the processing of the EMS messages
* Paul Derbyshire - https://github.com/proddy/EMS-ESP-Boiler *
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
*/ */
#include "ems.h" #include "ems.h"
#include "ems_devices.h"
#include "emsuart.h"
#include <Arduino.h>
#include <CircularBuffer.h> // https://github.com/rlogiacco/CircularBuffer
#include <MyESP.h>
#include <list> // std::list
// myESP // myESP
#define myDebug(...) myESP.myDebug(__VA_ARGS__) #define myDebug(...) myESP.myDebug(__VA_ARGS__)
@@ -42,42 +50,9 @@ void _process_RC35StatusMessage(uint8_t * data, uint8_t length);
// Easy // Easy
void _process_EasyStatusMessage(uint8_t * data, uint8_t length); void _process_EasyStatusMessage(uint8_t * data, uint8_t length);
// EMS types for known Buderus devices
// Note: This is still incomplete
const _Model_Type Model_Types[] = {
// me
{EMS_MODEL_SERVICEKEY, 999, 0x0B, "Service Key"},
// various boilers and buderus type devices
{EMS_MODEL_UBA, 123, 0x08, "MC10/UBA3 Boiler"}, // verified
{EMS_MODEL_BK15, 64, 0x08, "Sieger BK15 Boiler"}, // verified
{EMS_MODEL_BC10, 190, 0x09, "BC10 Base Controller"}, // verified
{EMS_MODEL_MM10, 125, 0x21, "MM10 Mixer Module"}, // warning, fake product id!
{EMS_MODEL_WM10, 126, 0x11, "WM10 Switch Module"}, // warning, fake product id!
// controllers and thermostats
{EMS_MODEL_ES73, 76, 0x10, "Sieger ES73"},
{EMS_MODEL_RC20, 77, 0x17, "RC20 (Nefit Moduline 300)"},
{EMS_MODEL_RC30, 78, 0x10, "RC30 (Nefit Moduline 400)"},
{EMS_MODEL_RC35, 86, 0x10, "RC35 (or compatible"},
{EMS_MODEL_EASY, 202, 0x18, "TC100 (Nefit Easy/CT100)"}
};
/* /*
* Known thermostat types and their abilities * Recognized EMS types and the functions they call to process the telegrams
*/ */
const _Thermostat_Type Thermostat_Types[] = {
{EMS_MODEL_RC20, EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_RC30, EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_RC35, EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_EASY, EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_ES73, EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}
};
const _EMS_Type EMS_Types[] = { const _EMS_Type EMS_Types[] = {
// common // common
@@ -93,13 +68,19 @@ const _EMS_Type EMS_Types[] = {
{EMS_MODEL_UBA, EMS_TYPE_UBAParametersMessage, "UBAParametersMessage", NULL}, {EMS_MODEL_UBA, EMS_TYPE_UBAParametersMessage, "UBAParametersMessage", NULL},
{EMS_MODEL_UBA, EMS_TYPE_UBAMaintenanceStatusMessage, "UBAMaintenanceStatusMessage", NULL}, {EMS_MODEL_UBA, EMS_TYPE_UBAMaintenanceStatusMessage, "UBAMaintenanceStatusMessage", NULL},
// RC20 // RC20 and RC20F
{EMS_MODEL_RC20, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage}, {EMS_MODEL_RC20, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage},
{EMS_MODEL_RC20, EMS_TYPE_RCTime, "RCTime", _process_RCTime}, {EMS_MODEL_RC20, EMS_TYPE_RCTime, "RCTime", _process_RCTime},
{EMS_MODEL_RC20, EMS_TYPE_RC20Set, "RC20Set", _process_RC20Set}, {EMS_MODEL_RC20, EMS_TYPE_RC20Set, "RC20Set", _process_RC20Set},
{EMS_MODEL_RC20, EMS_TYPE_RC20StatusMessage, "RC20StatusMessage", _process_RC20StatusMessage}, {EMS_MODEL_RC20, EMS_TYPE_RC20StatusMessage, "RC20StatusMessage", _process_RC20StatusMessage},
{EMS_MODEL_RC20, EMS_TYPE_UBASetPoints, "UBASetPoints", _process_SetPoints}, {EMS_MODEL_RC20, EMS_TYPE_UBASetPoints, "UBASetPoints", _process_SetPoints},
{EMS_MODEL_RC20F, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage},
{EMS_MODEL_RC20F, EMS_TYPE_RCTime, "RCTime", _process_RCTime},
{EMS_MODEL_RC20F, EMS_TYPE_RC20Set, "RC20Set", _process_RC20Set},
{EMS_MODEL_RC20F, EMS_TYPE_RC20StatusMessage, "RC20StatusMessage", _process_RC20StatusMessage},
{EMS_MODEL_RC20F, EMS_TYPE_UBASetPoints, "UBASetPoints", _process_SetPoints},
// RC30 // RC30
{EMS_MODEL_RC30, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage}, {EMS_MODEL_RC30, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage},
{EMS_MODEL_RC30, EMS_TYPE_RCTime, "RCTime", _process_RCTime}, {EMS_MODEL_RC30, EMS_TYPE_RCTime, "RCTime", _process_RCTime},
@@ -125,7 +106,6 @@ const _EMS_Type EMS_Types[] = {
{EMS_MODEL_EASY, EMS_TYPE_EasyStatusMessage, "EasyStatusMessage", _process_EasyStatusMessage}, {EMS_MODEL_EASY, EMS_TYPE_EasyStatusMessage, "EasyStatusMessage", _process_EasyStatusMessage},
{EMS_MODEL_EASY, EMS_TYPE_UBASetPoints, "UBASetPoints", _process_SetPoints} {EMS_MODEL_EASY, EMS_TYPE_UBASetPoints, "UBASetPoints", _process_SetPoints}
}; };
// calculate sizes of arrays // calculate sizes of arrays
@@ -163,7 +143,7 @@ uint8_t _last_TxTelgramCRC; // CRC of last Tx sent, for checking duplicates
// init stats and counters and buffers // init stats and counters and buffers
// uses -255 or 255 for values that haven't been set yet (EMS_VALUE_INT_NOTSET and EMS_VALUE_FLOAT_NOTSET) // uses -255 or 255 for values that haven't been set yet (EMS_VALUE_INT_NOTSET and EMS_VALUE_FLOAT_NOTSET)
void ems_init(_EMS_MODEL_ID boiler_modelid, _EMS_MODEL_ID thermostat_modelid) { void ems_init(uint8_t boiler_modelid, uint8_t thermostat_modelid) {
// overall status // overall status
EMS_Sys_Status.emsRxPgks = 0; EMS_Sys_Status.emsRxPgks = 0;
EMS_Sys_Status.emsTxPkgs = 0; EMS_Sys_Status.emsTxPkgs = 0;
@@ -816,7 +796,7 @@ void _processType(uint8_t * telegram, uint8_t length) {
// there is a match, so write must have been successful // there is a match, so write must have been successful
EMS_TxQueue.shift(); // remove validate from queue EMS_TxQueue.shift(); // remove validate from queue
if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) { if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) {
myDebug("Write to 0x%02X successful.", EMS_TxTelegram.dest); myDebug("Write to 0x%02X was successful", EMS_TxTelegram.dest);
} }
ems_doReadCommand(EMS_TxTelegram.comparisonPostRead, ems_doReadCommand(EMS_TxTelegram.comparisonPostRead,
EMS_TxTelegram.dest, EMS_TxTelegram.dest,
@@ -1102,7 +1082,7 @@ void _process_Version(uint8_t * data, uint8_t length) {
ems_getThermostatValues(); // get Thermostat values (if supported) ems_getThermostatValues(); // get Thermostat values (if supported)
} }
} else { } else {
// otherwise assume its a boiler // otherwise assume its a boiler or some other EMS device
if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) { if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) {
myDebug("Boiler recognized. Model %s with TypeID 0x%02X, Product ID %d, Version %s", myDebug("Boiler recognized. Model %s with TypeID 0x%02X, Product ID %d, Version %s",
Model_Types[i].model_string, Model_Types[i].model_string,
@@ -1125,7 +1105,7 @@ void _process_Version(uint8_t * data, uint8_t length) {
* Given a MODEL_ID, look up its data and set either a Thermostat or Boiler * Given a MODEL_ID, look up its data and set either a Thermostat or Boiler
* return false if not found or no need to set * return false if not found or no need to set
*/ */
bool _ems_setModel(_EMS_MODEL_ID model_id) { bool _ems_setModel(uint8_t model_id) {
if (model_id == EMS_MODEL_NONE) { if (model_id == EMS_MODEL_NONE) {
return false; // invalid model_id return false; // invalid model_id
} }
@@ -1301,8 +1281,8 @@ void ems_getThermostatValues() {
return; return;
} }
_EMS_MODEL_ID model_id = EMS_Thermostat.model_id; uint8_t model_id = EMS_Thermostat.model_id;
uint8_t type = EMS_Thermostat.type_id; uint8_t type = EMS_Thermostat.type_id;
if (model_id == EMS_MODEL_RC20) { if (model_id == EMS_MODEL_RC20) {
ems_doReadCommand(EMS_TYPE_RC20StatusMessage, type); // to get the setpoint temp ems_doReadCommand(EMS_TYPE_RC20StatusMessage, type); // to get the setpoint temp
@@ -1332,7 +1312,7 @@ void ems_getBoilerValues() {
// return pointer to Model details // return pointer to Model details
int _ems_findModel(_EMS_MODEL_ID model_id) { int _ems_findModel(uint8_t model_id) {
uint8_t i = 0; uint8_t i = 0;
bool found = false; bool found = false;
@@ -1351,7 +1331,7 @@ int _ems_findModel(_EMS_MODEL_ID model_id) {
return i; return i;
} }
char * _ems_buildModelString(char * buffer, uint8_t size, _EMS_MODEL_ID model_id) { char * _ems_buildModelString(char * buffer, uint8_t size, uint8_t model_id) {
int i = _ems_findModel(model_id); int i = _ems_findModel(model_id);
if (i != -1) { if (i != -1) {
char tmp[6] = {0}; char tmp[6] = {0};
@@ -1394,12 +1374,12 @@ char * ems_getBoilerType(char * buffer) {
} }
// returns the model type for a thermostat // returns the model type for a thermostat
_EMS_MODEL_ID ems_getThermostatModel() { uint8_t ems_getThermostatModel() {
return (EMS_Thermostat.model_id); return (EMS_Thermostat.model_id);
} }
// returns the model type for a boiler // returns the model type for a boiler
_EMS_MODEL_ID ems_getBoilerModel() { uint8_t ems_getBoilerModel() {
return (EMS_Boiler.model_id); return (EMS_Boiler.model_id);
} }
@@ -1407,7 +1387,11 @@ _EMS_MODEL_ID ems_getBoilerModel() {
* Find the versions of our connected devices * Find the versions of our connected devices
*/ */
void ems_scanDevices() { void ems_scanDevices() {
myDebug("Scanning EMS bus for devices. This may take a few seconds."); if (!ems_getBusConnected()) {
return;
}
myDebug("Scanning EMS bus for devices. This may take a few seconds...");
// copy over the IDs from Model-type to a list // copy over the IDs from Model-type to a list
std::list<uint8_t> Device_Ids; std::list<uint8_t> Device_Ids;
@@ -1430,7 +1414,7 @@ void ems_scanDevices() {
* Print out all handled types * Print out all handled types
*/ */
void ems_printAllTypes() { void ems_printAllTypes() {
myDebug("These %d telegram TypeIDs are recognized currently:", _EMS_Types_max); myDebug("These %d telegram TypeIDs are recognized:", _EMS_Types_max);
uint8_t i; uint8_t i;
for (i = 0; i < _EMS_Types_max; i++) { for (i = 0; i < _EMS_Types_max; i++) {
@@ -1444,7 +1428,7 @@ void ems_printAllTypes() {
} }
} }
myDebug("\nThese %d thermostats are natively supported:", _Thermostat_Types_max); myDebug("\nThese %d thermostats models are supported:", _Thermostat_Types_max);
for (i = 0; i < _Thermostat_Types_max; i++) { for (i = 0; i < _Thermostat_Types_max; i++) {
// find the model's details // find the model's details
for (int j = 0; j < _Model_Types_max; j++) { for (int j = 0; j < _Model_Types_max; j++) {
@@ -1503,6 +1487,7 @@ void ems_doReadCommand(uint8_t type, uint8_t dest, bool forceRefresh) {
/** /**
* Send a raw telegram to the bus * Send a raw telegram to the bus
* telegram is a string of hex values
*/ */
void ems_sendRawTelegram(char * telegram) { void ems_sendRawTelegram(char * telegram) {
uint8_t count = 0; uint8_t count = 0;
@@ -1558,8 +1543,8 @@ void ems_setThermostatTemp(float temperature) {
_EMS_TxTelegram EMS_TxTelegram = EMS_TX_TELEGRAM_NEW; // create new Tx _EMS_TxTelegram EMS_TxTelegram = EMS_TX_TELEGRAM_NEW; // create new Tx
EMS_TxTelegram.timestamp = millis(); // set timestamp EMS_TxTelegram.timestamp = millis(); // set timestamp
_EMS_MODEL_ID model_id = EMS_Thermostat.model_id; uint8_t model_id = EMS_Thermostat.model_id;
uint8_t type = EMS_Thermostat.type_id; uint8_t type = EMS_Thermostat.type_id;
EMS_TxTelegram.action = EMS_TX_TELEGRAM_WRITE; EMS_TxTelegram.action = EMS_TX_TELEGRAM_WRITE;
EMS_TxTelegram.dest = type; EMS_TxTelegram.dest = type;
@@ -1606,8 +1591,8 @@ void ems_setThermostatMode(uint8_t mode) {
return; return;
} }
_EMS_MODEL_ID model_id = EMS_Thermostat.model_id; uint8_t model_id = EMS_Thermostat.model_id;
uint8_t type = EMS_Thermostat.type_id; uint8_t type = EMS_Thermostat.type_id;
myDebug("Setting thermostat mode to %d", mode); myDebug("Setting thermostat mode to %d", mode);
@@ -1756,33 +1741,3 @@ void ems_setWarmTapWaterActivated(bool activated) {
EMS_TxQueue.push(EMS_TxTelegram); // add to queue EMS_TxQueue.push(EMS_TxTelegram); // add to queue
} }
/**
* experimental code for debugging - not in production
*/
void ems_setExperimental(uint8_t value) {
/*
_EMS_TxTelegram EMS_TxTelegram = EMS_TX_TELEGRAM_NEW; // create new Tx
EMS_TxTelegram.timestamp = millis(); // set timestamp
EMS_TxTelegram.action = EMS_TX_TELEGRAM_READ; // read command
EMS_TxTelegram.dest = EMS_Thermostat.type; // set 8th bit to indicate a read
EMS_TxTelegram.offset = 0; // 0 for all data
EMS_TxTelegram.length = 8;
EMS_TxTelegram.type = 0xF0;
EMS_TxTelegram.type_validate = EMS_ID_NONE;
// EMS Plus test
// Sending read to 0x18: telegram: 0B 98 F0 00 01 B9 63 DB (len 8)
EMS_TxTelegram.data[0] = EMS_ID_ME; // src
EMS_TxTelegram.data[1] = EMS_TxTelegram.dest; // dest
EMS_TxTelegram.data[2] = 0xF0; // marker
EMS_TxTelegram.data[3] = EMS_TxTelegram.offset; // offset
EMS_TxTelegram.data[4] = 0x01; // hi byte
EMS_TxTelegram.data[5] = 0xB9; // low byte
EMS_TxTelegram.data[6] = 99; // max length
EMS_TxQueue.push(EMS_TxTelegram);
*/
}

211
src/ems.h
View File

@@ -1,25 +1,15 @@
/* /*
* Header file for EMS.cpp * Header file for ems.cpp
* *
*/ */
#pragma once #pragma once
#include "emsuart.h"
#include "my_config.h" // include custom configuration settings
#include <Arduino.h> #include <Arduino.h>
#include <CircularBuffer.h> // https://github.com/rlogiacco/CircularBuffer
#include <MyESP.h>
#include <list>
#include <vector>
// 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_ME 0x0B // Fixed - our device, hardcoded as "Service Key" #define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as the "Service Key"
//#define EMS_ID_THERMOSTAT 0xFF // Fixed - to recognize a Thermostat
//#define EMS_ID_BOILER 0x08 // Fixed - also known as MC10.
#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
@@ -27,70 +17,9 @@
// This can differs per firmware version and typically 32 is the max // This can differs per firmware version and typically 32 is the max
#define EMS_MAX_TELEGRAM_LENGTH 99 #define EMS_MAX_TELEGRAM_LENGTH 99
// define here the EMS telegram types you need
// Common for all EMS devices // Common for all EMS devices
#define EMS_TYPE_Version 0x02 // version of the UBA controller (boiler) #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
#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
/*
* 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
#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
// 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
// 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
@@ -98,6 +27,34 @@
#define EMS_VALUE_LONG_NOTSET 0xFFFFFF // for 3-byte longs #define EMS_VALUE_LONG_NOTSET 0xFFFFFF // for 3-byte longs
#define EMS_VALUE_FLOAT_NOTSET -255 // float unset #define EMS_VALUE_FLOAT_NOTSET -255 // float unset
#define EMS_THERMOSTAT_READ_YES true
#define EMS_THERMOSTAT_READ_NO false
#define EMS_THERMOSTAT_WRITE_YES true
#define EMS_THERMOSTAT_WRITE_NO false
// 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"
// 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
/* EMS UART transfer status */ /* EMS UART transfer status */
typedef enum { typedef enum {
EMS_RX_IDLE, EMS_RX_IDLE,
@@ -160,8 +117,6 @@ typedef struct {
uint8_t data[EMS_MAX_TELEGRAM_LENGTH]; uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
} _EMS_TxTelegram; } _EMS_TxTelegram;
#define EMS_TX_TELEGRAM_QUEUE_MAX 20 // max size of Tx FIFO queue
// 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
@@ -179,35 +134,11 @@ const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
{0x00} // data {0x00} // data
}; };
// Known Buderus non-Thermostat types
typedef enum {
EMS_MODEL_NONE,
EMS_MODEL_ALL, // common for all devices
// service key
EMS_MODEL_SERVICEKEY, // this is us
// main buderus boiler type devices
EMS_MODEL_BK15,
EMS_MODEL_UBA,
EMS_MODEL_BC10,
EMS_MODEL_MM10,
EMS_MODEL_WM10,
// thermostats
EMS_MODEL_ES73,
EMS_MODEL_RC20,
EMS_MODEL_RC30,
EMS_MODEL_RC35,
EMS_MODEL_EASY
} _EMS_MODEL_ID;
typedef struct { typedef struct {
_EMS_MODEL_ID model_id; uint8_t model_id;
uint8_t product_id; uint8_t product_id;
uint8_t type_id; uint8_t type_id;
char model_string[50]; char model_string[50];
} _Model_Type; } _Model_Type;
/* /*
@@ -259,39 +190,34 @@ typedef struct { // UBAParameterWW
uint8_t heatingActive; // Central heating is on/off uint8_t heatingActive; // Central heating is on/off
// settings // settings
char version[10]; char version[10];
uint8_t type_id; uint8_t type_id;
_EMS_MODEL_ID model_id; uint8_t model_id;
} _EMS_Boiler; } _EMS_Boiler;
// Definition for thermostat type // Definition for thermostat type
typedef struct { typedef struct {
_EMS_MODEL_ID model_id; uint8_t model_id;
bool read_supported; bool read_supported;
bool write_supported; bool write_supported;
} _Thermostat_Type; } _Thermostat_Type;
#define EMS_THERMOSTAT_READ_YES true
#define EMS_THERMOSTAT_READ_NO false
#define EMS_THERMOSTAT_WRITE_YES true
#define EMS_THERMOSTAT_WRITE_NO false
// Thermostat data // Thermostat data
typedef struct { typedef struct {
uint8_t type_id; // the type ID of the thermostat uint8_t type_id; // the type ID of the thermostat
_EMS_MODEL_ID model_id; // which Thermostat type uint8_t model_id; // which Thermostat type
bool read_supported; bool read_supported;
bool write_supported; bool write_supported;
char version[10]; 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
uint8_t hour; uint8_t hour;
uint8_t minute; uint8_t minute;
uint8_t second; uint8_t second;
uint8_t day; uint8_t day;
uint8_t month; uint8_t month;
uint8_t year; uint8_t year;
} _EMS_Thermostat; } _EMS_Thermostat;
// call back function signature // call back function signature
@@ -299,28 +225,15 @@ typedef void (*EMS_processType_cb)(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 {
_EMS_MODEL_ID model_id; 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_Type; } _EMS_Type;
// 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);
void ems_init(_EMS_MODEL_ID boiler_modelid, _EMS_MODEL_ID thermostat_modelid); void ems_init(uint8_t boiler_modelid, uint8_t thermostat_modelid);
void ems_doReadCommand(uint8_t type, uint8_t dest, bool forceRefresh = false); void ems_doReadCommand(uint8_t type, uint8_t dest, bool forceRefresh = false);
void ems_sendRawTelegram(char * telegram); void ems_sendRawTelegram(char * telegram);
@@ -329,7 +242,6 @@ 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_setLogging(_EMS_SYS_LOGGING loglevel); void ems_setLogging(_EMS_SYS_LOGGING loglevel);
@@ -348,8 +260,8 @@ bool ems_getBusConnected();
_EMS_SYS_LOGGING ems_getLogging(); _EMS_SYS_LOGGING ems_getLogging();
uint8_t ems_getEmsTypesCount(); uint8_t ems_getEmsTypesCount();
bool ems_getEmsRefreshed(); bool ems_getEmsRefreshed();
_EMS_MODEL_ID ems_getThermostatModel(); uint8_t ems_getThermostatModel();
_EMS_MODEL_ID ems_getBoilerModel(); uint8_t ems_getBoilerModel();
void ems_scanDevices(); void ems_scanDevices();
void ems_printAllTypes(); void ems_printAllTypes();
@@ -362,10 +274,9 @@ 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_findModel(_EMS_MODEL_ID model_id); int _ems_findModel(uint8_t model_id);
char * _ems_buildModelString(char * buffer, uint8_t size, _EMS_MODEL_ID model_id); char * _ems_buildModelString(char * buffer, uint8_t size, uint8_t model_id);
bool _ems_setModel(_EMS_MODEL_ID model_id); bool _ems_setModel(uint8_t model_id);
// 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;

132
src/ems_devices.h Normal file
View File

@@ -0,0 +1,132 @@
/*
* General information about known EMS devices
*
*/
#pragma once
#include "ems.h"
/*
* 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
/*
* 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
#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
// 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
// service key
EMS_MODEL_SERVICEKEY, // this is us
// main buderus boiler type devices
EMS_MODEL_BK15,
EMS_MODEL_UBA,
EMS_MODEL_BC10,
EMS_MODEL_BC25,
EMS_MODEL_MM10,
EMS_MODEL_WM10,
EMS_MODEL_RFM20,
// thermostats
EMS_MODEL_ES73,
EMS_MODEL_RC20,
EMS_MODEL_RC20F,
EMS_MODEL_RC30,
EMS_MODEL_RC35,
EMS_MODEL_EASY
} _EMS_MODEL_ID;
// EMS types for known Buderus devices. This list will be extended when new devices are recognized.
// format is MODEL_ID, PRODUCT ID, TYPE_ID, DESCRIPTION
const _Model_Type Model_Types[] = {
// me
{EMS_MODEL_SERVICEKEY, 999, 0x0B, "Service Key"},
// various boilers and buderus type devices
{EMS_MODEL_UBA, 123, 0x08, "MC10/UBA3 Boiler"},
{EMS_MODEL_BK15, 64, 0x08, "Sieger BK15 Boiler"},
{EMS_MODEL_BC10, 190, 0x09, "BC10 Base Controller"},
{EMS_MODEL_BC25, 125, 0x09, "BC25 Base Controller"},
{EMS_MODEL_RFM20, 68, 0x09, "RFM20 RC20F Receiver"},
{EMS_MODEL_MM10, 251, 0x21, "MM10 Mixer Module"}, // warning, fake product id!
{EMS_MODEL_WM10, 250, 0x11, "WM10 Switch Module"}, // warning, fake product id!
// controllers and thermostats
{EMS_MODEL_ES73, 76, 0x10, "Sieger ES73"},
{EMS_MODEL_RC20, 77, 0x17, "RC20 (e.g. Nefit Moduline 300)"},
{EMS_MODEL_RC20F, 93, 0x18, "RC20F"},
{EMS_MODEL_RC30, 78, 0x10, "RC30 (e.g. Nefit Moduline 400)"},
{EMS_MODEL_RC35, 86, 0x10, "RC35 (or compatible"},
{EMS_MODEL_EASY, 202, 0x18, "TC100 (e.g. Nefit Easy or CT100)"}
};
/*
* Known thermostat types and their abilities
*/
const _Thermostat_Type Thermostat_Types[] = {
{EMS_MODEL_RC20, EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_RC20F, EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_RC30, EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_RC35, EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_EASY, EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_ES73, EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}
};

View File

@@ -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"

View File

@@ -1,7 +1,7 @@
/* /*
* 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

View File

@@ -1,57 +1,43 @@
/* /*
* 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
/* // Set your wifi and mqtt params
#define WIFI_SSID "<my_ssid>" // If set to NULL (default) you'll need to set them in AP mode using the 'set' command
#define WIFI_PASSWORD "<my_password>" // as these values are stored in SPIFFs for persisted
#define MQTT_IP "<broker_ip>" #define WIFI_SSID NULL
#define MQTT_USER "<broker_username>" #define WIFI_PASSWORD NULL
#define MQTT_PASS "<broker_password>" #define MQTT_IP NULL
*/ #define MQTT_USER NULL
#define MQTT_PASS NULL
// All MQTT topics are prefixed with the following string
#define MQTT_BASE "home"
// default values for shower logic on/off // default values for shower logic on/off
#define BOILER_SHOWER_TIMER 1 // monitors how long a shower has taken #define BOILER_SHOWER_TIMER 1 // enable (1) to monitor shower time
#define BOILER_SHOWER_ALERT 0 // send alert if showetime exceeded #define BOILER_SHOWER_ALERT 0 // enable (1) to send alert of cold watewr when shower time limit has exceeded
#define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water
// trigger settings to determine if hot tap water or the heating is active // Set LED pin used for showing ems bus connection status. Solid is connected, Flashing is error
#define EMS_BOILER_BURNPOWER_TAPWATER 100 // can be either the onboard LED on the ESP8266 or external via an external pull-up LED (e.g. D1)
#define EMS_BOILER_SELFLOWTEMP_HEATING 70 // note: can be disabled completely using -DNO_LED build flag in platformio.ini
//define maximum settable tapwater temperature, not every installation supports 90 degrees
#define EMS_BOILER_TAPWATER_TEMPERATURE_MAX 60
// if using the shower timer, change these settings
#define SHOWER_PAUSE_TIME 15000 // in ms. 15 seconds, max time if water is switched off & on during a shower
#define SHOWER_MIN_DURATION 120000 // in ms. 2 minutes, before recognizing its a shower
#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
// The LED for showing connection errors, either onboard or via an external pull-up LED
// can be disabled using -DNO_LED build flag in platformio.ini
#define BOILER_LED LED_BUILTIN // LED_BULLETIN for onboard LED #define BOILER_LED LED_BUILTIN // LED_BULLETIN for onboard LED
//#define BOILER_LED D1 // for external LED like on the latest bbqkees boards
// set this if using an external temperature sensor like a DS18B20 // set this if using an external temperature sensor like a DS18B20
#define TEMPERATURE_SENSOR_PIN D7 #define TEMPERATURE_SENSOR_PIN D7
// logging - EMS_SYS_LOGGING_VERBOSE, EMS_SYS_LOGGING_NONE, EMS_SYS_LOGGING_BASIC (see ems.h) // By default the EMS bus will be scanned for known devices (EMS_MODEL_NONE).
// this can be changed via the Telnet console using the 'l' command // You can override this here by fixing the Boiler and Thermostat types.
#define BOILER_DEFAULT_LOGGING EMS_SYS_LOGGING_NONE // See ems.h for the list of recognized types. For example:
//#define BOILER_DEFAULT_LOGGING EMS_SYS_LOGGING_VERBOSE
//#define BOILER_DEFAULT_LOGGING EMS_SYS_LOGGING_BASIC
// By default the EMS bus will be scanned for known devices. You can override this here
// by fixing the Boiler and Thermostat types
// Options are in ems.h and include..
// boilers: EMS_MODEL_BK15, EMS_MODEL_UBA, EMS_MODEL_BC10, EMS_MODEL_MM10, EMS_MODEL_WM10 // boilers: EMS_MODEL_BK15, EMS_MODEL_UBA, EMS_MODEL_BC10, EMS_MODEL_MM10, EMS_MODEL_WM10
// thermostats: EMS_MODEL_ES73, EMS_MODEL_RC20, EMS_MODEL_RC30, EMS_MODEL_RC35, EMS_MODEL_EASY // thermostats: EMS_MODEL_ES73, EMS_MODEL_RC20, EMS_MODEL_RC30, EMS_MODEL_RC35, EMS_MODEL_EASY
#define MY_BOILER_MODELID EMS_MODEL_NONE #define MY_BOILER_MODELID EMS_MODEL_NONE

View File

@@ -1,5 +1,5 @@
#pragma once #pragma once
#define APP_NAME "EMS-ESP-Boiler" #define APP_NAME "EMS-ESP Interface"
#define APP_VERSION "1.2.4" #define APP_VERSION "1.3.0"
#define APP_HOSTNAME "boiler" #define APP_HOSTNAME "ems-esp"