mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 00:09:51 +03:00
version 1.2.0. See ChangeLog
This commit is contained in:
39
.clang-format
Normal file
39
.clang-format
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
Language: Cpp
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
UseTab: Never
|
||||||
|
IndentWidth: 4
|
||||||
|
ColumnLimit: 140
|
||||||
|
TabWidth: 4
|
||||||
|
#BreakBeforeBraces: Custom
|
||||||
|
BraceWrapping:
|
||||||
|
AfterControlStatement: false
|
||||||
|
AfterFunction: false
|
||||||
|
AfterClass: true
|
||||||
|
AfterEnum: true
|
||||||
|
BeforeElse: false
|
||||||
|
ReflowComments: false
|
||||||
|
AlignAfterOpenBracket: Align # If true, horizontally aligns arguments after an open bracket.
|
||||||
|
AlignConsecutiveAssignments: true # This will align the assignment operators of consecutive lines
|
||||||
|
AlignConsecutiveDeclarations: true # This will align the declaration names of consecutive lines
|
||||||
|
AlignTrailingComments: true
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: false
|
||||||
|
AllowShortBlocksOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: false
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
#AlwaysBreakAfterReturnType: TopLevel
|
||||||
|
AlwaysBreakTemplateDeclarations: true # If true, always break after the template<...> of a template declaration
|
||||||
|
BinPackArguments: false
|
||||||
|
BinPackParameters: false
|
||||||
|
BreakBeforeBinaryOperators: NonAssignment
|
||||||
|
BreakConstructorInitializersBeforeComma: true # Always break constructor initializers before commas and align the commas with the colon.
|
||||||
|
ExperimentalAutoDetectBinPacking: false
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
|
MaxEmptyLinesToKeep: 4
|
||||||
|
PenaltyBreakBeforeFirstCallParameter: 200
|
||||||
|
PenaltyExcessCharacter: 10
|
||||||
|
PointerAlignment: Middle
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInParentheses: false
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,4 +6,3 @@
|
|||||||
platformio.ini
|
platformio.ini
|
||||||
lib/readme.txt
|
lib/readme.txt
|
||||||
.travis.yml
|
.travis.yml
|
||||||
.clang-format
|
|
||||||
|
|||||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -5,11 +5,40 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [1.2.0] 2019-01-01
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Incorrect indenting in `climate.yaml` (thanks @mrfixit1)
|
||||||
|
- Improved support for slower WiFi connections
|
||||||
|
- Fixed issue with OTA not always giving back a completion response to platformio
|
||||||
|
- Fixed issue with repeating reads after a raw mode send
|
||||||
|
- Fixed handling of long integers (thanks @SpaceTeddy)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Setting the mode and setpoint temperature on a RC35
|
- added 'dout' flashmode to platformio.ini so OTA works now when uploading to a Wemos D1 Pro's or any other board with larger flash's
|
||||||
|
- added un tested supporting RC35 type of thermostats
|
||||||
|
- Try and discover and set Boiler and Thermostat types automatically
|
||||||
|
- Fetch UBATotalUptimeMessage from Boiler to get total working minutes
|
||||||
|
- Added check to see if bus is connected. Shown in stats page
|
||||||
|
- If no Wifi connection can be made, start up as a WiFi Access Point (AP)
|
||||||
|
- Report out service codes and water-flow [pull-request](https://github.com/proddy/EMS-ESP-Boiler/pull/20/files). Thanks @Bonusbartus
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Build option is called `DEBUG_SUPPORT` (was `USE_SERIAL`)
|
||||||
|
- Replaced old **ESPHelper** with my own **MyESP** library to handle Wifi, MQTT, MDNS and Telnet handlers. Supports asynchronous TCP and has smaller memory footprint. And moved to libs directory.
|
||||||
|
- Simplified LED error checking. If enabled (by default), solid means connected and flashing means error. Uses either an external pull-up or the onboard ESP8266 LED.
|
||||||
|
- Improved Telnet debugging which uses TelnetSpy to keep a buffer of previous output
|
||||||
|
- Optimized memory usage & heap conflicts, removing nasty things like strcpy, sprintf where possible
|
||||||
|
- Improved checking for tap water on/off (thanks @Bonusbartus)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Time and TimeLib's. Not used in code.
|
||||||
|
- Removed build option `MQTT_MAX_PACKAGE_SIZE` as not using the PubSubClient library any more
|
||||||
|
- Removed all of Espurna's pre-built firmwares and instructions to build. Keeping it simple.
|
||||||
|
|
||||||
## [1.1.1] 2018-12-23
|
## [1.1.1] 2018-12-23
|
||||||
|
|
||||||
@@ -21,7 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed handling of negative flaoting point values (like outdoor temp)
|
- Fixed handling of negative floating point values (like outdoor temp)
|
||||||
- Fixed handling of auto & manual mode on an RC30
|
- Fixed handling of auto & manual mode on an RC30
|
||||||
- [Fixed condition where all telegram types were processed, instead of only broadcasts or our own reads](https://github.com/proddy/EMS-ESP-Boiler/issues/15)
|
- [Fixed condition where all telegram types were processed, instead of only broadcasts or our own reads](https://github.com/proddy/EMS-ESP-Boiler/issues/15)
|
||||||
|
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -4,7 +4,7 @@ EMS-ESP-Boiler is a project to build a controller circuit running with an ESP826
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||
[](CHANGELOG.md)
|
[](CHANGELOG.md)
|
||||||
|
|
||||||
- [EMS-ESP-Boiler](#ems-esp-boiler)
|
- [EMS-ESP-Boiler](#ems-esp-boiler)
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
@@ -33,6 +33,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)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
- [Known Issues](#known-issues)
|
- [Known Issues](#known-issues)
|
||||||
- [Wish List](#wish-list)
|
- [Wish List](#wish-list)
|
||||||
- [Your Comments and Feedback](#your-comments-and-feedback)
|
- [Your Comments and Feedback](#your-comments-and-feedback)
|
||||||
@@ -208,9 +209,9 @@ 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 setting the -DUSE_LED build flag.
|
`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.
|
||||||
|
|
||||||
`ESPHelper.cpp` is my customized version of [ESPHelper](https://github.com/ItKindaWorks/ESPHelper) with added Telnet support and some other minor tweaking.
|
`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
|
||||||
|
|
||||||
@@ -227,11 +228,20 @@ 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 send these read commands. See the function *regularUpdates()*
|
In `boiler.ino` you can make calls to automatically request these types in the function *regularUpdates()*.
|
||||||
|
|
||||||
### Supported Thermostats
|
### Supported Thermostats
|
||||||
|
|
||||||
Modify `EMS_ID_THERMOSTAT` in `myconfig.h` to the thermostat type you want to support.
|
I am still working on adding more support to known thermostats.
|
||||||
|
|
||||||
|
Currently known types and collected versions:
|
||||||
|
|
||||||
|
Moduline 300 = Type 77 Version 03.03
|
||||||
|
Moduline 400 = Type 78 Version 03.03
|
||||||
|
Buderus RC35 = Type 86 Version 01.15
|
||||||
|
Nefit Easy = Type 202 Version 02.19
|
||||||
|
Nefit Trendline HRC30 = Type 123 Version 06.01
|
||||||
|
BC10 = Type 123 Version 04.05
|
||||||
|
|
||||||
#### RC20 (Moduline 300)
|
#### RC20 (Moduline 300)
|
||||||
|
|
||||||
@@ -245,8 +255,6 @@ Type's 3F, 49, 53, 5D are identical. So are 4B, 55, 5F and mostly zero's. Types
|
|||||||
|
|
||||||
#### RC35
|
#### RC35
|
||||||
|
|
||||||
***not implemented yet***!
|
|
||||||
|
|
||||||
An RC35 thermostat can support up to 4 heating circuits each controlled with their own Monitor and Working Mode IDs.
|
An RC35 thermostat can support up to 4 heating circuits each controlled with their own Monitor and Working Mode IDs.
|
||||||
|
|
||||||
Fetching the thermostats setpoint temp us by requesting 0x3E and looking at the 3rd byte in the data telegram (data[2]) and dividing by 2.
|
Fetching the thermostats setpoint temp us by requesting 0x3E and looking at the 3rd byte in the data telegram (data[2]) and dividing by 2.
|
||||||
@@ -259,8 +267,7 @@ There is limited support for an Nefit Easy TC100/TC200 type thermostat. The curr
|
|||||||
### Customizing The Code
|
### Customizing The Code
|
||||||
|
|
||||||
- To configure for your thermostat and specific boiler settings, modify `my_config.h`. Here you can
|
- To configure for your thermostat and specific boiler settings, modify `my_config.h`. Here you can
|
||||||
- set the thermostat type. The default ID is 0x17 for an RC30 Moduline 300.
|
- set flags for enabled/disabling functionality such as `BOILER_SHOWER_ENABLED` and `BOILER_SHOWER_TIMER`.
|
||||||
- set flags for enabled/disabling functionality such as `BOILER_THERMOSTAT_ENABLED`, `BOILER_SHOWER_ENABLED` and `BOILER_SHOWER_TIMER`.
|
|
||||||
- Set WIFI and MQTT settings, 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`
|
||||||
|
|
||||||
@@ -327,7 +334,7 @@ PlatformIO is my preferred way. The code uses a modified version [ESPHelper](htt
|
|||||||
% cd EMS-ESP-Boiler
|
% cd EMS-ESP-Boiler
|
||||||
% cp platformio.ini-example platformio.ini
|
% cp platformio.ini-example platformio.ini
|
||||||
```
|
```
|
||||||
- edit `platformio.ini` to set `env_default` and the flags `WIFI_SSID WIFI_PASSWORD, MQTT_IP, MQTT_USER, MQTT_PASS`. If you're not using MQTT leave MQTT_IP empty (`MQTT_IP=""`)
|
- edit `platformio.ini` to set `env_default` 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=""`)
|
||||||
```c
|
```c
|
||||||
% platformio run -t upload
|
% platformio run -t upload
|
||||||
```
|
```
|
||||||
@@ -339,7 +346,7 @@ Porting to the Arduino IDE can be a little tricky but it is possible.
|
|||||||
- Add the ESP8266 boards (from Preferences add Additional Board URL `http://arduino.esp8266.com/stable/package_esp8266com_index.json`)
|
- Add the ESP8266 boards (from Preferences add Additional Board URL `http://arduino.esp8266.com/stable/package_esp8266com_index.json`)
|
||||||
- Go to Boards Manager and install ESP8266 2.4.x platform
|
- Go to Boards Manager and install ESP8266 2.4.x platform
|
||||||
- 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 such as ArduinoJson 5.13.x, PubSubClient 2.6.x, CRC32 and Time
|
- 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`:
|
- 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`:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
@@ -353,6 +360,12 @@ Porting to the Arduino IDE can be a little tricky but it is possible.
|
|||||||
- Put all the files in a single sketch folder (`ESPHelper.*, boiler.ino, ems.*, emsuart.*`)
|
- 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...
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
The onboard LED will flash if there is no connection with the EMS bus. You can disable LED support by adding -DNO_LED to the build options.
|
||||||
|
|
||||||
## Known Issues
|
## Known Issues
|
||||||
|
|
||||||
Some annoying issues that need fixing:
|
Some annoying issues that need fixing:
|
||||||
@@ -364,7 +377,6 @@ Some annoying issues that need fixing:
|
|||||||
- Measure amount of gas in m3 per day for the hot water vs the central heating, and convert this into cost in Home Assistant
|
- Measure amount of gas in m3 per day for the hot water vs the central heating, and convert this into cost in Home Assistant
|
||||||
- Support changing temps on an Nefit Easy. To do this you must send XMPP messages directly to the thermostat. See this project: https://github.com/robertklep/nefit-easy-core
|
- Support changing temps on an Nefit Easy. To do this you must send XMPP messages directly to the thermostat. See this project: https://github.com/robertklep/nefit-easy-core
|
||||||
- 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
|
||||||
- Automatic detection of thermostat type
|
|
||||||
- Add support for a temperature sensor on the circuit (DS18B20)
|
- Add support for a temperature sensor on the circuit (DS18B20)
|
||||||
|
|
||||||
## Your Comments and Feedback
|
## Your Comments and Feedback
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
@@ -42,6 +42,12 @@
|
|||||||
unit_of_measurement: '°C'
|
unit_of_measurement: '°C'
|
||||||
value_template: '{{ value_json.wWSelTemp }}'
|
value_template: '{{ value_json.wWSelTemp }}'
|
||||||
|
|
||||||
|
- platform: mqtt
|
||||||
|
state_topic: 'home/boiler/boiler_data'
|
||||||
|
name: 'Warm Water tapwater flow rate'
|
||||||
|
unit_of_measurement: 'l/min'
|
||||||
|
value_template: '{{ value_json.wWCurFlow }}'
|
||||||
|
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: 'home/boiler/boiler_data'
|
state_topic: 'home/boiler/boiler_data'
|
||||||
name: 'Warm Water current temperature'
|
name: 'Warm Water current temperature'
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ views:
|
|||||||
- sensor.warm_water_current_temperature
|
- sensor.warm_water_current_temperature
|
||||||
- sensor.warm_water_activated
|
- sensor.warm_water_activated
|
||||||
- sensor.warm_water_3way_valve
|
- sensor.warm_water_3way_valve
|
||||||
|
- sensor.warm_water_tapwater_flow_rate
|
||||||
- type: divider
|
- type: divider
|
||||||
- sensor.boiler_temperature
|
- sensor.boiler_temperature
|
||||||
- sensor.return_temperature
|
- sensor.return_temperature
|
||||||
|
|||||||
@@ -1,12 +1,28 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from subprocess import call
|
||||||
|
import os
|
||||||
Import("env")
|
Import("env")
|
||||||
|
|
||||||
|
|
||||||
|
def code_check(source, target, env):
|
||||||
|
print("\n** Starting cppcheck...")
|
||||||
|
call(["cppcheck", os.getcwd()+"/.", "--force", "--enable=all"])
|
||||||
|
print("\n** Finished cppcheck...\n")
|
||||||
|
print("\n** Starting cpplint...")
|
||||||
|
call(["cpplint", "--extensions=ino,cpp,h", "--filter=-legal/copyright,-build/include,-whitespace",
|
||||||
|
"--linelength=120", "--recursive", "src"])
|
||||||
|
print("\n** Finished cpplint...")
|
||||||
|
|
||||||
#my_flags = env.ParseFlags(env['BUILD_FLAGS'])
|
#my_flags = env.ParseFlags(env['BUILD_FLAGS'])
|
||||||
#defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")}
|
#defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")}
|
||||||
# print defines
|
# print defines
|
||||||
|
|
||||||
#env.Replace(PROGNAME="firmware_%s" % defines.get("VERSION"))
|
|
||||||
|
|
||||||
# print env.Dump()
|
# print env.Dump()
|
||||||
|
|
||||||
env.Replace(PROGNAME="firmware_%s" % env['BOARD'])
|
|
||||||
|
|
||||||
|
# built in targets: (buildprog, size, upload, program, buildfs, uploadfs, uploadfsota)
|
||||||
|
env.AddPreAction("buildprog", code_check)
|
||||||
|
# env.AddPostAction(.....)
|
||||||
|
|
||||||
|
# see http://docs.platformio.org/en/latest/projectconf/advanced_scripting.html#before-pre-and-after-post-actions
|
||||||
|
# env.Replace(PROGNAME="firmware_%s" % defines.get("VERSION"))
|
||||||
|
# env.Replace(PROGNAME="firmware_%s" % env['BOARD'])
|
||||||
|
|||||||
626
lib/TelnetSpy/TelnetSpy.cpp
Normal file
626
lib/TelnetSpy/TelnetSpy.cpp
Normal file
@@ -0,0 +1,626 @@
|
|||||||
|
/*
|
||||||
|
* 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) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
278
lib/TelnetSpy/TelnetSpy.h
Normal file
278
lib/TelnetSpy/TelnetSpy.h
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
/*
|
||||||
|
* TELNET SERVER FOR ESP8266 / ESP32
|
||||||
|
* Cloning the serial port via Telnet.
|
||||||
|
*
|
||||||
|
* Written by Wolfgang Mattis (arduino@yasheena.de).
|
||||||
|
* Version 1.1 / September 7, 2018.
|
||||||
|
* MIT license, all text above must be included in any redistribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DESCRIPTION
|
||||||
|
*
|
||||||
|
* This module allows you "Debugging over the air". So if you already use
|
||||||
|
* ArduinoOTA this is a helpful extension for wireless development. Use
|
||||||
|
* "TelnetSpy" instead of "Serial" to send data to the serial port and a copy
|
||||||
|
* to a telnet connection. There is a circular buffer which allows to store the
|
||||||
|
* data while the telnet connection is not established. So its possible to
|
||||||
|
* collect data even when the Wifi and Telnet connections are still not
|
||||||
|
* established. Its also possible to create a telnet session only if it is
|
||||||
|
* neccessary: then you will get the already collected data as far as it is
|
||||||
|
* still stored in the circular buffer. Data send from telnet terminal to
|
||||||
|
* ESP8266 / ESP32 will be handled as data received by serial port. It is also
|
||||||
|
* possible to use more than one instance of TelnetSpy, for example to send
|
||||||
|
* control information on the first instance and data dumps on the second
|
||||||
|
* instance.
|
||||||
|
*
|
||||||
|
* USAGE
|
||||||
|
*
|
||||||
|
* Add the following line to your sketch:
|
||||||
|
* #include <TelnetSpy.h>
|
||||||
|
* TelnetSpy LOG;
|
||||||
|
*
|
||||||
|
* Add the following line to your initialisation block ( void setup() ):
|
||||||
|
* LOG.begin();
|
||||||
|
*
|
||||||
|
* Add the following line at the beginning of your main loop ( void loop() ):
|
||||||
|
* LOG.handle();
|
||||||
|
*
|
||||||
|
* Use the following functions of the TelnetSpy object to modify behavior
|
||||||
|
*
|
||||||
|
* Change the port number of this telnet server. If a client is already
|
||||||
|
* connected it will be disconnected.
|
||||||
|
* Default: 23
|
||||||
|
* void setPort(uint16_t portToUse);
|
||||||
|
*
|
||||||
|
* Change the message which will be send to the telnet client after a session
|
||||||
|
* is established.
|
||||||
|
* Default: "Connection established via TelnetSpy.\n"
|
||||||
|
* void setWelcomeMsg(char* msg);
|
||||||
|
*
|
||||||
|
* Change the message which will be send to the telnet client if another
|
||||||
|
* session is already established.
|
||||||
|
* Default: "TelnetSpy: Only one connection possible.\n"
|
||||||
|
* void setRejectMsg(char* msg);
|
||||||
|
*
|
||||||
|
* Change the amount of characters to collect before sending a telnet block.
|
||||||
|
* Default: 64
|
||||||
|
* void setMinBlockSize(uint16_t minSize);
|
||||||
|
*
|
||||||
|
* Change the time (in ms) to wait before sending a telnet block if its size is
|
||||||
|
* less than <minSize> (defined by setMinBlockSize).
|
||||||
|
* Default: 100
|
||||||
|
* void setCollectingTime(uint16_t colTime);
|
||||||
|
*
|
||||||
|
* Change the maximum size of the telnet packets to send.
|
||||||
|
* Default: 512
|
||||||
|
* void setMaxBlockSize(uint16_t maxSize);
|
||||||
|
*
|
||||||
|
* Change the size of the ring buffer. Set it to 0 to disable buffering.
|
||||||
|
* Changing size tries to preserve the already collected data. If the new
|
||||||
|
* buffer size is too small the youngest data will be preserved only. Returns
|
||||||
|
* false if the requested buffer size cannot be set.
|
||||||
|
* Default: 3000
|
||||||
|
* bool setBufferSize(uint16_t newSize);
|
||||||
|
*
|
||||||
|
* This function returns the actual size of the ring buffer.
|
||||||
|
* uint16_t getBufferSize();
|
||||||
|
*
|
||||||
|
* Enable / disable storing new data in the ring buffer if no telnet connection
|
||||||
|
* is established. This function allows you to store important data only. You
|
||||||
|
* can do this by disabling "storeOffline" for sending less important data.
|
||||||
|
* Default: true
|
||||||
|
* void setStoreOffline(bool store);
|
||||||
|
*
|
||||||
|
* Get actual state of storing data when offline.
|
||||||
|
* bool getStoreOffline();
|
||||||
|
*
|
||||||
|
* If no data is sent via TelnetSpy the detection of a disconnected client has
|
||||||
|
* a long timeout. Use setPingTime to define the time (in ms) without traffic
|
||||||
|
* after which a ping (chr(0)) is sent to the telnet client to detect a
|
||||||
|
* disconnect earlier. Use 0 as parameter to disable pings.
|
||||||
|
* Default: 1500
|
||||||
|
* void setPingTime(uint16_t pngTime);
|
||||||
|
*
|
||||||
|
* Set the serial port you want to use with this object (especially for ESP32)
|
||||||
|
* or NULL if no serial port should be used (telnet only).
|
||||||
|
* Default: Serial
|
||||||
|
* void setSerial(HardwareSerial* usedSerial);
|
||||||
|
*
|
||||||
|
* This function returns true, if a telnet client is connected.
|
||||||
|
* bool isClientConnected();
|
||||||
|
*
|
||||||
|
* This function installs a callback function which will be called on every
|
||||||
|
* telnet connect of this object (except rejected connect tries). Use NULL to
|
||||||
|
* remove the callback.
|
||||||
|
* Default: NULL
|
||||||
|
* void setCallbackOnConnect(void (*callback)());
|
||||||
|
*
|
||||||
|
* This function installs a callback function which will be called on every
|
||||||
|
* telnet disconnect of this object (except rejected connect tries). Use NULL
|
||||||
|
* to remove the callback.
|
||||||
|
* Default: NULL
|
||||||
|
* void setCallbackOnDisconnect(void (*callback)());
|
||||||
|
*
|
||||||
|
* HINT
|
||||||
|
*
|
||||||
|
* Add the following lines to your sketch:
|
||||||
|
* #define SERIAL TelnetSpy
|
||||||
|
* //#define SERIAL Serial
|
||||||
|
*
|
||||||
|
* Replace "Serial" with "SERIAL" in your sketch. Now you can switch between
|
||||||
|
* serial only and serial with telnet by changing the comments of the defines
|
||||||
|
* only.
|
||||||
|
*
|
||||||
|
* IMPORTANT
|
||||||
|
*
|
||||||
|
* To connect to the telnet server you have to:
|
||||||
|
* - establish the Wifi connection
|
||||||
|
* - execute "TelnetSpy.begin(WhatEverYouWant);"
|
||||||
|
*
|
||||||
|
* The order is not important.
|
||||||
|
*
|
||||||
|
* All you do with "Serial" you can also do with "TelnetSpy", but remember:
|
||||||
|
* Transfering data also via telnet will need more performance than the serial
|
||||||
|
* port only. So time critical things may be influenced.
|
||||||
|
*
|
||||||
|
* It is not possible to establish more than one telnet connection at the same
|
||||||
|
* time. But its possible to use more than one instance of TelnetSpy.
|
||||||
|
*
|
||||||
|
* If you have problems with low memory you may reduce the value of the define
|
||||||
|
* TELNETSPY_BUFFER_LEN for a smaller ring buffer on initialisation.
|
||||||
|
*
|
||||||
|
* Usage of void setDebugOutput(bool) to enable / disable of capturing of
|
||||||
|
* os_print calls when you have more than one TelnetSpy instance: That
|
||||||
|
* TelnetSpy object will handle this functionality where you used
|
||||||
|
* setDebugOutput at last. On default TelnetSpy has the capturing of OS_print
|
||||||
|
* calls enabled. So if you have more instances the last created instance will
|
||||||
|
* handle the capturing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TelnetSpy_h
|
||||||
|
#define TelnetSpy_h
|
||||||
|
|
||||||
|
#define TELNETSPY_BUFFER_LEN 3000
|
||||||
|
#define TELNETSPY_MIN_BLOCK_SIZE 64
|
||||||
|
#define TELNETSPY_COLLECTING_TIME 100
|
||||||
|
#define TELNETSPY_MAX_BLOCK_SIZE 512
|
||||||
|
#define TELNETSPY_PING_TIME 1500
|
||||||
|
#define TELNETSPY_PORT 23
|
||||||
|
#define TELNETSPY_CAPTURE_OS_PRINT true
|
||||||
|
#define TELNETSPY_WELCOME_MSG "Connection established via TelnetSpy2.\n"
|
||||||
|
#define TELNETSPY_REJECT_MSG "TelnetSpy: Only one connection possible.\n"
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#else // ESP32
|
||||||
|
#include <WiFi.h>
|
||||||
|
#endif
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
|
||||||
|
class TelnetSpy : public Stream {
|
||||||
|
public:
|
||||||
|
TelnetSpy();
|
||||||
|
~TelnetSpy();
|
||||||
|
void handle(void);
|
||||||
|
void setPort(uint16_t portToUse);
|
||||||
|
void setWelcomeMsg(char * msg);
|
||||||
|
void setRejectMsg(char * msg);
|
||||||
|
void setMinBlockSize(uint16_t minSize);
|
||||||
|
void setCollectingTime(uint16_t colTime);
|
||||||
|
void setMaxBlockSize(uint16_t maxSize);
|
||||||
|
bool setBufferSize(uint16_t newSize);
|
||||||
|
uint16_t getBufferSize();
|
||||||
|
void setStoreOffline(bool store);
|
||||||
|
bool getStoreOffline();
|
||||||
|
void setPingTime(uint16_t pngTime);
|
||||||
|
void setSerial(HardwareSerial * usedSerial);
|
||||||
|
bool isClientConnected();
|
||||||
|
|
||||||
|
void disconnectClient(); // added by Proddy
|
||||||
|
typedef std::function<void()> telnetSpyCallback; // added by Proddy
|
||||||
|
void setCallbackOnConnect(telnetSpyCallback callback); // changed by proddy
|
||||||
|
void setCallbackOnDisconnect(telnetSpyCallback callback); // changed by proddy
|
||||||
|
|
||||||
|
// Functions offered by HardwareSerial class:
|
||||||
|
#ifdef ESP8266
|
||||||
|
void begin(unsigned long baud) {
|
||||||
|
begin(baud, SERIAL_8N1, SERIAL_FULL, 1);
|
||||||
|
}
|
||||||
|
void begin(unsigned long baud, SerialConfig config) {
|
||||||
|
begin(baud, config, SERIAL_FULL, 1);
|
||||||
|
}
|
||||||
|
void begin(unsigned long baud, SerialConfig config, SerialMode mode) {
|
||||||
|
begin(baud, config, mode, 1);
|
||||||
|
}
|
||||||
|
void begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin);
|
||||||
|
#else // ESP32
|
||||||
|
void begin(unsigned long baud, uint32_t config = SERIAL_8N1, int8_t rxPin = -1, int8_t txPin = -1, bool invert = false);
|
||||||
|
#endif
|
||||||
|
void end();
|
||||||
|
#ifdef ESP8266
|
||||||
|
void swap() {
|
||||||
|
swap(1);
|
||||||
|
}
|
||||||
|
void swap(uint8_t tx_pin);
|
||||||
|
void set_tx(uint8_t tx_pin);
|
||||||
|
void pins(uint8_t tx, uint8_t rx);
|
||||||
|
bool isTxEnabled(void);
|
||||||
|
bool isRxEnabled(void);
|
||||||
|
#endif
|
||||||
|
int available(void) override;
|
||||||
|
int peek(void) override;
|
||||||
|
int read(void) override;
|
||||||
|
int availableForWrite(void);
|
||||||
|
void flush(void) override;
|
||||||
|
size_t write(uint8_t) override;
|
||||||
|
inline size_t write(unsigned long n) {
|
||||||
|
return write((uint8_t)n);
|
||||||
|
}
|
||||||
|
inline size_t write(long n) {
|
||||||
|
return write((uint8_t)n);
|
||||||
|
}
|
||||||
|
inline size_t write(unsigned int n) {
|
||||||
|
return write((uint8_t)n);
|
||||||
|
}
|
||||||
|
inline size_t write(int n) {
|
||||||
|
return write((uint8_t)n);
|
||||||
|
}
|
||||||
|
using Print::write;
|
||||||
|
operator bool() const;
|
||||||
|
void setDebugOutput(bool);
|
||||||
|
uint32_t baudRate(void);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void sendBlock(void);
|
||||||
|
void addTelnetBuf(char c);
|
||||||
|
char pullTelnetBuf();
|
||||||
|
char peekTelnetBuf();
|
||||||
|
int telnetAvailable();
|
||||||
|
WiFiServer * telnetServer;
|
||||||
|
WiFiClient client;
|
||||||
|
uint16_t port;
|
||||||
|
HardwareSerial * usedSer;
|
||||||
|
bool storeOffline;
|
||||||
|
bool started;
|
||||||
|
bool listening;
|
||||||
|
bool firstMainLoop;
|
||||||
|
unsigned long waitRef;
|
||||||
|
unsigned long pingRef;
|
||||||
|
uint16_t pingTime;
|
||||||
|
char * welcomeMsg;
|
||||||
|
char * rejectMsg;
|
||||||
|
uint16_t minBlockSize;
|
||||||
|
uint16_t collectingTime;
|
||||||
|
uint16_t maxBlockSize;
|
||||||
|
bool debugOutput;
|
||||||
|
char * telnetBuf;
|
||||||
|
uint16_t bufLen;
|
||||||
|
uint16_t bufUsed;
|
||||||
|
uint16_t bufRdIdx;
|
||||||
|
uint16_t bufWrIdx;
|
||||||
|
bool connected;
|
||||||
|
|
||||||
|
telnetSpyCallback callbackConnect; // added by proddy
|
||||||
|
telnetSpyCallback callbackDisconnect; // added by proddy
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
617
lib/myESP/MyESP.cpp
Normal file
617
lib/myESP/MyESP.cpp
Normal file
@@ -0,0 +1,617 @@
|
|||||||
|
/*
|
||||||
|
* MyESP - my ESP helper class to handle Wifi, MDNS, MQTT and Telnet
|
||||||
|
*
|
||||||
|
* Paul Derbyshire - December 2018
|
||||||
|
*
|
||||||
|
* Some ideas from https://github.com/JoaoLopesF/ESP8266-RemoteDebug-Telnet
|
||||||
|
* Ideas from Espurna https://github.com/xoseperez/espurna
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "MyESP.h"
|
||||||
|
|
||||||
|
// constructor
|
||||||
|
MyESP::MyESP() {
|
||||||
|
_app_hostname = strdup("MyESP");
|
||||||
|
_app_name = strdup("MyESP");
|
||||||
|
_app_version = strdup("1.0.0");
|
||||||
|
_boottime = strdup("unknown");
|
||||||
|
_extern_WIFICallback = NULL;
|
||||||
|
_extern_WIFICallbackSet = false;
|
||||||
|
_consoleCallbackProjectCmds = NULL;
|
||||||
|
_helpProjectCmds = NULL;
|
||||||
|
_helpProjectCmds_count = 0;
|
||||||
|
_mqtt_host = NULL;
|
||||||
|
_mqtt_password = NULL;
|
||||||
|
_mqtt_username = NULL;
|
||||||
|
_wifi_password = NULL;
|
||||||
|
_wifi_ssid = NULL;
|
||||||
|
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
|
||||||
|
_verboseMessages = true;
|
||||||
|
_command = (char *)malloc(TELNET_MAX_COMMAND_LENGTH); // reserve buffer for Serial/Telnet commands
|
||||||
|
}
|
||||||
|
|
||||||
|
MyESP::~MyESP() {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// end
|
||||||
|
void MyESP::end() {
|
||||||
|
free(_command);
|
||||||
|
SerialAndTelnet.end();
|
||||||
|
jw.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// general debug to the telnet or serial channels
|
||||||
|
void MyESP::myDebug(const char * format, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
char test[1];
|
||||||
|
int len = ets_vsnprintf(test, 1, format, args) + 1;
|
||||||
|
char * buffer = new char[len];
|
||||||
|
ets_vsnprintf(buffer, len, format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
SerialAndTelnet.println(buffer);
|
||||||
|
|
||||||
|
delete[] buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for flashmemory. Must use PSTR()
|
||||||
|
void MyESP::myDebug_P(PGM_P format_P, ...) {
|
||||||
|
char format[strlen_P(format_P) + 1];
|
||||||
|
memcpy_P(format, format_P, sizeof(format));
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format_P);
|
||||||
|
char test[1];
|
||||||
|
int len = ets_vsnprintf(test, 1, format, args) + 1;
|
||||||
|
char * buffer = new char[len];
|
||||||
|
ets_vsnprintf(buffer, len, format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
SerialAndTelnet.println(buffer);
|
||||||
|
|
||||||
|
delete[] buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// called when WiFi is connected, and used to start MDNS
|
||||||
|
void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
|
||||||
|
if ((code == MESSAGE_CONNECTED) || (code == MESSAGE_ACCESSPOINT_CREATED)) {
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
String hostname = String(WiFi.getHostname());
|
||||||
|
#else
|
||||||
|
String hostname = WiFi.hostname();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
myDebug_P(PSTR("[WIFI] ----------------------------------------------"));
|
||||||
|
myDebug_P(PSTR("[WIFI] SSID %s"), WiFi.SSID().c_str());
|
||||||
|
myDebug_P(PSTR("[WIFI] CH %d"), WiFi.channel());
|
||||||
|
myDebug_P(PSTR("[WIFI] RSSI %d"), WiFi.RSSI());
|
||||||
|
myDebug_P(PSTR("[WIFI] IP %s"), WiFi.localIP().toString().c_str());
|
||||||
|
myDebug_P(PSTR("[WIFI] MAC %s"), WiFi.macAddress().c_str());
|
||||||
|
myDebug_P(PSTR("[WIFI] GW %s"), WiFi.gatewayIP().toString().c_str());
|
||||||
|
myDebug_P(PSTR("[WIFI] MASK %s"), WiFi.subnetMask().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] ----------------------------------------------"));
|
||||||
|
|
||||||
|
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());
|
||||||
|
myDebug_P(PSTR("[WIFI] ----------------------------------------------"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// start MDNS
|
||||||
|
if (MDNS.begin((char *)hostname.c_str())) {
|
||||||
|
myDebug_P(PSTR("[MDNS] OK"));
|
||||||
|
} else {
|
||||||
|
myDebug_P(PSTR("[MDNS] FAIL"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// call any final custom settings
|
||||||
|
if (_extern_WIFICallbackSet) {
|
||||||
|
myDebug_P(PSTR("[WIFI] calling custom wifi settings function"));
|
||||||
|
_extern_WIFICallback(); // call callback to set any custom things
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code == MESSAGE_CONNECTING) {
|
||||||
|
myDebug_P(PSTR("[WIFI] Connecting to %s"), parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code == MESSAGE_CONNECT_FAILED) {
|
||||||
|
myDebug_P(PSTR("[WIFI] Could not connect to %s"), parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code == MESSAGE_DISCONNECTED) {
|
||||||
|
myDebug_P(PSTR("[WIFI] Disconnected"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// received MQTT message
|
||||||
|
void MyESP::_mqttOnMessage(char * topic, char * payload, size_t len) {
|
||||||
|
if (len == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
char message[len + 1];
|
||||||
|
strlcpy(message, (char *)payload, len + 1);
|
||||||
|
|
||||||
|
// myDebug_P(PSTR("[MQTT] Received %s => %s"), topic, message);
|
||||||
|
|
||||||
|
// check for our default ones
|
||||||
|
if ((strcmp(topic, MQTT_HA) == 0) && (strcmp(message, MQTT_TOPIC_START_PAYLOAD) == 0)) {
|
||||||
|
myDebug_P(PSTR("[MQTT] HA rebooted - restarting device"));
|
||||||
|
resetESP();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char s[100];
|
||||||
|
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, MQTT_TOPIC_START);
|
||||||
|
if (strcmp(topic, s) == 0) {
|
||||||
|
myDebug_P(PSTR("[MQTT] boottime: %s"), message);
|
||||||
|
setBoottime(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send message event to custom service
|
||||||
|
(_mqtt_callback)(MQTT_MESSAGE_EVENT, topic, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MQTT subscribe
|
||||||
|
void MyESP::mqttSubscribe(const char * topic) {
|
||||||
|
if (mqttClient.connected() && (strlen(topic) > 0)) {
|
||||||
|
char s[100];
|
||||||
|
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, topic);
|
||||||
|
unsigned int packetId = mqttClient.subscribe(s, MQTT_QOS);
|
||||||
|
myDebug_P(PSTR("[MQTT] Subscribing to %s (PID %d)"), s, packetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MQTT unsubscribe
|
||||||
|
void MyESP::mqttUnsubscribe(const char * topic) {
|
||||||
|
if (mqttClient.connected() && (strlen(topic) > 0)) {
|
||||||
|
char s[100];
|
||||||
|
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, topic);
|
||||||
|
unsigned int packetId = mqttClient.unsubscribe(s);
|
||||||
|
myDebug_P(PSTR("[MQTT] Unsubscribing to %s (PID %d)"), s, packetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MQTT Publish
|
||||||
|
void MyESP::mqttPublish(const char * topic, const char * payload) {
|
||||||
|
char s[500];
|
||||||
|
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, topic);
|
||||||
|
// myDebug_P(PSTR("[MQTT] Sending pubish to %s with payload %s"), s, payload);
|
||||||
|
mqttClient.publish(s, MQTT_QOS, false, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MQTT onConnect - when a connect is established automatically subscribe to my HA topics
|
||||||
|
void MyESP::_mqttOnConnect() {
|
||||||
|
myDebug_P(PSTR("[MQTT] Connected"));
|
||||||
|
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
|
||||||
|
|
||||||
|
#ifndef NO_HA
|
||||||
|
// standard subscribes for HA (Home Assistant)
|
||||||
|
mqttClient.subscribe(MQTT_HA, MQTT_QOS); // to "ha"
|
||||||
|
mqttSubscribe(MQTT_TOPIC_START); // to home/<hostname>/start
|
||||||
|
|
||||||
|
// send specific start command to HA via MQTT, which returns the boottime
|
||||||
|
char s[48];
|
||||||
|
snprintf(s, sizeof(s), "%s%s/%s", MQTT_BASE, _app_hostname, MQTT_TOPIC_START);
|
||||||
|
mqttClient.publish(s, MQTT_QOS, false, MQTT_TOPIC_START_PAYLOAD);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// call custom
|
||||||
|
(_mqtt_callback)(MQTT_CONNECT_EVENT, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MQTT setup
|
||||||
|
void MyESP::_mqtt_setup() {
|
||||||
|
if (!_mqtt_host) {
|
||||||
|
myDebug_P(PSTR("[MQTT] disabled"));
|
||||||
|
}
|
||||||
|
|
||||||
|
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
|
||||||
|
|
||||||
|
mqttClient.onConnect([this](bool sessionPresent) { _mqttOnConnect(); });
|
||||||
|
|
||||||
|
mqttClient.onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
|
||||||
|
if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {
|
||||||
|
myDebug_P(PSTR("[MQTT] TCP Disconnected"));
|
||||||
|
(_mqtt_callback)(MQTT_DISCONNECT_EVENT, NULL, NULL); // call callback with disconnect
|
||||||
|
}
|
||||||
|
if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) {
|
||||||
|
myDebug_P(PSTR("[MQTT] Identifier Rejected"));
|
||||||
|
}
|
||||||
|
if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) {
|
||||||
|
myDebug_P(PSTR("[MQTT] Server unavailable"));
|
||||||
|
}
|
||||||
|
if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) {
|
||||||
|
myDebug_P(PSTR("[MQTT] Malformed credentials"));
|
||||||
|
}
|
||||||
|
if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
|
||||||
|
myDebug_P(PSTR("[MQTT] Not authorized"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { myDebug_P(PSTR("[MQTT] Subscribe ACK for PID %d"), packetId); });
|
||||||
|
|
||||||
|
//mqttClient.onPublish([this](uint16_t packetId) { myDebug_P(PSTR("[MQTT] Publish ACK for PID %d"), packetId); });
|
||||||
|
|
||||||
|
mqttClient.onMessage(
|
||||||
|
[this](char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
|
||||||
|
_mqttOnMessage(topic, payload, len);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// WiFI setup
|
||||||
|
void MyESP::_wifi_setup() {
|
||||||
|
jw.setHostname(_app_hostname); // Set WIFI hostname (otherwise it would be ESP-XXXXXX)
|
||||||
|
jw.subscribe([this](justwifi_messages_t code, char * parameter) { _wifiCallback(code, parameter); });
|
||||||
|
jw.enableAP(false);
|
||||||
|
jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
|
||||||
|
jw.setReconnectTimeout(WIFI_RECONNECT_INTERVAL);
|
||||||
|
jw.enableAPFallback(true); // AP mode only as fallback, but disabled
|
||||||
|
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.cleanNetworks(); // Clean existing network configuration
|
||||||
|
jw.addNetwork(_wifi_ssid, _wifi_password); // Add a network
|
||||||
|
}
|
||||||
|
|
||||||
|
// MDNS setup
|
||||||
|
void MyESP::_mdns_setup() {
|
||||||
|
MDNS.addService("telnet", "tcp", TELNETSPY_PORT);
|
||||||
|
|
||||||
|
// for OTA discovery
|
||||||
|
MDNS.addServiceTxt("arduino", "tcp", "app_name", (const char *)_app_name);
|
||||||
|
MDNS.addServiceTxt("arduino", "tcp", "app_version", (const char *)_app_version);
|
||||||
|
MDNS.addServiceTxt("arduino", "tcp", "mac", WiFi.macAddress());
|
||||||
|
{
|
||||||
|
char buffer[6] = {0};
|
||||||
|
itoa(ESP.getFlashChipRealSize() / 1024, buffer, 10);
|
||||||
|
MDNS.addServiceTxt("arduino", "tcp", "mem_size", (const char *)buffer);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
char buffer[6] = {0};
|
||||||
|
itoa(ESP.getFlashChipSize() / 1024, buffer, 10);
|
||||||
|
MDNS.addServiceTxt("arduino", "tcp", "sdk_size", (const char *)buffer);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
char buffer[6] = {0};
|
||||||
|
itoa(ESP.getFreeSketchSpace(), buffer, 10);
|
||||||
|
MDNS.addServiceTxt("arduino", "tcp", "free_space", (const char *)buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OTA Setup
|
||||||
|
void MyESP::_ota_setup() {
|
||||||
|
ArduinoOTA.setPort(OTA_PORT);
|
||||||
|
ArduinoOTA.setHostname(_app_hostname);
|
||||||
|
ArduinoOTA.onStart([this]() { myDebug_P(PSTR("[OTA] Start")); });
|
||||||
|
ArduinoOTA.onEnd([this]() { myDebug_P(PSTR("[OTA] Done, restarting...")); });
|
||||||
|
ArduinoOTA.onProgress([this](unsigned int progress, unsigned int total) {
|
||||||
|
static unsigned int _progOld;
|
||||||
|
unsigned int _prog = (progress / (total / 100));
|
||||||
|
if (_prog != _progOld) {
|
||||||
|
myDebug_P(PSTR("[OTA] Progress: %u%%\r"), _prog);
|
||||||
|
_progOld = _prog;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ArduinoOTA.onError([this](ota_error_t error) {
|
||||||
|
if (error == OTA_AUTH_ERROR)
|
||||||
|
myDebug_P(PSTR("Auth Failed"));
|
||||||
|
else if (error == OTA_BEGIN_ERROR)
|
||||||
|
myDebug_P(PSTR("Begin Failed"));
|
||||||
|
else if (error == OTA_CONNECT_ERROR)
|
||||||
|
myDebug_P(PSTR("Connect Failed"));
|
||||||
|
else if (error == OTA_RECEIVE_ERROR)
|
||||||
|
myDebug_P(PSTR("Receive Failed"));
|
||||||
|
else if (error == OTA_END_ERROR)
|
||||||
|
myDebug_P(PSTR("End Failed"));
|
||||||
|
});
|
||||||
|
|
||||||
|
ArduinoOTA.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
// sets boottime
|
||||||
|
void MyESP::setBoottime(char * boottime) {
|
||||||
|
if (_boottime) {
|
||||||
|
free(_boottime);
|
||||||
|
}
|
||||||
|
_boottime = strdup(boottime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns boottime
|
||||||
|
char * MyESP::getBoottime() {
|
||||||
|
return _boottime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set callback of sketch function to process project messages
|
||||||
|
void MyESP::consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)()) {
|
||||||
|
_helpProjectCmds = cmds; // command list
|
||||||
|
_helpProjectCmds_count = count; // number of commands
|
||||||
|
_consoleCallbackProjectCmds = callback; // external function to handle commands
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyESP::_telnetConnected() {
|
||||||
|
myDebug_P(PSTR("[TELNET] Telnet connection established"));
|
||||||
|
_consoleShowHelp(); // Show the initial message
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyESP::_telnetDisconnected() {
|
||||||
|
myDebug_P(PSTR("[TELNET] Telnet connection closed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the telnet server
|
||||||
|
void MyESP::_telnet_setup() {
|
||||||
|
SerialAndTelnet.setWelcomeMsg("");
|
||||||
|
SerialAndTelnet.setCallbackOnConnect([this]() { _telnetConnected(); });
|
||||||
|
SerialAndTelnet.setCallbackOnDisconnect([this]() { _telnetDisconnected(); });
|
||||||
|
SerialAndTelnet.begin(115200);
|
||||||
|
SerialAndTelnet.setDebugOutput(false);
|
||||||
|
|
||||||
|
#ifndef DEBUG_SUPPORT
|
||||||
|
SerialAndTelnet.setSerial(NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// init command buffer for console commands
|
||||||
|
memset(_command, 0, TELNET_MAX_COMMAND_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show help of commands
|
||||||
|
void MyESP::_consoleShowHelp() {
|
||||||
|
String help = "\n\r**********************************************\n\r* Remote Telnet Command Center & Log Monitor "
|
||||||
|
"*\n\r**********************************************\n\r";
|
||||||
|
help += "* Device hostname: " + WiFi.hostname() + "\tIP: " + WiFi.localIP().toString() + "\tMAC address: " + WiFi.macAddress() + "\n\r";
|
||||||
|
help += "* Connected to WiFi AP: " + WiFi.SSID() + "\n\r";
|
||||||
|
help += "* Boot time: ";
|
||||||
|
help.concat(_boottime);
|
||||||
|
help += "\n\r* ";
|
||||||
|
help.concat(_app_name);
|
||||||
|
help += " Version ";
|
||||||
|
help.concat(_app_version);
|
||||||
|
help += "\n\r* Free RAM: ";
|
||||||
|
help.concat(ESP.getFreeHeap());
|
||||||
|
help += " bytes\n\r";
|
||||||
|
#ifdef DEBUG_SUPPORT
|
||||||
|
help += "* !! in DEBUG_SUPPORT mode !!\n\r";
|
||||||
|
#endif
|
||||||
|
help += "*\n\r* Commands:\n\r* ?=this help, CTRL-D=quit, $=show free memory, !=reboot ESP, &=suspend all messages\n\r";
|
||||||
|
|
||||||
|
// print custom commands if available
|
||||||
|
if (_consoleCallbackProjectCmds) {
|
||||||
|
for (uint8_t i = 0; i < _helpProjectCmds_count; i++) {
|
||||||
|
help += FPSTR("* ");
|
||||||
|
help += FPSTR(_helpProjectCmds[i].key);
|
||||||
|
for (int j = 0; j < (8 - strlen(_helpProjectCmds[i].key)); j++) { // padding
|
||||||
|
help += FPSTR(" ");
|
||||||
|
}
|
||||||
|
help += FPSTR(_helpProjectCmds[i].description);
|
||||||
|
help += FPSTR("\n\r");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SerialAndTelnet.println(help.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset / restart
|
||||||
|
void MyESP::resetESP() {
|
||||||
|
myDebug_P(PSTR("* Reboot ESP..."));
|
||||||
|
end();
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
ESP.reset(); // for ESP8266 only
|
||||||
|
#else
|
||||||
|
ESP.restart();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get last command received
|
||||||
|
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 == '&') {
|
||||||
|
_verboseMessages = !_verboseMessages; // toggle
|
||||||
|
myDebug("Suspend all messages is %s", _verboseMessages ? "disabled" : "enabled");
|
||||||
|
} else {
|
||||||
|
// custom Project commands
|
||||||
|
if (_consoleCallbackProjectCmds) {
|
||||||
|
_consoleCallbackProjectCmds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_verboseMessages) {
|
||||||
|
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) {
|
||||||
|
char payload[48];
|
||||||
|
snprintf(payload, sizeof(payload), "%s : %s", _app_hostname, message);
|
||||||
|
myDebug_P(PSTR("[MQTT] Sending HA notification %s"), payload);
|
||||||
|
mqttClient.publish(MQTT_NOTIFICATION, MQTT_QOS, false, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send specific command to HA via MQTT
|
||||||
|
// format is: home/<hostname>/command with payload <cmd>
|
||||||
|
void MyESP::sendHACommand(const char * cmd) {
|
||||||
|
myDebug_P(PSTR("[MQTT] Sending HA command %s"), cmd);
|
||||||
|
char topic[48];
|
||||||
|
snprintf(topic, sizeof(topic), "%s%s/%s", MQTT_BASE, _app_hostname, MQTT_TOPIC_COMMAND);
|
||||||
|
mqttClient.publish(topic, MQTT_QOS, false, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handler for Telnet
|
||||||
|
void MyESP::_telnetHandle() {
|
||||||
|
SerialAndTelnet.handle();
|
||||||
|
|
||||||
|
char last = ' '; // To avoid processing double "\r\n"
|
||||||
|
|
||||||
|
while (SerialAndTelnet.available()) {
|
||||||
|
char character = SerialAndTelnet.read(); // Get character
|
||||||
|
|
||||||
|
// check for ctrl-D
|
||||||
|
if ((character == 0xEC) || (character == 0x04)) {
|
||||||
|
SerialAndTelnet.disconnectClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we reached our buffer limit, send what we have
|
||||||
|
if (strlen(_command) >= TELNET_MAX_COMMAND_LENGTH) {
|
||||||
|
consoleProcessCommand(); // Process the command
|
||||||
|
memset(_command, 0, TELNET_MAX_COMMAND_LENGTH); // reset for next 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
|
||||||
|
|
||||||
|
} else if (isPrintable(character)) {
|
||||||
|
// Concat char to end of buffer
|
||||||
|
uint16_t len = strlen(_command);
|
||||||
|
_command[len] = character;
|
||||||
|
_command[len + 1] = '\0';
|
||||||
|
}
|
||||||
|
last = character; // remember last char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sets a custom function to run when wifi is started
|
||||||
|
void MyESP::setWIFICallback(void (*callback)()) {
|
||||||
|
_extern_WIFICallback = callback;
|
||||||
|
_extern_WIFICallbackSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the mqtt callback for after connecting to subscribe and process incoming messages
|
||||||
|
void MyESP::setMQTTCallback(mqtt_callback_f 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
|
||||||
|
void MyESP::_mqttConnect() {
|
||||||
|
if (!_mqtt_host || mqttClient.connected() || (WiFi.status() != WL_CONNECTED)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check reconnect interval
|
||||||
|
static unsigned long last = 0;
|
||||||
|
if (millis() - last < _mqtt_reconnect_delay)
|
||||||
|
return;
|
||||||
|
last = millis();
|
||||||
|
|
||||||
|
// Increase the reconnect delay
|
||||||
|
_mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP;
|
||||||
|
if (_mqtt_reconnect_delay > MQTT_RECONNECT_DELAY_MAX) {
|
||||||
|
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
mqttClient.setServer(_mqtt_host, MQTT_PORT);
|
||||||
|
mqttClient.setClientId(_app_hostname);
|
||||||
|
|
||||||
|
if (_mqtt_username && _mqtt_password) {
|
||||||
|
myDebug_P(PSTR("[MQTT] Connecting to MQTT using user %s & its password"), _mqtt_username);
|
||||||
|
mqttClient.setCredentials(_mqtt_username, _mqtt_password);
|
||||||
|
} else {
|
||||||
|
myDebug_P(PSTR("[MQTT] Connecting to MQTT..."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the MQTT broker
|
||||||
|
mqttClient.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup everything we need
|
||||||
|
void MyESP::setup(char * app_hostname,
|
||||||
|
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
|
||||||
|
if (!wifi_ssid || *wifi_ssid == 0x00 || strlen(wifi_ssid) > 31) {
|
||||||
|
_wifi_ssid = NULL;
|
||||||
|
} else {
|
||||||
|
_wifi_ssid = strdup(wifi_ssid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check PASS too long
|
||||||
|
if (wifi_password && strlen(wifi_password) > 63) {
|
||||||
|
_wifi_password = NULL;
|
||||||
|
} else {
|
||||||
|
_wifi_password = strdup(wifi_password);
|
||||||
|
}
|
||||||
|
|
||||||
|
// can be empty
|
||||||
|
if (!mqtt_host || *mqtt_host == 0x00) {
|
||||||
|
_mqtt_host = NULL;
|
||||||
|
} else {
|
||||||
|
_mqtt_host = strdup(mqtt_host);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mqtt username and password can be empty
|
||||||
|
if (!mqtt_username || *mqtt_username == 0x00) {
|
||||||
|
_mqtt_username = NULL;
|
||||||
|
} else {
|
||||||
|
_mqtt_username = strdup(mqtt_username);
|
||||||
|
}
|
||||||
|
|
||||||
|
// can be empty
|
||||||
|
if (!mqtt_password || *mqtt_password == 0x00) {
|
||||||
|
_mqtt_password = NULL;
|
||||||
|
} else {
|
||||||
|
_mqtt_password = strdup(mqtt_password);
|
||||||
|
}
|
||||||
|
|
||||||
|
// call setup of the services...
|
||||||
|
_telnet_setup(); // Telnet setup
|
||||||
|
_wifi_setup(); // WIFI setup
|
||||||
|
_mqtt_setup(); // MQTT Setup
|
||||||
|
_mdns_setup(); // MDNS setup
|
||||||
|
_ota_setup(); // OTA setup
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Loop. This is called as often as possible and it handles wifi, telnet, mqtt etc
|
||||||
|
*/
|
||||||
|
void MyESP::loop() {
|
||||||
|
jw.loop(); // WiFi
|
||||||
|
_telnetHandle(); // Telnet/Debugger
|
||||||
|
ArduinoOTA.handle(); // OTA
|
||||||
|
_mqttConnect(); // MQTT
|
||||||
|
|
||||||
|
yield(); // ...and breath
|
||||||
|
}
|
||||||
|
|
||||||
|
MyESP myESP;
|
||||||
169
lib/myESP/MyESP.h
Normal file
169
lib/myESP/MyESP.h
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* MyEsp.h
|
||||||
|
*
|
||||||
|
* Paul Derbyshire - December 2018
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef MyEMS_h
|
||||||
|
#define MyEMS_h
|
||||||
|
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <ArduinoOTA.h>
|
||||||
|
#include <AsyncMqttClient.h> // https://github.com/marvinroger/async-mqtt-client
|
||||||
|
#include <DNSServer.h>
|
||||||
|
#include <ESPAsyncTCP.h> // https://github.com/me-no-dev/ESPAsyncTCP
|
||||||
|
#include <JustWifi.h> // https://github.com/xoseperez/justwifi
|
||||||
|
#include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
#else
|
||||||
|
#include <ESP8266mDNS.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// WIFI
|
||||||
|
#define WIFI_CONNECT_TIMEOUT 10000 // Connecting timeout for WIFI in ms
|
||||||
|
#define WIFI_RECONNECT_INTERVAL 60000 // If could not connect to WIFI, retry after this time in ms
|
||||||
|
|
||||||
|
// OTA
|
||||||
|
#define OTA_PORT 8266 // OTA port
|
||||||
|
|
||||||
|
// MQTT
|
||||||
|
#define MQTT_BASE "home/"
|
||||||
|
#define MQTT_NOTIFICATION MQTT_BASE "notification"
|
||||||
|
#define MQTT_TOPIC_COMMAND "command"
|
||||||
|
#define MQTT_TOPIC_START "start"
|
||||||
|
#define MQTT_TOPIC_START_PAYLOAD "start"
|
||||||
|
#define MQTT_HA MQTT_BASE "ha"
|
||||||
|
#define MQTT_PORT 1883 // MQTT port
|
||||||
|
#define MQTT_QOS 1
|
||||||
|
#define MQTT_RECONNECT_DELAY_MIN 5000 // Try to reconnect in 5 seconds upon disconnection
|
||||||
|
#define MQTT_RECONNECT_DELAY_STEP 5000 // Increase the reconnect delay in 5 seconds after each failed attempt
|
||||||
|
#define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most
|
||||||
|
// Internal MQTT events
|
||||||
|
#define MQTT_CONNECT_EVENT 0
|
||||||
|
#define MQTT_DISCONNECT_EVENT 1
|
||||||
|
#define MQTT_MESSAGE_EVENT 2
|
||||||
|
|
||||||
|
// Telnet
|
||||||
|
#define TELNET_MAX_COMMAND_LENGTH 80 // length of a command
|
||||||
|
#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"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char key[10];
|
||||||
|
char description[400];
|
||||||
|
} command_t;
|
||||||
|
|
||||||
|
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
|
||||||
|
|
||||||
|
// calculates size of an 2d array at compile time
|
||||||
|
template <typename T, size_t N>
|
||||||
|
constexpr size_t ArraySize(T (&)[N]) {
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
|
||||||
|
// class definition
|
||||||
|
class MyESP {
|
||||||
|
public:
|
||||||
|
MyESP();
|
||||||
|
~MyESP();
|
||||||
|
|
||||||
|
// wifi
|
||||||
|
void setWIFICallback(void (*callback)());
|
||||||
|
void setMQTTCallback(mqtt_callback_f callback);
|
||||||
|
|
||||||
|
// ha
|
||||||
|
void sendHACommand(const char * cmd);
|
||||||
|
void sendHANotification(const char * message);
|
||||||
|
|
||||||
|
// mqtt
|
||||||
|
void mqttSubscribe(const char * topic);
|
||||||
|
void mqttUnsubscribe(const char * topic);
|
||||||
|
void mqttPublish(const char * topic, const char * payload);
|
||||||
|
|
||||||
|
// debug & telnet
|
||||||
|
void myDebug(const char * format, ...);
|
||||||
|
void myDebug_P(PGM_P format_P, ...);
|
||||||
|
void consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)());
|
||||||
|
char * consoleGetLastCommand();
|
||||||
|
void consoleProcessCommand();
|
||||||
|
|
||||||
|
void end();
|
||||||
|
void loop();
|
||||||
|
void setup(char * app_hostname,
|
||||||
|
char * app_name,
|
||||||
|
char * app_version,
|
||||||
|
char * wifi_ssid,
|
||||||
|
char * wifi_password,
|
||||||
|
char * mqtt_host,
|
||||||
|
char * mqtt_username,
|
||||||
|
char * mqtt_password);
|
||||||
|
|
||||||
|
char * getBoottime();
|
||||||
|
void setBoottime(char * boottime);
|
||||||
|
void resetESP();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// mqtt
|
||||||
|
AsyncMqttClient mqttClient;
|
||||||
|
unsigned long _mqtt_reconnect_delay;
|
||||||
|
void _mqttOnMessage(char * topic, char * payload, size_t len);
|
||||||
|
void _mqttConnect();
|
||||||
|
void _mqtt_setup();
|
||||||
|
mqtt_callback_f _mqtt_callback;
|
||||||
|
void _mqttOnConnect();
|
||||||
|
void _sendStart();
|
||||||
|
char * _mqtt_host;
|
||||||
|
char * _mqtt_username;
|
||||||
|
char * _mqtt_password;
|
||||||
|
char * _boottime;
|
||||||
|
|
||||||
|
// wifi
|
||||||
|
DNSServer dnsServer; // For Access Point (AP) support
|
||||||
|
void _wifiCallback(justwifi_messages_t code, char * parameter);
|
||||||
|
void _wifi_setup();
|
||||||
|
void (*_extern_WIFICallback)();
|
||||||
|
bool _extern_WIFICallbackSet;
|
||||||
|
char * _wifi_ssid;
|
||||||
|
char * _wifi_password;
|
||||||
|
|
||||||
|
// mdns
|
||||||
|
void _mdns_setup();
|
||||||
|
|
||||||
|
// ota
|
||||||
|
void _ota_setup();
|
||||||
|
|
||||||
|
// telnet & debug
|
||||||
|
TelnetSpy SerialAndTelnet;
|
||||||
|
void _telnetConnected();
|
||||||
|
void _telnetDisconnected();
|
||||||
|
void _telnetHandle();
|
||||||
|
void _telnet_setup();
|
||||||
|
char * _command; // the input command from either Serial or Telnet
|
||||||
|
command_t * _helpProjectCmds; // Help of commands setted by project
|
||||||
|
uint8_t _helpProjectCmds_count; // # available commands
|
||||||
|
void _consoleShowHelp();
|
||||||
|
void (*_consoleCallbackProjectCmds)(); // Callable for projects commands
|
||||||
|
void _consoleProcessCommand();
|
||||||
|
bool _isCRLF(char character);
|
||||||
|
bool _verboseMessages;
|
||||||
|
|
||||||
|
// general
|
||||||
|
char * _app_hostname;
|
||||||
|
char * _app_name;
|
||||||
|
char * _app_version;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern MyESP myESP;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
[platformio]
|
[platformio]
|
||||||
; change this for your ESP8266 device
|
; change this for your ESP8266 device
|
||||||
env_default = nodemcuv2
|
; env_default = nodemcuv2
|
||||||
; env_default = d1_mini
|
env_default = d1_mini
|
||||||
|
|
||||||
[common]
|
[common]
|
||||||
platform = espressif8266
|
platform = espressif8266
|
||||||
; optional flags are -DUSE_LED -DSHOWER_TEST -DUSE_SERIAL
|
flash_mode = dout
|
||||||
build_flags = -g -w -DMQTT_MAX_PACKET_SIZE=400
|
; optional flags are -DNO_LED -DDEBUG_SUPPORT
|
||||||
|
build_flags = -g -w
|
||||||
build_flags_custom = '-DWIFI_SSID="my_ssid"' '-DWIFI_PASSWORD="my_password"' '-DMQTT_IP="my_broker_ip"' '-DMQTT_USER="my_broker_username"' '-DMQTT_PASS="my_broker_password"'
|
build_flags_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 =
|
||||||
Time
|
|
||||||
PubSubClient
|
|
||||||
ArduinoJson
|
|
||||||
CRC32
|
CRC32
|
||||||
CircularBuffer
|
CircularBuffer
|
||||||
|
JustWifi
|
||||||
|
AsyncMqttClient
|
||||||
|
ArduinoJson
|
||||||
|
|
||||||
[env:nodemcuv2]
|
[env:nodemcuv2]
|
||||||
board = nodemcuv2
|
board = nodemcuv2
|
||||||
@@ -21,12 +22,13 @@ platform = ${common.platform}
|
|||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${common.lib_deps}
|
lib_deps = ${common.lib_deps}
|
||||||
build_flags = ${common.build_flags} ${common.build_flags_custom}
|
build_flags = ${common.build_flags} ${common.build_flags_custom}
|
||||||
|
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
|
; comment out next line if using USB and not OTA
|
||||||
upload_port = "boiler."
|
upload_port = "boiler"
|
||||||
; examples....
|
; examples....
|
||||||
;upload_port = "boiler"
|
;upload_port = "boiler."
|
||||||
;upload_port = "boiler.local"
|
;upload_port = "boiler.local"
|
||||||
;upload_port = 10.10.10.6
|
;upload_port = 10.10.10.6
|
||||||
|
|
||||||
@@ -36,12 +38,13 @@ platform = ${common.platform}
|
|||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${common.lib_deps}
|
lib_deps = ${common.lib_deps}
|
||||||
build_flags = ${common.build_flags} ${common.build_flags_custom}
|
build_flags = ${common.build_flags} ${common.build_flags_custom}
|
||||||
|
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
|
; comment out next line if using USB and not OTA
|
||||||
upload_port = "boiler."
|
upload_port = "boiler"
|
||||||
; examples....
|
; examples....
|
||||||
;upload_port = "boiler"
|
;upload_port = "boiler."
|
||||||
;upload_port = "boiler.local"
|
;upload_port = "boiler.local"
|
||||||
;upload_port = 10.10.10.6
|
;upload_port = 10.10.10.6
|
||||||
|
|
||||||
|
|||||||
@@ -1,895 +0,0 @@
|
|||||||
/*
|
|
||||||
Based off :
|
|
||||||
1) ESPHelper.cpp - Copyright (c) 2017 ItKindaWorks Inc All right reserved. github.com/ItKindaWorks
|
|
||||||
2) https://github.com/JoaoLopesF/ESP8266-RemoteDebug-Telnet
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ESPHelper.h"
|
|
||||||
|
|
||||||
WiFiServer telnetServer(TELNET_PORT);
|
|
||||||
|
|
||||||
//initializer with single netInfo network
|
|
||||||
ESPHelper::ESPHelper(netInfo * startingNet) {
|
|
||||||
//disconnect from and previous wifi networks
|
|
||||||
WiFi.softAPdisconnect();
|
|
||||||
WiFi.disconnect();
|
|
||||||
|
|
||||||
//setup current network information
|
|
||||||
_currentNet = *startingNet;
|
|
||||||
|
|
||||||
//validate various bits of network/MQTT info
|
|
||||||
|
|
||||||
//network pass
|
|
||||||
if (_currentNet.pass[0] == '\0') {
|
|
||||||
_passSet = false;
|
|
||||||
} else {
|
|
||||||
_passSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//ssid
|
|
||||||
if (_currentNet.ssid[0] == '\0') {
|
|
||||||
_ssidSet = false;
|
|
||||||
} else {
|
|
||||||
_ssidSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt host
|
|
||||||
if (_currentNet.mqttHost[0] == '\0') {
|
|
||||||
_mqttSet = false;
|
|
||||||
} else {
|
|
||||||
_mqttSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt port
|
|
||||||
if (_currentNet.mqttPort == 0) {
|
|
||||||
_currentNet.mqttPort = 1883;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt username
|
|
||||||
if (_currentNet.mqttUser[0] == '\0') {
|
|
||||||
_mqttUserSet = false;
|
|
||||||
} else {
|
|
||||||
_mqttUserSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt password
|
|
||||||
if (_currentNet.mqttPass[0] == '\0') {
|
|
||||||
_mqttPassSet = false;
|
|
||||||
} else {
|
|
||||||
_mqttPassSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//disable hopping on single network
|
|
||||||
_hoppingAllowed = false;
|
|
||||||
|
|
||||||
//disable ota by default
|
|
||||||
_useOTA = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//start the wifi & mqtt systems and attempt connection (currently blocking)
|
|
||||||
//true on: parameter check validated
|
|
||||||
//false on: parameter check failed
|
|
||||||
bool ESPHelper::begin(const char * hostname, const char * app_name, const char * app_version) {
|
|
||||||
#ifdef USE_SERIAL1
|
|
||||||
Serial1.begin(115200);
|
|
||||||
Serial1.setDebugOutput(true);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL
|
|
||||||
Serial.begin(115200);
|
|
||||||
Serial.setDebugOutput(true);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// set hostname first
|
|
||||||
strcpy(_hostname, hostname);
|
|
||||||
OTA_enable();
|
|
||||||
|
|
||||||
strcpy(_app_name, app_name); // app name
|
|
||||||
strcpy(_app_version, app_version); // app version
|
|
||||||
|
|
||||||
|
|
||||||
setBoottime("<unknown>");
|
|
||||||
|
|
||||||
if (_ssidSet) {
|
|
||||||
strcpy(_clientName, hostname);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Generate client name based on MAC address and last 8 bits of microsecond counter
|
|
||||||
|
|
||||||
_clientName += "esp-";
|
|
||||||
uint8_t mac[6];
|
|
||||||
WiFi.macAddress(mac);
|
|
||||||
_clientName += macToStr(mac);
|
|
||||||
*/
|
|
||||||
|
|
||||||
// set hostname
|
|
||||||
// can ping by <hostname> or on Windows10 it's <hostname>.
|
|
||||||
#if defined(ESP8266)
|
|
||||||
WiFi.hostname(_hostname);
|
|
||||||
#elif defined(ESP32)
|
|
||||||
WiFi.setHostname(_hostname);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//set the wifi mode to station and begin the wifi (connect using either ssid or ssid/pass)
|
|
||||||
WiFi.mode(WIFI_STA);
|
|
||||||
if (_passSet) {
|
|
||||||
WiFi.begin(_currentNet.ssid, _currentNet.pass);
|
|
||||||
} else {
|
|
||||||
WiFi.begin(_currentNet.ssid);
|
|
||||||
}
|
|
||||||
|
|
||||||
//as long as an mqtt ip has been set create an instance of PubSub for client
|
|
||||||
if (_mqttSet) {
|
|
||||||
//make mqtt client use either the secure or non-secure wifi client depending on the setting
|
|
||||||
if (_useSecureClient) {
|
|
||||||
client = PubSubClient(_currentNet.mqttHost, _currentNet.mqttPort, wifiClientSecure);
|
|
||||||
} else {
|
|
||||||
client = PubSubClient(_currentNet.mqttHost, _currentNet.mqttPort, wifiClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
//set the mqtt message callback if needed
|
|
||||||
if (_mqttCallbackSet) {
|
|
||||||
client.setCallback(_mqttCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//define a dummy instance of mqtt so that it is instantiated if no mqtt ip is set
|
|
||||||
else {
|
|
||||||
//make mqtt client use either the secure or non-secure wifi client depending on the setting
|
|
||||||
//(this shouldnt be needed if making a dummy connection since the idea would be that there wont be mqtt in this case)
|
|
||||||
if (_useSecureClient) {
|
|
||||||
client = PubSubClient("192.168.1.255", _currentNet.mqttPort, wifiClientSecure);
|
|
||||||
} else {
|
|
||||||
client = PubSubClient("192.168.1.255", _currentNet.mqttPort, wifiClient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//ota event handlers
|
|
||||||
ArduinoOTA.onStart([]() { /* ota start code */ });
|
|
||||||
ArduinoOTA.onEnd([]() {
|
|
||||||
//on ota end we disconnect from wifi cleanly before restarting.
|
|
||||||
WiFi.softAPdisconnect();
|
|
||||||
WiFi.disconnect();
|
|
||||||
uint8_t timeout = 0;
|
|
||||||
//max timeout of 2seconds before just dropping out and restarting
|
|
||||||
while (WiFi.status() != WL_DISCONNECTED && timeout < 200) {
|
|
||||||
timeout++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { /* ota progress code */ });
|
|
||||||
ArduinoOTA.onError([](ota_error_t error) { /* ota error code */ });
|
|
||||||
|
|
||||||
//initially attempt to connect to wifi when we begin (but only block for 2 seconds before timing out)
|
|
||||||
uint8_t timeout = 0; //counter for begin connection attempts
|
|
||||||
while (((!client.connected() && _mqttSet) || WiFi.status() != WL_CONNECTED)
|
|
||||||
&& timeout < 200) { //max 2 sec before timeout
|
|
||||||
reconnect();
|
|
||||||
timeout++;
|
|
||||||
}
|
|
||||||
|
|
||||||
//attempt to start ota if needed
|
|
||||||
OTA_begin();
|
|
||||||
|
|
||||||
// Initialize the telnet server
|
|
||||||
telnetServer.begin();
|
|
||||||
telnetServer.setNoDelay(true);
|
|
||||||
|
|
||||||
// init command buffer for console commands
|
|
||||||
memset(_command, 0, sizeof(_command));
|
|
||||||
|
|
||||||
consoleShowHelp(); // show this at bootup
|
|
||||||
|
|
||||||
// mark the system as started and return
|
|
||||||
_hasBegun = true;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//if no ssid was set even then dont try to begin and return false
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//end the instance of ESPHelper (shutdown wifi, ota, mqtt)
|
|
||||||
void ESPHelper::end() {
|
|
||||||
// Stop telnet Client & Server
|
|
||||||
if (telnetClient && telnetClient.connected()) {
|
|
||||||
telnetClient.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop server
|
|
||||||
telnetServer.stop();
|
|
||||||
|
|
||||||
OTA_disable();
|
|
||||||
WiFi.softAPdisconnect();
|
|
||||||
WiFi.disconnect();
|
|
||||||
|
|
||||||
uint8_t timeout = 0;
|
|
||||||
while (WiFi.status() != WL_DISCONNECTED && timeout < 200) {
|
|
||||||
timeout++;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL
|
|
||||||
Serial.flush();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL1
|
|
||||||
Serial1.flush();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
//main loop - should be called as often as possible - handles wifi/mqtt connection and mqtt handler
|
|
||||||
//true on: network/server connected
|
|
||||||
//false on: network or server disconnected
|
|
||||||
int ESPHelper::loop() {
|
|
||||||
if (_ssidSet) {
|
|
||||||
//check for good connections and attempt a reconnect if needed
|
|
||||||
if (((_mqttSet && !client.connected()) || setConnectionStatus() < WIFI_ONLY) && _connectionStatus != BROADCAST) {
|
|
||||||
reconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
//run the wifi loop as long as the connection status is at a minimum of BROADCAST
|
|
||||||
if (_connectionStatus >= BROADCAST) {
|
|
||||||
//run the MQTT loop if we have a full connection
|
|
||||||
if (_connectionStatus == FULL_CONNECTION) {
|
|
||||||
client.loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
//check for whether we want to use OTA and whether the system is running
|
|
||||||
if (_useOTA && _OTArunning) {
|
|
||||||
ArduinoOTA.handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
//if we want to use OTA but its not running yet, start it up.
|
|
||||||
else if (_useOTA && !_OTArunning) {
|
|
||||||
OTA_begin();
|
|
||||||
ArduinoOTA.handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
// do the telnet stuff
|
|
||||||
consoleHandle();
|
|
||||||
|
|
||||||
return _connectionStatus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//return -1 for no connection because of bad network info
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//subscribe to a specific topic (does not add to topic list)
|
|
||||||
//true on: subscription success
|
|
||||||
//false on: subscription failed (either from PubSub lib or network is disconnected)
|
|
||||||
bool ESPHelper::subscribe(const char * topic, uint8_t qos) {
|
|
||||||
if (_connectionStatus == FULL_CONNECTION) {
|
|
||||||
//set the return value to the output of subscribe
|
|
||||||
bool returnVal = client.subscribe(topic, qos);
|
|
||||||
|
|
||||||
//loop mqtt client
|
|
||||||
client.loop();
|
|
||||||
return returnVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
//if not fully connected return false
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//add a topic to the list of subscriptions and attempt to subscribe to the topic on the spot
|
|
||||||
//true on: subscription added to list (does not guarantee that the topic was subscribed to, only that it was added to the list)
|
|
||||||
//false on: subscription not added to list
|
|
||||||
bool ESPHelper::addSubscription(const char * topic) {
|
|
||||||
//default return value is false
|
|
||||||
bool subscribed = false;
|
|
||||||
|
|
||||||
//loop through finding the next available slot for a subscription and add it
|
|
||||||
for (uint8_t i = 0; i < MAX_SUBSCRIPTIONS; i++) {
|
|
||||||
if (_subscriptions[i].isUsed == false) {
|
|
||||||
_subscriptions[i].topic = topic;
|
|
||||||
_subscriptions[i].isUsed = true;
|
|
||||||
subscribed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//if added to the list, subscribe to the topic
|
|
||||||
if (subscribed) {
|
|
||||||
subscribe(topic, _qos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return subscribed;
|
|
||||||
}
|
|
||||||
|
|
||||||
//loops through list of subscriptions and attempts to subscribe to all topics
|
|
||||||
void ESPHelper::resubscribe() {
|
|
||||||
for (uint8_t i = 0; i < MAX_SUBSCRIPTIONS; i++) {
|
|
||||||
if (_subscriptions[i].isUsed) {
|
|
||||||
subscribe(_subscriptions[i].topic, _qos);
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//manually unsubscribes from a topic (This is basically just a wrapper for the pubsubclient function)
|
|
||||||
bool ESPHelper::unsubscribe(const char * topic) {
|
|
||||||
return client.unsubscribe(topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
//publish to a specified topic
|
|
||||||
void ESPHelper::publish(const char * topic, const char * payload) {
|
|
||||||
if (_mqttSet) {
|
|
||||||
publish(topic, payload, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//publish to a specified topic with a given retain level
|
|
||||||
void ESPHelper::publish(const char * topic, const char * payload, bool retain) {
|
|
||||||
client.publish(topic, payload, retain);
|
|
||||||
}
|
|
||||||
|
|
||||||
//set the callback function for MQTT
|
|
||||||
void ESPHelper::setMQTTCallback(MQTT_CALLBACK_SIGNATURE) {
|
|
||||||
_mqttCallback = callback;
|
|
||||||
|
|
||||||
//only set the callback if using mqtt AND the system has already been started. Otherwise just save it for later
|
|
||||||
if (_hasBegun && _mqttSet) {
|
|
||||||
client.setCallback(_mqttCallback);
|
|
||||||
}
|
|
||||||
_mqttCallbackSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//sets a custom function to run when connection to wifi is established
|
|
||||||
void ESPHelper::setWifiCallback(void (*callback)()) {
|
|
||||||
_wifiCallback = callback;
|
|
||||||
_wifiCallbackSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//sets a custom function to run when telnet is started
|
|
||||||
void ESPHelper::setInitCallback(void (*callback)()) {
|
|
||||||
_initCallback = callback;
|
|
||||||
_initCallbackSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//attempts to connect to wifi & mqtt server if not connected
|
|
||||||
void ESPHelper::reconnect() {
|
|
||||||
static uint8_t tryCount = 0;
|
|
||||||
|
|
||||||
if (_connectionStatus != BROADCAST && setConnectionStatus() != FULL_CONNECTION) {
|
|
||||||
logger(LOG_CONSOLE, "Attempting WiFi Connection...");
|
|
||||||
//attempt to connect to the wifi if connection is lost
|
|
||||||
if (WiFi.status() != WL_CONNECTED) {
|
|
||||||
_connectionStatus = NO_CONNECTION;
|
|
||||||
|
|
||||||
//increment try count each time it cannot connect (this is used to determine when to hop to a new network)
|
|
||||||
tryCount++;
|
|
||||||
if (tryCount == 20) {
|
|
||||||
//change networks (if possible) when we have tried to connect 20 times and failed
|
|
||||||
changeNetwork();
|
|
||||||
tryCount = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure we are connected to WIFI before attempting to reconnect to MQTT
|
|
||||||
//----note---- maybe want to reset tryCount whenever we succeed at getting wifi connection?
|
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
|
||||||
//if the wifi previously wasnt connected but now is, run the callback
|
|
||||||
if (_connectionStatus < WIFI_ONLY && _wifiCallbackSet) {
|
|
||||||
_wifiCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
logger(LOG_CONSOLE, "---WiFi Connected!---");
|
|
||||||
_connectionStatus = WIFI_ONLY;
|
|
||||||
|
|
||||||
//attempt to connect to mqtt when we finally get connected to WiFi
|
|
||||||
if (_mqttSet) {
|
|
||||||
static uint8_t timeout = 0; //allow a max of 5 mqtt connection attempts before timing out
|
|
||||||
if (!client.connected() && timeout < 5) {
|
|
||||||
logger(LOG_CONSOLE, "Attempting MQTT connection...");
|
|
||||||
|
|
||||||
uint8_t connected = 0;
|
|
||||||
|
|
||||||
//connect to mqtt with user/pass
|
|
||||||
if (_mqttUserSet) {
|
|
||||||
connected = client.connect(_clientName, _currentNet.mqttUser, _currentNet.mqttPass);
|
|
||||||
}
|
|
||||||
|
|
||||||
//connect to mqtt without credentials
|
|
||||||
else {
|
|
||||||
connected = client.connect(_clientName);
|
|
||||||
}
|
|
||||||
|
|
||||||
//if connected, subscribe to the topic(s) we want to be notified about
|
|
||||||
if (connected) {
|
|
||||||
logger(LOG_CONSOLE, " -- Connected");
|
|
||||||
|
|
||||||
//if using https, verify the fingerprint of the server before setting full connection (return on fail)
|
|
||||||
// removing this as not supported with ESP32, see https://github.com/espressif/arduino-esp32/issues/278
|
|
||||||
/*
|
|
||||||
if (wifiClientSecure.verify(_fingerprint,
|
|
||||||
_currentNet.mqttHost)) {
|
|
||||||
logger(LOG_CONSOLE,
|
|
||||||
"Certificate Matches - SUCCESS\n");
|
|
||||||
} else {
|
|
||||||
logger(LOG_CONSOLE,
|
|
||||||
"Certificate Doesn't Match - FAIL\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
_connectionStatus = FULL_CONNECTION;
|
|
||||||
resubscribe();
|
|
||||||
timeout = 0;
|
|
||||||
} else {
|
|
||||||
logger(LOG_CONSOLE, " -- Failed\n");
|
|
||||||
}
|
|
||||||
timeout++;
|
|
||||||
}
|
|
||||||
|
|
||||||
//if we still cant connect to mqtt after 10 attempts increment the try count
|
|
||||||
if (timeout >= 5 && !client.connected()) {
|
|
||||||
timeout = 0;
|
|
||||||
tryCount++;
|
|
||||||
if (tryCount == 20) {
|
|
||||||
changeNetwork();
|
|
||||||
tryCount = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//reset the reconnect metro
|
|
||||||
//reconnectMetro.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t ESPHelper::setConnectionStatus() {
|
|
||||||
//assume no connection
|
|
||||||
uint8_t returnVal = NO_CONNECTION;
|
|
||||||
|
|
||||||
//make sure were not in broadcast mode
|
|
||||||
if (_connectionStatus != BROADCAST) {
|
|
||||||
//if connected to wifi set the mode to wifi only and run the callback if needed
|
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
|
||||||
if (_connectionStatus < WIFI_ONLY
|
|
||||||
&& _wifiCallbackSet) { //if the wifi previously wasn't connected but now is, run the callback
|
|
||||||
_wifiCallback();
|
|
||||||
}
|
|
||||||
returnVal = WIFI_ONLY;
|
|
||||||
|
|
||||||
//if mqtt is connected as well then set the status to full connection
|
|
||||||
if (client.connected()) {
|
|
||||||
returnVal = FULL_CONNECTION;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
returnVal = BROADCAST;
|
|
||||||
}
|
|
||||||
|
|
||||||
//set the connection status and return
|
|
||||||
_connectionStatus = returnVal;
|
|
||||||
return returnVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
//changes the current network settings to the next listed network if network hopping is allowed
|
|
||||||
void ESPHelper::changeNetwork() {
|
|
||||||
//only attempt to change networks if hopping is allowed
|
|
||||||
if (_hoppingAllowed) {
|
|
||||||
//change the index/reset to 0 if we've hit the last network setting
|
|
||||||
_currentIndex++;
|
|
||||||
if (_currentIndex >= _netCount) {
|
|
||||||
_currentIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//set the current netlist to the new network
|
|
||||||
_currentNet = *_netList[_currentIndex];
|
|
||||||
|
|
||||||
//verify various bits of network info
|
|
||||||
|
|
||||||
//network password
|
|
||||||
if (_currentNet.pass[0] == '\0') {
|
|
||||||
_passSet = false;
|
|
||||||
} else {
|
|
||||||
_passSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//ssid
|
|
||||||
if (_currentNet.ssid[0] == '\0') {
|
|
||||||
_ssidSet = false;
|
|
||||||
} else {
|
|
||||||
_ssidSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt host
|
|
||||||
if (_currentNet.mqttHost[0] == '\0') {
|
|
||||||
_mqttSet = false;
|
|
||||||
} else {
|
|
||||||
_mqttSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt username
|
|
||||||
if (_currentNet.mqttUser[0] == '\0') {
|
|
||||||
_mqttUserSet = false;
|
|
||||||
} else {
|
|
||||||
_mqttUserSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//mqtt password
|
|
||||||
if (_currentNet.mqttPass[0] == '\0') {
|
|
||||||
_mqttPassSet = false;
|
|
||||||
} else {
|
|
||||||
_mqttPassSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Trying next network: %s\n", _currentNet.ssid);
|
|
||||||
|
|
||||||
//update the network connection
|
|
||||||
updateNetwork();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESPHelper::updateNetwork() {
|
|
||||||
logger(LOG_CONSOLE, "\tDisconnecting from WiFi");
|
|
||||||
WiFi.disconnect();
|
|
||||||
logger(LOG_CONSOLE, "\tAttempting to begin on new network...");
|
|
||||||
|
|
||||||
//set the wifi mode
|
|
||||||
WiFi.mode(WIFI_STA);
|
|
||||||
|
|
||||||
//connect to the network
|
|
||||||
if (_passSet && _ssidSet) {
|
|
||||||
WiFi.begin(_currentNet.ssid, _currentNet.pass);
|
|
||||||
} else if (_ssidSet) {
|
|
||||||
WiFi.begin(_currentNet.ssid);
|
|
||||||
} else {
|
|
||||||
WiFi.begin("NO_SSID_SET");
|
|
||||||
}
|
|
||||||
|
|
||||||
logger(LOG_CONSOLE, "\tSetting new MQTT server");
|
|
||||||
//setup the mqtt broker info
|
|
||||||
if (_mqttSet) {
|
|
||||||
client.setServer(_currentNet.mqttHost, _currentNet.mqttPort);
|
|
||||||
} else {
|
|
||||||
client.setServer("192.168.1.3", 1883);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger(LOG_CONSOLE, "\tDone - Ready for next reconnect attempt");
|
|
||||||
}
|
|
||||||
|
|
||||||
//enable use of OTA updates
|
|
||||||
void ESPHelper::OTA_enable() {
|
|
||||||
_useOTA = true;
|
|
||||||
ArduinoOTA.setHostname(_hostname);
|
|
||||||
OTA_begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
//begin the OTA subsystem but with a check for connectivity and enabled use of OTA
|
|
||||||
void ESPHelper::OTA_begin() {
|
|
||||||
if (_connectionStatus >= BROADCAST && _useOTA) {
|
|
||||||
ArduinoOTA.begin();
|
|
||||||
_OTArunning = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//disable use of OTA updates
|
|
||||||
void ESPHelper::OTA_disable() {
|
|
||||||
_useOTA = false;
|
|
||||||
_OTArunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is CR or LF ?
|
|
||||||
bool ESPHelper::isCRLF(char character) {
|
|
||||||
return (character == '\r' || character == '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
// handler for Telnet
|
|
||||||
void ESPHelper::consoleHandle() {
|
|
||||||
// look for Client
|
|
||||||
if (telnetServer.hasClient()) {
|
|
||||||
if (telnetClient && telnetClient.connected()) {
|
|
||||||
// Verify if the IP is same than actual connection
|
|
||||||
WiFiClient newClient;
|
|
||||||
newClient = telnetServer.available();
|
|
||||||
String ip = newClient.remoteIP().toString();
|
|
||||||
|
|
||||||
if (ip == telnetClient.remoteIP().toString()) {
|
|
||||||
// Reconnect
|
|
||||||
telnetClient.stop();
|
|
||||||
telnetClient = newClient;
|
|
||||||
} else {
|
|
||||||
// Desconnect (not allow more than one connection)
|
|
||||||
newClient.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// New TCP client
|
|
||||||
telnetClient = telnetServer.available();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!telnetClient) { // No client yet ???
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set client
|
|
||||||
telnetClient.setNoDelay(true); // faster
|
|
||||||
telnetClient.flush(); // clear input buffer, to prevent strange characters
|
|
||||||
|
|
||||||
_lastTimeCommand = millis(); // To mark time for inactivity
|
|
||||||
|
|
||||||
// Show the initial message
|
|
||||||
consoleShowHelp();
|
|
||||||
|
|
||||||
_initCallback(); // call callback to set any custom things
|
|
||||||
|
|
||||||
// Empty buffer
|
|
||||||
while (telnetClient.available()) {
|
|
||||||
telnetClient.read();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is client connected ? (to reduce overhead in active)
|
|
||||||
_telnetConnected = (telnetClient && telnetClient.connected());
|
|
||||||
|
|
||||||
// Get command over telnet
|
|
||||||
if (_telnetConnected) {
|
|
||||||
char last = ' '; // To avoid processing double "\r\n"
|
|
||||||
|
|
||||||
while (telnetClient.available()) { // get data from Client
|
|
||||||
|
|
||||||
// Get character
|
|
||||||
char character = telnetClient.read();
|
|
||||||
|
|
||||||
// Newline (CR or LF) - once one time if (\r\n)
|
|
||||||
if (isCRLF(character) == true) {
|
|
||||||
if (isCRLF(last) == false) {
|
|
||||||
// Process the command
|
|
||||||
if (strlen(_command) > 0) {
|
|
||||||
consoleProcessCommand();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// reset for next command
|
|
||||||
memset(_command, 0, sizeof(_command));
|
|
||||||
} else if (isPrintable(character)) {
|
|
||||||
// Concat char to end of buffer
|
|
||||||
uint16_t len = strlen(_command);
|
|
||||||
_command[len] = character;
|
|
||||||
_command[len + 1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Last char
|
|
||||||
last = character;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inactivity - close connection if not received commands from user in telnet to reduce overheads
|
|
||||||
if ((millis() - _lastTimeCommand) > MAX_TIME_INACTIVE) {
|
|
||||||
telnetClient.println("* Closing telnet session due to inactivity");
|
|
||||||
telnetClient.flush();
|
|
||||||
telnetClient.stop();
|
|
||||||
_telnetConnected = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set callback of sketch function to process project messages
|
|
||||||
void ESPHelper::consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)()) {
|
|
||||||
_helpProjectCmds = cmds; // command list
|
|
||||||
_helpProjectCmds_count = count; // number of commands
|
|
||||||
_consoleCallbackProjectCmds = callback; // external function to handle commands
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set bootime received as a string from HA
|
|
||||||
void ESPHelper::setBoottime(const char * boottime) {
|
|
||||||
strcpy(_boottime, boottime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// overrides the write call to print to the telnet connection
|
|
||||||
size_t ESPHelper::write(uint8_t character) {
|
|
||||||
if (!_verboseMessages)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
//static uint32_t elapsed = 0;
|
|
||||||
|
|
||||||
// If start of a new line, initiate a new string buffer with time counter as a prefix
|
|
||||||
if (_newLine) {
|
|
||||||
unsigned long upt = millis();
|
|
||||||
sprintf(bufferPrint,
|
|
||||||
"(%s%02d:%02d:%02d%s) ",
|
|
||||||
COLOR_CYAN,
|
|
||||||
(uint8_t)((upt / (1000 * 60 * 60)) % 24),
|
|
||||||
(uint8_t)((upt / (1000 * 60)) % 60),
|
|
||||||
(uint8_t)((upt / 1000) % 60),
|
|
||||||
COLOR_RESET);
|
|
||||||
_newLine = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print ?
|
|
||||||
bool doPrint = false;
|
|
||||||
|
|
||||||
// New line ?
|
|
||||||
if (character == '\n') {
|
|
||||||
_newLine = true;
|
|
||||||
doPrint = true;
|
|
||||||
} else if (strlen(bufferPrint) == BUFFER_PRINT - 1) { // Limit of buffer
|
|
||||||
doPrint = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add character to telnet buffer
|
|
||||||
uint16_t len = strlen(bufferPrint);
|
|
||||||
bufferPrint[len] = character;
|
|
||||||
|
|
||||||
if (_newLine) {
|
|
||||||
// add additional \r for windows
|
|
||||||
bufferPrint[++len] = '\r';
|
|
||||||
}
|
|
||||||
|
|
||||||
// terminate string
|
|
||||||
bufferPrint[++len] = '\0';
|
|
||||||
|
|
||||||
// Send the characters buffered by print.h
|
|
||||||
if (doPrint) {
|
|
||||||
if (_telnetConnected) {
|
|
||||||
telnetClient.print(bufferPrint);
|
|
||||||
}
|
|
||||||
|
|
||||||
// echo to Serial if enabled
|
|
||||||
#ifdef USE_SERIAL
|
|
||||||
Serial.print(bufferPrint);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL1
|
|
||||||
Serial1.print(bufferPrint);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Empty the buffer
|
|
||||||
bufferPrint[0] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
return len + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show help of commands
|
|
||||||
void ESPHelper::consoleShowHelp() {
|
|
||||||
String help = "**********************************************\n\r* Remote Telnet Command Center & Log Monitor "
|
|
||||||
"*\n\r**********************************************\n\r";
|
|
||||||
help += "* Device hostname: " + WiFi.hostname() + "\tIP: " + WiFi.localIP().toString()
|
|
||||||
+ "\tMAC address: " + WiFi.macAddress() + "\n\r";
|
|
||||||
help += "* Connected to WiFi AP: " + WiFi.SSID() + "\n\r";
|
|
||||||
help += "* Boot time: ";
|
|
||||||
help.concat(_boottime);
|
|
||||||
help += "\n\r* ";
|
|
||||||
help.concat(_app_name);
|
|
||||||
help += " Version ";
|
|
||||||
help.concat(_app_version);
|
|
||||||
help += "\n\r* Free RAM: ";
|
|
||||||
help.concat(ESP.getFreeHeap());
|
|
||||||
help += " bytes\n\r";
|
|
||||||
help += "*\n\r* Commands:\n\r* ?=this help, q=quit telnet, $=show free memory, !=reboot, &=suspend all "
|
|
||||||
"notifications\n\r";
|
|
||||||
|
|
||||||
char s[100];
|
|
||||||
|
|
||||||
// print custom commands if available
|
|
||||||
if (_consoleCallbackProjectCmds) {
|
|
||||||
for (uint8_t i = 0; i < _helpProjectCmds_count; i++) {
|
|
||||||
help += FPSTR("* ");
|
|
||||||
help += FPSTR(_helpProjectCmds[i].key);
|
|
||||||
for (int j = 0; j < (8 - strlen(_helpProjectCmds[i].key)); j++) { // padding
|
|
||||||
help += FPSTR(" ");
|
|
||||||
}
|
|
||||||
help += FPSTR(_helpProjectCmds[i].description);
|
|
||||||
help += FPSTR("\n\r");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
telnetClient.print(help);
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL
|
|
||||||
Serial.print(help);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL1
|
|
||||||
Serial1.print(help);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset / restart
|
|
||||||
void ESPHelper::resetESP() {
|
|
||||||
telnetClient.println("* Reboot ESP...");
|
|
||||||
telnetClient.flush();
|
|
||||||
telnetClient.stop();
|
|
||||||
// end();
|
|
||||||
|
|
||||||
// Reset
|
|
||||||
ESP.restart();
|
|
||||||
// ESP.reset(); // for ESP8266 only
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get last command received
|
|
||||||
char * ESPHelper::consoleGetLastCommand() {
|
|
||||||
return _command;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process user command over telnet
|
|
||||||
void ESPHelper::consoleProcessCommand() {
|
|
||||||
// Set time of last command received
|
|
||||||
_lastTimeCommand = millis();
|
|
||||||
uint8_t cmd = _command[0];
|
|
||||||
|
|
||||||
if (!_verboseMessages) {
|
|
||||||
telnetClient.println("Warning, all messages are supsended. Use & to enable.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the command
|
|
||||||
if (cmd == '?') {
|
|
||||||
consoleShowHelp(); // Show help
|
|
||||||
} else if (cmd == 'q') { // quit
|
|
||||||
telnetClient.println("* Closing telnet connection...");
|
|
||||||
telnetClient.stop();
|
|
||||||
} else if (cmd == '$') {
|
|
||||||
telnetClient.print("* Free RAM (bytes): ");
|
|
||||||
telnetClient.println(ESP.getFreeHeap());
|
|
||||||
} else if (cmd == '!') {
|
|
||||||
resetESP();
|
|
||||||
} else if (cmd == '&') {
|
|
||||||
_verboseMessages = !_verboseMessages; // toggle
|
|
||||||
telnetClient.printf("Suspend all messages is %s\n\r", _verboseMessages ? "disabled" : "enabled");
|
|
||||||
} else {
|
|
||||||
// custom Project commands
|
|
||||||
if (_consoleCallbackProjectCmds) {
|
|
||||||
_consoleCallbackProjectCmds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logger
|
|
||||||
// LOG_CONSOLE sends to the Telnet session
|
|
||||||
// LOG_HA sends to Telnet session plus a MQTT for Home Assistant
|
|
||||||
// LOG_NONE turns off all logging
|
|
||||||
void ESPHelper::logger(log_level_t level, const char * message) {
|
|
||||||
// do we log to the telnet window?
|
|
||||||
if ((level == LOG_CONSOLE) && (telnetClient && telnetClient.connected())) {
|
|
||||||
telnetClient.println(message);
|
|
||||||
telnetClient.flush();
|
|
||||||
} else if (level == LOG_HA) {
|
|
||||||
char s[100];
|
|
||||||
sprintf(s, "%s: %s\n", _hostname, message); // add new line, for the debug telnet printer
|
|
||||||
publish(MQTT_NOTIFICATION, s, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// print to Serial if set in platform.io (requires recompile)
|
|
||||||
#ifdef USE_SERIAL
|
|
||||||
Serial.println(message);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SERIAL1
|
|
||||||
Serial.println(message);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// send specific command to HA via MQTT
|
|
||||||
// format is: home/<hostname>/command/<cmd>
|
|
||||||
void ESPHelper::sendHACommand(const char * cmd) {
|
|
||||||
//logger(LOG_CONSOLE, "Sending command to HA...");
|
|
||||||
|
|
||||||
char s[100];
|
|
||||||
sprintf(s, "%s%s/%s", MQTT_BASE, _hostname, MQTT_TOPIC_COMMAND);
|
|
||||||
|
|
||||||
publish(s, cmd, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// send specific start command to HA via MQTT, which returns the boottime
|
|
||||||
// format is: home/<hostname>/start
|
|
||||||
void ESPHelper::sendStart() {
|
|
||||||
//logger(LOG_CONSOLE, "Sending Start command to HA...");
|
|
||||||
|
|
||||||
char s[100];
|
|
||||||
sprintf(s, "%s%s/%s", MQTT_BASE, _hostname, MQTT_TOPIC_START);
|
|
||||||
|
|
||||||
// send initial payload of "start" to kick things off
|
|
||||||
publish(s, MQTT_TOPIC_START, false);
|
|
||||||
}
|
|
||||||
219
src/ESPHelper.h
219
src/ESPHelper.h
@@ -1,219 +0,0 @@
|
|||||||
/*
|
|
||||||
ESPHelper.h
|
|
||||||
Copyright (c) 2017 ItKindaWorks Inc All right reserved.
|
|
||||||
github.com/ItKindaWorks
|
|
||||||
|
|
||||||
This file is part of ESPHelper
|
|
||||||
|
|
||||||
ESPHelper is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
ESPHelper is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with ESPHelper. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <ArduinoOTA.h>
|
|
||||||
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
|
|
||||||
#include <ESP8266mDNS.h>
|
|
||||||
#include <Print.h>
|
|
||||||
#include <PubSubClient.h>
|
|
||||||
#include <WiFiClientSecure.h>
|
|
||||||
#include <WiFiUdp.h>
|
|
||||||
#include <pgmspace.h>
|
|
||||||
|
|
||||||
// MQTT stuff
|
|
||||||
#define DEFAULT_QOS 1 //at least once - devices are guarantee to get a message.
|
|
||||||
#define MQTT_BASE "home/"
|
|
||||||
#define MQTT_NOTIFICATION MQTT_BASE "notification"
|
|
||||||
#define MQTT_TOPIC_COMMAND "command"
|
|
||||||
#define MQTT_TOPIC_START "start"
|
|
||||||
#define MQTT_HA MQTT_BASE "ha"
|
|
||||||
|
|
||||||
#define MAX_SUBSCRIPTIONS 25 // max # of subscriptions
|
|
||||||
#define MAX_TIME_INACTIVE 600000 // Max time for inactivity (ms) - 10 mins
|
|
||||||
#define TELNET_PORT 23 // telnet port
|
|
||||||
#define BUFFER_PRINT 500 // length of telnet buffer (default was 150)
|
|
||||||
#define COMMAND_LENGTH 20 // length of a command
|
|
||||||
|
|
||||||
// ANSI Colors
|
|
||||||
#define COLOR_RESET "\x1B[0m"
|
|
||||||
#define COLOR_BLACK "\x1B[0;30m"
|
|
||||||
#define COLOR_RED "\x1B[0;31m"
|
|
||||||
#define COLOR_GREEN "\x1B[0;32m"
|
|
||||||
#define COLOR_YELLOW "\x1B[0;33m"
|
|
||||||
#define COLOR_BLUE "\x1B[0;34m"
|
|
||||||
#define COLOR_MAGENTA "\x1B[0;35m"
|
|
||||||
#define COLOR_CYAN "\x1B[0;36m"
|
|
||||||
#define COLOR_WHITE "\x1B[0;37m"
|
|
||||||
|
|
||||||
// Logger
|
|
||||||
typedef enum { LOG_NONE, LOG_CONSOLE, LOG_HA } log_level_t;
|
|
||||||
|
|
||||||
enum connStatus { NO_CONNECTION, BROADCAST, WIFI_ONLY, FULL_CONNECTION };
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const char * mqttHost;
|
|
||||||
const char * mqttUser;
|
|
||||||
const char * mqttPass;
|
|
||||||
uint16_t mqttPort;
|
|
||||||
const char * ssid;
|
|
||||||
const char * pass;
|
|
||||||
} netInfo;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool isUsed = false;
|
|
||||||
const char * topic;
|
|
||||||
} subscription;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char key[10];
|
|
||||||
char description[400];
|
|
||||||
} command_t;
|
|
||||||
|
|
||||||
// class ESPHelper {
|
|
||||||
class ESPHelper : public Print {
|
|
||||||
public:
|
|
||||||
void consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)());
|
|
||||||
char * consoleGetLastCommand();
|
|
||||||
void resetESP();
|
|
||||||
void logger(log_level_t level, const char * message);
|
|
||||||
|
|
||||||
virtual size_t write(uint8_t);
|
|
||||||
|
|
||||||
ESPHelper(netInfo * startingNet);
|
|
||||||
|
|
||||||
bool begin(const char * hostname, const char * app_name, const char * app_version);
|
|
||||||
|
|
||||||
void end();
|
|
||||||
|
|
||||||
void useSecureClient(const char * fingerprint);
|
|
||||||
|
|
||||||
int loop();
|
|
||||||
|
|
||||||
bool subscribe(const char * topic, uint8_t qos);
|
|
||||||
bool addSubscription(const char * topic);
|
|
||||||
bool removeSubscription(const char * topic);
|
|
||||||
bool unsubscribe(const char * topic);
|
|
||||||
bool addHASubscription(const char * topic);
|
|
||||||
|
|
||||||
void publish(const char * topic, const char * payload);
|
|
||||||
void publish(const char * topic, const char * payload, bool retain);
|
|
||||||
|
|
||||||
bool setCallback(MQTT_CALLBACK_SIGNATURE);
|
|
||||||
void setMQTTCallback(MQTT_CALLBACK_SIGNATURE);
|
|
||||||
|
|
||||||
void setWifiCallback(void (*callback)());
|
|
||||||
void setInitCallback(void (*callback)());
|
|
||||||
|
|
||||||
void sendHACommand(const char * s);
|
|
||||||
void sendStart();
|
|
||||||
|
|
||||||
void reconnect();
|
|
||||||
|
|
||||||
void updateNetwork();
|
|
||||||
|
|
||||||
const char * getSSID();
|
|
||||||
void setSSID(const char * ssid);
|
|
||||||
|
|
||||||
const char * getPASS();
|
|
||||||
void setPASS(const char * pass);
|
|
||||||
|
|
||||||
const char * getMQTTIP();
|
|
||||||
void setMQTTIP(const char * mqttIP);
|
|
||||||
void setMQTTIP(const char * mqttIP, const char * mqttUser, const char * mqttPass);
|
|
||||||
|
|
||||||
uint8_t getMQTTQOS();
|
|
||||||
void setMQTTQOS(uint8_t qos);
|
|
||||||
|
|
||||||
String getIP();
|
|
||||||
IPAddress getIPAddress();
|
|
||||||
|
|
||||||
uint8_t getStatus();
|
|
||||||
|
|
||||||
void setNetInfo(netInfo newNetwork);
|
|
||||||
void setNetInfo(netInfo * newNetwork);
|
|
||||||
netInfo * getNetInfo();
|
|
||||||
|
|
||||||
void setHopping(bool canHop);
|
|
||||||
|
|
||||||
void listSubscriptions();
|
|
||||||
|
|
||||||
void OTA_enable();
|
|
||||||
void OTA_disable();
|
|
||||||
void OTA_begin();
|
|
||||||
|
|
||||||
void setBoottime(const char * boottime);
|
|
||||||
|
|
||||||
void consoleHandle();
|
|
||||||
|
|
||||||
private:
|
|
||||||
netInfo _currentNet;
|
|
||||||
PubSubClient client;
|
|
||||||
WiFiClient wifiClient;
|
|
||||||
WiFiClientSecure wifiClientSecure;
|
|
||||||
const char * _fingerprint;
|
|
||||||
bool _useSecureClient = false;
|
|
||||||
char _clientName[40];
|
|
||||||
void (*_wifiCallback)();
|
|
||||||
bool _wifiCallbackSet = false;
|
|
||||||
void (*_initCallback)();
|
|
||||||
bool _initCallbackSet = false;
|
|
||||||
|
|
||||||
std::function<void(char *, uint8_t *, uint8_t)> _mqttCallback;
|
|
||||||
|
|
||||||
bool _mqttCallbackSet = false;
|
|
||||||
uint8_t _connectionStatus = NO_CONNECTION;
|
|
||||||
uint8_t _netCount = 0;
|
|
||||||
uint8_t _currentIndex = 0;
|
|
||||||
bool _ssidSet = false;
|
|
||||||
bool _passSet = false;
|
|
||||||
bool _mqttSet = false;
|
|
||||||
bool _mqttUserSet = false;
|
|
||||||
bool _mqttPassSet = false;
|
|
||||||
bool _useOTA = false;
|
|
||||||
bool _OTArunning = false;
|
|
||||||
bool _hoppingAllowed = false;
|
|
||||||
bool _hasBegun = false;
|
|
||||||
netInfo ** _netList;
|
|
||||||
bool _verboseMessages = true;
|
|
||||||
subscription _subscriptions[MAX_SUBSCRIPTIONS];
|
|
||||||
char _hostname[24];
|
|
||||||
uint8_t _qos = DEFAULT_QOS;
|
|
||||||
IPAddress _apIP = IPAddress(192, 168, 1, 254);
|
|
||||||
void changeNetwork();
|
|
||||||
String macToStr(const uint8_t * mac);
|
|
||||||
bool checkParams();
|
|
||||||
void resubscribe();
|
|
||||||
uint8_t setConnectionStatus();
|
|
||||||
|
|
||||||
char _boottime[24];
|
|
||||||
char _app_name[24];
|
|
||||||
char _app_version[10];
|
|
||||||
|
|
||||||
// console/telnet specific
|
|
||||||
WiFiClient telnetClient;
|
|
||||||
|
|
||||||
bool _telnetConnected = false; // Client is connected ?
|
|
||||||
bool _newLine = true; // New line write ?
|
|
||||||
|
|
||||||
char _command[COMMAND_LENGTH]; // Command received, includes options seperated by a space
|
|
||||||
uint32_t _lastTimeCommand = millis(); // Last time command received
|
|
||||||
|
|
||||||
command_t * _helpProjectCmds; // Help of commands setted by project
|
|
||||||
uint8_t _helpProjectCmds_count; // # available commands
|
|
||||||
|
|
||||||
void (*_consoleCallbackProjectCmds)(); // Callable for projects commands
|
|
||||||
void consoleShowHelp();
|
|
||||||
void consoleProcessCommand();
|
|
||||||
bool isCRLF(char character);
|
|
||||||
|
|
||||||
char bufferPrint[BUFFER_PRINT];
|
|
||||||
};
|
|
||||||
652
src/boiler.ino
652
src/boiler.ino
File diff suppressed because it is too large
Load Diff
996
src/ems.cpp
996
src/ems.cpp
File diff suppressed because it is too large
Load Diff
126
src/ems.h
126
src/ems.h
@@ -1,26 +1,28 @@
|
|||||||
/*
|
/*
|
||||||
* Header file for EMS.cpp
|
* Header file for EMS.cpp
|
||||||
*
|
*
|
||||||
* You shouldn't need to change much in this file
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#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>
|
||||||
|
|
||||||
// EMS IDs
|
// EMS IDs
|
||||||
#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs
|
#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs
|
||||||
#define EMS_ID_BOILER 0x08 // Fixed - also known as MC10.
|
|
||||||
#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as "Service Key"
|
#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as "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
|
||||||
#define EMS_MAX_TELEGRAM_LENGTH 99 // max length of a telegram, including CRC
|
|
||||||
|
|
||||||
#define EMS_TX_MAXBUFFERSIZE 128 // max size of the buffer. packets are 32 bits
|
// max length of a telegram, including CRC, for Rx and Tx.
|
||||||
|
// This can differs per firmware version and typically 32 is the max
|
||||||
#define EMS_ID_THERMOSTAT_RC20 0x17 // RC20 (e.g. Moduline 300)
|
#define EMS_MAX_TELEGRAM_LENGTH 99
|
||||||
#define EMS_ID_THERMOSTAT_RC30 0x10 // RC30 (e.g. Moduline 400)
|
|
||||||
#define EMS_ID_THERMOSTAT_EASY 0x18 // TC100 (Nefit Easy)
|
|
||||||
|
|
||||||
// define here the EMS telegram types you need
|
// define here the EMS telegram types you need
|
||||||
|
|
||||||
@@ -43,6 +45,9 @@
|
|||||||
|
|
||||||
#define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature
|
#define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature
|
||||||
#define EMS_OFFSET_UBAParameterWW_wwactivated 1 // WW Activated
|
#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...
|
* Thermostat...
|
||||||
@@ -64,6 +69,12 @@
|
|||||||
#define EMS_OFFSET_RC30Set_mode 23 // position of thermostat mode
|
#define EMS_OFFSET_RC30Set_mode 23 // position of thermostat mode
|
||||||
#define EMS_OFFSET_RC30Set_temp 28 // position of thermostat setpoint temperature
|
#define EMS_OFFSET_RC30Set_temp 28 // position of thermostat setpoint temperature
|
||||||
|
|
||||||
|
// RC35 specific - not implemented yet
|
||||||
|
#define EMS_TYPE_RC35StatusMessage 0x3E // is an automatic thermostat broadcast giving us temps
|
||||||
|
#define EMS_TYPE_RC35Set 0x3D // for setting values like temp and mode
|
||||||
|
#define EMS_OFFSET_RC35Set_mode 7 // position of thermostat mode
|
||||||
|
#define EMS_OFFSET_RC35Set_temp 2 // position of thermostat setpoint temperature
|
||||||
|
|
||||||
// Easy specific
|
// Easy specific
|
||||||
#define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat
|
#define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat
|
||||||
|
|
||||||
@@ -71,6 +82,7 @@
|
|||||||
#define EMS_VALUE_INT_ON 1 // boolean true
|
#define EMS_VALUE_INT_ON 1 // boolean true
|
||||||
#define EMS_VALUE_INT_OFF 0 // boolean false
|
#define EMS_VALUE_INT_OFF 0 // boolean false
|
||||||
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit ints
|
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit ints
|
||||||
|
#define EMS_VALUE_LONG_NOTSET 0xFFFFFF // for 3-byte longs
|
||||||
#define EMS_VALUE_FLOAT_NOTSET -255 // float unset
|
#define EMS_VALUE_FLOAT_NOTSET -255 // float unset
|
||||||
|
|
||||||
/* EMS UART transfer status */
|
/* EMS UART transfer status */
|
||||||
@@ -116,6 +128,7 @@ typedef struct {
|
|||||||
bool emsBoilerEnabled; // is the boiler online
|
bool emsBoilerEnabled; // is the boiler online
|
||||||
_EMS_SYS_LOGGING emsLogging; // logging
|
_EMS_SYS_LOGGING emsLogging; // logging
|
||||||
bool emsRefreshed; // fresh data, needs to be pushed out to MQTT
|
bool emsRefreshed; // fresh data, needs to be pushed out to MQTT
|
||||||
|
bool emsBusConnected; // is there an active bus
|
||||||
} _EMS_Sys_Status;
|
} _EMS_Sys_Status;
|
||||||
|
|
||||||
// The Tx send package
|
// The Tx send package
|
||||||
@@ -130,11 +143,13 @@ typedef struct {
|
|||||||
uint8_t comparisonValue; // value to compare against during a validate
|
uint8_t comparisonValue; // value to compare against during a validate
|
||||||
uint8_t comparisonOffset; // offset of where the byte is we want to compare too later
|
uint8_t comparisonOffset; // offset of where the byte is we want to compare too later
|
||||||
uint8_t comparisonPostRead; // after a successful write call this to read
|
uint8_t comparisonPostRead; // after a successful write call this to read
|
||||||
bool hasSent; // has been sent, just pending ack
|
|
||||||
bool forceRefresh; // should we send to MQTT after a successful Tx?
|
bool forceRefresh; // should we send to MQTT after a successful Tx?
|
||||||
uint8_t data[EMS_TX_MAXBUFFERSIZE];
|
unsigned long timestamp; // when created
|
||||||
|
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
|
||||||
} _EMS_TxTelegram;
|
} _EMS_TxTelegram;
|
||||||
|
|
||||||
|
#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
|
||||||
@@ -147,11 +162,42 @@ const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
|
|||||||
0, // comparisonValue
|
0, // comparisonValue
|
||||||
0, // comparisonOffset
|
0, // comparisonOffset
|
||||||
EMS_ID_NONE, // comparisonPostRead
|
EMS_ID_NONE, // comparisonPostRead
|
||||||
false, // hasSent
|
|
||||||
false, // forceRefresh
|
false, // forceRefresh
|
||||||
|
0, // timestamp
|
||||||
{0x00} // data
|
{0x00} // data
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
_EMS_MODEL_ID model_id;
|
||||||
|
uint8_t product_id;
|
||||||
|
uint8_t type_id;
|
||||||
|
char model_string[50];
|
||||||
|
} _Model_Type;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Telegram package defintions
|
* Telegram package defintions
|
||||||
*/
|
*/
|
||||||
@@ -160,6 +206,7 @@ typedef struct { // UBAParameterWW
|
|||||||
uint8_t wWSelTemp; // Warm Water selected temperature
|
uint8_t wWSelTemp; // Warm Water selected temperature
|
||||||
uint8_t wWCircPump; // Warm Water circulation pump Available
|
uint8_t wWCircPump; // Warm Water circulation pump Available
|
||||||
uint8_t wWDesiredTemp; // Warm Water desired temperature
|
uint8_t wWDesiredTemp; // Warm Water desired temperature
|
||||||
|
uint8_t wWComfort; // Warm water comfort or ECO mode
|
||||||
|
|
||||||
// UBAMonitorFast
|
// UBAMonitorFast
|
||||||
uint8_t selFlowTemp; // Selected flow temperature
|
uint8_t selFlowTemp; // Selected flow temperature
|
||||||
@@ -175,30 +222,56 @@ typedef struct { // UBAParameterWW
|
|||||||
uint8_t curBurnPow; // Burner current power
|
uint8_t curBurnPow; // Burner current power
|
||||||
float flameCurr; // Flame current in micro amps
|
float flameCurr; // Flame current in micro amps
|
||||||
float sysPress; // System pressure
|
float sysPress; // System pressure
|
||||||
|
uint8_t serviceCodeChar1; // First Character in status/service code
|
||||||
|
uint8_t serviceCodeChar2; // Second Character in status/service code
|
||||||
|
|
||||||
// UBAMonitorSlow
|
// UBAMonitorSlow
|
||||||
float extTemp; // Outside temperature
|
float extTemp; // Outside temperature
|
||||||
float boilTemp; // Boiler temperature
|
float boilTemp; // Boiler temperature
|
||||||
uint8_t pumpMod; // Pump modulation
|
uint8_t pumpMod; // Pump modulation
|
||||||
uint16_t burnStarts; // # burner restarts
|
uint32_t burnStarts; // # burner restarts
|
||||||
uint16_t burnWorkMin; // Total burner operating time
|
uint32_t burnWorkMin; // Total burner operating time
|
||||||
uint16_t heatWorkMin; // Total heat operating time
|
uint32_t heatWorkMin; // Total heat operating time
|
||||||
|
|
||||||
// UBAMonitorWWMessage
|
// UBAMonitorWWMessage
|
||||||
float wWCurTmp; // Warm Water current temperature:
|
float wWCurTmp; // Warm Water current temperature:
|
||||||
uint32_t wWStarts; // Warm Water # starts
|
uint32_t wWStarts; // Warm Water # starts
|
||||||
uint32_t wWWorkM; // Warm Water # minutes
|
uint32_t wWWorkM; // Warm Water # minutes
|
||||||
uint8_t wWOneTime; // Warm Water one time function on/off
|
uint8_t wWOneTime; // Warm Water one time function on/off
|
||||||
|
uint8_t wWCurFlow; // Warm Water current flow in l/min
|
||||||
|
|
||||||
|
// UBATotalUptimeMessage
|
||||||
|
uint32_t UBAuptime; // Total UBA working hours
|
||||||
|
|
||||||
// calculated values
|
// calculated values
|
||||||
uint8_t tapwaterActive; // Hot tap water is on/off
|
uint8_t tapwaterActive; // Hot tap water is on/off
|
||||||
uint8_t heatingActive; // Central heating is on/off
|
uint8_t heatingActive; // Central heating is on/off
|
||||||
|
|
||||||
|
// settings
|
||||||
|
char version[10];
|
||||||
|
uint8_t type_id;
|
||||||
|
_EMS_MODEL_ID model_id;
|
||||||
} _EMS_Boiler;
|
} _EMS_Boiler;
|
||||||
|
|
||||||
|
// Definition for thermostat type
|
||||||
|
typedef struct {
|
||||||
|
_EMS_MODEL_ID model_id;
|
||||||
|
bool read_supported;
|
||||||
|
bool write_supported;
|
||||||
|
} _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; // thermostat type (RC30, Easy etc)
|
uint8_t type_id; // the type ID of the thermostat
|
||||||
|
_EMS_MODEL_ID model_id; // which Thermostat type
|
||||||
|
bool read_supported;
|
||||||
|
bool write_supported;
|
||||||
|
char version[10];
|
||||||
float setpoint_roomTemp; // current set temp
|
float setpoint_roomTemp; // current set temp
|
||||||
float curr_roomTemp; // current room temp
|
float curr_roomTemp; // current room temp
|
||||||
uint8_t mode; // 0=low, 1=manual, 2=auto
|
uint8_t mode; // 0=low, 1=manual, 2=auto
|
||||||
@@ -215,17 +288,11 @@ 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 {
|
||||||
uint8_t src;
|
_EMS_MODEL_ID model_id;
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
const char typeString[50];
|
const char typeString[50];
|
||||||
EMS_processType_cb processType_cb;
|
EMS_processType_cb processType_cb;
|
||||||
} _EMS_Types;
|
} _EMS_Type;
|
||||||
|
|
||||||
// Definition for thermostat type
|
|
||||||
typedef struct {
|
|
||||||
uint8_t id;
|
|
||||||
const char typeString[50];
|
|
||||||
} _Thermostat_Types;
|
|
||||||
|
|
||||||
// ANSI Colors
|
// ANSI Colors
|
||||||
#define COLOR_RESET "\x1B[0m"
|
#define COLOR_RESET "\x1B[0m"
|
||||||
@@ -255,28 +322,37 @@ void ems_setExperimental(uint8_t value);
|
|||||||
void ems_setPoll(bool b);
|
void ems_setPoll(bool b);
|
||||||
void ems_setTxEnabled(bool b);
|
void ems_setTxEnabled(bool b);
|
||||||
void ems_setThermostatEnabled(bool b);
|
void ems_setThermostatEnabled(bool b);
|
||||||
|
void ems_setBoilerEnabled(bool b);
|
||||||
void ems_setLogging(_EMS_SYS_LOGGING loglevel);
|
void ems_setLogging(_EMS_SYS_LOGGING loglevel);
|
||||||
void ems_setEmsRefreshed(bool b);
|
void ems_setEmsRefreshed(bool b);
|
||||||
|
void ems_setBusConnected(bool b);
|
||||||
|
void ems_setWarmWaterModeComfort(bool comfort);
|
||||||
|
|
||||||
void ems_getThermostatValues();
|
void ems_getThermostatValues();
|
||||||
|
void ems_getBoilerValues();
|
||||||
bool ems_getPoll();
|
bool ems_getPoll();
|
||||||
bool ems_getTxEnabled();
|
bool ems_getTxEnabled();
|
||||||
bool ems_getThermostatEnabled();
|
bool ems_getThermostatEnabled();
|
||||||
bool ems_getBoilerEnabled();
|
bool ems_getBoilerEnabled();
|
||||||
|
bool ems_getBusConnected();
|
||||||
_EMS_SYS_LOGGING ems_getLogging();
|
_EMS_SYS_LOGGING ems_getLogging();
|
||||||
uint8_t ems_getEmsTypesCount();
|
uint8_t ems_getEmsTypesCount();
|
||||||
uint8_t ems_getThermostatTypesCount();
|
|
||||||
bool ems_getEmsRefreshed();
|
bool ems_getEmsRefreshed();
|
||||||
|
void ems_getVersions();
|
||||||
|
_EMS_MODEL_ID ems_getThermostatModel();
|
||||||
|
|
||||||
void ems_printAllTypes();
|
void ems_printAllTypes();
|
||||||
void ems_printThermostatType();
|
char * ems_getThermostatType(char * buffer);
|
||||||
void ems_printTxQueue();
|
void ems_printTxQueue();
|
||||||
|
char * ems_getBoilerType(char * buffer);
|
||||||
|
|
||||||
// private functions
|
// private functions
|
||||||
uint8_t _crcCalculator(uint8_t * data, uint8_t len);
|
uint8_t _crcCalculator(uint8_t * data, uint8_t len);
|
||||||
void _processType(uint8_t * telegram, uint8_t length);
|
void _processType(uint8_t * telegram, uint8_t length);
|
||||||
void _debugPrintPackage(const char * prefix, uint8_t * data, uint8_t len, const char * color);
|
void _debugPrintPackage(const char * prefix, uint8_t * data, uint8_t len, const char * color);
|
||||||
void _ems_clearTxData();
|
void _ems_clearTxData();
|
||||||
|
int _ems_findModel(_EMS_MODEL_ID model_id);
|
||||||
|
char * _ems_buildModelString(char * buffer, uint8_t size, _EMS_MODEL_ID 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;
|
||||||
|
|||||||
@@ -9,31 +9,25 @@
|
|||||||
|
|
||||||
// these are set as -D build flags during compilation
|
// these are set as -D build flags during compilation
|
||||||
// they can be set in platformio.ini or alternatively hard coded here
|
// they can be set in platformio.ini or alternatively hard coded here
|
||||||
|
/*
|
||||||
|
#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>"
|
||||||
|
*/
|
||||||
|
|
||||||
// WIFI settings
|
// default values for shower logic on/off
|
||||||
//#define WIFI_SSID "<my_ssid>"
|
|
||||||
//#define WIFI_PASSWORD "<my_password>"
|
|
||||||
|
|
||||||
// MQTT settings
|
|
||||||
// Note port is the default 1883
|
|
||||||
//#define MQTT_IP "<broker_ip>"
|
|
||||||
//#define MQTT_USER "<broker_username>"
|
|
||||||
//#define MQTT_PASS "<broker_password>"
|
|
||||||
|
|
||||||
// default values
|
|
||||||
#define BOILER_THERMOSTAT_ENABLED 1 // thermostat support is enabled (1)
|
|
||||||
#define BOILER_SHOWER_TIMER 1 // monitors how long a shower has taken
|
#define BOILER_SHOWER_TIMER 1 // monitors how long a shower has taken
|
||||||
#define BOILER_SHOWER_ALERT 0 // send alert if showetime exceeded
|
#define BOILER_SHOWER_ALERT 0 // send alert if showetime exceeded
|
||||||
|
|
||||||
// define here the Thermostat type. see ems.h for the supported types
|
|
||||||
#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_RC20
|
|
||||||
//#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_RC30
|
|
||||||
//#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_EASY
|
|
||||||
|
|
||||||
// trigger settings to determine if hot tap water or the heating is active
|
// trigger settings to determine if hot tap water or the heating is active
|
||||||
#define EMS_BOILER_BURNPOWER_TAPWATER 100
|
#define EMS_BOILER_BURNPOWER_TAPWATER 100
|
||||||
#define EMS_BOILER_SELFLOWTEMP_HEATING 70
|
#define EMS_BOILER_SELFLOWTEMP_HEATING 70
|
||||||
|
|
||||||
|
//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
|
// 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_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_MIN_DURATION 120000 // in ms. 2 minutes, before recognizing its a shower
|
||||||
@@ -41,8 +35,10 @@
|
|||||||
#define SHOWER_OFFSET_TIME 5000 // in ms. 5 seconds grace time, to calibrate actual time under the shower
|
#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
|
#define SHOWER_COLDSHOT_DURATION 10 // in seconds. 10 seconds for cold water before turning back hot water
|
||||||
|
|
||||||
// if using LEDs to show traffic, configure the GPIOs here
|
// The LED for showing connection errors, either onboard or via an external pull-up LED
|
||||||
// only works if -DUSE_LED is set in platformio.ini
|
// can be disabled using -DNO_LED build flag in platformio.ini
|
||||||
#define LED_RX D1 // GPIO5
|
#define BOILER_LED LED_BUILTIN // LED_BULLETIN for onboard LED
|
||||||
#define LED_TX D2 // GPIO4
|
//#define BOILER_LED D1 // for external LED like on the latest bbqkees boards
|
||||||
#define LED_ERR D3 // GPIO0
|
|
||||||
|
// set this if using an external temperature sensor like a DS18B20
|
||||||
|
#define TEMPERATURE_SENSOR_PIN D7
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
#define APP_NAME "EMS-ESP-Boiler"
|
#define APP_NAME "EMS-ESP-Boiler"
|
||||||
#define APP_VERSION "1.1.0"
|
#define APP_VERSION "1.2.0"
|
||||||
|
#define APP_HOSTNAME "boiler"
|
||||||
|
|||||||
Reference in New Issue
Block a user