From d671e64c5f45655618e48ae558f4dfead0b8d41f Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 24 Mar 2019 21:13:08 +0100 Subject: [PATCH] 1.6 --- .gitignore | 3 +- CHANGELOG.md | 30 +- README.md | 60 +-- checkcode.py | 26 + clean_fw.py | 14 + debug.py | 22 + decoder.py | 307 +++++++++++ doc/telnet/telnet_menu.jpg | Bin 139774 -> 68345 bytes doc/telnet/telnet_stats.PNG | Bin 45917 -> 45112 bytes lib/myESP/MyESP.cpp | 808 +++++++++++++++++++--------- lib/myESP/MyESP.h | 89 ++- platformio.ini-example | 13 +- rename_fw.py | 8 + src/ds18.cpp | 35 +- src/ds18.h | 13 +- src/{ems-esp.ino => ems-esp.cpp} | 895 ++++++++++++++++++++----------- src/ems.cpp | 743 +++++++++++++++---------- src/ems.h | 87 ++- src/ems_devices.h | 52 +- src/emsuart.cpp | 41 +- src/emsuart.h | 11 +- src/my_config.h | 30 +- src/version.h | 2 +- 23 files changed, 2286 insertions(+), 1003 deletions(-) create mode 100644 checkcode.py create mode 100644 clean_fw.py create mode 100644 debug.py create mode 100644 decoder.py create mode 100644 rename_fw.py rename src/{ems-esp.ino => ems-esp.cpp} (58%) diff --git a/.gitignore b/.gitignore index e93465a46..074281300 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ platformio.ini lib/readme.txt .travis.yml -*.py +stackdmp.txt +*.jar \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f059cb5f7..12c35617c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.6.0] 2019-03-24 + +### Added + +- `system` command to show ESP8266 stats +- `crash` command to see stack of last system crash, with .py files to track stack dump (compile with `-DCRASH`) +- publish dallas external temp sensors to MQTT (thanks @JewelZB) +- shower timer and shower alert options available via set commands +- added support for warm water modes Hot, Comfort and Intelligent [(issue 67)](https://github.com/proddy/EMS-ESP/issues/67) +- added `set publish_time` to set how often to publish MQTT +- support for SM10 Solar Module including MQTT [(issue 77)](https://github.com/proddy/EMS-ESP/issues/77) +- `refresh` command to force a fetch of all known data from the connected EMS devices + +### Fixed + +- incorrect rendering of null temperature values (the -3200 degrees issue) +- OTA is more stable +- Added a hack to overcome WiFi power issues in arduino core 2.5.0 libraries causing constant wifi re-connects +- Performance issues with telnet output + +### Changed + +- included various fixes and suggestions from @nomis +- upgraded MyESP library with many optimizations +- `test_mode` renamed to `silent_mode` +- `set wifi` replaced with `set wifi_ssid` and `set wifi_password` to allow values with spaces +- EMS values are stored in the raw format and only converted to strings when displayed or published, removing the need for parsing floats +- All floating point temperatures are to one decimal place [(issue 79)](https://github.com/proddy/EMS-ESP/issues/79) + ## [1.5.6] 2019-03-09 ### Added @@ -16,7 +45,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - upgraded MyESP library - minor changes - ## [1.5.5] 2019-03-07 ### Fixed diff --git a/README.md b/README.md index f9aad9cf8..d54127a5b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ EMS-ESP is a project to build an electronic controller circuit using an Espressi There are 3 parts to this project, first the design of the circuit, secondly the code for the ESP8266 microcontroller firmware with telnet and MQTT support, and lastly an example configuration for Home Assistant to monitor the data and issue direct commands via a MQTT broker. [![Codacy Badge](https://api.codacy.com/project/badge/Grade/b8880625bdf841d4adb2829732030887)](https://app.codacy.com/app/proddy/EMS-ESP?utm_source=github.com&utm_medium=referral&utm_content=proddy/EMS-ESP&utm_campaign=Badge_Grade_Settings) -[![version](https://img.shields.io/badge/version-1.5.5-brightgreen.svg)](CHANGELOG.md) +[![version](https://img.shields.io/badge/version-1.6.0-brightgreen.svg)](CHANGELOG.md) - [EMS-ESP](#ems-esp) - [Introduction](#introduction) @@ -30,7 +30,6 @@ There are 3 parts to this project, first the design of the circuit, secondly the - [Home Assistant Configuration](#home-assistant-configuration) - [Building The Firmware](#building-the-firmware) - [Using PlatformIO Standalone](#using-platformio-standalone) - - [Building Using Arduino IDE](#building-using-arduino-ide) - [Using the Pre-built Firmware](#using-the-pre-built-firmware) - [Troubleshooting](#troubleshooting) - [Known Issues](#known-issues) @@ -64,14 +63,14 @@ The code and circuit has been tested with a few ESP8266 development boards such 1. Either build the circuit described below or purchase a ready built board from bbqkees. 2. Grab any ESP8266 dev board. The latest bbqkees boards have a Wemos D1 pre-mounted with a copy of this firmware. -3. Optionally add external Dallas temperature sensors and an external LED. The default pins for these are D1 and D5 respectively. -4. Decide whether to compile and upload the code yourself using PlatformIO or just upload the pre-baked firmware using the esptool (read these [instructions](#using-the-pre-built-firmware)). If you want to build yourself now is the time to customize your settings in `my_custom.h`. Upload the firmware. -5. Connect a USB 5v power supply to the ESP8266 board, either via laptop/PC or external power supply. -7. When the ESP8266 starts up for the first time the onboard LED will be flashing. This is because the EMS bus is not yet connected. +3. Optionally add external Dallas temperature sensors (to D1) and an external LED (to D5). +4. Decide whether to compile and upload the code yourself using PlatformIO or just upload the pre-baked firmware using the esptool (read these [instructions](#using-the-pre-built-firmware)). If you want to build yourself now is the time to customize your settings in `my_custom.h`. Upload the firmware via USB. +5. Connect an external USB 5v power adapter to the ESP8266 board. +7. When the ESP8266 starts up for the first time the onboard LED will be flashing. This is because the EMS bus is not yet connected and receiving data. 8. If you haven't hardcoded the WiFi credentials in step 4, the ESP8266 will boot up in a WiFi Access Point (AP) mode with the ssid name `ems-esp`. Now you can either use a laptop and connect to this AP using Telnet to `192.168.1.4` or if its powered from a computers USB use a Serial monitor tool to the ESP's COM port. Tip: to enable Telnet on Windows 10 run `dism /online /Enable-Feature /FeatureName:TelnetClient` or install something like [putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html). -9. Next is to change some of the settings. Type `set` to list the current stored settings. Use `set wifi` to add your wifi credentials and if you're using MQTT set the host, username and password. There is no need to reboot the device. +9. Next is to customize some of the onboard settings. Type `set` to list the current stored settings and `?` to see the syntax. Use `set wifi_ssid` and `set wifi_password` to add your WiFi credentials and if you're using MQTT set the host, username and password. There is no need to reboot the ESP. 10. The `led_gpio` will default to the onboard LED (which is probably blinking now). Ignore `thermostat_type` and `boiler_type` as these will be auto-detected hopefully later on. -11. **Important**: If `serial` is set to `on` set it to `off` using `set serial off`. The EMS bus is disabled when the serial is on. This mode is only used for setting up a new board or debugging startup issues. +11. **Important**: By default the serial port is enabled and the EMS bus disabled. This is to allow users to configure their ESP via the serial monitor when pluged into a PC/laptop. You must disable serial with `set serial off` to get the EMS transmission working. 12. Hook up the ESP to the EMS board as follows: | EMS board | ESP8266 dev board | @@ -79,11 +78,11 @@ The code and circuit has been tested with a few ESP8266 development boards such | Ground/G/J2| GND/G | | Rx/J2 | D7 | | Tx/J2 | D8 | -| VC/J2 | 3v3 or 5v | -13. Connect the EMS lines to the ESP. This can be done via the two EMS wires or via the 3.5" service jack if you have an bbqkees board. +| VC/J2 | 3v3 | +13. Connect the EMS lines to the ESP. This can be done via the two EMS wires or via the 3.5mm service jack if you have an bbqkees board. 14. Reboot the ESP, either by the reset switch or pulling the power. -15. The ESP will first perform an autodetect to try and discover the EMS devices attached. If your boiler and thermostat are recognized it will set these types and store them for ever and ever. You can trace the output by telnet'ing to the board `telnet ems-esp.local`. Also type `info` to check what happened. -16. If your boiler/thermostat is not discovered create a GitHub issue stating the type and product ID. These will be added to the file `ems_devices.h` in a future release. +15. The ESP will first perform an autodetect to try and discover the EMS devices attached. If your boiler and thermostat are recognized it will set these types and store them for ever and ever. You can trace the output by telnet'ing to the board `telnet ems-esp.local`. Also use `info` to check the status. +16. If your boiler/thermostat is not discovered create a GitHub issue stating the type and Product ID. These will be added to the file `ems_devices.h` in a future release. 17. If all is well and there is traffic on the EMS bus the onboard LED will stop blinking and be permanently on. If this is annoying you can disable with `set led off`. To see the EMS messages type `set log v` for verbose logging. 18. And all is not well, check the wiring, make sure serial is off and look at the telnet session for errors. If in doubt, wipe the ESP with `pio run -t erase` and start again with step #3 @@ -127,8 +126,8 @@ The EMS circuit will work with both 3.3V and 5V. It's easiest though to power di - via the USB if your dev board has one - using an external 5V power supply into the 5V vin on the board -- powering from the 3.5" service jack on the boiler. This will give you 8V so you need a buck converter (like a [Pololu D24C22F5](https://www.pololu.com/product/2858)) to step this down to 5V to provide enough power to the ESP8266 (250mA at least) -- powering from the EMS line, which is 15V A/C and using a buck converter as described above. Note the current design has stability issues when sending packages in this configuration so this is not recommended yet if you plan to many send commands to the thermostat or boiler. +- powering from the 3.5mm service jack (stereo jack) on the boiler. This will give you 8V so you need a buck converter (like a [Pololu D24C22F5](https://www.pololu.com/product/2858)) to step this down to 5V to provide enough power to the ESP8266 (250mA at least) +- powering direct from the EMS line, which is 15V DC and using a buck converter as described above. | With Power Circuit | | ------------------------------------------ | @@ -203,13 +202,15 @@ Every telegram sent is echo'd back to Rx, along the same Bus used for all Rx/Tx `ems.cpp` is the logic to read the EMS data packets (telegrams), validates them and process them based on the type. -`ems-esp.ino` is the Arduino code for the ESP8266 that kicks it all off. This is where we have specific logic such as the code to monitor and alert on the Shower timer and light up the LEDs. +`ems-esp.cpp` 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. `my_config.h` has all the custom settings tailored to your environment. Specific values here are also stored in the ESP's SPIFFs (File system). `ems_devices.h` has all the configuration for the known EMS devices currently supported. -`MyESP.cpp` is my custom library to handle WiFi, MQTT and Telnet. Uses a modified version of [TelnetSpy](https://github.com/yasheena/telnetspy) +`MyESP.cpp` is my custom library to handle WiFi, MQTT and Telnet. Uses a modified version of [TelnetSpy](https://github.com/yasheena/telnetspy). + +`ds18.*` are the Dallas libraries for any external temperature sensors. ### Special EMS Types @@ -228,18 +229,15 @@ In `ems.cpp` you can add scheduled calls to specific EMS types in the functions I am still working on adding more support to known thermostats. Any contributions here are welcome. The know types are listed in `ems_devices.h` and include -- RC20 and RC30, both are fully supported -- RC10 support is being added +- RC10, RC20 and RC30 are fully supported - RC35 with support for the 1st heating circuit (HC1) -- TC100/TC200/Easy but only with support for reading the temperatures. There seems to be no way to set settings using EMS bus messages that I know of. One option is to send XMPP messages but a special server is needed and out of scope for this project. +- TC100/TC200/Easy but only with support for *reading* the temperature values. There seems to be no way to set settings using EMS bus messages that I know of. One option is to send XMPP messages but a special server is needed and out of scope for this project. ### Customizing The Code -- To configure for your thermostat and specific boiler settings, modify `my_config.h`. Here you can - - set flags for enabled/disabling functionality such as `BOILER_SHOWER_ENABLED` and `BOILER_SHOWER_TIMER`. - - Set WIFI and MQTT settings. The values can also be set from the telnet command menu using the **set** command. -- To add new handlers for EMS data types, first create a callback function and add to the `EMS_Types` array at the top of the file `ems.cpp` and modify `ems.h` -- To add new devices modify `ems_devices.h` +- To configure for your thermostat and specific boiler settings, modify `my_config.h`. +- Most values can also be set from the telnet command menu using the **set** command. +- 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`. Also add to `ems_devices.h`. ### Using MQTT @@ -302,6 +300,7 @@ Make sure Python 2.7 is installed, then... % pip install -U platformio % sudo platformio upgrade % platformio platform update +% platformio lib upgrade % git clone https://github.com/proddy/EMS-ESP.git % cd EMS-ESP @@ -312,17 +311,6 @@ edit `platformio.ini` to set `env_default` to your board type, then % platformio run -t upload ``` -### Building Using Arduino IDE - -Porting to the Arduino IDE can be a little tricky but it did it once. Something along these lines: - -- Add the ESP8266 boards (from Preferences add Additional Board URL `http://arduino.esp8266.com/stable/package_esp8266com_index.json`) -- Go to Boards Manager and install ESP8266 2.4.x platform. Make sure your board supports SPIFFS. -- Select your ESP8266 from Tools->Boards and the correct port with Tools->Port -- From the Library Manager install the needed libraries from platformio.ini. Note make sure you pick ArduinoJson v5 (5.13.4 and above) and not v6. See https://arduinojson.org/v5/doc/ -- Put all the files in a single sketch folder -- cross your fingers and hit CTRL-R to compile - ## Using the Pre-built Firmware pre-baked firmware for the Wemos D1 mini is available in the GitHub [releases](https://github.com/proddy/EMS-ESP/releases) which you can upload yourself using the [esptool](https://github.com/espressif/esptool) bootloader like `esptool.py -p write_flash 0x00000 `. Here's how to set it up on Windows: @@ -330,7 +318,7 @@ pre-baked firmware for the Wemos D1 mini is available in the GitHub [releases](h 1. Check if you have **python 2.7** installed. If not [download it](https://www.python.org/downloads/) and make sure you select the option to add Python to the windows PATH 2. Then install the ESPTool by running `pip install esptool` from a command prompt -The ESP8266 will start in Access Point (AP) mode. Connect via WiFi to the SSID **EMS-ESP** and telnet to **192.168.4.1**. Then use the `set wifi` command to configure your own network settings like `set wifi your_ssid your_password`. Alternatively connect the ESP8266 to your PC and open a Serial monitor (with baud 115200) to configure the settings. Make sure you disable Serial support before connecting the EMS lines using `set serial off`. +The ESP8266 will start in Access Point (AP) mode. Connect via WiFi to the SSID **EMS-ESP** and telnet to **192.168.4.1**. Then use the `set wifi_ssid/set wifi_password` command to configure your own network settings. Alternatively connect the ESP8266 to your PC and open a Serial monitor (with baud 115200) to configure the settings. Make sure you disable Serial support before connecting the EMS lines using `set serial off`. `set` wil list all currently stored settings. diff --git a/checkcode.py b/checkcode.py new file mode 100644 index 000000000..53da63054 --- /dev/null +++ b/checkcode.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +from subprocess import call +import os +Import("env") + +def code_check(source, target, env): + print("\n** Starting cppcheck...") + call(["cppcheck", os.getcwd()+"/.", "--force", "--enable=all"]) + print("\n** Finished cppcheck...\n") + print("\n** Starting cpplint...") + call(["cpplint", "--extensions=ino,cpp,h", "--filter=-legal/copyright,-build/include,-whitespace", + "--linelength=120", "--recursive", "src", "lib/myESP"]) + print("\n** Finished cpplint...") + +#my_flags = env.ParseFlags(env['BUILD_FLAGS']) +#defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")} +# print defines +# print env.Dump() + +# built in targets: (buildprog, size, upload, program, buildfs, uploadfs, uploadfsota) +env.AddPreAction("buildprog", code_check) +# env.AddPostAction(.....) + +# see http://docs.platformio.org/en/latest/projectconf/advanced_scripting.html#before-pre-and-after-post-actions +# env.Replace(PROGNAME="firmware_%s" % defines.get("VERSION")) +# env.Replace(PROGNAME="firmware_%s" % env['BOARD']) diff --git a/clean_fw.py b/clean_fw.py new file mode 100644 index 000000000..140f23ee4 --- /dev/null +++ b/clean_fw.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +from subprocess import call +import os +Import("env") + +def clean(source, target, env): + print("\n** Starting clean...") + call(["pio", "run", "-t", "erase"]) + call(["esptool.py", "-p COM6", "write_flash 0x00000", os.getcwd()+"../firmware/*.bin"]) + print("\n** Finished clean.") + +# built in targets: (buildprog, size, upload, program, buildfs, uploadfs, uploadfsota) +env.AddPreAction("buildprog", clean) + diff --git a/debug.py b/debug.py new file mode 100644 index 000000000..c54d05902 --- /dev/null +++ b/debug.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +from subprocess import call +import os + +# example stackdmp.txt would contain text like below copied & pasted from a 'crash dump' command +# >>>stack>>> +# 3fffff20: 3fff32f0 00000003 3fff3028 402101b2 +# 3fffff30: 3fffdad0 3fff3280 0000000d 402148aa +# 3fffff40: 3fffdad0 3fff3280 3fff326c 3fff32f0 +# 3fffff50: 0000000d 3fff326c 3fff3028 402103bd +# 3fffff60: 0000000d 3fff34cc 40211de4 3fff34cc +# 3fffff70: 3fff3028 3fff14c4 3fff301c 3fff34cc +# 3fffff80: 3fffdad0 3fff14c4 3fff3028 40210493 +# 3fffff90: 3fffdad0 00000000 3fff14c4 4020a738 +# 3fffffa0: 3fffdad0 00000000 3fff349c 40211e90 +# 3fffffb0: feefeffe feefeffe 3ffe8558 40100b01 +# <<[0-9]*)\\):$") +COUNTER_REGEX = re.compile('^epc1=(?P0x[0-9a-f]+) epc2=(?P0x[0-9a-f]+) epc3=(?P0x[0-9a-f]+) ' + 'excvaddr=(?P0x[0-9a-f]+) depc=(?P0x[0-9a-f]+)$') +CTX_REGEX = re.compile("^ctx: (?P.+)$") +POINTER_REGEX = re.compile('^sp: (?P[0-9a-f]+) end: (?P[0-9a-f]+) offset: (?P[0-9a-f]+)$') +STACK_BEGIN = '>>>stack>>>' +STACK_END = '<<[0-9a-f]+):\W+(?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+)(\W.*)?$') + +StackLine = namedtuple("StackLine", ["offset", "content"]) + + +class ExceptionDataParser(object): + def __init__(self): + self.exception = None + + self.epc1 = None + self.epc2 = None + self.epc3 = None + self.excvaddr = None + self.depc = None + + self.ctx = None + + self.sp = None + self.end = None + self.offset = None + + self.stack = [] + + def _parse_exception(self, line): + match = EXCEPTION_REGEX.match(line) + if match is not None: + self.exception = int(match.group('exc')) + return self._parse_counters + return self._parse_exception + + def _parse_counters(self, line): + match = COUNTER_REGEX.match(line) + if match is not None: + self.epc1 = match.group("epc1") + self.epc2 = match.group("epc2") + self.epc3 = match.group("epc3") + self.excvaddr = match.group("excvaddr") + self.depc = match.group("depc") + return self._parse_ctx + return self._parse_counters + + def _parse_ctx(self, line): + match = CTX_REGEX.match(line) + if match is not None: + self.ctx = match.group("ctx") + return self._parse_pointers + return self._parse_ctx + + def _parse_pointers(self, line): + match = POINTER_REGEX.match(line) + if match is not None: + self.sp = match.group("sp") + self.end = match.group("end") + self.offset = match.group("offset") + return self._parse_stack_begin + return self._parse_pointers + + def _parse_stack_begin(self, line): + if line == STACK_BEGIN: + return self._parse_stack_line + return self._parse_stack_begin + + def _parse_stack_line(self, line): + if line != STACK_END: + match = STACK_REGEX.match(line) + if match is not None: + self.stack.append(StackLine(offset=match.group("off"), + content=(match.group("c1"), match.group("c2"), match.group("c3"), + match.group("c4")))) + return self._parse_stack_line + return None + + def parse_file(self, file, stack_only=False): + func = self._parse_exception + if stack_only: + func = self._parse_stack_begin + + for line in file: + func = func(line.strip()) + if func is None: + break + + if func is not None: + print("ERROR: Parser not complete!") + sys.exit(1) + + +class AddressResolver(object): + def __init__(self, tool_path, elf_path): + self._tool = tool_path + self._elf = elf_path + self._address_map = {} + + def _lookup(self, addresses): + cmd = [self._tool, "-aipfC", "-e", self._elf] + [addr for addr in addresses if addr is not None] + + if sys.version_info[0] < 3: + output = subprocess.check_output(cmd) + else: + output = subprocess.check_output(cmd, encoding="utf-8") + + line_regex = re.compile("^(?P[0-9a-fx]+): (?P.+)$") + + last = None + for line in output.splitlines(): + line = line.strip() + match = line_regex.match(line) + + if match is None: + if last is not None and line.startswith('(inlined by)'): + line = line [12:].strip() + self._address_map[last] += ("\n \-> inlined by: " + line) + continue + + if match.group("result") == '?? ??:0': + continue + + self._address_map[match.group("addr")] = match.group("result") + last = match.group("addr") + + def fill(self, parser): + addresses = [parser.epc1, parser.epc2, parser.epc3, parser.excvaddr, parser.sp, parser.end, parser.offset] + for line in parser.stack: + addresses.extend(line.content) + + self._lookup(addresses) + + def _sanitize_addr(self, addr): + if addr.startswith("0x"): + addr = addr[2:] + + fill = "0" * (8 - len(addr)) + return "0x" + fill + addr + + def resolve_addr(self, addr): + out = self._sanitize_addr(addr) + + if out in self._address_map: + out += ": " + self._address_map[out] + + return out + + def resolve_stack_addr(self, addr, full=True): + addr = self._sanitize_addr(addr) + if addr in self._address_map: + return addr + ": " + self._address_map[addr] + + if full: + return "[DATA (0x" + addr + ")]" + + return None + + +def print_addr(name, value, resolver): + print("{}:{} {}".format(name, " " * (8 - len(name)), resolver.resolve_addr(value))) + + +def print_stack_full(lines, resolver): + print("stack:") + for line in lines: + print(line.offset + ":") + for content in line.content: + print(" " + resolver.resolve_stack_addr(content)) + + +def print_stack(lines, resolver): + print("stack:") + for line in lines: + for content in line.content: + out = resolver.resolve_stack_addr(content, full=False) + if out is None: + continue + print(out) + + +def print_result(parser, resolver, full=True, stack_only=False): + if not stack_only: + print('Exception: {} ({})'.format(parser.exception, EXCEPTIONS[parser.exception])) + + print("") + print_addr("epc1", parser.epc1, resolver) + print_addr("epc2", parser.epc2, resolver) + print_addr("epc3", parser.epc3, resolver) + print_addr("excvaddr", parser.excvaddr, resolver) + print_addr("depc", parser.depc, resolver) + + print("") + print("ctx: " + parser.ctx) + + print("") + print_addr("sp", parser.sp, resolver) + print_addr("end", parser.end, resolver) + print_addr("offset", parser.offset, resolver) + + print("") + if full: + print_stack_full(parser.stack, resolver) + else: + print_stack(parser.stack, resolver) + + +def parse_args(): + parser = argparse.ArgumentParser(description="decode ESP Stacktraces.") + + parser.add_argument("-p", "--platform", help="The platform to decode from", choices=PLATFORMS.keys(), + default="ESP8266") + parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain", + default="~/.platformio/packages/toolchain-xtensa/") + parser.add_argument("-e", "--elf", help="path to elf file", required=True) + parser.add_argument("-f", "--full", help="Print full stack dump", action="store_true") + parser.add_argument("-s", "--stack_only", help="Decode only a stractrace", action="store_true") + parser.add_argument("file", help="The file to read the exception data from ('-' for STDIN)", default="-") + + return parser.parse_args() + + +if __name__ == "__main__": + args = parse_args() + + if args.file == "-": + file = sys.stdin + else: + if not os.path.exists(args.file): + print("ERROR: file " + args.file + " not found") + sys.exit(1) + file = open(args.file, "r") + + addr2line = os.path.join(os.path.abspath(os.path.expanduser(args.tool)), + "bin/xtensa-" + PLATFORMS[args.platform] + "-elf-addr2line.exe") + if not os.path.exists(addr2line): + print("ERROR: addr2line not found (" + addr2line + ")") + + elf_file = os.path.abspath(os.path.expanduser(args.elf)) + if not os.path.exists(elf_file): + print("ERROR: elf file not found (" + elf_file + ")") + + parser = ExceptionDataParser() + resolver = AddressResolver(addr2line, elf_file) + + parser.parse_file(file, args.stack_only) + resolver.fill(parser) + + print_result(parser, resolver, args.full, args.stack_only) diff --git a/doc/telnet/telnet_menu.jpg b/doc/telnet/telnet_menu.jpg index 9355e493faa6f598efbec9cc906ff5d0dbfe6052..563fdd8bac40f29e7f3732d5aa12d369ddfe3bf6 100644 GIT binary patch literal 68345 zcmeFZ1yo#3n=ab8yF0;yyF0-`aM$3_SmW**2u^_D1b26LcMWvo79hA2vWM=*K3qS?{FddIb0SIr$P~Xy` zZvr4adiz5BEB1Hz8-c$O_#1)05%?Q{zY+L95djqwH@i2xq5h2{Ae#P#!`~d?|HRht zx6OYe@HYZ~Bk(r@e_P9SOev}oh{V8l{Cz} zZOsJC>BU6RkcB-3J?$OsEnH0~J?-rrTm(HusQ!_=;9LB!!)#Qd!p`QFf@&XR{;Bbn z5~2F%y?A(duzGN@f}E|`I0OU)*w{JQI5}C~G+11`99&I2SsYxb|CzxD3l}qIprb1g zucsBf5Y{kLf}88{CDg6-wdvQqly*|Z~V~XjR?Mi z0a5@2I5>DXSOj=DctivQL}V*SF)bZE!+S<<9$r3v z0YRw`(lWAg@*mYTG_|yKboI>4EiA39fi^C#ZtfnQUfv<0Vc`*9BBK(Mz9pxmeosr! z%P%M_DlRE4tE+ElY-(<4ZR_hF7#tcN86BIOUszmP{<*TcwY{^uw}0^K@aW?5>iXvP z?*8HNFT0=sF#lrKzgqTxu?y?XE@)U-7+8eA?1F;!coP^bSh#l_@YoWn2qsQARGdMG zxRMD!YI~8Wxzx__Or2+u@oBiXXfOUU?H`u?#|#VpFIo1lhW(pes{m9OsJF_4!2*Z@ zu59CO?60eAZpZs0H;;Y3DpanP^G#Ev4`N_@lXz*68&hV6 zj|d3l=f)sD(H1M9d`7d>oEPm|HnpsKJ_K_Pm~lk(2sohsCg+spr|9!`t6PVBw&jwx zaf&SFwL##_=7f3pD-_sI>v0B!X~do^WhuC zH`JWc5(l12$)lB%gO)X%FH)?7M3*WBeh3ycK6jHm4&6)LyA;cNuK?IX`!0$f=_foa zmg}SMI#Z_Jn|rDV$;j-R(3D6%jo(RSceqY*ud-yNM8fUom&m2^0jXEPU~r^nvQClV zc-4%qJ|SAqB-MSrHkzw>`ue7!(lU1CWF{Be_ccS!nK1e;h%0$;`Ed2;;esqaw~mx< z-o^B#_rxL_K4x_(pLWFFlU^+q>!U6%T~{J+NX_oevx4+4Pk(l;u;6K_9|&UWPYTJQ z1Og~G$&vV5{m!5C4fI!fD^?Z!?|`=4@H$FE0^2MYam%er8fzw2aVA z+)}ltPrxUX+O1$+q4(Y)k$}le2nm|hSQM5jwdFn6-#p3=sf%uV2NM_jld zXwF50HO8D=l<*U0!ruWGP?^+m+as_|a)OV>rMAcny1|pLh&bhyoM6>=?0E%P?a|ix zaY#2u-U!K`ng>;C7$gHtc-gsWQ%`HY6b(`@0K7zt%41yAus^r9d-zEs8@2~dGBA@Q zdXWY?GZ5T72YhytY-iC{nq#^4V`T)T9XjuCotus|c;x5V^Q!7p3OqcJ2P zN649NFh8Y#WVI!NvMfGdlq-X1q>+ZDp+`N)5mtKmCrQ2G+4x@Q#ot3BA2U zLS&(w*>2ex$x7E)9$yBn#X?tZ$ZLp0rm*Vs(X2EcxUZ&9Ue5kxCzxGXYlXOMche>M zT!c~0U7M-AwsxzMc+ss=2F>2}A!TWBAB}cg0bo~!Wa~~7%_mKtp86?5BKwcJwjz~6 zo{COCbF->0fA2?ye|{1@(o?^zZ>blU3C!BvPN2sW+H zt!2Qru+hZ1A(B)_i&%R)$b%g`J63+59{~69saw@m(2q&HHhl22;T-o*<=cweyGb|y zna&Ei+RP7K*6T9+!`L{(23NhGsBuOpnN1?HUguB68QlJNOv5M=8q$oPA5V z|MJ8TDzL4YU?~R1dI89lOG~=9s1{=;2++LGxC!iiNMnC||Kz?mN}bcvK^n{Od3o!t zO9g3d+$_I9rH^SwW#?`#OL*B9iv?Dko!E7J{o#9iIbmtV8ARM9_a{GvzvnPIX{q(r zbVBD+eza2FSH5KO2W#vr!2Az(yd+CBa&OB+bLd(-0D5JVOp(HJJ?A@c(mzxXKb_Opu4yc!=71G46W-CX~R`rjqw;z&4qJnziCYOy!d52ZLH+Tm(%X|jD!SsVPw-r+ArAlI>*|2^^k&8kHD`XG+1pP(K(4Z z3lT>U2#=sn)cUtiOi{j;hMs?1R*GY2sSme+1ct6wh0@K)L5cTls?PAXHWyfXyER9G zgvH7Riw(!gdc)t7RpEP$!ZHpvh8iDG9P^L6@eJ103CP0fDj|x**ywv^Xfy(|-{)~n zQ-8cu)0d|0aajzVC|tN3F$tO08#`V~$)91!i!w!K$P4`ME+TY0&-#8$>75s}VbdX% z*Wa%790%iSwXNdZk*s!xqilX zP+wqRl4eK~;zVm}TNXgrOJic3UR;fQP?^xmpG_3V!Uj_4&KdCgEP@J|0q8yP9Lsr0 zIW|7!0s49F9xqK+P6QDZv5m&5Mnr82r4`u;6tj1oPVV5QMQu}+snv)f- zYG&vhbz4?l6Q?k>O1X8KzUD}+k52n@!9wL`)qytZOha9ElDUNAr^Wit6&)<9 z;sxu5EAoBm`0?!7H}>+bf1S(_Uo%cl&PpFhXy})xCZXU-yVlTzw8rH=gz)J~H=B3H zHH$(4Y*J{$!KqXVpL-i!V=&xswV#${kdquFEwLQipV~6RcEY{T)rx;1O~q6J^1%FF z?zeQ=4C+13>J&Jf)D1wiMWT)NVWIJp2nI9jg<^YUCgdbf!D~ck0%W+-Y0Gh4KM+4x z|2+E<_lNI~kXr@Yp4W@R^9V*-Hn_eWWVhWQj8m{;vzj%xA;kigu(JrCB8;wd1b@TC zGLOkX)~%_)AlijSH!A+IafpPPT03Ogf-shjw3E%(+Y7a0X%NfFm5^}6BpTYEC&D-{ zb^fbmVLbq24KDJW<*Wk?e8&W2o`8-d4AkR@8T_KYQAy*ESDXDuBe-8;R2O&9#SL$M z1zZ}bQ<}mzhN+X;wH0k%P4h6puHeERg}5r~5S34}l;LTm7Belh%Eot~M~MC!qf*^X*c2$Z0=@ z?wR?|L&7~L2U}ESptP4Jt&b~cJI&NG+N^tuAnND6({ruccI4zYK0YL7F26dtEmp`N z9gHXvNk)^uDS{fDMei1ime7~%_dmiGdv!lV^OBxJP%lm0t<`XVf1DiGUMLr&huloWp30pA@Yb3n$*dEVUlZTFRb~Q~a>j0>QZTJzZp~Jt5dz+U za^Xn&2kjSMwEa)Bercb%SOG#&J7EGnqPdHzsG+Zba?n_rR?6&keyS0%8pXF;>m7G`gAPta}zP!aE5NGhr;F5SHZ0?t#xZX*ep43q^qn=I4?d zz+}Ga@5ZMFfihb&AmPoZ%W*JMjNqfK!pKwWmUAMjL@xqDLhsXi78d&Mj;N(MF6JwK zxfu_-^ktCP@Z&?`VuO|L_N3s>06CAfYdOd5MlDUVBT%TuvJu2 z&u#kRY0x%_pXk_;kANMi|2Cv1(QtN40V;*kJZzeX23^tV&Jjg_)EH;bk=nV-j^>-M zhno1vU8!A#C>RZrW82vLhgsBNWp{P%5`KYV0CGScIQNHbQf$CaJ301BA~T?ms@lD+ zF(nl(W3P}M(0}fe9#W;|^zFgM53n~%8wy?pFab?M4?K$T?B(y*w zlX;D^U+7EHoMJ>*$=*$Lmd{t4XKnbp1N8k;7pnaXDW^)d zKrP@*M5GMq%@x!smo}Wc{yJ$2G_BQ%4Ci8w;lAiA2@Fb{>Oq$~TQ3UfUk#hQzO7+t z&u}CLhCz%z7+=%buj@=X-L(q4<)V%;Sz;g_Yx8dV*nUXo;!7kJ`U0kRU+YI3vx<2I zc&wLZF$Vl@1oF#!HI5STd!vBl=Omi9QT z4ahIdUzZOQ=3!qcF)67}ulYBhI%@h!Gm{Jphaa0Bwb{Hj|CEa%4_0>eaXXttOTLF; zY+5T$-Z>*z!ripDoFqe7DBZIC#eIecywgrZtlatTRV5*$ajpcQ|M8p45O1lr@Hu6Q z4dW=m`ice2r4-5SuQwDO`Hms1?l5|X-pN#a(z%T;ohQb9T#56$^u##PH@fG*?3R%! z(Z*Sfh|`3JX5y$pT;~Y~l2Q8cV0O80s8546jva1Bj?QGitDjw8y0;>apS_YG$LW4? z(H+vID@VM|2}hVpOTX>YrCBebu@ZI8l@J`Y*GuoUwNXlsxQ>X|x{~1*8*0X(2Wgz9 zjOeAGAy~`3QG5#;o%Yozw76^@SL>d71uR316^RFhh7t?L zD#R0@karf$VM0!!s;ZT`=E41@-~r;494`RG?+;Ra1jH5N)bh7qw!`vT!V+A%sXnajzZddX z4@Adk6suz`YFxX`aoLK9(_LtI{&>re#cZ^+Zr&Oi+|3+267>rgYt$K?H&e6Rc9Ohb z?L6}p08U9oD#l9`)xlA%2>hQ&oNU;D7g2t7z28!5P; zD%B2Nh9S2v>c+w=k0D4*THXgAoMcmVwfqUVP0irqTYbu5nmRbMdm(+)sJl1Qh)|1A z<^Xp>1_Ps*TFl=!pMh3~$A<;hqKyxJl&1Esg4ei_>~Zj#|7fQ-I}t`p1gB!3{uHN& zJ&1LgnOoa(ca2i_h~d$Wvf6UC&}3v-cjzC=XGV{1#j%{ zKh??X-rsW&4#TQU*$`}tKZ z3wbW-ShC}$Z}X?^k>ZL^2oD|h?Ouw=crk66=o!r0Ju)|caniM?Fq6c(vDNlJ;R#@) zE`0@r=F9CoGh8j-*$*G84ORi~n5moFV+tJcc`PVXPL`|ZT3W7|9=MAZK1$)0tTh(5 z#?mE$)_+#?=8(NH&Fw0ib=S=kNKF>7LZ&2!VVx9p z^6h2TFOjaOrJN2V@a7dA61mMkn4$AQb?gS~6Ov|!MCbGqcVP8R+ErO3n_%?|JC7vt zy65NBnwy1O)*u865&KtemB$dsHFmUTukE(9q);%fd+sCEs_b^y_0#+FW@5#8FA-`h z371y4cTa2$SdHUpYLp`xx@R>lKLp^kH0mZPHz=f_Io4u1_TT6D7{}RXASmU|KMkTv zafTeWb#$^?#XTF$vHhu%)-n97sv6|y6z7Fh^)b!2ke;d1x55veAliDiptQ|-zC4xd zq1kM9&fG$%Ac}7qDkoUv3w&AY!bIp03%;;Z;>1P~Q-m_6O~NyH1>?_8(1ALAWFww{ z3u?~Uhwgbiij^DuB|)*J-(=Fjt7--R*aH*RYLIoHQ9W~nm^(r5qti{rY?Ot#T3!I`%;}f zUDOurZqOvjXd`v3%vc8aU|#e4*EVm8x34? z>vj&bPNa6Nj0ILzKBg^4jCYU&J)vs#rE3Nreo<1r(Sm87ABjT;zFn>t`lRjQ9xFJq z9Sl8I$%uWs#;8|<5m7sx^K9HJJV@ljf5_)ZeEceKPLOub_=ot=>d=Nu2&Xs(V`)+paYgP|VufPDQQS$)BErBJ7rS&&Jp(BI$|^$cqE^0_?-7TjFw zO4=c^V>WN-YWBH5Fs*M$il0RKI)2$pdD4R{v5S@;%#@xNn?j(U6<%C<*yd24JDxj9 zww_U_c+l@)Bq8b%0pdyLF&qZ?1_gpQN)N6p)&Fp*C7z95dGreF^O=#G+oeqjIZSsk ziAIT4cX%L!8yBxzgjrk)$BT{Rot&sXE~9vt1oil3^MA-^!xm5-dZ zDYdU(9^ZpY4hzq!vsFHnkTQqHOCldOuDv4~CtjH+uK2S;kxfeTE!g*1H_;rz$@d=C z>auJ>y)&!7Fg9Go=VQZC%wBksbG$O?tfBt2FU@))G@=YvsTveNy@%aF8^kjYrdXj^ z2Y3RPR>xb5#3T-t(6=*#ocQU2uz)M{{+f>nmVooqnN1GG(1B zLZckMe?eo4cJU4~uz!IRJw(C0?VrYfuOW%F0H^4t?g{-~e*b9F9D<-}}H{b8l> z7$uu)twXQh(MMj%TH(!W{$i=zd)wc^H`lo4=^K-n&t=BcgB_QoWGgh^wUZJ?dk#aa zub>xyULG+map%AR3A`u!yzI z4sSt)GIxCG7JKvS4VNJ8FVpol;V7>F=0E`iQAnZ)!OiN<$H)B0yiR;YN?;f6l44bC zq~)q&J6+lIJKJI}V@s5jpW-yZc+TzO^z@nC1iv_dk1UmYRk5xudJ_2KBM$G{T^bVQ z+&S7V?vh9ok`02SW)w67j?Je8qZXD={D=Ke16I0Mz?CKeOw(5$r{ca7w`*BoA1l1n z|Mng0f3@TPXNTOU@Z%^RSYD!N;+RqyOlwXFa0(XfMR4m5$I4Z~JD7Pz7vYG^p0YnznlKqlh#XLb(LbPH4ATl~AtFBwX z`B^+w?TaxGSDyO4@z-o61bUd7{q)7eTbcz|a3|yW>PDaGH$aa$yM=JL77=QJGa?PK zsjh*%3D1#ihNrn)fnWcg6{?0=^*fTws+bOSTZ%7*={|%Gzbm)?&=xpK6H3SEGxiwB zavUU0PhOevZx?*{n*yt)@YrG8MVzV53`jQgXhIGt4*0O&CL=}Ep|yX zqB&n+ghud3gP8@mkNA~k!0u?+9fs0=JgYKT906}*#j{zyEH6@Ej~|lmI0?5wJas=f0sO|gUB-e|q$$n0WZ1(B zDM0WE!3WAT7(n!lh(=5NL*?=)`3& zx8SV&aJJg8b)H@9R5EPl@MzbJ9jI!yXs^RT4<&(ZTuXm?Ai9u^w_xvjV%_+sLF*2G zUaw;$BihK^F5_Z@XcQ*XvS@B;Sn|R@&ULl1@}z;!Ns+s0cJj-j0Z#lysW9p}nrMmA ztzFbcpJ3zK#JT=gdRuqtgrR&;y~}}3#71Le?EQp zSzJ98%4o5ou$C-v==;;QqliGZ<_hMIU!4zN^ut{ofXXL4_T~p&T{?#*DwM;mV zsie>|xrfmZ>no)IX$_h9na0xX8uxI6phf_~S~d*4uQrT!zjC(^1)#jnfh`rt^Do=`MG5J>=e(T*rpOM(df<(in+lc^PI*>! zYhCbUzMv?IDYnd~tW}!CR9O76$|C*R?8CU*gJi7l*!wQli=Mr3DWUjC6syJ1dFs%$ z70clR`*bpewU+|gMU}V~OsosIQ?%&{J(WK_dunX^#RaXv-#nP_TTG8{N#W0{UuBB! z=^kHC92~gY<1p8G76&^Q*H`Fu3H9@bg7Hb%CCkGp-@!S>Lk%i*aVHC#pq^Fs!YZ4w zG&HBXriNmVQbvzhPQ+?{$MotM5;Tqf?RA3ZqLre1Bfs0S(A`LD%4g(|(6Npn5D$O5 zi=M0$^k40O|G&QOpD%#%bx+Zw>AqT9kYfz#cQec=@v+Kg;GXB7;iNEr65V=3>3_`H zWu_Hcpt!S9m&TDkRk&7)Vw6MP=*npsv6vBY+9!8 zXsFFzV)`mpH)ijUf|4rK{E%(uRI(Az>F&%gAiRioA{R?{4Ba71zn zK#j)8H0$k^cp~G!D_|Zt4${{?Ns$+Qv?Hh}m;khdRz`85VhO zaei8kHaLK-hsDy_r#BL`ki%2or~z|@6LRNrtjBpDyR_3UpPykH;Y-Cb`8+n&)Pt!r z04uQr8`nk{jngZ|MIuk5aYg*0Gb~Sf zigPaFXUC>>lRq@)Zy)VY?ZJX{0Eu zwIp`}rE+k>0VfpSc0yaTnahlPK#4O~$Acz%pwOK%_Q*kcK`I!5Drgm45X|l!Vn}$= z&Sc~eetUL;Iw0@ocknS6Jc^F;o1}K%D)%tXJ62IaW>KlR^yq>SGU<@;6U;PXNDD>a zFOH~0wW^G?$pNmlvlKH%J3Bcn$u05W zF-K=5Z`B#G>ggA#Y+w2cl*J}?sgzI%QrLnF1~V!CRkN4pG|}2}Y=>sM7|fA2jGBHV z3kz*+kOdOYE;21-1PXNuzK`PFkj6PzBBR9iIF{{m+H(Tit=LL5xiVNRAc-<5Afw*F z18Qi(EZQpF_(HQH^-c^pPuB488@tzjyA@0&6CGVV!60FmI|>d->dAk6dIf9-=s|CM zJ_g;3Efote+oMk7Br`{Ad~+O=yQZd$a~kB$dLKQp4`&`-`?CuC{cS}bUbzclF5~|f zO!}Xl=A_Yzr;{)l$O-7yu4(zObER_?Ix^JTGy3xDIKuaBEc=%?X*F+tZeN8Cie=ZA z&+;@X4iD=LA1cH)X}^*C4QiMEKHaTpSNM*PpTHHwjl}3W%&=MdB+cKDwZdjr)IG}r z>M+@s?re)tYMw_;6sW6v&|xkoU-%j@o&|yac_T6yL*JKf;JvrKex#M8udnl5M_5gr zEheCcc{D|ZDo!#a?t3w0o85ImS6l*FgBU7cWcUyyJI?A$6QsBIUt-A$eIr}*JXR$G zsc;Dw?HnX7=+^%17{H`7%2`w4T8?dGk^@&p&2}G;zhG=SNN=6+skB4^ZxHQpRN8Iz zaci0qt#ztqbkYn2iwwO_9Xe1^uLv+j8W(EhV=e?_hChXb=m~-%8x84}f8X*U)<$sB zr@=ZCW=4Zz(&S@WNJ|ldelfA6WqlQ3o^X~IpqW3;?fmeMaeQ%mT}w@xX?xtbMB?bgXG2}u=Wv`EIACaUP$9d}w?oW6?HF_`}WOvlXir63n z-u!s}L&B8XcD}&-LL8zIzQ28ur7~uJlDrvM`zKM31`;zZBat*uoqllqQ5o~|XyKbf zWr!_atESc^^H$_8N-&IIiRCWLr~oC-B=;P8u|mZ?;}8hbaP{8%K8Hy|pFUjYV*$xs z^6K7KewAo+abc(I#*Y+;dNx|!z?mEQs=R?Cd|R>i6A4AnKcs5<*`zWLN-E;+t8>1L{of^TWC2Df~ zG||`B1x+vQY)e}UR$qh#_#*tLQq>ERKADe1Tkw|KqgFmiw2Hv-eVk)q3zuG{4V?k1 zvxy((_dnV;_?O(IMuVh;HM*xLX_cJdKvDtjtqZ1(0h2 z_%ar>NC4*oS3JSheyz?|KvnlSTAD9$Y#U0bZU&S(d54 zor>0SvqszO@Oo%c-Uo8pemv;KclS_hc-(kI25x|G22owOF!(>HV~vew4eX%He%i@2 z>yB#FUNl_@SsnJkf0D<<$rHPT;p+SFl2qJ7yFkV|`ET{dNC#}6J zPivB96Tu0pItmvzjAN3+;^*~;jGCT!GnkOk&1~oGm;IyF^>r^kX;Ax^VXi#{9H-Rr zxY+t6T>?3rT`>^ps2%u9>MT+qW1%U=fPHcWAOoz;$9`jJB-v1m@lzL{YuLcB@t0m$ zPLGLs;++?g*pnqz^X28y!o*VJjI5KApz(5miu*+U8idrf2LJ*d%I865>A+$qw9yX zBSa@makx!#aA&E2rGyM5XQjE-8hlV_63U~il+a0v6y@EvMbxe)aSRybjq!&Ve#q_U z+(oRGl;QbXAnd`=AK4_YoeVyWvp}-V3goe~a!phvOLk@e4CFb><1a|^mJwwhmBj@XB9=V6kQXVwK$wN6#t|pkN^1j92 z5~pxU{3DeIZ<4(<=N%J!rqgDxSB3xP69hzCjXUwkYP4Po8?^y>4dCu03k5jom%dcRe5NnH&eeG4r zDq2qb)t=2E1canses7izZSUJ0d&l`iHi+fJ-4&Sz9Z< z#!GYkMZ7s6wDC`EKI_o!5~~Ii{^!C^9d479vmIQfOH_xIhUu$1DPD!il@6?$3)quH z5KQPEydHiv&nd5r92^@D7kQ?}3TFkJL2CUKDfF9w&URDOq}bww)imHX${!|Ank9IrM>P5Pr@aU)88S2)MT2s zc$-YV&@JWmFiFVttB|U8t|pQEhb$)r8bC{7KaPHs#W z9rvvd#a&trSwDW9Y3RZOHR$%;8S@M#PtG!tOA>zSw?4>x5b+#3jUX*N2;XWt9}uh{ z&?c}us(jbnq#X=Bd@4_w(d z{vrqeuM5;6AH@;)&|vW_l9|<)3{)^&^x(nK^yxk>8;3sh$UR|*B&ao~(6#v=S~P9s zrSR!(p-TivHb1GfBmoG7X1~z4f;~m*qXaQZWvZ?F)df2!ep3D3*nRkHy0(8U0B+7s zI|TE{L^-*8VExidjW|f%Mu<$ml1+a+G1AXq5!FmU`w6UVfqMsmW8QdwK<0?1KmYWu z2x6oCVw6lWjp|;cfF!yosLxeSq>gE+(o+KC0Yk$v#WYcF^>Lxmk59Heanydd=4uB|n&#oNgsd2K3ppXx_=Y$+Iq;8|f zdRk3QR=syzhqJ>TB?knij(lM0Y+{;Sc6uV;J#Tx}D`GW0oAh12M@Ti<@HY=gy@3o3 zAlcx-3$2+sKdk3)wOra{|4^G?!?scR-K^-;rKv8TRkG2hSrV8kxsrsV({QR-Wxm0w za2>1dQBs^vH!!?D80(Xd}eI5DR0HZynXLbkk)y4H4apNt`@xxT+ zWEJR$Bb)O+5JP60`}P;f+wNPmx>vxQ_0e6>51*iFS^V#h6%n5v>}CWwb3{oUb2wm1 z0U?22WVV^@oh{vI7VSwBu8~yg4XBI^nxx(UI0>c4nv*?H&+3Ima#c>17rtXjsax|iROHTkS5+4f-1GbJnj;?{@K+rN(_qC&H-cpLRrEbb zWvsVzndfyu#G;ra@`d%UX4Wv4TdU_4*@}W8YfXKn~rfFbM$!RHTRdo+17= zhC>rL2hV?R$T52H|NWhkL4lsVh_eZs>ay)+3?*j_3k$zOXcVa_Su0vb)zAfY&7C0R zGVb{+uvfuKA~uz{BE5DFo&hfs(2VfL|Rzy_W1k@?uxbUs$M^|S3rcBhN$4YSnc2z)wWo}bE?N<4u=N_{6xl;&A6Vh zH6Et;X>x1S%}{N9n|~^Ub1a6`jxY`Rowf+BRqoo9GgB6^NS|lOAM#5%<|_Q1-O zxw1G`u=UZ=Fc8bqNs1)l1du#0hv6^h%8&)}?X0DoW#iNCs^Vf`Hs`QZt@BV!H zWG3?=>%UIsMU!fEtkJgUHVUadqy{|a8WgxJpVkIPDHcU;>K!LuZdtWqX|KgmAqxIT{t5E_CQoy+rHuJcqi+zO0$j;ia2Wj305R7HoR{g_{%uM2;qK? ze#o*^UDFJ=)8TrMI%5&}AF&kkVw*{`rX`n|O&1#{0c*c%fvbkQT5@g-(Y*#;k&Blne^ zs0Ojk(I$og=H~(;zr24lf&GWN!++FTyM3*c83cu8#bx0tQ|Fys<%`R!ArZPHB;=@g zkzt;ncdUJJl&*@L`0Pg)(L9Qyf8q4uFyD_=q)IAY;3T~ z8_v6T2CFyLEAld3cP*RO=&m10?R|h1^DqNUphloN^Y4;#7)|%D3VOZ=na}f(N$&+) zrJT;q?%~(HH!ho^(-dc~kdhe)f5S*XpAFrnEv&I}FzZJmQ+tvv%0m?2M|k(lZ2kj! zm}+5-+IwZal)x+v%+gnbr&?VuMeg znD9=@PMryy7-L#<;9TN$P_3X#Z2XBAT)*;HRV$_@^KpE4L!@Knl#$F&l+lRZAtEgH z*EPs+kr_681t3l~E)^H{yX00S`G_nI+0k9C6gSJ=F@57?N~Ph9Da8BIGlZt#wH3pP zfvY2n5pjd0cA&`B`OQ7CHe8t_o@=0Of<^d0vfvhDiFJPc$neapGiTaMKe{01pONpr|czb2e z?3NoD(KMm1hPD~C8ODTg(%DsYewR!k{*bSkSbC}$RjA^`kUCxQ_>$`YL^RDWyj?cb zODk)=B&`LTZ-w^TQDS%|s`0tplRKn{IVvTFGGUfllaqalkDVlgh#xkdYfwGEq>Vh) zxlVCM8OdlkR~iW9PWMO+K+LQNr7<2NT8lrSjD~98+s$^7Ql8^h^gjF4!VBF?*WMlo z?xh}aKvm5jD)x!4Cs>uNv&vmnbaYj3asAVcl6A74kS|fO#`TM!Y23b0WL$Scf~5oq z8^{F5+5Vey^XVz;=P==C3_VXD&DxIcmh)?4COpha2w0`CqAs!6-OD;+oh4A&h@)p# zA^)ZA6(A@0HnQ$6@&=w4WwyoR8&McauG;pJfsyuL6hnte97Y0C&sx{$wl2+Tr#_LB zHD&Y>c;I`hvH;Si3jNS}qCTieK5*uH`Lqlz8({lijHnK%O2HK!0JqVWvk_(;PJD%+ z^$N>n?q$?+R`=)W?5smO0D^Cx>bChm_&<@++Lz}Dfa$6cYqu@7iFC<(mV&&Bw zh_ak>BR(_xd1LV9W!-dVy#h#rmY!t8%`YqIL&|lj66%DQo&*uas5)x4oty6;DaBrc%qkc+YG2)<;IuiV6(%j_!F^MRKr8 zd+LWM;EoS1uNq{$Zcd&$p)mTyco+@=t`0-ZQ6hWe!{W|o;zL26szRqi=`MyJku-^1 zRFJ`-pm+ny$L)^GsDR~Nk#zr@panhPs`|YyPa4ZY22aZBHhx04pAgn=GXizESx}&F z(y(O>LCpoc;b*6uAkcpDhQrv9Fc|SBHE)N6Ua>VePY+AAF z+&>%?)tNk$9igD`kPsqGgPaOS+sB<~{C!x)viJHI{}}<0-P(H&IK!}p*5Pjpt#oGr z(a+Arnvd$wi=SHVYlCaNO|}xdrCeO{847;{@k+l;e}U_NiiX}Lu%AwT@OE_3x)i~Y zoB6GC$Z>?`$*NhUw=HdqFQZ|;>K-za8IAxGG%8$(yI!Fn{>Chc0M{bN%`G$B-0ihj z8elzJk6E6IaH*FV+rZC>j!FkBDO18tRa8;HFW34T7}n$4ikx#Gj3X`N{hcZK6Z1y- zMQU1i{B$)r>O^z_Qt!F*iKW$YTNuUjzEQSYRWuV@=1Z1-zdR~*J|cK{P8eqd65EKp z#Chj7$Tj`>S~|q2D|Ksc5wwk8&YM^_*0-3=X~8dhg}w~sW!8{LLYHN+WiPmWrqeX< zn(tm~$SLLLo-}HO3;0dR%YU9nDEP-;J~(-hQ+Kz{yqn#- z6dgsu7pD}LUHj$K``~2~E+9bHuJDgPk1)FGz0>l?`>O8~YQ5V}7^()ZY2U8QG`udH z`Mzg$GW~6kH?1wd`JR{!zaWxRzi!pACS97jHUUIGAr2caxP$P^Hly6zDAlXt2+6ES z(yBF{L#H1qzD(y*jtr_O)SZubKPP~+&F`hHnE<1ZM3={FCoM%+bfzv)Uj3a)c>{K* zj~RT$+aBK`9u#|uL8hYmqAXI6BBI#>9b@w(hXG6VL=jF-uBe@=C%!V>iWqLJ1}E@y z!8qE0^N`s(HKZz9v&$JCiiWt=YI9BIOtHNMp256{cR~%AAA{;bE!djyo$;I?WRxYg zL#2BDartTZ+5hu7nyO#^k@h-6$IC*)yt=wf)GE?ehR5TtUC0NgxHnbZEra5EyLL|!2$0|ew;)Y`;O-uz zL-3%DyEg6+g1cLAg1a^}8r&^7bmK0;UH{Y1dt}bk%rob$b7ns5Z@X$&?b`dk@3q!- z{Vu9)4U%@T{7G%lQc2@1q-gb~3jB`a5}%9zbV%@frhM1Y*EsobPpK`9bTezQDGC*c z7H&P8$F_WCdq0E2h~um7o3cl~gOXjc7xoLj)5c8OTx4l;r^+B6yuMl;?NP_NEL`R6 z_jz%Sr1uFeqM3lH%}zqO8tJM*)~7Uix6f%xvYPMWH&yy2N%VF_V&95nWi1Gch4|T) z$oM$3ZboWoezq+|SQMt(tKetedk#w88f*BTaw=ylb0d>GqYhZzKfcz}*j;?9M`g+U zF@zNzn_j{$ju?@>)@L!A(lwx(GCN1@urBTwQ6Y~Y#7rEDf)F9alZI|hzQb0wX)E?L z0k&(|7wJQ|nOoD}6#uf*`P0M^YiR7UYzXl z83TLTT~ShA!5e9mn))~K@t0et1eCL`tX-27IgEI+ku&XQCq+hoiuxSWiyb2z2*=dM zNUk(vv|Dpc&_vuyMmPL)g)2aA(AbTW<-xa&EwOGEBqJv=;RwePN;Gm(Ka$y$ZrMhM ztfEY1{v)6sVBj)Ld4)w_on>BxQ|HyS69({(%n?UDN>p_Duwkie%0oR!tEq10)K})sb3K%0%>G+eNv=ZG0lb+*`SKo_-~PLXpb&cis+INAH&u zog5tNM)u<{&s%7exJIJ;aT<(XzWE}KtI(%c{~#HrPfM0B>H2CX=`@`4@ZI3AWR@{R zfVF&O^#|}#d?M(Y_#WpRFc0}2K9-dZv|eZNUD|85g`BLXq$NG3$wx6!_EygfqLGq( zCqLV{jc5$7tfWq#t-UOYB#M{HUJO7GABH!pmhYs_4vBHC&sNHH^Lcx1>g0^3h$i^a z;(uTZ04|h-ql@z8LCdh|c926+V@zXlg=?x}wW8=oJm0-NXasqT+IQ*IMS&n)!gRAW zUpl$v%JATHBSP%fz&m?$WKX7u`5OoXn?w&Y+?p=;j)}y+7x(g$1=%7#*jwdCGh8X! zB|v2TiVQ^}z$nc;<|p^7#ulV2vE){q(T77f4+Gk1<=!Po(HxQUJO>NkMdNIuoPaBP z?+1D$^1#2OgQVBJ3$!!lGp@ou`rZQ@$G`Yy2qM=}R(Rq%cf-vkoU+XJ@_1t(*nWM; z{=G&e1MzV7J-X<7@rY#9duVHZipK3wnka4oG>W3#ju%J0u4_JBgr}Ix;M`M zJdl;ttkM_SKO;1|VGQRk+T;GVeA(q7!_tO8vf0=?->AUe@|;)eWT3dWM|=zJO*K~aitfi0K$P>*%W8h6#X)OQ zQmjDXS+0HJbJ39i2k|AN$Ik<>6Q;o8^_CG%v_vj6y0XobhcNR4ME~^Y%8*dIxAG8b zs?JCLl|zRmQ6FG$Po-7Vf@%0l9#d*3lUKExN1;QxKWVUXNk_Vjlok>z&p>J@CWvfI zXH*eFM~M(>ITNRvFWAzqs0fe#6l3qN%NvrolJF<=u<$Bdk?yP zn{tLA19j}r-IBR>MN9FZ_sTP1o*k&Iwy7=Y_oY2K+T+!c7LM22M9UoVra8b#fgGaQ z6IQdf2Iv}CWFT4r;51H4Bm7ee4rTI~uG$7;erDq#K zQM_~4=<@=Fse;%U!OgLJ9z&OR;!Ma%Id^4EIUxqeorv~7w8--pPZhC3eW>;jKfdVv zekMoY5I@t&7gWkS6V8kSdqC6t^}dKq!6@s-G>w&^QXmY-ke1FT^rLyPJ9JYXJ|QbV z`E!{C!-W0Td9u&12O6?&=@8?Rc(x{nw%@Qbmw{f*szcKzJ5 zTI)mZQ$wo|uFIbw$zaPPc@dI~k8COiS%o1AJaN^lmkuTG$fa2xf~Ip!A-C*52v2;d zS?cMBw?ADqpW}kr-7rL@rCcWS zX}DxSYcz5b_k4s>i=CYBBA1UV??SGF{8fmIoy>;5i60HGz;VGNOM=K$YIBUiXDR6v zDR?G$CJQ;XqaUrOwf=y;OoW5J-Ldj7)dgXKscDDqw$5An~(M+kp!hjQInkV>DT|A%#tgIi?FD^Mj5$-V13rJ8+kf zuw^qBhduQAmE}K3?~$olNLCm9esZ9_D!5;4&7$(G9D+Qi|D|t_WIdCn{rs22jC;>& z(7-*%DN;c{U|%!8$@na%%E?DGOBME9zN(G^Z@j{waeDexbg>5=!4YvQ9Z0czAS zQ{ZS)2}{{&BU=~7n0=fuR$oagP!bEomz|h()ntiw^fbaj`!myk{3)`n?0{eBYX#+& zZbOqNv@O!twC-Z5Dc3ZzNdEPIs7(H)H2L4ZaDdJKAZf0BxlH1zdfj8@g3oClT?Z>% z$44vcdJ6I8{SQ$luaEgg-LmVov zjy)!4N1rqQLOGkILru(Z!CI^(y&`-f#o>_~^Ndb3k}D=?<#U z!S7_SpZ38l^KXtG-|Ysbzh(HqD~lNDYx2d%dFOqY-}CX6kj!&mu4SW7c~y{J z{_CN|9#kWjiDe^=$vYXyqgCTuH&FvCV&z2ikJ@&9l@y0+$)U&mv|M-q?snK^3>MfX=owU zkQN^JU~_W!hJ8~ZL@IXX_>t;jg@bh94s}S z%T0<5Rr1_pEa|*{b|+RKcVg`fa}&l=8`YY*2C`PGsiM3U%1u7m9>$M$LR2YNWh}he z!bsey{TG0QT*Rc2n^3KS+(*1S*s)u1(*Wnr4jK0tc0TN;=WC zMpOsYgQ1D|Z_!`v>aU=E^_fq6l1=%Q@ljE2n(wCq=?t&BUDv%;u;60%;1K#cUefTm z6M+hOQ zwX^Rfn<1Sud-ainLjf6!QX(q@JOPvv3?DefEP(4yGc&pQ*}bL>af9v-l^+ZI^LhK4 z9+IIqrO-@5%;-vu7b(yiNfi6NQo6`V$hl}EG^}K%u)d|$D^;Sx$vF|!H=dhK%L$f4 zdfssaQk?g@B&!`5@qPeVL7a*YYM&FCzWG&4zzbUiCrE2Kg6gq_v-@6i?f)mKQ2RUQ zB3K*AGx*cH*74N9Q?QzoL*UyBxnwd9`Ny`QZb6hwy&bS&uO&keWOUKaxT%F-9X<+B zrnw9ReC9?qc{K`M>AEQ?1&`)1oM-(81mDM%7qsW=&}6D}>juTTe^w#bKEI^EX6EnH zt;&Ix150(66s<)^A<$PRpUWo(0eOo>vM$}M*A=jp>;mbHMV8$F)@s&aVl`b1k6lYC z^WNWo2}Sl!wjw+2H$>TH1~NoLo?y}Q{7L0+b%%+B`ddu7VTli~+zdptk1sCOCe2Xn@lur#2J=^D z==b;cA>UO~YyYIU>hjoH6A!WSbdU_wPu#I+OJsP@Xa=v z{dkr~sIxkD;_;ZVvtGmTN*jqFU55(ftfe(qF?t#J_V=8j5;F_A0c){%dYXsfs&hx( zd?oBoc4Bj6RH&io`5JPI3x7`IEDaw*_u`MX92Mxv^>w40Lv-ME{o?@$zFp^Ta9V#; zT(MHZ0b6TGe>9P@E4V(ugmb;pM*#4-W%PrcD?j@S&8UC*k_|qVMh?c}$#!F=1{Vmw z1OPDS%DTGGM2@wo{EgH2$Xry6FqPmfN`JpOwyo!=N4Xd2xnCCPeiEQ-7Ev2rKa$N+ z@L|>2&6$5-X72Yt@<<#=o_QmXA6anr;ev0(Aj8w`b%WYGse5@Y2moyV=@#ho*QpP2py=1w%TX)TvJ~S0c`!X^z6rE6x zKoz(niELk02pI%y%%8hwmnc{0tyCCd*oMo*jt_drrL<2rFUKr%95t06ypY$;^G5xI zJR`I=jbY{fZufy9uXY!`%(q)_Pk*R+G{}58xqYer2WhOgvm;G)7R=6uerXv$ zw7CCPulg|?*%Bms_9`d%d^O{YeVJ{-Il|1J(G ziL9!svfK9KES&vopEJL@u6G<|3f?Jzry6(EO23++-pb0>Qh%B8k-hM(Y0#CkzlDMq z5yBwStE%1lY5ONMbJuh0z=uP@2;0;)?mDecB6Xw}p=h^9N{_v(RI&5>U6J7T3bGe! z78a2jGG6rEg58*T$b+o0S^RSdAvGsk)XqHxIHoj)K^ywp%J&=@d~tg`iQ?8><;O6< zwWvL8?FYU+pXj%=W}wMjUFO>UL%JzAyIS-0g7pTY%-%E0*8D37mm$FJa-`-Xw8A=6>0d7Ka{u6pTn+*(SLtWNPL>3eLc#fa_b+fYFA~yAf zdS#{+iy#e$^YiaP_r-p75@v}8WeqHFz!1%OA{RC~r>B4Sxy^(2@6I`+nfz3XK%!Ln zZ$v5>M3?kZ1Ym=2f=7d2Cym#`i_g|>I-aj*IY=zh#-a7^C4Pel(Wlc1&`bpMD0aN| z2t+gd=Ywl#-?nR+v!yuUA8@f=j|UTR>1DR(X-9i9Q%$_Zi{6QA5+a-z(K0S=TyL7L zW-XYlAv*jA$!*`=mxzW=OdDOyeLGZCRItMDmvs}F9XnqQ1AMO`_K?}XE|(>AaL8;W zenoj@5IG?WtN6|}cqw>Jmp}D{85CX;`+;jETPJ{tVnUV=R~kiZ#c?h7y5>Gi`3QPX z0;_H)u*s+@n!M4tQt5py$zljY*OiIo{rqWFiQp+OD=25N0{^afxZT*L8U(XR&9!Db zrTkr3{(R(-eb0YA%vq2 z>-?YcX(F}NsJch z+ET*{uQ>jA0^|*K>>uWJK1|~bL90L;lbJ$4}8~8@CSYM zn>#uv%&6I2GCGG`kfh&g=}zHG>jiMZ{PmHVpV(|s>?vpRM}WgBR#`e)*+KW-o0?~D z_KTNl;)RE6M$lc-UxelP4zhA1j1k3t-RVDm`5l~PI)1R`pqeA3Z>#%<-It1WM*ZW4 zU0#eyplH2te(BOj`))>T?}^?Y68orMzoW@aVXeH>*xEWyjk0jG|9RsWl7{A5es2O{B69?g&~`WRQKdx)DqbTb|CTFi0ruamCJUFe9w->Rz^J`hO3 zcCv{}*=oVJ`zkQLW*Z&RwNU@gunh ziiuwyH_u9kmzHTa5jpzl6Dwm%+-p^aXD6*Z%)@}xh2!|40#{VUyw+M*a99E37Db>O zY3cPd!y~!cBO~Zf8**8DBQ7^`js*644F!h)Z@M};y_x#lp}QFI`K6~j-Y#fWnIEnQ zAAWNWB}Vzd_vLlsx=}orplQ~2^v1hM){^6^)Lz)Y@*<|?r2A8K&i8Y0B zGkN-h>K{vfHQ4gFr3cnrWhm2z69C+y5ilg4EdvrGoY|PYo(tSWc55Ovi=E|EccJ}d z9!Hn^^9P5ZDB8yi`ozV%U+W9vP2giS(0Fr&=@P>D;m%Bxl>V|Ukn>cZ{xpPWQ~VPvlCDkt{=g0kYlfCFN9!V@ zs)x7NIu~ZSbkRj=e#f1mo@F;}o?&ALSJ_uKl)$BK71L?3STfbD7B|kgNCMhQ=-#yY zgZ@FvVD3a{uqXI%2yFhyiP(#10i8H=Bz^1&C(J~Mscf1^Zs}0gEsJE(!r6~q=j#y! zIMr4zo(M~H1YdGv_joNRQS4Sp$VnfZzW#`y?9{)EPdiY}xh=7t68R0Cf#Mj;M}vYz zhPdN_TY~-cC1DV=GwlvPcGX!bZA&kwiu1HjPS=-t~#t$peCh`kpfp!UP;m zL`VJfDHb)7u~`0pSK_HzxJoz6#-n{zCN_8@KcTAG^TxnaFB6lB?>Gcy^%oUuPI37f8uBj*uD@Di)h;_9Ee@+ZG+Q&Oco+Q06NE(@8x zT6`zMS|sOhTBeMh2wQpXOxa$<;RuYD_j)%V-L%ZOe%|>p{5VgXt1&FvZH05?m|bhk zHppdH0mZp>FqGEx!F~x%BZT zaumziNZ8qJaAyF#fd?a0@W+J6dS{opPoyS2p&< zcDot>>jFOA#|3k#pTd$9mX1b_0u+fk^Dj+^t}Uh}zy-Tro|NF|}pFZnL~?&$Iftdx5Hok-+R`0-~W{VZK_(e&sZOF0tFW!8(E zm#=d9$I{#Lj!G8=yhx4M5yHrC3;+>HK7Nr49BS`(hpsw2;f=Z2HmMK1MdiP1Z=z#F z#(t^(Vy2;gxm)!kKyU^domT-(&5iR^!sG8Bi4f=@=z^T1x6|6(w^4h@zP!f%seV(-fTY~r!vDyA0LKHCnq`u z=5?$wC1BT9m8%%aTq8M_!6rHRV}(^=eXEDOg_))~G`<2C2S6oxta*ij;RhZ+b*s>9 z`JL$T$dWYq(GA%2e!FfN2hDo984zE%lN?Yp`tTGXcy<2fVgINow|>|rawHJMsQ-s^ z7*0pywwHrmOzEM@f4SP&#l0-EJu<*cGxN$R+BA<)R(q8xM^LrE)ub{S{ga%EWc9^q zuY1!Ad1tz-d_RQDt^MYqO@ZwwLVt4&;;<;OdMKd|MP?Ay)tY6kVHz>Ki&CAndl}{i z^@H_O<0>|tp$``RJgI7fZyeuaI}04m=lX9{abAxKqd$q4E-ot)E^42!0-y?E z+Q2A_gF+T~*S>_HYMe$>=llX?&95Q$i0jXDR4@FEvDPM>U+R{m>b?YThlPI}*vpaJ z#*tKloXZv&`-$%4FAnnd9J@Scg1=!E$!jo$px|Tw;m&c<*UVgp%Y*G`=B|!{{GO9< zm5u5k^~ww|Qm35=yn4DloBG@|fTQG&h^oD<^(N^?U446Pga)_0*V{;WxYf!>rxH!*8nSqlC+UV$Lvjut?@(T2v!-vi5U;JDS9DTKe7Xgg~ zM%d!ncH>*QxD!JLHyoO7EhD-;Aw?<@?maQ%fnI`>-h2Cb7`NW5zx*tkpjuRBpG;}o z83%^?Y==lUBtATQhbhg1Ar!A z3XgrzfdaSpQuSSGB&M9w1LeKPgJe`K~4?c%+l8DnahB z`E0yhm*iLIP*Q7a5v3d`&YtLWkvBB@J}4eI!L6#iX|J@q3$upJh2$AyelQ7^$ozeN zA?4*I*5=Z6MVKUm1e$GB=+NDu@9-RsL9t4ZN}y#N?#V~WGfV0^1ePP7@&6Yx;QvDw zj0|DjhH!-f`+Jz)A)I zs8R`+?co^W0#cWfAerIFJiFu1<@l(tEa_=~tS-@j)ZsG(j>`V!D2kvC~J+);@@4f|?BmuNmgkv(ezK>LiZ{_d*hZ=Ytw zdKId0SKMM$IIt-SpA~%fm;8s*tP9X4)G1kGTvx|(jv;YN?PWG4e8sC(_R^DLSArZK zFQg%af?(4j{jXevq8%yyv;17$R9B5r<3CT3ZB8^MMZL@^yk`m5mhU3F?%W?%McrW2 z_F}8#_JnFGb@DZM@lf(t^m?HvNzL0q-9}LP^?z>)S#a5`A*q~4z_*PKiL5a_Fj(_J z9f$h5jB2mrIHR3@U{BKt2#~BhLUZUFayJ3qjW2D_Wk#pMaF$LhhgxDZi*F%xwuIts zfS49EYBlVz+<$LO)^A^B2;J&)5FIEf>34SBg*MIE%WUU-4!K3O_(|$vzx?Zmur#LY zW^vS}!YK2x4a|XflH33uK$YL1_Y-Dx9zVEq$=#^q0u^mlp;Y36%=F$K7gU#iAEm6Q z;39P}mUApYv$NZP=Ln#d{#$yBw2k}49rnlwtMb=KThHD1Aic$O29pVG^z91}SgxRo zEns#iv*%&#Ewh4|hDoEspqNRP6r!(z7>r3yqQzp#lv(jVe71r8fX4hh=BQd${zPGk zLsn<@4^qIZ*lgRfmUH`#@&4n8))>2xxDm`;X_wObhQ-l?DtV^>p(%TKdI}V(Qc^0(8QGl2ur7(S z^H5DU77kecmHu|CWUNx~s*3&HDqP#PLUeX>_MWSfpB@9l>kDq8(Q8i=YzcrHGHn~W zuX>40xz4alHJ0oHB~#VHw^m|F)7(kR$Dz-QjP9WZjuO-k7Et`r=Te_$8BY@I=o%c)$z=k-cxB!oF=)2X2sFk+lPU_`U|KnqB#gK5U~EGWHbGPs`SQNtZGYG zqdDhNX%6?0H9tz-1>A5c4wLq+;WGos3b+fBkaTz9;^4!S>1S_NX_CIovf&Sx%o5UH zete7iQDTT6%w=>TRYw8+Vl#g9Z2Qdd(n%NE(U7b3J7?lToNaSenj#|Jy;gvZE=WbQ zCX(G~))!R1x-`5CU)CQ5I1|!2rvfObWZZs#I44RnUN10qDyuoIJW2EcW*kgrb@%z; zDaR9^WYw~eU*gw{x{EiO2LIbHDF0++v&TI0M4WXMkRrS8T>UHg{1BaY*5>!ej#*!Z zqJYh)T(N1z0q4%Lx}V!YxfWLix+J^ z%u%Ia^FYx9TjEU4Wz@OliX88Y5rOMc=064Jv2ONi7>4+Ii$w#u1Q?~@bWPi2&Xe@O z*Y`%z{CI6BmZZGh`u_&-B;iUiul2U1-*;+rRmT9}*PE(R6e@H^=`R%6I@B)h;&k@x z+OVl}3~0*GTN-A0M5JR$)K&k{LaOks3b^#i=jNzTa_tElk5#{H)m-scSh;N~a8zSAq>VbKb?No!6^tsd-ldt<1A|=P%hd zQ1K<`w2mCh#R#4D7czYycfPsAs}4r_7X!B4w?&lcNo9==f;_VZSytLqsq9Az0ZBAn*z|RA4qJ7 zx4e$6=;jYQ&n4F}u=&kuFFVhdzL`qg8Vxd6>K~XhQ*#fbn?RKi{d9cNQfOl!cy?1H zR%aF1Bam~kH`;2RFzpQg*6c-cO!ua_>xC*gwF#~0uM-~`?@7r`f<8mRQ|~m*NP-^)W2O{F$dK>} zY*YaXle;5Lp@|OqZSPB)rs+{Y{O55#d%{?EJlY?kVrp_9zKGWhS-ko8r<2g-l8q+_)|2*d5XJFb^Ati)88=NWg;bGPAb+kU34WqeeZK z6A?i!QDjm#`P@v~Ud8k6Ow4yvBS#*E)|ITq5zyLBSPbE0x zh3Q3amGH)ywR)b3KDW0bMyVdexD%=46g%f+BmXD&kf7D@kGtRqbK8GxS%EXkGnW4% zzR~=T#5Z{0f0N}+Xhr#IxK$hyr#3wVCmpcTwsX|p&iza@#(HC-_ItWsIhQYCyf4(>t)nczYDlym0wOiUi->+K3@+oB?sy1Nz z`R|sG@(wAxW5MBr*n@P)BmzkPT0oG@*1z30xg0FaEUn7zEQ5wj6S%PF(NN4BZCj}5 zAvp?V$L-P|Q%d#dlaxz8DeQj5$NU-)j|g??-VQMl1gDI6=HFn+^|w|XXXV&;^e@-T z(Wvn=nMihTt6aIiwBi(vu>L<|_WsTOGu^*Qo*5U0=zHK#TGK7fH!<#k z&2KfGL=gfE@w7v{NCl&2*mMtKEJ!%2;_Ug=ALiTNseRGEC0()q%6T+<>@ zp!%h2>@7zFB7k&@Q9$q24-OoX!bX2j|E?z`w#aKN^#SkiB|a|nznkq&m0@a=Dx6=$ z#zM`_k;+f>he*sN>6|9GY_e-2j?u!|b``-SL?A2bmQGd|2Pfx4F)v!r0uwGdF_Wid z{FZ%IHS9fV|7ND}b9M-kDs%pzs^oTx#|eK_hMc zU1C{8yD!Y%&M|38Pq*8i=lnSKkeh|UQ8<~KM#S|#B}^;@iBy1a-d2pJW$uZxM4COq zGU0fGQ}1AARFIoLYpmu^0a9g$HXf8@U5ne&!^zl-axVD-K1n*FfRSwefHzdGx!VBS z+-E(`dT_hC@kqG$Ai5yg{ud2#E#FbEFp zOGt5hS73^Fkyt=F`Gu!SLZ;N#y;Z4(GNn4R$S(tS4^1|_3WtvHF>|o=hf|xGxeiEa z)gy#K{vgy-W%j;H81uBfh<@>4nGJroABx8@A;h+9<++;EW#^X;(AJrrJk!>n+R>^ zROEx3<%V`6v{^RxD`&V2wYer7a?$R$HQbYhRk>81{~w^(iLcWTjJFA6^8Kk?gEmP& z$?!@Z+p^QNQdHMKGF&9WwC$ z-KY|N7w+F9U2>#gis_L$T{sgyv3NqMO$nFic^~UE;Dz+9iEJXK#Iqbr7wgkqH~5Ih z{rG!k`krDou%vzL_}LcP<&oaPJTTzqEOc0hZ*ErO~u>w z{TMQIO7cmaR1$ie_d~KdoY%Z*MFiB~9VZ@+@BBPraCy5U;dO9P9xKW$HV;!6Erf>T zYsIgV7HcQ_=?tmfW&D||u|I)i|znWNSfK`*f=XFDDaJ_U~a|x3${g1oy@*eA*DuPo#*yEXwIyhYphOFP>b0+ zEkTq(`2kLy8zJ7+B9>w6?U3M&o#y6P^lnkqdlEJ!Y*c4QKP*@EyVd#2z6_-bzmheC ze$f1L1#X)98cTJ3ICheul4#v@#Q_yZl#Ea`5$<8?yjC1V)Q!IKaC1zW_{E%Q zFVnIo)4{=rf&ct3gyiVut<*w~(A?G+gnHG2Jjmw7Z%k#=7y4eJT4^^Q!3CeGj4XkY~pWXkZ*k`|e z={!ScbM?veV_Q>Ir|d6WV@k!>{`~ZEd^Z9m?LHJliTtG-R|U}(m~mbFK1@(9wm@w^ ztxvIsxkw_pghn~B`HIx_Au8`bGilU3soGGeVvwt!r8*q>))F>m7JETtE@n#ou6!rU zH!l&5l=_M{1jKcu*2zko@F7B2xCdsBntJ98ix`hV4Sht+8g~C=6t-@B>xMFEl3p6eKDkoygcN z-E}Go{dFmdIvDjT?e!|v6FC-8i)*{JDKFAuQF?1SgSKFfNOHku4~HDpRj{9=?r6(V z+5ZtSdo2a)548|r7>&n8bw*`!MNV!wi;k)}fPtaTO+jj(Sc@7w9gvCq^0E_u5DV^! zHlx=6b+zsIqLVl?jPbU2)X9kgFVGw7fYUa&)`yp*)l1t^X25?uBw7m|ynol+`z6D(BJn6foU`<$kJ0j4vbd}`D_8}Xoe z=k4vx?1ppq;Y&WiTTt{VRdzlVPB&w3-8AHrPI^)fXntI`Xl3-ZH9|4ZiboZ$1A|9$dvea;e`+z1Ria?hEY^NYC9%T|>eiSEG+lPi_y)R=)KbKOi zrnv}zjBsGYFJH(}I2(92R)edH7}Q%qQA7|lwb}2}=O;$a$Rt7 zeL2O}>FSqp@5p*PEmT1w8FS-!jpP&~IY&k7gH?i-*>*vv3TZd9K+;4Sf`xclZ4PJP z>UtTu0E&6N{J!nGMo5+J!~(EmrjePcI~^YJZ;s`wIshwln9!I3=m5oLu2& z5gMoRv3j0dt5`-|=5QTi;R}G`s!0-bM*wO4@!RFyhdv^hso}UulwHDo^qO^et^Tjy z`}Rw)k;MT=m+6B4d58yO_k1Z4EyndZdJ0J5AS|$k$!SUM<0Xh_dzv;WOrJjNQ>w=C zhg@3`;oqDm@eCtYVLJQ_ZM9ZxH6lMf-=C?uo+j0co33M}V zkR%)Dwl4B^RCqq<4l#kWUR|ehaA+WO4#S}prI;P~klCeb!7rm;TLD*?YGrVCU zp%9+6#UL=Q)QKRlq5B)=KE2lGS7@87CL<@1Wek9<@G7e(W zgmvM5b5l>qt)<53iTt`sEvK9v6z5fh7pjV8TskW-7nsczP-hP>5r(E_>11XlL z+efG;;&Y`ddHaqB4Duj4LC+r8AqwoT4;RSBV`7$?8bK8S$X~!44ovmP%vetXD=(EB zamLyiso8vKp7tkHtFi+w4lWds@IwCNhq9lK!*K0)0#&Sl;*ofEq7^yJs3uNH~y@P2S9+pW*)YSx+C7cE=2 zHs!MJR5k#Pw)Mg(DJ4I67Q99`O^CkQ)(pJ>mQRi$NS?TI^Te^Kzl%`_ryKO=+cn+oC6L+iiQVR{g31VICU1aqJ)p z*kY{rvgYFHCizimg4ND6)Xf}}aLAOFQOISb+3J=r^m2UyPX9r^Ey750SY8l)Yf(j> zO9)c7bpm*s%9X7w6=mI`?zzekFpdmDaQkR4v}2no&RTJ*B5Iz{5M)Hf!lWfzG?lh@ zYD=z77u_rp(jWhx_Uu`GsoL~^JG>EYKY#2-*b>k750Y5#ND#d=ZXz-L5CHii_By@X z#vf*DB7!&*r$*S{HgsAucqj>76@UD+eVVTNV*zt-Q(fi@PYQ z+W_1qnyII+s|yEVL|AqPGJAZ|W@Mx?ihfInwTmR{l=BKG!cYvNlmdT6)@?}hi*U`8} z^E^A7X>$yWWUD1n&^#8yb5m-Fgb6W0@3J_E-pe3Fa8Ps+wG^dT`ud94LSJgrZ|aWZ z!qx_D8LW=qh?2$5O_K$_Fsj~+88cWzfq9a?fwx3??cmDz)MiytHM9oTQZK-m&4}7AGsLaY@t)5AVS1 z_gsI3E`QGCc(rcw)fF{;vx=-&eu@Qb?|M(?r~O=n8NIe20r^1fa`_?C9h6;!L&_T~ zqFjLyQN+)<>dScS zBa(2j6)WVVc;w@6xbTD(3_;`ucCMdTsw&0GMt)1=4wJ)&YRWwyw;hMLyvLKKu88#uUM%SC;vfu%^-TI@%Q@qOP3TY_%#a`khUyF2bduBTM~uiQy!rp)ojqi zzi{M4PjV#(O_~`z8Pek{9-8*dqaxoyLp|2S^vJaa1lTxs*Sth=1u#SF+gn)7Rch*u zEJqKyzu=k|wGol(tOs56GnStgsB?Zh;F>8_-GMj>7qOGZaLKEbBO&2C3cDU?Z2oQz z9NC;^;_G%+8OfAf{+8H&O)`8Sr^)9=MjUAGwzMP9>n@*h^mjgu9aD8skQ%QqiEb|; zMaG#+XxlB!KlGxroO{G?>~i^r^TJu|;an#}F7i6&-J7VdEwS{# z=<~FuSlO5%r1}OX=6EE;K&*=I1HlolPEn;+rg7|B`#Il2fmro4y)BWPXE{5Sn?DL; zqtP@eD2SX>^510v7R|0uB^GrHu=Jrsu8{aIhyW zU9eV)8z^Fr=qu|76MhI9I|yf&h){OzC{r~7&I*ZF`4?F1cNDP1l`_=ErWu9;6(jRT zX_*A+P6ZIV>-A8YAvBT+7Y$IaF3(#RC%2lfO-_c5(JnG30505DmmGjDC1ok)ugE#Xa_7-} zs%vZ|Mg8vJ?O5ey83%kC8RI@>i~;VpQWfvZ;mT|fC`m1GgC>w!me?jl8nY^y6E52hBm6J@pvq2>dRv*W727F1Xl#F0X|6GV$l7NR4 zO%)uamGcVHG_}*JXusVYi<&m{NcD=Dz&WAQU$MS)Lg;3YH|#&Sr(cPP7B~wr2;wo} zpY`NEdgM8`H6i%U0+T$$BC3^&@8#%sRi_fdlCvskW#yDl27=D6KZeb(bmD?4Qh2I_ z^bNHdXO5TD6w)<$E%#eQm20cxJssqK*G_B6HFvbwR=|H2xA=UE=_z!AqFXFMz^2Xh zXG-2$%+_wAG28r_{|9St71q|fx9dWI;!=uRDemqBE5(`u#oe6{+@&}ax8e}oU4y$8 zD-OZEXn`U{yYrv(TWjyN*R-|wcaVczS2*Cx$au&5{+{Q)6C2g~wssPfS$cNKmi$SN zai%MqexF{m%j#9=WefPU zkgYTA?&0g7aZhTon2lhJ;~)qfkcx1Iv+-AqO>ur|3+vl3)+;j(64z_+O2sJm6hchP zq7o`k{9U7ys@Bx)lQwYUL}I++K$b7-VIhD5)7m!);tr1es{6Y{qaL!aC)f04jR)|h z`O^tjI_z7fKpv?O3*vrgSNB5qE-o0uyDRHnyve6{jArB}se|o!Ix>+*f@N){gDfK_ z+~||w1lVa;5%B3-ZD_hFa&~LxxI>%ldvo(w5Oj*u>(ikkf3edO2zYQHhOH(*kXx7$ zDYmKTUw=3JERStxV!|B}H2^_uXxQ6%&=oR(tc=tT{6+;aj|c&%WF zV@?ze${4+ScPx}6tG{VMSVYV}p;dJ2IL%;NF+t@8nl{M(>ZmP2-%26!^O0<b63$SxlIu1j({T9i6Kd^yS+SKl++zbg-} z+zMvSiWguS^2QgY4IUn)W%kZY`c0fYC!HYj zCnB0dsm0d3zXk3eRN6Wt&&W&7u(9(;@OZvC^0Mqw<|azgB9V?(DoU%g&2z{MUIpni zvycj~Gz4&A;=SCzHcRx$$W&VR=~`<4w$b{dps4RsNpq~ZX=^X+NRpNj*=4lAP!Cm2 zbGWa0QM0*jq6{FCeBGETu&}-2YC-IdG}=PQXl9H;TP#0Cu?CXbjxk*MNQAe>(pNfN zKtjti6my$w!P=?5yZZu_ac)5o*NHt&YR=UU1z=Hf_({*k61ORTiU8N^;Oj~v9f!Ni z^DsZT4HZFi?83H(R?~xdkivqTD{MM7OmN91@X~(emvevVPFBt!3_d6lR8_F$FaeJN z+_~sNPFH^MtM+#Bps~{#1mW;p8*J z17S?%YniT>v-@5q>C&${b=KlONg)SukG!n3&ah1K*veM=rP-1)Uv&na5jF{*aHh2Q z7Q$9B`Q2H*$53Q*-&VJCzlP1hj|TcRlQxzqk!eT&0OQMx8afCn-G0$b)04Dpq;;k4 z##s9F8e?OmQ!8&%K!P5!Js?h(u<|FoNhodE@r(}gGo9=MUbDMYh-Y@vj2Lto(j8Cw zZnO_^FI4nzrCFTMwPhIS>nl=S-rTs4F@-P>9mb`RasAblzB&M)*#RtSY^rB;H;`EM z$#Vp(&C(at`h~1i)&E-X#Ev?|-_EgL-8KZkhGHvYXqNjJSe91-YwhRGa_fh~O3bcs z3ORf`k}gA__TPs$mO-618#YYT|zoc$sz%$L{a4qicV7Yv#E5!d|a2%@;Py z^P|&6%I9N{gOE+aDR~rzg;AsJZOBl=;m4C{|FKJZ=lSVZGVHPJH)D-)%8@=`&_vP`gZt7)Gxj|QuzKF$uTJyF z3!(&Y!P3$Z_wUc5KogcB@HJC)Zfn}9@LIY+{YCb4vM}visX@mp#H2v=%>6d}6}4%% z2`WXk?VJShVC+I-L1O&jx3OC(PY9f?a%qBzsEDLeY$*DQJpHm4$<5Z|>7dhv#taxEtqiS!tGUZU_Qz}bB z+mv0@o}V!%YfArQ z>RER63}h7{wlx>MgZtb zY67W__6PbMQ`~QvOXaYlUf(5B)B1eW7|{|98fu{}{du6Sc7&SyJM)8xa)_HX?euHK zCPmeCQLphtzSJ*eT=Aug6`{DF_rZn>CG39H*xtkXO;~RQK5Vfrf=X4%OP1KUVkK~QQ;lbV)yL)-tr?d zNTz>rb(G(mRFSv8v_@j&e`(o8E8LNf?BevHAXT1|zmEZBsz0vLZ zb)?JQ(dL63V9=K+{A$SDdQF-M-?&z_&_aszj0t(z!4w%gSNDgT2Gr|&xlqm!HaET4TJaKT@I7J-SC zT0N4xFSJ$8>ombY===xknV)giJo(MPw?7{xQV@Ug#z&p+x|oDQA!lx33#5$64b4Jx z4HQ^eh^Hzu|J&{@Kk|PCmQ_#QN(OjRVg7DRGA5+qH8ARRqDUg5P1Ey8rpY=W*Qgbm z#MB~{;X3lR|4(`-SYzyr@(VIC6V0*lbs0DG^^snBrfRNFcs{ zUxMnxi*$rOB23|0?oFWi>W4^kfnUk6KYfMd+gKYqb@DN!?C8RCIF`*PI8cUCd%Yz0 zw~Op|H!%)B;+z>(Fb9gHRGp2BBmjR#-H zY&-y3b-mP=(Iz()qX)>?Umt=5=%_k};f%+L`gM*BqtJa8pB`kzdqPKZ!|RwTNPR!^+@cY`hsY``bs^ z&u~-Q)Ap%)vlhQN!2`tK4Wsx*>|By@Nr>hW@T3lTDdYHmbPM{_EF-3~V%vpFj9c1U zXhYH!{vbBdSy@BVS1&oiJ!*L;czPE>?KH{yVWTgDKr5qFMCQ3T-nK!vsWY@Ol@o-& zV}oM(u5hn*oPx4bNch)r9Q4S&rcD3y4XX)ug~DY__gn2nq?$3(1IpH8b(`+v^XD#; zrIQm^G>(o}VryNoV{|rbaiz9+`fPT&S!ji_m$yza)g90aDiqEyKtsGj#q0-7&A-m1Yuc&X>LUyLB}#(Pk9A6;ZGQ6$6(msOrJ&T-~8G%EA>L=Z70 zQQKPzAu$Gy{#5qy{eyt7*iB?pyMzrtq;K%Acd!b~I96Ug7=1gU)->w2+9G7?=lx2{ z7w6dx(r)oG;$?*!d+d*G2cc1>*Y7`|#4OSxK#?Rk_06^DdMXG3c#gK9e-^ncY)hW^ zjopxq{N~4~Fw1X1CLq}Mcr!?)pmq|Nf&$Lv5)G3TOp12KaZ#|^I;8{rGv!3-%s<)?EO?9;C>S1g)mAbXW?nARL$kR)MHy1@;e zZ(3EL~CpCWDT6I<*`oHcz#o1=H>6S^`l3V<*i<8a~@E~f?nz5avHZo z4*}9)diU><$5ZL*hZausA&X^H;H?Cjgj?L?EN+$9ju3}$IeG~*!zQTV;NtVpwL!>N zpd^D!nUS<3O#(rD*T>Rz!!z!tMdX|gX9C^<@)`hc{u=DJJ8~0HGv;R=F;?>l2i+Zy zISX&RV^2;_G?rOZerjr-mKg28I{BAL7ijKL^JbE@YfYv-33Zb;<-Uw`HX1>S<0<0_a|vwEXG|PJ(50UNLAkmq?0W^y9m)F|isb^2|G~)+gBh+Ki;F57QZEZ!94tStx!QjhWs_7Y zIvq)(KliBskDh?i8{ol{wf#Kn23Y#ZsrVeAqSQPlbzEJvP-`g-OFPkXSj|1zwvADRBOss(*o!fTeW8 zUaZE7bDddt!f3H)+(3{XIeC;ap8S1sW5HOQVoR%@^(SWXWO^BHsMIBX%M|8DR#Lx6 zqpsFX@Csu*txo#;i*!{1Aua1*2Mu8oQ|^&#f`;E#DoqeHd&RFjlPm8XN+rp7qql}0 zhv6}dVSrotV&>!@gfiCmZi3I$O`X4^URHQ*dsSCrH7Ghn*e6#)nVDiju?pP3`{sM1 zq8913Tokqau261iTGT~kNN2CQUMp)u-%ntQiHJ|vXEvwKN{B@mLZz?r)I5;8cztMO zTc36}MdAF^jDl)2WOP77d$Oy3?;pfZ-uY=+A7}%9j-vne!GYR5K|?<#rW=HnhaBX7 zsr(`NyaN0O0r5H1%~HIIt>GwX)Tzwh_QQaE#~Q6y2aXFh_UQ;EYwr2aFZSR^IDwsc zTj$E1SBAZnXOfl$Ip{r=wkal&+AJzX#=iZjd3~FYR|bQt5CdtVI>0LIo%-|3Cf6EN zP{#b1j@c@Fc}zUYGt)RB>epe$j+h+EQZ3%Ix)pDY)N*St__J#Q3u^0gl!BBSCMegc zHdc5=>+D1-twzlnOHq2LOHUB1ST`t^SAQhg?5KH!o)$)!TyDI%`1vCL>TsW}WEKF) zT_l3wFF+O)2J-{oq=4g4960ndlOIey?~U;$DF=KmS#0`u$c0x-#|??=`Y_5D(Vk`Wv`so!iZW07P{j zi@z;Q#y}lkPg7^T65NmADIQiTg$xyZ)$2e$#uxpHR)%d74)a4*`yi;S*n8r(;N4m2 zeN*Fx`-Lv(=mr7I5B#!=FF80@4^1hA>j7IO#Ey$OS zydQi?tLax$E?1;6Wxt$VuD?qN7zm{@6EYYGzB~X>3GuNf2X|jDT^XT?JuPIrl-US{ zZG&~a026@KLAwK6Fh1H=LbPP!yAiV>-=nbXx4DSQDIX7 zBD3hZpqOy1oDl0%cHtA~2H%fVcH&l*>?W*L5h}@PnE7de5b)$r4NXw17e4UEMk%y2l@WUM)nDq z8jl%0S!QVY_HX#d_o!+$7h2JpAWyha$PJS(Of)oQntlD6ql$nTQ7&yrs0Y>N6+A2C zC^UGnQ0bQK4IH`Np9dGEC{Raa*uhvSPH1EhynPE}#gkA5g_INSrQK;h?R# zh4_;0GGYKkZOoH-YceE=Aauk^EOY)`vW>nQt^n16aADdf(;~4EI1gZ@P$RC!vn-!m zwxl}ije)V)$!Ochyf9>u@Cm%4w>dhN%(KEB`$B%eq@5RW!Y zu=nFd*6H2o6*Z!+0n}gO(m%IZlkwku`MAOSqkM{v(3`A#S7JOMLE^v}f@MrHX^``~ zI@AF9AsOwtj@K@f*`e!jZmN(&_6z}tmSUpO%6X`AH;?pfXB<+eqF_I-LNPU1D9V-j^JT};-_!U-QeXHwMF;=c9?x1-=+ZtF`=Y)6{&~HJf@4J4&8Zz5R zeyVXmr0_}}O)JIc%ci74U(5^_sgMxqXzvvqt6pV^PDnqyr^&;U>inV_r4ncv#o> z{(TC*Ehl?}-Y6;ba`ojKt3VBJN5Ej}%3sS9>dP2fsj>+V#^qThgPMRhu0_q2W~dZf zv3>;CWz`+5l{$wxFk*q}LaL80e-MVpEgqpmi%aD0vn^OVq&rpUhewPT`bYe6uVDIg z<@`%-BzxUqe&Xn-d)$Y0%ybpZ`b@nVtW=xto_L@T_OOjX7*!Qfx0e&0TCm@ZPesJr zSoWh2atACUhzNT)TeFbeD*1hAx|Y0a{X`^onqWA0(}Qr|VJq8Rl&?KiwmWt~vE81o z*)95vnt5H8%bCk%z1o%vb@8ggq0wl05KF3$NYF96uzAw)(IgNV5?;*=Eq7?CI%{mF zJr18i~$VM_uVI6`NLhX}=mw3KkHKP;6$w zq5o1ITN6qnV+YdYY?|ALg<;#mp9b_9Hb({o$0awDRBxW!`3%nBOrQhcmIDz%TlXA) z40wA^Uy2L}fU+>exU@S#Std78hJFQ4Dtl)?s?+~L2uYj*UhuWaG~UO-?Bs1`Xo)OV z%j?Qm+r2695QoKBlY;v*qHm3?1}k~)RK|(+Z%91YB1zFWHYtK|P-y{k->sPzM22Ap zkQJ4gMITsG3yWj|Ep^vp7|K1EHnwr&`Zv0lmw%7IRi~Vs0k3jv+k8Qg4?D-@IE;kO z%8Tw-Gv2#XnD6wYGumOSZEV$cduo0C?1@96pXh@)i9Oiq%kmudvMQGzweOQBiBP2v zCGV-X-_uF`9hc5fiKvG|LhLJ4+8Rc&pqwJZs6MUkrBv_im3{{bq6!xZS!uXFH&O|D~WWcw5gv3-}in`}_=wPgRIu$f<$G*E+bi9tF%IX0i z8flW5HKLvLt7hA8H3JAJxpt<8bEi3d%j=YVUHg`~w;wUiJ94rAKJ+Yyey@DxTS9@T zEXVe*pc*z`xRC>a@$8E9P|c4+qp2=a#u^(_g=dsBP@@1EyEpbBl1Ny<&QxItH!bJm zuV}LFD$jSf^6gDf1#k`3;oHj@*gi8IC(X;1I!d@u3Oia5*^N-sl#8hfH1YCSHUnm0 zinrhlY=TT4F-wDKy~E#qqd@w8HL{(%a5Z%jqpK&yl)*(LZ-GQ*Zl9M>x5Co1(?tE> znH&2b>`kWZiqD1*b1J0L^$9qb0fto1 zMA@9dyM!ud>p;ZD$8YKsmpzGM*!(^}K30LHuA@lE)eLz0JWb}<-=kQy;Wt`MC}5h? z?E0n+i5~FRnC%?0BVXKUeV5@Qs!N%@MgntL)4U4qc#EQKp3HhUMX@;Rn*HKvNB8CB z518(f*O(J~sSd5b2!G#DJ>Due5*zBoBzFd61uo3;aGkZSAj-jzoL&_Q6HJma&-G2)NEU>{w{}s^de|yU}7K6rr8c! z1V!_i@?kqj?lK796$c-xoKS;1q+(hjdaUw3VBS@59dqqH$B52ofH=U zqD2yM#nX*CHNJUne9mv1rFKym&r&ksT+&V}s zGWw0Kb=54ZT75X4FpUDiX_6j9aPQyafBxZUddQ%uVNFzfkWFCsF=2u!VKMfEQ0g(% z!N!pNd3<`O?twUS@8{j)s+H^a(b+dw!*9;nZVq0af)fuHhIA~UsJn^QA@-{tpT<%5 zL00e1S4D=>ot*8fsXQ}nb(U7x2gbRx_prXK_*h7rW$i@IxX+_kv=bLyR5dQLe!iFX zx9Llgao}ounkkGv*pIW+NgcPt`zB)UG_Ll$ZAOo^F9XO>;A4Z`2$el%>e7i0syp2g z|1?7PYnBqn=f{m^8+PYSo`^D=et4M$qXM{10+`P)}S0Ej)ttH6V z(WRaypoB3lZPQ6xu6K9CMKp<1bud2S@KQblfn%`()SmR%yNE+ zr)5pWp3T^-ni)5X3i1mc(1gucaA_4q+1r(P$4WH>m+0Fe=AYXutZM>d5}Gp>kOiUb zm0g1`Spc7*(?JsBaO!BQzzI>g8GwY-c2qlgS?c)~c5#+bQww6*y-i}_qZvv-Xr9J#B zV6as_=P@i!K1BVu7QJgMUD|m_c6P!P*FDda?qr=;&rt4-f5ZE(-iA%dbgMg)M7N(T$ zFL59?rX*I#uY-rzCPAzQ({$f|E}+xvsJ_P^SX$a6F?Wc59C2EDlA;b$KEB31zT#!d zk3{%aFV=$8fpftywU0nxda-06V`r&;zm5eFVnZ;W=EwznNs8}=f)Rucd`=* zP=o=#!$^@67ep0SRY!@oM9pft%Fd_kJw6F64RxbT0?aB|=RiN({i>}@AUW$HTa#d0 z&c|%TBGw`q{R-rS&mC)7LwCijIJn)FbgrC%!FJt)$k!~wp3jr%p8-d3~ zwep61+ScHF>dPO5Ey|Weqxz>3-*|DrMB3%P4)^!#o8rP+(Cq0D}AMKqe zhXi*T$BUojPy&-6iXd(XD!<&o^BZN{HQh?$3LliU?i`9pGdk??!Z++|B4s~@&jIS* z;&9b;$;O7Q;Eb-`GD$xg`0>DRH!wg)PGvQg_lo0I6H{k(C44yL($@VxtStYj*hh4~ zqglH;TE4y6w^E?a&W;3r>8uKsz8?DJ-^Wk&b*j9_BvQUWKsP;%Skv^cQW0*@-#eW- z#hxrvpTdBti&dNTpp>A6ZE1pcin;ic_1~fk_Kn&ou6K&fW&uQPVZrprMwvc=SifWl zg7A{qBojZ~t()O~js7w+a@|@jN!WjK2f+8Tz)) zy+y&YrGDvxPleQ_&H&1+vM79WJ+jc)?kA+9XyV2`Uv=7@Lq35JF+>?3l$Yivy*jhx zpspXRKck8{Mjpd(WFX=-vn@1uSvlPnB%JgO))QtgfHVscUTXNAOh-)Rdav)yYuGs! zl4@CWQ56dGw)^^>x1?Rg?i7-`$oti6*e`SbKsJ(PNon-21wzs)R9n$i_`?nK)5mlz-Ko-fFeX|986A&=i` z+4tb`XbOGeeq`z7UCXFc+*TGn?f#MWpqYshem~W|=Vc_J+V#?<-tC`Aaztm`<>&=+5{;g1?ZiTsBdBA+ekY;~!IYqWB~qXmQzm>h;dbojVXCxo zl zEh0T5y4KK$Zysm-sw8gzH%c*MxOfjtseZ9drs$+OC4ZZlA<=?5-7Jnof#6j*V=?^h zt=SoyTnMN9o7>>qsIbAp(r!^d=@j7)Gx6X#>KAXU1<0<4Tw=qCZ(i_{LrTs4B|K)8 zMMcwo`8G(N4NHYUI8GTF`;H(;!n3R!;OvsL!QaXlx1bltef8Dtm!>rNM5{I?v;`)+ zF0}zYpK7vP_p~Q>z>+TSb#ro`ET19wvGAm3`@EZoE=cRcg9S;aSU?eNzn2)$)Vvl| z1a80T7FcfT{Hj;AiR)9E8G#FRgb(2=q5D;X85KRh*Kirkew z!R}4tsguQ!>gh!ly4}f!k4CQ-AHTIfcl~OF$9x2!w@E99P|XN{=Cmx^HBp%S0>N_a zXla2D!{VbzfYrB*{{Z6<5199p|br9Z=)P7p-=~bG$P_{ybDC?aJ|)*X_fQI zo6JM^D`WqSw?#%Zh)Tumw<(+t(Mo zR2ak1=r2Q3W>X(s0fPjXRxZs8zV=;oaC$JNcs2#pX%)S?24W3%XJyjf(SCk^*Mfn* z(@ipQfl~$A#)Z%I@`47*9WrU!h6d`q-e6!NOpZ$qNm9F4+eeD5ifB(?ARRh;23Bz%K2c!lmQ@7HrSGVUDlj9Mc`JINh~^!N|`E+s;~K~h6J8Jxv4KUJKFM z7oz(m{rO@W^OwjO0=`oyg(KR|kAkA$s!7Pspu3ulAXjw~&4z)tlo|bK`!|#?!$EZ? zn+YV7+7D)VF&!%QZL!JadWSO%1;(|7(YK;`>86swyJ(~e7(5=3M=fC)(Qu%@fy5{| zG^0Al2LviST&O8p@++NSgYssEVfAo0mv*z1{vd#KWK?Wpe-KmC_NoewvMz{SUsbs6 zX!%Z)boOrdAxY<}>c0&h{T~ds|7&>N0qbP=8zUm4^MXEfD!A$@?P`N#Utl!n)vrGD z^K7LVp?*g}-m8M#N&BLv){MzB@(LX+wZ3inHSY?fcz=<|Hf2P{%ZV9S*Y!8~Or)#12Q`$o>)F-EGy0>H zUmncHUdW#FXL(?ZVwfnze2q(%TOX2~0%RQeZ&5VbXoQC^KA78hCf=R=_!bcXN&5!UIx=TrINdM?L3OP04;8xiK%}6 z^|KFfbUE_SBB}z@<_4C4g2~>O=8g1HGm(%`q9V;y>-{d!Zi*zy?SRE2zlvkVS>25Rk>vb2 zQcI8U6&-lT+-xlW=6uKapFZ;-c|bI-jHB+e9Is#`zfX)Hd~#hDGE~QlMvLD8x>YH} z?EGr%TbKYRk5WE&`PU@jnS#CEZ0Q{rx=kPUkXGy5UzKYtmt#VNs+OF`U%Nwh@@0=a1?mX zj!bNpxkny6NZbsAz6EP>ck(qkS#LGjN3?JLQD=iYC}6>>OTgMBhi1W_F@dy&x!Hh^<9)Wezwo~nc-ve zm)4OBs#@rM{phVQ3zTB7nP^l75mZtA?5tY`GC1XYjrBU#q`$Cl5J-f%tPbB;g;G1R z=i^-Dp%7+q><>*xLs$l@s?yK>#_wI7){cY+m$I;Lrmeyvgt5?vjD!s{)qRJRjo3Xl zv+*xi`csZUlnrw9Clb~cP!lJ{5$zFm`@gHeh5A>{@y}cYbtj&FbP)6Yd~oY&ZMh?G zh_~-|6nZ%A>Dez`bme2m1HsvRCwJEz*V;QpY-20(f4Np}JnM`R z@R}m4bAmcdqV?}7;hgNcP3YEL(CR4od#3!Jv(LBI)W(p;vhK%z_$tX88-j;;fIQd5 zQ{PZGw;4MI?3x*qB-7?(H{4)v*-9`}Ah+k9kE4D<`57*;(!C<+zhndx6(4ju@QeE_ z=!Ng)(|o%Y)B1BZgQyJN601qO0qt5YdDhCREN#mE3r*w(jRvyBZjEM?jiUR+7SNPYCk=0U}=vdVz&Ug{DtAH=VutbYXlZS#N zWH14>9#ZImA34k~um8ZqMe6k@O0;`XC=06i%|V-JI^cq7e_5!OwPV|iO4@7dy(E$* zk{ZzjHeBlUbB-Yw@&dHb#uOWKk;!Vjlsz89_YyBIw`ogWNb;xL3kee5!9q0GBB3rr z(Z-WPj()Y?iK}wn915;D)MPHOr6F|_VqdtcG8E$2G7Em#5S|~y!c$S}rVO)4Nh$=< z4DGMT$!kye;4jMns%{zxxesPrcIebioKBxS&Pio+dQhI z`B;8=BQA{^dDg$%Wp8MxGHGxAhCnv^~6m zy<=CL=!dru9G~_o;XNLh!4%<%qdcI1#IO9)s~5>ad^08jwZ>8Gb+^NJVxWhxu&-9- zM3F*?f-8L7pGYbh#C4gE_lY-jb;Ui}JPQYvFdgh7mOQ5=kl4ukSNRJEqjnb$)SV7KeJzUo&4ccqQL#Gbaul|lcdhlf$yxqRo_y7vsAXwxcI!@#% z3ZSKhl`&3Ov~sMb!acHxL}vi4O8sxKx3A>tEHvcV^CKiYi}X)qFk8xc?W{@w7?Y5j z?ITQUt0Y(Vy0koUI5IVTWT$2vPNuJ}A6k4j|7b*&^px1FfIM5C#*ir61}ibQtKMPL zk-XF4ZAU^yHq7aV`5fK8=D_+5dE+e=xRhd5o zzp?0xDY`i$DZ(j*xfS8`e_Qs};r>x#T%fK`YFq5xhD_3js1twl)Ktyd!0b37(Qo!6 zt4qQoQzVsb|83#ho`-wHsnO~QKg#T``U)42Euma8g^H4j?2EeRqM__&CUgWJmGK^1&ji5)}vLwT=qR?vgF{w5fphHT`Dxm!@E!= zS95%t3*d4x!+JM=M%xh0BRi18Hcsq`e6Ytp#XrCD+e`s;_cFN<+jbCcW<29S=}$00 zSP5H=Mxbb2zf*mEoK-aalkT@q*{Dg8K~tmPbaH2ilIqdh1ABM!XmgJFyP#;RqzQ(r z?vlf2GoaD&{STKJ(Cwv-|8oCHonErKKg}cwM6x4bvYfOP;ZjK+`Ik=9vaLJW?jHo~ zE3a!&zxS|S4}sB=*mnt%qX_TNH{S{m2?4g7+@`R8q-c5j6_Uy3Cv@dup?|)qjyD(> zM!x%h2q&8JyW3qRnX2OO^lgf_fr=9Hv4d6Bbjkw4yDlt9fgQrM9;Dw+DV|`%WlBds zz&cCt1;Z4B=|A)t{{Q%#*xxq^A*s^y)U#TjGWxdI^^iY|NFwZD`5~@yDWf(!8La&+ z=t}IgE&7oCf|AOJ_9VW}`~@`Ur@wOyW-*P41Bc}k-L{>OV zGbClcNIP?Y{i%W~%h(8y=cB);<+>!e%U-}j5IjX^=D?S7{{+`CcBFU6rz~6^7S9~% z;JcB9452D>mA@Uqks1r|*%2ma^ZSEP$_{@pp^a{y;pDh#uX?v;;>nJ{i`ytm_eq}g za?GBVt$^ArqsV|GlqmCMPPMdKjdstiz+X5|UoBKG{Go?u?O1{kp-CL?K;0WEhK9y- zK6fJc62{{Pu|7Mse_qZEH(hemo=P0H`GgrDrLu%14cjp)gTtW(8n!FST#pq+mFDOK z-YkFbbpl7rnj_fL%l8^f-nE@`Iq7tfc7-cm$Yy)E?u&X><9+L~LeJtJ{*1mI+e{2h zcN5D_9(>#tCxKjbv9ZaidT%p0EK$rt53!hdoeS|~D1tI!MFQ?%E`VjDL z7ZRKUm1tAv`E6)XclHlL-8HFerf_Ao_+0b4shk6#{5QF}d5CEt7?rY3WlqG_Yo2iJ zx6i<*L#S^18c6fkQ4&B_OmiE!S)YsQ>=0$wUw$r&GwID-MJYHOuMJZwD;)%}L0^A6 zv>=FjW!VxCrXL+xXF&^yhJ?(1IP0@YpD9S&)iNk7L$?3ae}NionCDZ|YgN3wDo|UX zm@#t|!%Ukb7^ZNb&L$87Lr2}WtDX&nbm`-e;3FR7{Z!3tRn(qK(wHxtW4|7vH-{!8 zzox@TkR1)v&_JLbHpn%DWX%h+bI3${Tr!?9Bya2R2)kiuo7Kn}O}3d;9}0SR1^PLq z*`2J>W`4Yc%Do6e_k7@is*AUORosn#L|MSM2(D3Y zeX#nu7G;ubCay+VB*}vnRFH*W(}~Bfgi+W6h0M5z9g$Kef4*{N)}*kC2UXAUXM%DN z2hGIm$*j#{jO*)Cw#)$kYmTsize;n#fNJVv44emI*{qWNPJ>0o>A6@OiQvuY!Bc#J zX!snl^o#$lV}~$fUE+lD-2}X4XweAt=mb{CU`VR$bH{Qa!rohzSbuvVHp?I-po& zi>zBj<&R%A;vs|Nk^Wj9X(9kMC?<E?6Hu)@!gkwMrOoZmAX>(iu3KzTf`bL0OQ;QU0yIOF&s;ymWRX_raKdbv0|LPH zci?ESqFfss;4Ny96ZVy@4{CjtJi6X;S9c`Ck$OcL>l}G@?eJP>YO`_nYWZpp3X(Si zk$zr2kfS+W|JEw7nS%0P0j1>a4MGFk`_R3rt~|PXpnMG$oA~>;-sK6GSxJdpN=h!$ zYkBO|E_|9BAvJ7Z{voo)AxzdNo7@b^qFOQ@RD{=ylogf112D3qg@&c|>J0ooomYg_ zCSk1UbSMQ5)Tl55e_T>bSQ?eThmVs#&zJ{e+9Ce?5^ECmPvt^a2pB=_r&fbE)9c4j zZ=%9!{k5XhWGDMX+3}IIuVHreSqF5@O3I83mDYw54-9e7$>kk_NeXNYc3)y9XIJ8f zZFfY841LUR0SWWL-+yJW?AP#ViR@k747>8Ij$FiZC$q`#KnY>CRBz88+t_z{nMA(A ze1ZFDX`&-X31g(-mcB2SyuGWA&bFF$EwI~4uiyXtO1ygW+Ov7E@fA^ARmY;hZJmM{ z3((yvk~+3hXHQ5|SpQ3pQH?bx0B9vKAb$;d@_10~UH53#`TTvum;-0L3`2H-#ot|l z+!;r*WbytabmkyO#o%-!s;+X)P$18Ik@kZp`&s4ZxxGT3m-?MUMxF=Q-w|2>SWX$S zC0-S4PZg>ypv<7i7`Chi1TI0UN~hJ`nd2v~xckR2`s9I>*M@~&(6INBa;xH+j)Sry zm#?~6g4(8VeuwRE^ufXsA@n(NW_+qENo?CZx$<;Udtuad7GhjZqZNNGT}^*2U22El z6>hu=+MC)REUnzx!!@Gs*xZv5T964T1Nhccq1U;o(jobs0=L5g9wKxw&$+cN9~&BN zU+D0oPfuy5h*uQz)f8hXKW9XTv;}CJb5~IRn7Y{QnY?4!`lr4o#wKHvZ!No zU7NhisBP0$Tx`rQCi(C-JhFPjWGp*`X``nOb6-`HVlti+bZ? zqniEZB*&8I4}#s(@27*z?ulin`yYfG?pSV!4fs*3grR;Bs;k&POqpKa6h{6XqA$x3 z{nL!x!A`*WMfA<^-<|`jx+V3FpWrPRLQ0K~2W5vMen&XM1j|6d(fGT#f%$(TOfE;*3|7?3gmZ@17CKm63Vugi z)imjR^gW0b{=D)cxN(Q`SHi0sXXc8{CtKi9bL={?1@u8B!G4R|OSC!lm!KUr75Ws3 z@DJOI3ya9#qX6lbsZJD=m!nRk9jH7a32n4{!KBD3q-#N_Mb^L_MNut5IqjgQd;>Zi zn$gb|kgcwndh>77@i@HmPM4cz)X2?RI39Gaww+~$(ifr77I+wwvB!qJT$f~CwM=D~4k?3|Q z6673z7>{m=7M?eXEW+%XN9iI>1mnXsWTbmITk2LnOA2Da7k(AEJ8rlrTPi-?e z>dSa(NC0L28l0`yPuROECiAgvFbhHn=D8WNp#w&6u$M&cQp>t&yqznse?tSofOIUx zmhI>?DZUUzH=j360Ph4QihX=a=tDr{A~=#H?KcDLG~teT8{PkWmW4OP{frcve4-va zcjT8fW%RzR?XV}dCur9P$D$)panEV$;{jcqD2OgP8svNN`BcYGln@Zy_+9=^(W}Uo z9=-Iv!QMMtg9eCfdaM7_+>*845zWs>TCLOntGV-xYI6JAbdV-Rnt*gsiVC6kuAsCC zq4yp^0wgpk0V5p&=?F*>L^=VahbFyBQ+f#yArPb&sRCzm&ivP`H6PwH?^-h-=JT`i zZD&9G*}v<)ZWO3k6}Fah^Atnx@s5L2oJ;*483lmgx@6@qO6Y9HrC9Z!n5AqNsYq+V zKgUZk+|YaLM5zV|r65nx2Z!}AQ}_g}|6+HmXE7EQs;E@_TFIikF^ht+r|%|W7?ebt zb7Kg9PH@gQ!Mpd#TJhu)b>+FQ(7kit}%;Ohc>o&K~oxqYG9QxsR zNm)h}c7E?Yw>DU;|MO9Iw-f%fMoZkX^{t(+9D9}cH;IYZi<5lxF!eb$b9zNn*umc^ zI!22l#yIJ%-V+>~qxmEu{AY5!`(+(kL(y= zjm!tV_fhwyCT%=THvVRP1Mu#;0VY1Lyv}mS_P;@+q27`MbiAkNN3v)_p$E^mTP(cD zMVwuB=--+;0hI_Jvo!uxp?q^*`sA6a1cC|@*sii0&$#~wOya|s!?*u?iGBJ(P3Fqd zPe|$yF4k2q)ojO9)|k6aotG#ACnS;Hlb9=ZY~=SJC{NFGbRgzNc8)S6q}S=}opJ5- zv6d8-<)exRO}=SZrGZrQS=@Ve#*$HgzP7sdF9cO<&-Xft?fR6i@y*1= zVYtvPPpGSJFbR8is=%K?mCbWf4V&ZAyD@d=_-VM5j=+lfvGL);_6&;Q8P{(QfHNkU z8j>8~yJxiHgA}HvvW9v=(FvZO_qAuJ0?IT_%4DhzrfNT`y#qdJSo8|A&=KrypUYJARdG0a z{q-+^N>=P{CaXN;`oroqX$~f7#^a5Kk|Kkpl@`fXjahbn>GqROrh1gW0bc9Jc0Gk~ zc_HY->uJqBZYQ;MaR8H5`rU`TAtA}MYUUOZ%L~T}sF57od47cNO9ekM*|9hEZwSir z*8Qv0>}d(AAx1HR!2+PJH!v0%o5#C0`$BZ5SGy&qZPm9eEFnV5K`2(t5<^uwz9az) zr0&op)XG^xK87EiR)4=RVk-(M+4#|S5iVAyxx?aI9Isc|h>5F?pJrt>43!vfxqD_d zeG!>o-0$t~=L+RQ!Ti@<*K-tJ#U>g&>;gnbZixH$D?(DIrLrt5lsRRzYSX4iA1(cC z{gWWC#cM~VMfnN9Xa^g!N||dH*>XSQZv97DfMo>!Zdp{9v*}yJA8M1Eox8P=>fI?F zqLU@But4vx=X(mMla`C{g2bt^HtaCvnk-AJ58^+%fN9PmE>-xp`pGj-nb#jK_)bto znwXQ5^D_PFpGL2qMU|G>E*9nbz)lK747W|!DR}5ZDKl=o$iJOTMoXL{0oI|IF%+OB z-DKD}MJJCyVNxHzEqwjji=!7+QQqG5Fbd7Sq3o6{&o z(^P>#fN@UR;YxVclq2i#2hToUjW+ryNkh4(;JsK);U0IY&@=WJQTu2bf6iv3#Ixxa zgfoSj*58Lbe+U6P483E**N$NBVo&=Aj6IfL7`rAvPc0mtQxrk<79ra}{ykjU7^0J? z%WnEU4viLsO+ZMXh20!A70t-~r%jLIqS7UlpCi;m&QH>f#sn z;EPodKV6Am&&Q=6Ng^F)^dguw8s2NtA4T>e0`y67w)k&_RD0QFl>?Voc!{U!d5H72eyyc9=2Q}6m9X1n|| z2*#tGM)g>jH7&4RYHlsO_hTm`sh$@ZCdUhTujYVP`oO&9@d{qIDtYP0`%vlx9`uI* z*gUUn-y27=gsr~?lKSMAJTfx!g*%n0KpD}w!L_fG6B_QUZdm&QJ2`IbOJ3^LN|BAF z8&mGri+snCC=zK6$W=PnHJVGOQ(ItH&vQ^k24LcwT5fI$^<#rq6b~U>frIkrckYvG z=xHoH>ZO=dpnv;1hOkfESpU(|PvUpS#3XWBH3NGbR_q zzG8z7WMSIkb?+ki)k!A>g2^<62K!ys1kNT8es&ikl;FK%SuIzgEuIOl@lvYsdEfty z_x*qI>~~heG3ctbM=G$!i2dL1Ge(zIkHKNWZ!qhD#N*@V%|Mgp?JSl*1cZy2}w&Og`V3Q zCm;5Rf7MveU48D*;wZ86JD(h&b^~DJ&fozivIkC8B5^bG_Xpo@Uu!kI8#p}rn4`Na z!ir}PIGV{cQ?xF(+`PREoZj=4N4OoCs|ioen>vuG()%X2m=be9{`n01atR=G-P_pi zYdC+OTXbfd$Cz~;Em*vZf};m`)$mWVJlLTuPMZZs)0Ol7(sHe`nW^g)FsS6dmQt%Y z2tZS22Jq9axzT%*9b!-5(*>`ct{6FfMx zV`*v-oH%>{Uc9>+GB8S}`w=2sTsl8%2|KDR(u80 zEL-w9^=AfO#Jn7R7*A=Zfjk`&T5&vUtvE9Iy>6%ivK{aSrC8OGJmlvs^x<(d3-Gy} zCJ09yyR(z+9cL}k;cHP|1#++W)*$mRlGSF2!5!M8U{3OZ#fki00@tMpM~=YMi>dpQ zPt$%_YqXpunyWEiEeHA=5gm>C5ohNb>JmmL?Ur{yihi!_+_CqcxwC1oXfQl9qGWpG z!5%Cgfag#C<$-j2#TFFy`yJ9ky?JU!xFUZd^F(&{XO4(WD3Lv#108)1i`Z&R-krB~ zi>fP+iYM8S2hxEXZKYvnn#JaKUJ*v#%3Fg%TG`lHssWnHD*48a?rV~ zCRhF$FFJEUlPVa%y+a=GCVkEsC)V?Wy|PjPd-Igtl=TE#gFx0DJ#UWW4R>@3a8yfCr9 z{m3ALhZp8R7Vt9G@Ym_Js#HQmdFZ?49W6pa>P?UE@cJ-fR{Vo1!p2FWH7JM$uT-{! zj+7L1uD9J0Qr$JuQ5GCB&#NVgTG9-5C^A03b|^$}q%>kpNOPm5K=cd?82*7zu0AoE z3{Rbh(gg;-sofd|57W>n5h4O$6o07{L8?ZN&k^B&mbN4lm%mD}R`Y&dI+ohb4#F6K zAoR2R?UW7b6knr{&VD?65UIdA<4Ql9NYQyRcriVddl_ZXeibgH@|$9d*_;iUV@k<7 zTYWRc==KH~fU7&EiKY;@aVNk%HMb6;!d+y5JCpP+ev}tEgW6pKrNNv;Lskr>- z*jf(Ctz!`)WIQMk1hW}Os)HK0mu5$3X?kKS2dz&cS@@D@dE!5L9;1*e)?fp%dQ4T_ zkLCekgXxEOAUSEFF-<=vWlRaWaev&|!g0F#L`lUs$IMR4v85?(nEPogGruS=-yu<- z@5!iKuk}kU^u*7a=Bn1f7KPclRfPRvfkC-(>U#;If$jbp%`bX*XjRU^9+eLr4m)T1O=;7|+erR(DJ9cdoa z`4`}jGyS^rwEaSd&wgs>uEF(Sn`^G%$fCQXf*;{($^5oPHum>owqLJ)(Wy-?iYD@s zZax9945m%z>tEp3YF!X(14dJxd+m+VjD29@Y+5PDoEd`A)d;dLlIf!6mdjwuxwuk} zHk-!|msLkZeE^FSKM zJH3l{zta~<`)yxj&Z|(#3~B0zN2+hReMz$I0t|Qm%|8t-*jwjRR_~F2ljVNkpu?pj z%^s(8Ea6lI;>{mY6UIcE-b$2zk?Y}-8(~+EwAlx()%}>qDAJ8NJ%~Q(+4ggsEG6>i zmLXUa!>$>2Z2tuQdFhjT^469R;$0@3j@scPEk~FBi+j)O4s?qhgFFLWfChmP3-_)^ z(SmV>n#PYJNEm3o6PPZ&Hg@IV0PD**6>5JWpDW2_%;KWu!c|@#RH(f5I9MQ3L`?Xz zWGD9M`|n;lfAJ-2@;~Y@2P&t`M-S>_UWO?47<9mIiFyp{1v7EHh!6NVFB2yOl>7lJ z*{HUaZ4M0fU5FaRHrWbmay&c_TFcO-t&&Py;%j#$uU>G5X$1*C0aNx?}o91M? z{yaGWM>^*eh)h#u#)+M6nM0udLm0ZP_e@^_~tInL$a>w*rmWn?^i2E@52ocrCa z;If6;##344DMP~pn!Iv#REbn;pRyck26$bd1xIKmGWe`s>dM!u0;$G`mS^Ud(j=s8$kHjm$^3{ZGL?k%W3{e6sx1 zPy)s5YWQAk^)QtM}Gbb){>H#aVgkd6&Pxy7E>gF8=sr@;-EHw%A zU)QkitTLXAFQe6+@)BX*60^1%g+nqbV=@d@<{v@${GY3D8vDkR0XndDhTpAkY1a9_ z`k)Ag0;@1f<_lOvn*H}a$GE3aYn>YB{rfo*V^SagHQw^P;lSyO>&e%eh2fcUi*-=e z!ocmLH47(ceu6WxxljozvhG}v#QCY`LJ@OGtAJSxW+$Hrt_a!S8s5=_}DdSscCoYnCwOY)idvQT%F(fQ3 zusC61orI*szT(h3^<|i?|NKj+!UhCmjBvC>@W-jbm-Ioftv=6bv_|mKEmc9#pFZm! zL%`&P0w>9*9(kujysq`NlS#97DOO7vr35ERxL@PhF!ZVKBk|0=)}=_4;*)pA|ArTc9J27~*mHg%o5c&2M8Cgpx*xZ92S zad(gSP+)mJ>z4NV_qHQ zrNL6uwOR}uv!p&+h(+lL>c(H-4L?FaQP`I7ZelQnkJH`zy0a?xNR+!Tf?aa%Ou%0w zz4M(-4SE?3!FXg5Zyan~F_|gTusztrtk^9nSzWhyW#pwFf| zCQYvqv)k=K+-X5puSM&80b9x#!%}xPn6q4SEGewrB|(BGtX+=eA`=-@IdptSP1lxu zw!c)1VJm46%Jj}g3kYHy4Y7r)R%;J69^X}#W9^s4*RuezEel0UQfp1m2vor9B9eIH zblw}Z91KdEi2AKxMVWfAJSbNBkjhmv&qUQreNs;%QLYcUC7*`ACR|rwX~A1-gt^dc z&5!LYHTj~7Afa**1wgtvX3@@0d`FM?yn`g@*1I&0ER4v zshwQm$3;6qLMparH%!qvMLX#5a-T=QW$kI+?={c0*qN8PDtPk$!eHYOyi3hS~=QPSr}o66{g=&Qu@9shmGQ zwzAK1cxce$VHW!vA7Xyto9|O-tAViRj`u%giXkJRd-4yt9D1{Jgb#n^tT$BO04la* zb*LJni;ZS&H}qL!Xi_pAxEQ-IZ!Ew#gIG)*KrMskE0+*X^Q)7*9g z6bR)vt<$^FD$v_`q-%y(@CRg*b)ZZygc?d$*^x?c?wb1R5v|@H6B`?SSnJ{#i|_fs zrW!{M5=9wA=W<7HuHOq*$Q(!lJn=~HiWR738v38FejUG+3X79Xg^Yp*^Zba4sh>5G zJZm)wN8KJ)c3IX9d57GfEyBbSea_a9L-|&}EgU+7PM1zGlR7;2y;o@kEV}>hW@03) zTP3In>}DbpRfj^}u}tfl-n7i4tUOI4x-FPUp_-RP>K!(wr{VJTlYiAO%lt{$n`OAr zt#Sh9v-Txx{kQ)j`})|D;Y_Hgpo+~D_tdePkE=aXxFr*bz5q2A;yGK^q_SO>-~vXH z#LoX;AdvrtS^fu)96^o4Ri@D6t@9xfjQWp5|63^3|K9BT-@~W=|N0H>UvvKh1(rzg literal 139774 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 diff --git a/doc/telnet/telnet_stats.PNG b/doc/telnet/telnet_stats.PNG index 892d66f74673a7a8c26601739e50855741f7f4a1..2e13c456f39c88021172e4f78858d340b4c1a32b 100644 GIT binary patch literal 45112 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"); + _boottime = NULL; _load_average = 100; // calculated load average _telnetcommand_callback = NULL; _telnet_callback = NULL; + _command[0] = '\0'; + _fs_callback = NULL; _fs_settings_callback = NULL; @@ -55,6 +53,9 @@ MyESP::MyESP() { _wifi_callback = NULL; _wifi_connected = false; + _ota_pre_callback = NULL; + _ota_post_callback = NULL; + _suspendOutput = false; } @@ -88,7 +89,6 @@ void MyESP::myDebug(const char * format, ...) { delete[] buffer; } - // for flashmemory. Must use PSTR() void MyESP::myDebug_P(PGM_P format_P, ...) { if (_suspendOutput) @@ -107,10 +107,12 @@ void MyESP::myDebug_P(PGM_P format_P, ...) { va_end(args); +#ifdef MYESP_TIMESTAMP // capture & print timestamp char timestamp[10] = {0}; snprintf_P(timestamp, sizeof(timestamp), PSTR("[%06lu] "), millis() % 1000000); SerialAndTelnet.print(timestamp); +#endif SerialAndTelnet.println(buffer); @@ -152,26 +154,38 @@ void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) { // finally if we don't want Serial anymore, turn it off if (!_use_serial) { - Serial.println("Disabling serial port"); + myDebug_P(PSTR("Disabling serial port")); Serial.flush(); Serial.end(); SerialAndTelnet.setSerial(NULL); } else { - Serial.println("Using serial port output"); + myDebug_P(PSTR("Using serial port output")); } // call any final custom settings if (_wifi_callback) { _wifi_callback(); } + + jw.enableAPFallback(false); // Disable AP mode after initial connect was succesfull. Thanks @JewelZB } if (code == MESSAGE_ACCESSPOINT_CREATED) { + _wifi_connected = true; + 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()); + // we could be in panic mode so enable Serial again + if (!_use_serial) { + SerialAndTelnet.setSerial(&Serial); + _use_serial = true; + } + + myDebug_P(PSTR("Enabling serial port output")); + // call any final custom settings if (_wifi_callback) { _wifi_callback(); @@ -194,6 +208,12 @@ void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) { } } +// return true if in WiFi AP mode +// does not work after wifi reset on ESP32 yet. See https://github.com/espressif/arduino-esp32/issues/1306 +bool MyESP::isAPmode() { + return (WiFi.getMode() & WIFI_AP); +} + // received MQTT message // we send this to the call back function. Important to parse are the event strings such as MQTT_MESSAGE_EVENT and MQTT_CONNECT_EVENT void MyESP::_mqttOnMessage(char * topic, char * payload, size_t len) { @@ -263,9 +283,8 @@ void MyESP::_mqtt_setup() { mqttClient.onDisconnect([this](AsyncMqttClientDisconnectReason reason) { if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) { - myDebug_P(PSTR("[MQTT] TCP Disconnected. Check mqtt logs.")); - (_mqtt_callback)(MQTT_DISCONNECT_EVENT, NULL, - NULL); // call callback with disconnect + 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")); @@ -297,7 +316,7 @@ void MyESP::_mqtt_setup() { // WiFI setup void MyESP::_wifi_setup() { - jw.setHostname(_app_hostname); // Set WIFI hostname (otherwise it would be ESP-XXXXXX) + jw.setHostname(_app_hostname); // Set WIFI hostname jw.subscribe([this](justwifi_messages_t code, char * parameter) { _wifiCallback(code, parameter); }); jw.enableAP(false); jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT); @@ -307,19 +326,39 @@ void MyESP::_wifi_setup() { 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 + +#if defined(ESP8266) + WiFi.setSleepMode(WIFI_NONE_SLEEP); // added to possibly fix wifi dropouts in arduino core 2.5.0 +#endif } // set the callback function for the OTA onstart -void MyESP::setOTA(ota_callback_f OTACallback) { - _ota_callback = OTACallback; +void MyESP::setOTA(ota_callback_f OTACallback_pre, ota_callback_f OTACallback_post) { + _ota_pre_callback = OTACallback_pre; + _ota_post_callback = OTACallback_post; } // OTA callback when the upload process starts void MyESP::_OTACallback() { myDebug_P(PSTR("[OTA] Start")); - SerialAndTelnet.handle(); // force flush - if (_ota_callback) { - (_ota_callback)(); // call custom function to handle mqtt receives + +#ifdef CRASH + // If we are not specifically reserving the sectors we are using as + // EEPROM in the memory layout then any OTA upgrade will overwrite + // all but the last one. + // Calling rotate(false) disables rotation so all writes will be done + // to the last sector. It also sets the dirty flag to true so the next commit() + // will actually persist current configuration to that last sector. + // Calling rotate(false) will also prevent any other EEPROM write + // to overwrite the OTA image. + // In case the OTA process fails, reenable rotation. + // See onError callback below. + EEPROMr.rotate(false); + EEPROMr.commit(); +#endif + + if (_ota_pre_callback) { + (_ota_pre_callback)(); // call custom function } } @@ -354,6 +393,11 @@ void MyESP::_ota_setup() { myDebug_P(PSTR("[OTA] Receive Failed")); else if (error == OTA_END_ERROR) myDebug_P(PSTR("[OTA] End Failed")); + +#ifdef CRASH + // There's been an error, reenable rotation + EEPROMr.rotate(true); +#endif }); } @@ -365,6 +409,14 @@ void MyESP::setBoottime(const char * boottime) { _boottime = strdup(boottime); } +// eeprom +void MyESP::_eeprom_setup() { +#ifdef CRASH + EEPROMr.size(4); + EEPROMr.begin(SPI_FLASH_SEC_SIZE); +#endif +} + // Set callback of sketch function to process project messages void MyESP::setTelnet(command_t * cmds, uint8_t count, telnetcommand_callback_f callback_cmd, telnet_callback_f callback) { _helpProjectCmds = cmds; // command list @@ -400,133 +452,114 @@ void MyESP::_telnet_setup() { memset(_command, 0, TELNET_MAX_COMMAND_LENGTH); } -// https://stackoverflow.com/questions/43063071/the-arduino-ntp-i-want-print-out-datadd-mm-yyyy -void MyESP::_printBuildTime(unsigned long unix) { - // compensate for summer/winter time and CET. Can't be bothered to work out DST. - // add 3600 to the UNIX time during winter, (3600 s = 1 h), and 7200 during summer (DST). - unix += 3600; // add 1 hour - - uint8_t Day, Month; - - uint8_t Seconds = unix % 60; /* Get seconds from unix */ - unix /= 60; /* Go to minutes */ - uint8_t Minutes = unix % 60; /* Get minutes */ - unix /= 60; /* Go to hours */ - uint8_t Hours = unix % 24; /* Get hours */ - unix /= 24; /* Go to days */ - uint8_t WeekDay = (unix + 3) % 7 + 1; /* Get week day, monday is first day */ - - uint16_t year = 1970; /* Process year */ - while (1) { - if (RTC_LEAP_YEAR(year)) { - if (unix >= 366) { - unix -= 366; - } else { - break; - } - } else if (unix >= 365) { - unix -= 365; - } else { - break; - } - year++; - } - - /* Get year in xx format */ - uint8_t Year = (uint8_t)(year - 2000); - /* Get month */ - for (Month = 0; Month < 12; Month++) { - if (RTC_LEAP_YEAR(year)) { - if (unix >= (uint32_t)RTC_Months[1][Month]) { - unix -= RTC_Months[1][Month]; - } else { - break; - } - } else if (unix >= (uint32_t)RTC_Months[0][Month]) { - unix -= RTC_Months[0][Month]; - } else { - break; - } - } - - Month++; /* Month starts with 1 */ - Day = unix + 1; /* Date starts with 1 */ - - SerialAndTelnet.printf("%02d:%02d:%02d %d/%d/%d", Hours, Minutes, Seconds, Day, Month, Year); -} - // Show help of commands void MyESP::_consoleShowHelp() { - SerialAndTelnet.println(); - SerialAndTelnet.printf("* Connected to: %s version %s", _app_name, _app_version); - SerialAndTelnet.println(); + myDebug_P(PSTR("")); + myDebug_P(PSTR("* Connected to: %s version %s"), _app_name, _app_version); - if (WiFi.getMode() & WIFI_AP) { - SerialAndTelnet.printf("* ESP is in AP mode with SSID %s", jw.getAPSSID().c_str()); - SerialAndTelnet.println(); + if (isAPmode()) { + myDebug_P(PSTR("* Device is in AP mode with SSID %s"), jw.getAPSSID().c_str()); } else { -#if defined(ARDUINO_ARCH_ESP32) - String hostname = String(WiFi.getHostname()); -#else - String hostname = WiFi.hostname(); -#endif - SerialAndTelnet.printf("* Hostname: %s IP: %s MAC: %s", - hostname.c_str(), - WiFi.localIP().toString().c_str(), - WiFi.macAddress().c_str()); -#ifdef ARDUINO_BOARD - SerialAndTelnet.printf(" Board: %s", ARDUINO_BOARD); -#endif - SerialAndTelnet.printf(" (MyESP v%s)", MYESP_VERSION); - -#ifdef BUILD_TIME - SerialAndTelnet.print(" (Build "); - _printBuildTime(BUILD_TIME); - SerialAndTelnet.print(")"); -#endif - SerialAndTelnet.println(); - SerialAndTelnet.printf("* Connected to WiFi SSID: %s (signal %d%%)", WiFi.SSID().c_str(), getWifiQuality()); - SerialAndTelnet.println(); - SerialAndTelnet.printf("* MQTT is %s", mqttClient.connected() ? "connected" : "disconnected"); - SerialAndTelnet.println(); - SerialAndTelnet.printf("* Boot time: %s", _boottime); - SerialAndTelnet.println(); + myDebug_P(PSTR("* Hostname: %s (%s)"), _getESPhostname().c_str(), WiFi.localIP().toString().c_str()); + myDebug_P(PSTR("* WiFi SSID: %s (signal %d%%)"), WiFi.SSID().c_str(), getWifiQuality()); + myDebug_P(PSTR("* MQTT is %s"), mqttClient.connected() ? "connected" : "disconnected"); } - SerialAndTelnet.printf("* Free RAM: %d KB Load: %d%%", (ESP.getFreeHeap() / 1024), getSystemLoadAverage()); - SerialAndTelnet.println(); - // for battery power is ESP.getVcc() - - SerialAndTelnet.println(FPSTR("*")); - SerialAndTelnet.println(FPSTR("* Commands:")); - SerialAndTelnet.println(FPSTR("* ?=help, CTRL-D=quit")); - SerialAndTelnet.println(FPSTR("* reboot")); - SerialAndTelnet.println(FPSTR("* set")); - SerialAndTelnet.println(FPSTR("* set wifi [ssid] [password]")); - SerialAndTelnet.println(FPSTR("* set [value]")); - SerialAndTelnet.println(FPSTR("* set erase")); - SerialAndTelnet.println(FPSTR("* set serial")); + myDebug_P(PSTR("*")); + myDebug_P(PSTR("* Commands:")); + myDebug_P(PSTR("* ?=help, CTRL-D=quit telnet")); + myDebug_P(PSTR("* set, system, reboot")); +#ifdef CRASH + myDebug_P(PSTR("* crash ")); +#endif // print custom commands if available. Taken from progmem if (_telnetcommand_callback) { // find the longest key length so we can right align it uint8_t max_len = 0; for (uint8_t i = 0; i < _helpProjectCmds_count; i++) { - if (strlen(_helpProjectCmds[i].key) > max_len) + if ((strlen(_helpProjectCmds[i].key) > max_len) && (!_helpProjectCmds[i].set)) { max_len = strlen(_helpProjectCmds[i].key); + } } for (uint8_t i = 0; i < _helpProjectCmds_count; i++) { - SerialAndTelnet.print(FPSTR("* ")); - SerialAndTelnet.print(FPSTR(_helpProjectCmds[i].key)); - for (uint8_t j = 0; j < ((max_len + 5) - strlen(_helpProjectCmds[i].key)); j++) { // account for longest string length - SerialAndTelnet.print(FPSTR(" ")); // padding + if (!_helpProjectCmds[i].set) { + SerialAndTelnet.print(FPSTR("* ")); + SerialAndTelnet.print(FPSTR(_helpProjectCmds[i].key)); + for (uint8_t j = 0; j < ((max_len + 5) - strlen(_helpProjectCmds[i].key)); j++) { // account for longest string length + SerialAndTelnet.print(FPSTR(" ")); // padding + } + SerialAndTelnet.println(FPSTR(_helpProjectCmds[i].description)); + } + } + } + myDebug_P(PSTR("")); // newline +} + +// print all set commands and current values +void MyESP::_printSetCommands() { + myDebug_P(PSTR("")); // newline + myDebug_P(PSTR("The following set commands are available:")); + myDebug_P(PSTR("")); // newline + myDebug_P(PSTR("* set erase")); + myDebug_P(PSTR("* set [value]")); + myDebug_P(PSTR("* set [value]")); + myDebug_P(PSTR("* set serial ")); + + // print custom commands if available. Taken from progmem + if (_telnetcommand_callback) { + // find the longest key length so we can right align it + uint8_t max_len = 0; + for (uint8_t i = 0; i < _helpProjectCmds_count; i++) { + if ((strlen(_helpProjectCmds[i].key) > max_len) && (_helpProjectCmds[i].set)) { + max_len = strlen(_helpProjectCmds[i].key); + } + } + + for (uint8_t i = 0; i < _helpProjectCmds_count; i++) { + if (_helpProjectCmds[i].set) { + SerialAndTelnet.print(FPSTR("* set ")); + SerialAndTelnet.print(FPSTR(_helpProjectCmds[i].key)); + for (uint8_t j = 0; j < ((max_len + 5) - strlen(_helpProjectCmds[i].key)); j++) { // account for longest string length + SerialAndTelnet.print(FPSTR(" ")); // padding + } + SerialAndTelnet.println(FPSTR(_helpProjectCmds[i].description)); } - SerialAndTelnet.println(FPSTR(_helpProjectCmds[i].description)); } } - SerialAndTelnet.println(); // newline + myDebug_P(PSTR("")); // newline + myDebug_P(PSTR("Stored settings:")); + myDebug_P(PSTR("")); // newline + myDebug_P(PSTR(" wifi_ssid=%s "), (!_wifi_ssid) ? "" : _wifi_ssid); + SerialAndTelnet.print(FPSTR(" wifi_password=")); + if (!_wifi_password) { + SerialAndTelnet.print(FPSTR("")); + } else { + for (uint8_t i = 0; i < strlen(_wifi_password); i++) { + SerialAndTelnet.print(FPSTR("*")); + } + } + myDebug_P(PSTR("")); // newline + myDebug_P(PSTR(" mqtt_host=%s"), (!_mqtt_host) ? "" : _mqtt_host); + myDebug_P(PSTR(" mqtt_username=%s"), (!_mqtt_username) ? "" : _mqtt_username); + SerialAndTelnet.print(FPSTR(" mqtt_password=")); + if (!_mqtt_password) { + SerialAndTelnet.print(FPSTR("")); + } else { + for (uint8_t i = 0; i < strlen(_mqtt_password); i++) { + SerialAndTelnet.print(FPSTR("*")); + } + } + + myDebug_P(PSTR("")); // newline + myDebug_P(PSTR(" serial=%s"), (_use_serial) ? "on" : "off"); + + // print any custom settings + (_fs_settings_callback)(MYESP_FSACTION_LIST, 0, NULL, NULL); + + myDebug_P(PSTR("")); // newline } // reset / restart @@ -541,54 +574,47 @@ void MyESP::resetESP() { } // read next word from string buffer -char * MyESP::_telnet_readWord() { - return (strtok(NULL, ", \n")); -} - -// change setting for 2 params (set ) -void MyESP::_changeSetting2(const char * setting, const char * value1, const char * value2) { - if (strcmp(setting, "wifi") == 0) { - if (_wifi_ssid) - free(_wifi_ssid); - if (_wifi_password) - free(_wifi_password); - _wifi_ssid = NULL; - _wifi_password = NULL; - - if (value1) { - _wifi_ssid = strdup(value1); - } - - if (value2) { - _wifi_password = strdup(value2); - } - - (void)fs_saveConfig(); - SerialAndTelnet.println("WiFi settings changed. Reconnecting..."); - jw.disconnect(); - jw.cleanNetworks(); - jw.addNetwork(_wifi_ssid, _wifi_password); +// if parameter true then a word is only terminated by a newline +char * MyESP::_telnet_readWord(bool allow_all_chars) { + if (allow_all_chars) { + return (strtok(NULL, "\n")); // allow only newline + } else { + return (strtok(NULL, ", \n")); // allow space and comma } } // change settings - always as strings // messy code but effective since we don't have too many settings // wc is word count, number of parameters after the 'set' command -void MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) { +bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) { bool ok = false; // check for our internal commands first if (strcmp(setting, "erase") == 0) { _fs_eraseConfig(); - return; - } else if ((strcmp(setting, "wifi") == 0) && (wc == 1)) { // erase wifi settings + return true; + + } else if (strcmp(setting, "wifi_ssid") == 0) { if (_wifi_ssid) free(_wifi_ssid); + _wifi_ssid = NULL; // just to be sure + if (value) { + _wifi_ssid = strdup(value); + } + ok = true; + jw.enableSTA(false); + myDebug_P(PSTR("Note: please reboot to apply new WiFi settings")); + } else if (strcmp(setting, "wifi_password") == 0) { if (_wifi_password) free(_wifi_password); - _wifi_ssid = NULL; - _wifi_password = NULL; - ok = true; + _wifi_password = NULL; // just to be sure + if (value) { + _wifi_password = strdup(value); + } + ok = true; + jw.enableSTA(false); + myDebug_P(PSTR("Note: please reboot to apply new WiFi settings")); + } else if (strcmp(setting, "mqtt_host") == 0) { if (_mqtt_host) free(_mqtt_host); @@ -613,6 +639,7 @@ void MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) _mqtt_password = strdup(value); } ok = true; + } else if (strcmp(setting, "serial") == 0) { ok = true; _use_serial = false; @@ -620,9 +647,11 @@ void MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) if (strcmp(value, "on") == 0) { _use_serial = true; ok = true; + myDebug_P(PSTR("Reboot ESP to activate Serial mode.")); } else if (strcmp(value, "off") == 0) { _use_serial = false; ok = true; + myDebug_P(PSTR("Reboot ESP to deactivate Serial mode.")); } else { ok = false; } @@ -632,28 +661,30 @@ void MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) ok = (_fs_settings_callback)(MYESP_FSACTION_SET, wc, setting, value); } - if (!ok) { - SerialAndTelnet.println("\nInvalid parameter for set command."); - return; + // if we were able to recognize the set command, continue + if (ok) { + // check for 2 params + if (value == nullptr) { + myDebug_P(PSTR("%s setting reset to its default value."), setting); + } else { + // must be 3 params + myDebug_P(PSTR("%s changed."), setting); + } + + myDebug_P(PSTR("")); // newline + + (void)fs_saveConfig(); // always save the values } - // check for 2 params - if (value == nullptr) { - SerialAndTelnet.printf("%s setting reset to its default value.", setting); - } else { - // must be 3 params - SerialAndTelnet.printf("%s changed.", setting); - } - SerialAndTelnet.println(); - - (void)fs_saveConfig(); + return ok; } void MyESP::_telnetCommand(char * commandLine) { + char * str = commandLine; + bool state = false; + // count the number of arguments - char * str = commandLine; - bool state = false; - unsigned wc = 0; + unsigned wc = 0; while (*str) { if (*str == ' ' || *str == '\n' || *str == '\t') { state = false; @@ -665,57 +696,28 @@ void MyESP::_telnetCommand(char * commandLine) { } // check first for reserved commands - char * temp = strdup(commandLine); // because strotok kills original string buffer - char * ptrToCommandName = strtok((char *)temp, ", \n"); + char * temp = strdup(commandLine); // because strotok kills original string buffer + char * ptrToCommandName = strtok((char *)temp, " \n"); // space and newline // set command if (strcmp(ptrToCommandName, "set") == 0) { + bool ok = false; if (wc == 1) { - SerialAndTelnet.println(); - SerialAndTelnet.println("Stored settings:"); - SerialAndTelnet.printf(" wifi=%s ", (!_wifi_ssid) ? "" : _wifi_ssid); - if (!_wifi_password) { - SerialAndTelnet.print(""); - } else { - for (uint8_t i = 0; i < strlen(_wifi_password); i++) - SerialAndTelnet.print("*"); - } - SerialAndTelnet.println(); - SerialAndTelnet.printf(" mqtt_host=%s", (!_mqtt_host) ? "" : _mqtt_host); - SerialAndTelnet.println(); - SerialAndTelnet.printf(" mqtt_username=%s", (!_mqtt_username) ? "" : _mqtt_username); - SerialAndTelnet.println(); - SerialAndTelnet.printf(" mqtt_password="); - if (!_mqtt_password) { - SerialAndTelnet.print(""); - } else { - for (uint8_t i = 0; i < strlen(_mqtt_password); i++) - SerialAndTelnet.print("*"); - } - - SerialAndTelnet.println(); - SerialAndTelnet.printf(" serial=%s", (_use_serial) ? "on" : "off"); - - SerialAndTelnet.println(); - - // print custom settings - (_fs_settings_callback)(MYESP_FSACTION_LIST, 0, NULL, NULL); - - SerialAndTelnet.println(); - SerialAndTelnet.println("Usage: set [value...]"); - } else if (wc == 2) { - char * setting = _telnet_readWord(); - _changeSetting(1, setting, NULL); - } else if (wc == 3) { - char * setting = _telnet_readWord(); - char * value = _telnet_readWord(); - _changeSetting(2, setting, value); - } else if (wc == 4) { - char * setting = _telnet_readWord(); - char * value1 = _telnet_readWord(); - char * value2 = _telnet_readWord(); - _changeSetting2(setting, value1, value2); + _printSetCommands(); + ok = true; + } else if (wc == 2) { // set + char * setting = _telnet_readWord(false); + ok = _changeSetting(wc - 1, setting, NULL); + } else { // set + char * setting = _telnet_readWord(false); + char * value = _telnet_readWord(true); // allow strange characters + ok = _changeSetting(wc - 1, setting, value); } + + if (!ok) { + myDebug_P(PSTR("\nInvalid parameter for set command.")); + } + return; } @@ -724,10 +726,170 @@ void MyESP::_telnetCommand(char * commandLine) { resetESP(); } + // show system stats + if ((strcmp(ptrToCommandName, "system") == 0) && (wc == 1)) { + showSystemStats(); + return; + } + +// crash command +#ifdef CRASH + if ((strcmp(ptrToCommandName, "crash") == 0) && (wc >= 2)) { + char * cmd = _telnet_readWord(false); + if (strcmp(cmd, "dump") == 0) { + crashDump(); + } else if (strcmp(cmd, "clear") == 0) { + crashClear(); + } else if ((strcmp(cmd, "test") == 0) && (wc == 3)) { + char * value = _telnet_readWord(false); + crashTest(atoi(value)); + } + return; // don't call custom command line callback + } +#endif + // call callback function (_telnetcommand_callback)(wc, commandLine); } +// returns WiFi hostname as a String object +String MyESP::_getESPhostname() { + String hostname; + +#if defined(ARDUINO_ARCH_ESP32) + hostname = String(WiFi.getHostname()); +#else + hostname = WiFi.hostname(); +#endif + + return (hostname); +} + +// returns build time as a String - copied for espurna. see (c) +// takes the time from the gcc during compilation +String MyESP::_buildTime() { + const char time_now[] = __TIME__; // hh:mm:ss + unsigned int hour = atoi(&time_now[0]); + unsigned int minute = atoi(&time_now[3]); + unsigned int second = atoi(&time_now[6]); + + const char date_now[] = __DATE__; // Mmm dd yyyy + const char * months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + unsigned int month = 0; + for (int i = 0; i < 12; i++) { + if (strncmp(date_now, months[i], 3) == 0) { + month = i + 1; + break; + } + } + unsigned int day = atoi(&date_now[3]); + unsigned int year = atoi(&date_now[7]); + + char buffer[20]; + snprintf_P(buffer, sizeof(buffer), PSTR("%04d-%02d-%02d %02d:%02d:%02d"), year, month, day, hour, minute, second); + + return String(buffer); +} + +// returns system uptime in seconds - copied for espurna. see (c) +unsigned long MyESP::_getUptime() { + static unsigned long last_uptime = 0; + static unsigned char uptime_overflows = 0; + + if (millis() < last_uptime) + ++uptime_overflows; + last_uptime = millis(); + unsigned long uptime_seconds = uptime_overflows * (UPTIME_OVERFLOW / 1000) + (last_uptime / 1000); + + return uptime_seconds; +} + +// print out ESP system stats +// for battery power is ESP.getVcc() +void MyESP::showSystemStats() { +#if defined(ESP8266) + myDebug_P(PSTR("%sESP8266 System stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF); +#else + myDebug_P(PSTR("ESP32 System stats:")); +#endif + myDebug_P(PSTR("")); + + myDebug_P(PSTR(" [APP] %s version: %s"), _app_name, _app_version); + myDebug_P(PSTR(" [APP] MyESP version: %s"), MYESP_VERSION); + myDebug_P(PSTR(" [APP] Build timestamp: %s"), _buildTime().c_str()); + if (_boottime != NULL) { + myDebug_P(PSTR(" [APP] Boot time: %s"), _boottime); + } + uint32_t t = _getUptime(); // seconds + uint32_t h = (uint32_t)t / (uint32_t)3600L; + uint32_t rem = (uint32_t)t % (uint32_t)3600L; + uint32_t m = rem / 60; + uint32_t s = rem % 60; + myDebug_P(PSTR(" [APP] Uptime: %d seconds (%02d:%02d:%02d)"), t, h, m, s); + myDebug_P(PSTR(" [APP] System Load: %d%%"), getSystemLoadAverage()); + + if (isAPmode()) { + myDebug_P(PSTR(" [WIFI] Device is in AP mode with SSID %s"), jw.getAPSSID().c_str()); + } else { + myDebug_P(PSTR(" [WIFI] WiFi Hostname: %s"), _getESPhostname().c_str()); + myDebug_P(PSTR(" [WIFI] WiFi IP: %s"), WiFi.localIP().toString().c_str()); + myDebug_P(PSTR(" [WIFI] WiFi signal strength: %d%%"), getWifiQuality()); + } + + myDebug_P(PSTR(" [WIFI] WiFi MAC: %s"), WiFi.macAddress().c_str()); + +#ifdef CRASH + char output_str[80] = {0}; + char buffer[16] = {0}; + /* Crash info */ + myDebug_P(PSTR(" [EEPROM] EEPROM size: %u"), EEPROMr.reserved() * SPI_FLASH_SEC_SIZE); + strlcpy(output_str, PSTR(" [EEPROM] EEPROM Sector pool size is "), sizeof(output_str)); + strlcat(output_str, itoa(EEPROMr.size(), buffer, 10), sizeof(output_str)); + strlcat(output_str, PSTR(", and in use are: "), sizeof(output_str)); + for (uint32_t i = 0; i < EEPROMr.size(); i++) { + strlcat(output_str, itoa(EEPROMr.base() - i, buffer, 10), sizeof(output_str)); + strlcat(output_str, PSTR(" "), sizeof(output_str)); + } + myDebug_P(output_str); +#endif + +#ifdef ARDUINO_BOARD + myDebug_P(PSTR(" [SYSTEM] Board: %s"), ARDUINO_BOARD); +#endif + + myDebug_P(PSTR(" [SYSTEM] CPU frequency: %u MHz"), ESP.getCpuFreqMHz()); + myDebug_P(PSTR(" [SYSTEM] SDK version: %s"), ESP.getSdkVersion()); + +#if defined(ESP8266) + myDebug_P(PSTR(" [SYSTEM] CPU chip ID: 0x%06X"), ESP.getChipId()); + myDebug_P(PSTR(" [SYSTEM] Core version: %s"), ESP.getCoreVersion().c_str()); + myDebug_P(PSTR(" [SYSTEM] Boot version: %d"), ESP.getBootVersion()); + myDebug_P(PSTR(" [SYSTEM] Boot mode: %d"), ESP.getBootMode()); + //myDebug_P(PSTR("[SYSTEM] Firmware MD5: %s"), (char *)ESP.getSketchMD5().c_str()); +#endif + + FlashMode_t mode = ESP.getFlashChipMode(); +#if defined(ESP8266) + myDebug_P(PSTR(" [FLASH] Flash chip ID: 0x%06X"), ESP.getFlashChipId()); +#endif + myDebug_P(PSTR(" [FLASH] Flash speed: %u Hz"), ESP.getFlashChipSpeed()); + myDebug_P(PSTR(" [FLASH] Flash mode: %s"), + mode == FM_QIO ? "QIO" : mode == FM_QOUT ? "QOUT" : mode == FM_DIO ? "DIO" : mode == FM_DOUT ? "DOUT" : "UNKNOWN"); +#if defined(ESP8266) + myDebug_P(PSTR(" [FLASH] Flash size (CHIP): %d"), ESP.getFlashChipRealSize()); +#endif + myDebug_P(PSTR(" [FLASH] Flash size (SDK): %d"), ESP.getFlashChipSize()); + myDebug_P(PSTR(" [FLASH] Flash Reserved: %d"), 1 * SPI_FLASH_SEC_SIZE); + myDebug_P(PSTR(" [MEM] Firmware size: %d"), ESP.getSketchSize()); + myDebug_P(PSTR(" [MEM] Max OTA size: %d"), (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); + myDebug_P(PSTR(" [MEM] OTA Reserved: %d"), 4 * SPI_FLASH_SEC_SIZE); + myDebug_P(PSTR(" [MEM] Free Heap: %d"), ESP.getFreeHeap()); +#if defined(ESP8266) + myDebug_P(PSTR(" [MEM] Stack: %d"), ESP.getFreeContStack()); +#endif + myDebug_P(PSTR("")); +} + // handler for Telnet void MyESP::_telnetHandle() { SerialAndTelnet.handle(); @@ -737,7 +899,7 @@ void MyESP::_telnetHandle() { while (SerialAndTelnet.available()) { char c = SerialAndTelnet.read(); - SerialAndTelnet.serialPrint(c); // echo to Serial if connected + SerialAndTelnet.serialPrint(c); // echo to Serial (if connected) switch (c) { case '\r': // likely have full command in buffer now, commands are terminated by CR and/or LF @@ -753,9 +915,8 @@ void MyESP::_telnetHandle() { } break; - case '\b': // (^H) handle backspace in input: put a space in last char - coded by Simon Arlott + case '\b': // (^H) case 0x7F: // (^?) - if (charsRead > 0) { _command[--charsRead] = '\0'; @@ -938,7 +1099,6 @@ char * MyESP::_mqttTopic(const char * topic) { return _mqtt_topic; } - // print contents of file // assumes Serial is open void MyESP::_fs_printConfig() { @@ -946,14 +1106,14 @@ void MyESP::_fs_printConfig() { File configFile = SPIFFS.open(MYEMS_CONFIG_FILE, "r"); if (!configFile) { - Serial.println(F("[FS] Failed to read file for printing")); + myDebug_P(PSTR("[FS] Failed to read file for printing")); return; } while (configFile.available()) { SerialAndTelnet.print((char)configFile.read()); } - SerialAndTelnet.println(); + myDebug_P(PSTR("")); // newline configFile.close(); } @@ -1001,6 +1161,7 @@ bool MyESP::_fs_loadConfig() { const char * value; + // fetch the standard system parameters value = json["wifi_ssid"]; _wifi_ssid = (value) ? strdup(value) : NULL; @@ -1029,6 +1190,13 @@ bool MyESP::_fs_loadConfig() { // save settings to spiffs bool MyESP::fs_saveConfig() { + bool ok = true; + + // call any custom functions before handling SPIFFS + if (_ota_pre_callback) { + (_ota_pre_callback)(); + } + StaticJsonDocument doc; JsonObject json = doc.to(); @@ -1045,43 +1213,49 @@ bool MyESP::fs_saveConfig() { // if file exists, remove it just to be safe if (SPIFFS.exists(MYEMS_CONFIG_FILE)) { - // delete it SPIFFS.remove(MYEMS_CONFIG_FILE); } + // open for writing File configFile = SPIFFS.open(MYEMS_CONFIG_FILE, "w"); if (!configFile) { - Serial.println("[FS] Failed to open config file for writing"); + myDebug_P(PSTR("[FS] Failed to open config file for writing")); return false; } + // Serialize JSON to file if (serializeJson(json, configFile) == 0) { - Serial.println(F("[FS] Failed to write to file")); + myDebug_P(PSTR("[FS] Failed to write config file")); + ok = false; } configFile.close(); - return true; + // call any custom functions before handling SPIFFS + if (_ota_post_callback) { + (_ota_post_callback)(); + } + + return ok; // it worked } // init the SPIFF file system and load the config // if it doesn't exist try and create it -// force Serial for debugging, and turn it off afterwards void MyESP::_fs_setup() { if (!SPIFFS.begin()) { - Serial.println("[FS] Failed to mount the file system"); + myDebug_P(PSTR("[FS] Failed to mount the file system. Erasing...")); _fs_eraseConfig(); // fix for ESP32 return; } // load the config file. if it doesn't exist (function returns false) create it if (!_fs_loadConfig()) { - // Serial.println("[FS] Re-creating config file"); + //myDebug_P(PSTR("[FS] Re-creating config file")); fs_saveConfig(); } - //_fs_printConfig(); // TODO: for debugging + // _fs_printConfig(); // enable for debugging } uint16_t MyESP::getSystemLoadAverage() { @@ -1108,6 +1282,11 @@ void MyESP::_calculateLoad() { } } +// returns true is MQTT is alive +bool MyESP::isMQTTConnected() { + return mqttClient.connected(); +} + // return true if wifi is connected // WL_NO_SHIELD = 255, // for compatibility with WiFi Shield library // WL_IDLE_STATUS = 0, @@ -1143,13 +1322,160 @@ int MyESP::getWifiQuality() { return 2 * (dBm + 100); } -// register new instance +#ifdef CRASH +/** + * Save crash information in EEPROM + * This function is called automatically if ESP8266 suffers an exception + * It should be kept quick / consise to be able to execute before hardware wdt may kick in + */ +extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack_start, uint32_t stack_end) { + // write crash time to EEPROM + uint32_t crash_time = millis(); + EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time); + + // write reset info to EEPROM + EEPROMr.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON, rst_info->reason); + EEPROMr.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE, rst_info->exccause); + + // write epc1, epc2, epc3, excvaddr and depc to EEPROM + EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, rst_info->epc1); + EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, rst_info->epc2); + EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, rst_info->epc3); + EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, rst_info->excvaddr); + EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, rst_info->depc); + + // write stack start and end address to EEPROM + EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start); + EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end); + + // write stack trace to EEPROM and avoid overwriting settings + int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE; + for (uint32_t i = stack_start; i < stack_end; i++) { + byte * byteValue = (byte *)i; + EEPROMr.write(current_address++, *byteValue); + } + + EEPROMr.commit(); +} + +void MyESP::crashTest(uint8_t t) { + if (t == 1) { + myDebug("[CRASH] Attempting to divide by zero ..."); + int result, zero; + zero = 0; + result = 1 / zero; + myDebug("Result = %d", result); + } + + if (t == 2) { + myDebug("[CRASH] Attempting to read through a pointer to no object ..."); + int * nullPointer; + nullPointer = NULL; + // null pointer dereference - read + // attempt to read a value through a null pointer + Serial.println(*nullPointer); + } + + if (t == 3) { + Serial.printf("[CRASH] Crashing with hardware WDT (%ld ms) ...\n", millis()); + ESP.wdtDisable(); + while (true) { + // stay in an infinite loop doing nothing + // this way other process can not be executed + // + // Note: + // Hardware wdt kicks in if software wdt is unable to perfrom + // Nothing will be saved in EEPROM for the hardware wdt + } + } + + if (t == 4) { + Serial.printf("[CRASH] Crashing with software WDT (%ld ms) ...\n", millis()); + while (true) { + // stay in an infinite loop doing nothing + // this way other process can not be executed + } + } +} + +/** + * Clears crash info + */ +void MyESP::crashClear() { + myDebug_P(PSTR("[CRASH] Clearing crash dump")); + uint32_t crash_time = 0xFFFFFFFF; + EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time); + EEPROMr.commit(); +} + +/** + * Print out crash information that has been previously saved in EEPROM + */ +void MyESP::crashDump() { + uint32_t crash_time; + EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time); + if ((crash_time == 0) || (crash_time == 0xFFFFFFFF)) { + myDebug_P(PSTR("[CRASH] No crash info")); + return; + } + + myDebug_P(PSTR("[CRASH] Latest crash was at %lu ms after boot"), crash_time); + myDebug_P(PSTR("[CRASH] Reason of restart: %u"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON)); + myDebug_P(PSTR("[CRASH] Exception cause: %u"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE)); + + uint32_t epc1, epc2, epc3, excvaddr, depc; + EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, epc1); + EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, epc2); + EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, epc3); + EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, excvaddr); + EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, depc); + + myDebug_P(PSTR("[CRASH] epc1=0x%08x epc2=0x%08x epc3=0x%08x"), epc1, epc2, epc3); + myDebug_P(PSTR("[CRASH] excvaddr=0x%08x depc=0x%08x"), excvaddr, depc); + + uint32_t stack_start, stack_end; + EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start); + EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end); + + myDebug_P(PSTR("[CRASH] sp=0x%08x end=0x%08x"), stack_start, stack_end); + + int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE; + int16_t stack_len = stack_end - stack_start; + + uint32_t stack_trace; + + myDebug(">>>stack>>>"); + + for (int16_t i = 0; i < stack_len; i += 0x10) { + SerialAndTelnet.printf("%08x: ", stack_start + i); + for (byte j = 0; j < 4; j++) { + EEPROMr.get(current_address, stack_trace); + SerialAndTelnet.printf("%08x ", stack_trace); + current_address += 4; + } + SerialAndTelnet.println(); + } + myDebug("<< #include @@ -19,6 +19,13 @@ #include // https://github.com/xoseperez/justwifi #include // modified from https://github.com/yasheena/telnetspy +#ifdef CRASH +#include +extern "C" { +void custom_crash_callback(struct rst_info *, uint32_t, uint32_t); +} +#endif + #if defined(ARDUINO_ARCH_ESP32) //#include #include // added for ESP32 @@ -68,13 +75,44 @@ #define COLOR_CYAN "\x1B[0;36m" #define COLOR_WHITE "\x1B[0;37m" #define COLOR_BOLD_ON "\x1B[1m" -#define COLOR_BOLD_OFF "\x1B[22m" // fixed by Scott Arlott +#define COLOR_BOLD_OFF "\x1B[22m" // fix by Scott Arlott to support Linux // SPIFFS -#define SPIFFS_MAXSIZE 500 // https://arduinojson.org/v5/assistant/ +#define SPIFFS_MAXSIZE 600 // https://arduinojson.org/v6/assistant/ + +// CRASH +/** + * Structure of the single crash data set + * + * 1. Crash time + * 2. Restart reason + * 3. Exception cause + * 4. epc1 + * 5. epc2 + * 6. epc3 + * 7. excvaddr + * 8. depc + * 9. address of stack start + * 10. address of stack end + * 11. stack trace bytes + * ... + */ +#define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data +#define SAVE_CRASH_CRASH_TIME 0x00 // 4 bytes +#define SAVE_CRASH_RESTART_REASON 0x04 // 1 byte +#define SAVE_CRASH_EXCEPTION_CAUSE 0x05 // 1 byte +#define SAVE_CRASH_EPC1 0x06 // 4 bytes +#define SAVE_CRASH_EPC2 0x0A // 4 bytes +#define SAVE_CRASH_EPC3 0x0E // 4 bytes +#define SAVE_CRASH_EXCVADDR 0x12 // 4 bytes +#define SAVE_CRASH_DEPC 0x16 // 4 bytes +#define SAVE_CRASH_STACK_START 0x1A // 4 bytes +#define SAVE_CRASH_STACK_END 0x1E // 4 bytes +#define SAVE_CRASH_STACK_TRACE 0x22 // variable typedef struct { - char key[40]; + bool set; // is it a set command + char key[50]; char description[100]; } command_t; @@ -94,6 +132,8 @@ constexpr size_t ArraySize(T (&)[N]) { return N; } +#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value + // class definition class MyESP { public: @@ -104,8 +144,10 @@ class MyESP { void setWIFICallback(void (*callback)()); void setWIFI(const char * wifi_ssid, const char * wifi_password, wifi_callback_f callback); bool isWifiConnected(); + bool isAPmode(); // mqtt + bool isMQTTConnected(); void mqttSubscribe(const char * topic); void mqttUnsubscribe(const char * topic); void mqttPublish(const char * topic, const char * payload); @@ -122,7 +164,7 @@ class MyESP { mqtt_callback_f callback); // OTA - void setOTA(ota_callback_f OTACallback); + void setOTA(ota_callback_f OTACallback_pre, ota_callback_f OTACallback_post); // debug & telnet void myDebug(const char * format, ...); @@ -134,6 +176,12 @@ class MyESP { void setSettings(fs_callback_f callback, fs_settings_callback_f fs_settings_callback); bool fs_saveConfig(); + // Crash + void crashClear(); + void crashDump(); + void crashTest(uint8_t t); + void crashInfo(); + // general void end(); void loop(); @@ -142,7 +190,7 @@ class MyESP { void resetESP(); uint16_t getSystemLoadAverage(); int getWifiQuality(); - + void showSystemStats(); private: // mqtt @@ -177,19 +225,24 @@ class MyESP { char * _wifi_ssid; char * _wifi_password; bool _wifi_connected; + String _getESPhostname(); // ota - ota_callback_f _ota_callback; + ota_callback_f _ota_pre_callback; + ota_callback_f _ota_post_callback; void _ota_setup(); void _OTACallback(); + // crash + void _eeprom_setup(); + // telnet & debug TelnetSpy SerialAndTelnet; void _telnetConnected(); void _telnetDisconnected(); void _telnetHandle(); void _telnetCommand(char * commandLine); - char * _telnet_readWord(); + char * _telnet_readWord(bool allow_all_chars); void _telnet_setup(); char _command[TELNET_MAX_COMMAND_LENGTH]; // the input command from either Serial or Telnet command_t * _helpProjectCmds; // Help of commands setted by project @@ -197,8 +250,7 @@ class MyESP { void _consoleShowHelp(); telnetcommand_callback_f _telnetcommand_callback; // Callable for projects commands telnet_callback_f _telnet_callback; // callback for connect/disconnect - void _changeSetting(uint8_t wc, const char * setting, const char * value); - void _changeSetting2(const char * setting, const char * value1, const char * value2); + bool _changeSetting(uint8_t wc, const char * setting, const char * value); // fs void _fs_setup(); @@ -206,17 +258,20 @@ class MyESP { void _fs_printConfig(); void _fs_eraseConfig(); + // settings fs_callback_f _fs_callback; fs_settings_callback_f _fs_settings_callback; + void _printSetCommands(); // general - char * _app_hostname; - char * _app_name; - char * _app_version; - char * _boottime; - bool _suspendOutput; - bool _use_serial; - void _printBuildTime(unsigned long rawTime); + char * _app_hostname; + char * _app_name; + char * _app_version; + char * _boottime; + bool _suspendOutput; + bool _use_serial; + unsigned long _getUptime(); + String _buildTime(); // load average (0..100) void _calculateLoad(); diff --git a/platformio.ini-example b/platformio.ini-example index fdbbeb257..0a597bab5 100644 --- a/platformio.ini-example +++ b/platformio.ini-example @@ -1,11 +1,14 @@ [platformio] +; add here your board, e.g. nodemcuv2, d1_mini, d1_mini_pro env_default = d1_mini [common] -platform = espressif8266 flash_mode = dout + build_flags = -g -w -;build_flags = -g -w -DBUILD_TIME=$UNIX_TIME + +; for debug use these... +; build_flags = -g -Wall -Wextra -Werror -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-variable -DCRASH wifi_settings = ; hard code if you prefer. Recommendation is to set from within the app when in Serial or AP mode @@ -17,21 +20,21 @@ lib_deps = JustWifi AsyncMqttClient ArduinoJson -; https://github.com/bblanchon/ArduinoJson#v5.13.5 OneWire + EEPROM_rotate [env:d1_mini] board = d1_mini -platform = ${common.platform} +platform = espressif8266 framework = arduino lib_deps = ${common.lib_deps} build_flags = ${common.build_flags} ${common.wifi_settings} board_build.flash_mode = ${common.flash_mode} upload_speed = 921600 monitor_speed = 115200 - ; for OTA comment out these sections ;upload_protocol = espota ;upload_port = ems-esp.local +;upload_port = diff --git a/rename_fw.py b/rename_fw.py new file mode 100644 index 000000000..7b4d42299 --- /dev/null +++ b/rename_fw.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +from subprocess import call +import os +Import("env") + +# 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']) diff --git a/src/ds18.cpp b/src/ds18.cpp index a95c60441..6a827970f 100644 --- a/src/ds18.cpp +++ b/src/ds18.cpp @@ -4,9 +4,6 @@ * * Paul Derbyshire - https://github.com/proddy/EMS-ESP * - * See ChangeLog.md for history - * See README.md for Acknowledgments - * */ #include "ds18.h" @@ -14,9 +11,10 @@ std::vector _devices; DS18::DS18() { - _wire = NULL; - _count = 0; - _gpio = GPIO_NONE; + _wire = NULL; + _count = 0; + _gpio = GPIO_NONE; + _parasite = 0; } DS18::~DS18() { @@ -25,10 +23,11 @@ DS18::~DS18() { } // init -uint8_t DS18::setup(uint8_t gpio) { +uint8_t DS18::setup(uint8_t gpio, bool parasite) { uint8_t count; - _gpio = gpio; + _gpio = gpio; + _parasite = (parasite ? 1 : 0); // OneWire if (_wire) @@ -62,8 +61,7 @@ void DS18::loop() { // Start conversion _wire->reset(); _wire->skip(); - _wire->write(DS18_CMD_START_CONVERSION, DS18_PARASITE); - + _wire->write(DS18_CMD_START_CONVERSION, _parasite); } else { // Read scratchpads for (unsigned char index = 0; index < _devices.size(); index++) { @@ -117,7 +115,7 @@ char * DS18::getDeviceString(char * buffer, unsigned char index) { char a[30] = {0}; snprintf(a, sizeof(a), - "(%02X%02X%02X%02X%02X%02X%02X%02X) @ GPIO%d", + " (%02X%02X%02X%02X%02X%02X%02X%02X) @ GPIO%d", address[0], address[1], address[2], @@ -136,7 +134,6 @@ char * DS18::getDeviceString(char * buffer, unsigned char index) { return buffer; } - /* * Read sensor values * @@ -154,14 +151,14 @@ char * DS18::getDeviceString(char * buffer, unsigned char index) { DS18B20 & DS1822: store for crc byte 8: SCRATCHPAD_CRC */ -double DS18::getValue(unsigned char index) { +int16_t DS18::getRawValue(unsigned char index) { if (index >= _count) return 0; uint8_t * data = _devices[index].data; if (OneWire::crc8(data, DS18_DATA_SIZE - 1) != data[DS18_DATA_SIZE - 1]) { - return 0; + return DS18_CRC_ERROR; } int16_t raw = (data[1] << 8) | data[0]; @@ -181,11 +178,13 @@ double DS18::getValue(unsigned char index) { // 12 bit res, 750 ms } - double value = (float)raw / 16.0; - if (value == DS18_DISCONNECTED) { - return 0; - } + return raw; +} +// return real value as a double +// The raw temperature data is in units of sixteenths of a degree, so the value must be divided by 16 in order to convert it to degrees. +double DS18::getValue(unsigned char index) { + double value = (float)getRawValue(index) / 16.0; return value; } diff --git a/src/ds18.h b/src/ds18.h index d4dd9cfeb..e6e97b300 100644 --- a/src/ds18.h +++ b/src/ds18.h @@ -4,9 +4,6 @@ * * Paul Derbyshire - https://github.com/proddy/EMS-ESP * - * See ChangeLog.md for history - * See README.md for Acknowledgments - * */ #pragma once @@ -20,8 +17,8 @@ #define DS18_CHIP_DS1825 0x3B #define DS18_DATA_SIZE 9 -#define DS18_PARASITE 1 #define DS18_DISCONNECTED -127 +#define DS18_CRC_ERROR -126 #define GPIO_NONE 0x99 #define DS18_READ_INTERVAL 2000 // Force sensor read & cache every 2 seconds @@ -39,10 +36,11 @@ class DS18 { DS18(); ~DS18(); - uint8_t setup(uint8_t gpio); + uint8_t setup(uint8_t gpio, bool parasite); void loop(); char * getDeviceString(char * s, unsigned char index); double getValue(unsigned char index); + int16_t getRawValue(unsigned char index); // raw values, needs / 16 protected: bool validateID(unsigned char id); @@ -50,6 +48,7 @@ class DS18 { uint8_t loadDevices(); OneWire * _wire; - uint8_t _count; // # devices - uint8_t _gpio; // the sensor pin + uint8_t _count; // # devices + uint8_t _gpio; // the sensor pin + uint8_t _parasite; // parasite mode }; diff --git a/src/ems-esp.ino b/src/ems-esp.cpp similarity index 58% rename from src/ems-esp.ino rename to src/ems-esp.cpp index 1c1b733c0..b03ef638b 100644 --- a/src/ems-esp.ino +++ b/src/ems-esp.cpp @@ -31,9 +31,13 @@ DS18 ds18; #define myDebug(...) myESP.myDebug(__VA_ARGS__) #define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__) +// set to value >0 if the ESP is overheating or there are timing issues. Recommend a value of 1. +#define EMSESP_DELAY 1 // initially set to 0 for no delay + // timers, all values are in seconds -#define PUBLISHVALUES_TIME 120 // every 2 minutes publish MQTT values +#define DEFAULT_PUBLISHWAIT 120 // every 2 minutes publish MQTT values, including Dallas sensors Ticker publishValuesTimer; +Ticker publishSensorValuesTimer; #define SYSTEMCHECK_TIME 20 // every 20 seconds check if Boiler is online Ticker systemCheckTimer; @@ -56,17 +60,21 @@ Ticker showerColdShotStopTimer; #define SHOWER_MIN_DURATION 120000 // in ms. 2 minutes, before recognizing its a 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_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water typedef struct { - bool shower_timer; // true if we want to report back on shower times - bool shower_alert; // true if we want the alert of cold water - bool led_enabled; // LED on/off - bool test_mode; // test mode to stop automatic Tx on/off - unsigned long timestamp; // for internal timings, via millis() uint8_t dallas_sensors; // count of dallas sensors - uint8_t led_gpio; - uint8_t dallas_gpio; + + // custom params + bool shower_timer; // true if we want to report back on shower times + bool shower_alert; // true if we want the alert of cold water + bool led; // LED on/off + bool silent_mode; // stop automatic Tx on/off + uint16_t publish_wait; // frequency of MQTT publish in seconds + uint8_t led_gpio; + uint8_t dallas_gpio; + uint8_t dallas_parasite; } _EMSESP_Status; typedef struct { @@ -79,29 +87,34 @@ typedef struct { command_t PROGMEM project_cmds[] = { - {"set led ", "toggle status LED on/off"}, - {"set led_gpio ", "set the LED pin. Default is the onboard LED (D1=5)"}, - {"set dallas_gpio ", "set the pin for external Dallas temperature sensors (D5=14)"}, - {"set thermostat_type ", "set the thermostat type id (e.g. 10 for 0x10)"}, - {"set boiler_type ", "set the boiler type id (e.g. 8 for 0x08)"}, - {"set test_mode ", "test_mode turns off all automatic reads"}, - {"info", "show data captured on the EMS bus"}, - {"log ", "set logging mode to none, basic, thermostat only, raw or verbose"}, - {"publish", "publish all values to MQTT"}, - {"types", "list supported EMS telegram type IDs"}, - {"queue", "show current Tx queue"}, - {"autodetect", "discover EMS devices and attempt to automatically set boiler and thermostat"}, - {"shower ", "toggle either timer or alert on/off"}, - {"send XX ...", "send raw telegram data as hex to EMS bus"}, - {"thermostat read ", "send read request to the thermostat"}, - {"thermostat temp ", "set current thermostat temperature"}, - {"thermostat mode ", "set mode (0=low/night, 1=manual/day, 2=auto)"}, - {"thermostat scan ", "do a read on all type IDs"}, - {"boiler read ", "send read request to boiler"}, - {"boiler wwtemp ", "set boiler warm water temperature"}, - {"boiler tapwater ", "set boiler warm tap water on/off"} + {true, "led ", "toggle status LED on/off"}, + {true, "led_gpio ", "set the LED pin. Default is the onboard LED (D1=5)"}, + {true, "dallas_gpio ", "set the pin for external Dallas temperature sensors (D5=14)"}, + {true, "dallas_parasite ", "set to on if powering Dallas via parasite"}, + {true, "thermostat_type ", "set the thermostat type id (e.g. 10 for 0x10)"}, + {true, "boiler_type ", "set the boiler type id (e.g. 8 for 0x08)"}, + {true, "silent_mode ", "when on all automatic Tx is disabled"}, + {true, "shower_timer ", "notify via MQTT all shower durations"}, + {true, "shower_alert ", "send a warning of cold water after shower time is exceeded"}, + {true, "publish_wait ", "set frequency for publishing to MQTT"}, -}; + {false, "info", "show data captured on the EMS bus"}, + {false, "log ", "set logging mode to none, basic, thermostat only, raw or verbose"}, + {false, "publish", "publish all values to MQTT"}, + {false, "refresh", "fetch values from the EMS devices"}, + {false, "types", "list supported EMS telegram type IDs"}, + {false, "queue", "show current Tx queue"}, + {false, "autodetect", "detect EMS devices and attempt to automatically set boiler and thermostat types"}, + {false, "shower ", "toggle either timer or alert on/off"}, + {false, "send XX ...", "send raw telegram data as hex to EMS bus"}, + {false, "thermostat read ", "send read request to the thermostat"}, + {false, "thermostat temp ", "set current thermostat temperature"}, + {false, "thermostat mode ", "set mode (0=low/night, 1=manual/day, 2=auto)"}, + {false, "thermostat scan ", "do a read on all type IDs"}, + {false, "boiler read ", "send read request to boiler"}, + {false, "boiler wwtemp ", "set boiler warm water temperature"}, + {false, "boiler tapwater ", "set boiler warm tap water on/off"}, + {false, "boiler comfort ", "set boiler warm water comfort setting"}}; // store for overall system status _EMSESP_Status EMSESP_Status; @@ -120,7 +133,7 @@ char * _float_to_char(char * a, float f, uint8_t precision = 2) { char * ret = a; // check for 0x8000 (sensor missing) - if (f == EMS_VALUE_FLOAT_NOTSET) { + if (f == EMS_VALUE_SHORT_NOTSET) { strlcpy(ret, "?", sizeof(ret)); } else { long whole = (long)f; @@ -146,64 +159,97 @@ char * _bool_to_char(char * s, uint8_t value) { return s; } -// convert int (single byte) to text value -char * _int_to_char(char * s, uint8_t value) { - if (value == EMS_VALUE_INT_NOTSET) { +// convert short (two bytes) to text value +// negative values are assumed stored as 1-compliment (https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c) +char * _short_to_char(char * s, int16_t value, uint8_t decimals = 1) { + // remove errors on invalid values + if (abs(value) >= EMS_VALUE_SHORT_NOTSET) { strlcpy(s, "?", sizeof(s)); - } else { - itoa(value, s, 10); + return (s); } + + if (decimals == 0) { + itoa(value, s, 10); + return (s); + } + + // floating point + char s2[5] = {0}; + // check for negative values + if (value < 0) { + strlcpy(s, "-", 2); + value = abs(value); + } + strlcpy(s, itoa(value / (decimals * 10), s2, 10), 5); + strlcat(s, ".", sizeof(s)); + strlcat(s, itoa(value % (decimals * 10), s2, 10), 5); + return s; } -// takes a float value at prints it to debug log -void _renderFloatValue(const char * prefix, const char * postfix, float value) { +// takes a short value (2 bytes), converts to a fraction +// most values stored a s short are either *10 or *100 +void _renderShortValue(const char * prefix, const char * postfix, int16_t value, uint8_t decimals = 1) { char buffer[200] = {0}; char s[20] = {0}; strlcpy(buffer, " ", sizeof(buffer)); strlcat(buffer, prefix, sizeof(buffer)); strlcat(buffer, ": ", sizeof(buffer)); - strlcat(buffer, _float_to_char(s, value), sizeof(buffer)); + + strlcat(buffer, _short_to_char(s, value, decimals), sizeof(buffer)); if (postfix != NULL) { strlcat(buffer, " ", sizeof(buffer)); strlcat(buffer, postfix, sizeof(buffer)); } + myDebug(buffer); } -// takes an int (single byte) value at prints it to debug log -void _renderIntValue(const char * prefix, const char * postfix, uint8_t value) { - char buffer[200] = {0}; - char s[20] = {0}; - strlcpy(buffer, " ", sizeof(buffer)); - strlcat(buffer, prefix, sizeof(buffer)); - strlcat(buffer, ": ", sizeof(buffer)); - strlcat(buffer, _int_to_char(s, value), sizeof(buffer)); - - if (postfix != NULL) { - strlcat(buffer, " ", sizeof(buffer)); - strlcat(buffer, postfix, sizeof(buffer)); - } - myDebug(buffer); -} - -// takes an int value, converts to a fraction -void _renderIntfractionalValue(const char * prefix, const char * postfix, uint8_t value, uint8_t decimals) { - char buffer[200] = {0}; - char s[20] = {0}; - strlcpy(buffer, " ", sizeof(buffer)); - strlcat(buffer, prefix, sizeof(buffer)); - strlcat(buffer, ": ", sizeof(buffer)); - +// convert int (single byte) to text value +char * _int_to_char(char * s, uint8_t value, uint8_t div = 1) { if (value == EMS_VALUE_INT_NOTSET) { - strlcat(buffer, "?", sizeof(buffer)); - } else { - strlcat(buffer, _int_to_char(s, value / (decimals * 10)), sizeof(buffer)); - strlcat(buffer, ".", sizeof(buffer)); - strlcat(buffer, _int_to_char(s, value % (decimals * 10)), sizeof(buffer)); + strlcpy(s, "?", sizeof(s)); + return (s); } + char s2[5] = {0}; + + switch (div) { + case 1: + itoa(value, s, 10); + break; + + case 2: + strlcpy(s, itoa(value >> 1, s2, 10), 5); + strlcat(s, ".", sizeof(s)); + strlcat(s, ((value & 0x01) ? "5" : "0"), 5); + break; + + case 10: + strlcpy(s, itoa(value / 10, s2, 10), 5); + strlcat(s, ".", sizeof(s)); + strlcat(s, itoa(value % 10, s2, 10), 5); + break; + + default: + itoa(value, s, 10); + break; + } + + return s; +} + +// takes an int value (1 byte), converts to a fraction +void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, uint8_t div = 1) { + char buffer[200] = {0}; + char s[20] = {0}; + strlcpy(buffer, " ", sizeof(buffer)); + strlcat(buffer, prefix, sizeof(buffer)); + strlcat(buffer, ": ", sizeof(buffer)); + + strlcat(buffer, _int_to_char(s, value, div), sizeof(buffer)); + if (postfix != NULL) { strlcat(buffer, " ", sizeof(buffer)); strlcat(buffer, postfix, sizeof(buffer)); @@ -251,7 +297,9 @@ void _renderBoolValue(const char * prefix, uint8_t value) { void showInfo() { // General stats from EMS bus - myDebug("%sEMS-ESP System stats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); + char buffer_type[128] = {0}; + + myDebug("%sEMS-ESP system stats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); _EMS_SYS_LOGGING sysLog = ems_getLogging(); if (sysLog == EMS_SYS_LOGGING_BASIC) { myDebug(" System logging set to Basic"); @@ -263,10 +311,8 @@ void showInfo() { myDebug(" System logging set to None"); } - myDebug(" LED is %s", EMSESP_Status.led_enabled ? "on" : "off"); - myDebug(" Test Mode is %s", EMSESP_Status.test_mode ? "on" : "off"); - - myDebug(" # connected Dallas temperature sensors=%d", EMSESP_Status.dallas_sensors); + myDebug(" LED is %s, Silent mode is %s", EMSESP_Status.led ? "on" : "off", EMSESP_Status.silent_mode ? "on" : "off"); + myDebug(" %d external temperature sensor%s connected", EMSESP_Status.dallas_sensors, (EMSESP_Status.dallas_sensors > 1) ? "s" : ""); myDebug(" Thermostat is %s, Boiler is %s, Shower Timer is %s, Shower Alert is %s", (ems_getThermostatEnabled() ? "enabled" : "disabled"), @@ -274,37 +320,48 @@ void showInfo() { ((EMSESP_Status.shower_timer) ? "enabled" : "disabled"), ((EMSESP_Status.shower_alert) ? "enabled" : "disabled")); - myDebug("\n%sEMS Bus Stats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); - myDebug(" Bus Connected=%s, # Rx telegrams=%d, # Tx telegrams=%d, # Crc Errors=%d", + myDebug("\n%sEMS Bus stats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); + myDebug(" Bus Connected=%s, Tx is %s, # Rx telegrams=%d, # Tx telegrams=%d, # Crc Errors=%d", (ems_getBusConnected() ? "yes" : "no"), + (ems_getTxCapable() ? "active" : "not active"), EMS_Sys_Status.emsRxPgks, EMS_Sys_Status.emsTxPkgs, EMS_Sys_Status.emxCrcErr); myDebug(""); - myDebug("%sBoiler stats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); // version details - char buffer_type[64]; myDebug(" Boiler type: %s", ems_getBoilerDescription(buffer_type)); // active stats if (ems_getBusConnected()) { - myDebug(" Hot tap water is %s", (EMS_Boiler.tapwaterActive ? "running" : "off")); - myDebug(" Central Heating is %s", (EMS_Boiler.heatingActive ? "active" : "off")); + if (EMS_Boiler.tapwaterActive != EMS_VALUE_INT_NOTSET) { + myDebug(" Hot tap water: %s", EMS_Boiler.tapwaterActive ? "running" : "off"); + } + + if (EMS_Boiler.heatingActive != EMS_VALUE_INT_NOTSET) { + myDebug(" Central heating: %s", EMS_Boiler.heatingActive ? "active" : "off"); + } } // UBAParameterWW _renderBoolValue("Warm Water activated", EMS_Boiler.wWActivated); _renderBoolValue("Warm Water circulation pump available", EMS_Boiler.wWCircPump); - myDebug(" Warm Water is set to %s", (EMS_Boiler.wWComfort ? "Comfort" : "ECO")); + if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Hot) { + myDebug(" Warm Water comfort setting: Hot"); + } else if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Eco) { + myDebug(" Warm Water comfort setting: Eco"); + } else if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Intelligent) { + myDebug(" Warm Water comfort setting: Intelligent"); + } + _renderIntValue("Warm Water selected temperature", "C", EMS_Boiler.wWSelTemp); _renderIntValue("Warm Water desired temperature", "C", EMS_Boiler.wWDesiredTemp); // UBAMonitorWWMessage - _renderFloatValue("Warm Water current temperature", "C", EMS_Boiler.wWCurTmp); - _renderIntfractionalValue("Warm Water current tap water flow", "l/min", EMS_Boiler.wWCurFlow, 1); + _renderShortValue("Warm Water current temperature", "C", EMS_Boiler.wWCurTmp); + _renderIntValue("Warm Water current tap water flow", "l/min", EMS_Boiler.wWCurFlow, 10); _renderLongValue("Warm Water # starts", "times", EMS_Boiler.wWStarts); if (EMS_Boiler.wWWorkM != EMS_VALUE_LONG_NOTSET) { myDebug(" Warm Water active time: %d days %d hours %d minutes", @@ -316,8 +373,8 @@ void showInfo() { // UBAMonitorFast _renderIntValue("Selected flow temperature", "C", EMS_Boiler.selFlowTemp); - _renderFloatValue("Current flow temperature", "C", EMS_Boiler.curFlowTemp); - _renderFloatValue("Return temperature", "C", EMS_Boiler.retTemp); + _renderShortValue("Current flow temperature", "C", EMS_Boiler.curFlowTemp); + _renderShortValue("Return temperature", "C", EMS_Boiler.retTemp); _renderBoolValue("Gas", EMS_Boiler.burnGas); _renderBoolValue("Boiler pump", EMS_Boiler.heatPmp); _renderBoolValue("Fan", EMS_Boiler.fanWork); @@ -325,22 +382,26 @@ void showInfo() { _renderBoolValue("Circulation pump", EMS_Boiler.wWCirc); _renderIntValue("Burner selected max power", "%", EMS_Boiler.selBurnPow); _renderIntValue("Burner current power", "%", EMS_Boiler.curBurnPow); - _renderFloatValue("Flame current", "uA", EMS_Boiler.flameCurr); - _renderFloatValue("System pressure", "bar", EMS_Boiler.sysPress); - myDebug(" Current System Service Code: %s", EMS_Boiler.serviceCodeChar); + _renderShortValue("Flame current", "uA", EMS_Boiler.flameCurr); + _renderIntValue("System pressure", "bar", EMS_Boiler.sysPress, 10); + if (EMS_Boiler.serviceCode == EMS_VALUE_SHORT_NOTSET) { + myDebug(" System service code: %s", EMS_Boiler.serviceCodeChar); + } else { + myDebug(" System service code: %s (%d)", EMS_Boiler.serviceCodeChar, EMS_Boiler.serviceCode); + } // UBAParametersMessage _renderIntValue("Heating temperature setting on the boiler", "C", EMS_Boiler.heating_temp); - _renderIntValue("Boiler circuit pump modulation max. power", "%", EMS_Boiler.pump_mod_max); - _renderIntValue("Boiler circuit pump modulation min. power", "%", EMS_Boiler.pump_mod_min); + _renderIntValue("Boiler circuit pump modulation max power", "%", EMS_Boiler.pump_mod_max); + _renderIntValue("Boiler circuit pump modulation min power", "%", EMS_Boiler.pump_mod_min); // UBAMonitorSlow - if (EMS_Boiler.extTemp != EMS_VALUE_FLOAT_NOTSET) { - _renderFloatValue("Outside temperature", "C", EMS_Boiler.extTemp); + if (EMS_Boiler.extTemp != EMS_VALUE_SHORT_NOTSET) { + _renderShortValue("Outside temperature", "C", EMS_Boiler.extTemp); } - _renderFloatValue("Boiler temperature", "C", EMS_Boiler.boilTemp); + _renderShortValue("Boiler temperature", "C", EMS_Boiler.boilTemp); _renderIntValue("Pump modulation", "%", EMS_Boiler.pumpMod); - _renderLongValue("Burner # restarts", "times", EMS_Boiler.burnStarts); + _renderLongValue("Burner # starts", "times", EMS_Boiler.burnStarts); if (EMS_Boiler.burnWorkMin != EMS_VALUE_LONG_NOTSET) { myDebug(" Total burner operating time: %d days %d hours %d minutes", EMS_Boiler.burnWorkMin / 1440, @@ -360,15 +421,35 @@ void showInfo() { EMS_Boiler.UBAuptime % 60); } - myDebug(""); // newline + // For SM10 Solar Module + if (EMS_Other.SM10) { + myDebug(""); // newline + myDebug("%sSolar Module stats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); + _renderShortValue(" Collector temperature", "C", EMS_Other.SM10collectorTemp); + _renderShortValue(" Bottom temperature", "C", EMS_Other.SM10bottomTemp); + _renderIntValue(" Pump modulation", "%", EMS_Other.SM10pumpModulation); + _renderBoolValue(" Pump active", EMS_Other.SM10pump); + } // Thermostat stats if (ems_getThermostatEnabled()) { + myDebug(""); // newline myDebug("%sThermostat stats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug(" Thermostat type: %s", ems_getThermostatDescription(buffer_type)); - _renderFloatValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp); - _renderFloatValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp); - if ((ems_getThermostatModel() != EMS_MODEL_EASY) && (ems_getThermostatModel() != EMS_MODEL_BOSCHEASY)) { + if ((ems_getThermostatModel() == EMS_MODEL_EASY) || (ems_getThermostatModel() == EMS_MODEL_BOSCHEASY)) { + // for easy temps are * 100 + // also we don't have the time or mode + _renderShortValue("Set room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 10); + _renderShortValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 10); + } else { + // because we store in 2 bytes short, when converting to a single byte we'll loose the negative value if its unset + if ((EMS_Thermostat.setpoint_roomTemp <= 0) || (EMS_Thermostat.curr_roomTemp <= 0)) { + EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_INT_NOTSET; + EMS_Thermostat.curr_roomTemp = EMS_VALUE_INT_NOTSET; + } + _renderIntValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 2); // convert to a single byte * 2 + _renderIntValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 10); // is *10 + myDebug(" Thermostat time is %02d:%02d:%02d %d/%d/%d", EMS_Thermostat.hour, EMS_Thermostat.minute, @@ -387,27 +468,55 @@ void showInfo() { myDebug(" Mode is set to ?"); } } + myDebug(""); // newline } // Dallas if (EMSESP_Status.dallas_sensors != 0) { + myDebug(""); // newline + char buffer[128] = {0}; + char valuestr[8] = {0}; // for formatting temp myDebug("%sExternal temperature sensors:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); for (uint8_t i = 0; i < EMSESP_Status.dallas_sensors; i++) { - char s[80] = {0}; - snprintf(s, sizeof(s), "Sensor #%d", i + 1); - _renderFloatValue(s, "C", ds18.getValue(i)); + myDebug(" Sensor #%d %s: %s C", i + 1, ds18.getDeviceString(buffer, i), _float_to_char(valuestr, ds18.getValue(i))); } - myDebug(""); // newline } // show the Shower Info if (EMSESP_Status.shower_timer) { + myDebug(""); // newline myDebug("%sShower stats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug(" Shower is %s", (EMSESP_Shower.showerOn ? "running" : "off")); } } +// send all dallas sensor values as a JSON package to MQTT +void publishSensorValues() { + StaticJsonDocument doc; + JsonObject sensors = doc.to(); + + bool hasdata = false; + char label[8] = {0}; + char valuestr[8] = {0}; // for formatting temp + + // see if the sensor values have changed, if so send + for (uint8_t i = 0; i < EMSESP_Status.dallas_sensors; i++) { + double sensorValue = ds18.getValue(i); + if (sensorValue != DS18_DISCONNECTED && sensorValue != DS18_CRC_ERROR) { + sprintf(label, PAYLOAD_EXTERNAL_SENSORS, (i + 1)); + sensors[label] = _float_to_char(valuestr, sensorValue); + hasdata = true; + } + } + + if (hasdata) { + char data[MQTT_MAX_SIZE] = {0}; + serializeJson(doc, data, sizeof(data)); + myESP.mqttPublish(TOPIC_EXTERNAL_SENSORS, data); + } +} + // send values via MQTT // a json object is created for the boiler and one for the thermostat // CRC check is done to see if there are changes in the values since the last send to avoid too much wifi traffic @@ -419,33 +528,42 @@ void publishValues(bool force) { uint32_t fchecksum; static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off - static uint32_t previousBoilerPublishCRC = 0; // CRC check - static uint32_t previousThermostatPublishCRC = 0; // CRC check + static uint32_t previousBoilerPublishCRC = 0; // CRC check for boiler values + static uint32_t previousThermostatPublishCRC = 0; // CRC check for thermostat values + static uint32_t previousOtherPublishCRC = 0; // CRC check for other values (e.g. SM10) JsonObject rootBoiler = doc.to(); rootBoiler["wWSelTemp"] = _int_to_char(s, EMS_Boiler.wWSelTemp); - rootBoiler["selFlowTemp"] = _float_to_char(s, EMS_Boiler.selFlowTemp); - rootBoiler["outdoorTemp"] = _float_to_char(s, EMS_Boiler.extTemp); + rootBoiler["selFlowTemp"] = _int_to_char(s, EMS_Boiler.selFlowTemp); + rootBoiler["outdoorTemp"] = _short_to_char(s, EMS_Boiler.extTemp); rootBoiler["wWActivated"] = _bool_to_char(s, EMS_Boiler.wWActivated); - rootBoiler["wWComfort"] = EMS_Boiler.wWComfort ? "Comfort" : "ECO"; - rootBoiler["wWCurTmp"] = _float_to_char(s, EMS_Boiler.wWCurTmp); - snprintf(s, sizeof(s), "%i.%i", EMS_Boiler.wWCurFlow / 10, EMS_Boiler.wWCurFlow % 10); - rootBoiler["wWCurFlow"] = s; - rootBoiler["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat); - rootBoiler["curFlowTemp"] = _float_to_char(s, EMS_Boiler.curFlowTemp); - rootBoiler["retTemp"] = _float_to_char(s, EMS_Boiler.retTemp); - rootBoiler["burnGas"] = _bool_to_char(s, EMS_Boiler.burnGas); - rootBoiler["heatPmp"] = _bool_to_char(s, EMS_Boiler.heatPmp); - rootBoiler["fanWork"] = _bool_to_char(s, EMS_Boiler.fanWork); - rootBoiler["ignWork"] = _bool_to_char(s, EMS_Boiler.ignWork); - rootBoiler["wWCirc"] = _bool_to_char(s, EMS_Boiler.wWCirc); - rootBoiler["selBurnPow"] = _int_to_char(s, EMS_Boiler.selBurnPow); - rootBoiler["curBurnPow"] = _int_to_char(s, EMS_Boiler.curBurnPow); - rootBoiler["sysPress"] = _float_to_char(s, EMS_Boiler.sysPress); - rootBoiler["boilTemp"] = _float_to_char(s, EMS_Boiler.boilTemp); - rootBoiler["pumpMod"] = _int_to_char(s, EMS_Boiler.pumpMod); - rootBoiler["ServiceCode"] = EMS_Boiler.serviceCodeChar; + + if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Hot) { + rootBoiler["wWComfort"] = "Hot"; + } else if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Eco) { + rootBoiler["wWComfort"] = "Eco"; + } else if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Intelligent) { + rootBoiler["wWComfort"] = "Intelligent"; + } + + rootBoiler["wWCurTmp"] = _short_to_char(s, EMS_Boiler.wWCurTmp); + rootBoiler["wWCurFlow"] = _int_to_char(s, EMS_Boiler.wWCurFlow, 10); + rootBoiler["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat); + rootBoiler["curFlowTemp"] = _short_to_char(s, EMS_Boiler.curFlowTemp); + rootBoiler["retTemp"] = _short_to_char(s, EMS_Boiler.retTemp); + rootBoiler["burnGas"] = _bool_to_char(s, EMS_Boiler.burnGas); + rootBoiler["heatPmp"] = _bool_to_char(s, EMS_Boiler.heatPmp); + rootBoiler["fanWork"] = _bool_to_char(s, EMS_Boiler.fanWork); + rootBoiler["ignWork"] = _bool_to_char(s, EMS_Boiler.ignWork); + rootBoiler["wWCirc"] = _bool_to_char(s, EMS_Boiler.wWCirc); + rootBoiler["selBurnPow"] = _int_to_char(s, EMS_Boiler.selBurnPow); + rootBoiler["curBurnPow"] = _int_to_char(s, EMS_Boiler.curBurnPow); + rootBoiler["sysPress"] = _int_to_char(s, EMS_Boiler.sysPress, 10); + rootBoiler["boilTemp"] = _short_to_char(s, EMS_Boiler.boilTemp); + rootBoiler["pumpMod"] = _int_to_char(s, EMS_Boiler.pumpMod); + rootBoiler["ServiceCode"] = EMS_Boiler.serviceCodeChar; + rootBoiler["ServiceCodeNumber"] = EMS_Boiler.serviceCode; serializeJson(doc, data, sizeof(data)); @@ -475,15 +593,20 @@ void publishValues(bool force) { // handle the thermostat values separately if (ems_getThermostatEnabled()) { // only send thermostat values if we actually have them - if (((int)EMS_Thermostat.curr_roomTemp == (int)0) || ((int)EMS_Thermostat.setpoint_roomTemp == (int)0)) + if ((EMS_Thermostat.curr_roomTemp <= 0) || (EMS_Thermostat.setpoint_roomTemp <= 0)) return; // build new json object doc.clear(); JsonObject rootThermostat = doc.to(); - rootThermostat[THERMOSTAT_CURRTEMP] = _float_to_char(s, EMS_Thermostat.curr_roomTemp); - rootThermostat[THERMOSTAT_SELTEMP] = _float_to_char(s, EMS_Thermostat.setpoint_roomTemp); + if ((ems_getThermostatModel() == EMS_MODEL_EASY) || (ems_getThermostatModel() == EMS_MODEL_BOSCHEASY)) { + rootThermostat[THERMOSTAT_SELTEMP] = _short_to_char(s, EMS_Thermostat.setpoint_roomTemp, 10); + rootThermostat[THERMOSTAT_CURRTEMP] = _short_to_char(s, EMS_Thermostat.curr_roomTemp, 10); + } else { + rootThermostat[THERMOSTAT_SELTEMP] = _int_to_char(s, EMS_Thermostat.setpoint_roomTemp, 2); + rootThermostat[THERMOSTAT_CURRTEMP] = _int_to_char(s, EMS_Thermostat.curr_roomTemp, 10); + } // RC20 has different mode settings if (ems_getThermostatModel() == EMS_MODEL_RC20) { @@ -512,15 +635,45 @@ void publishValues(bool force) { for (size_t i = 0; i < measureJson(doc) - 1; i++) { crc.update(data[i]); } - uint32_t checksum = crc.finalize(); - if ((previousThermostatPublishCRC != checksum) || force) { - previousThermostatPublishCRC = checksum; + fchecksum = crc.finalize(); + if ((previousThermostatPublishCRC != fchecksum) || force) { + previousThermostatPublishCRC = fchecksum; myDebugLog("Publishing thermostat data via MQTT"); // send values via MQTT myESP.mqttPublish(TOPIC_THERMOSTAT_DATA, data); } } + + // handle the other values separately + // For SM10 Solar Module + if (EMS_Other.SM10) { + // build new json object + doc.clear(); + JsonObject rootSM10 = doc.to(); + + rootSM10[SM10_COLLECTORTEMP] = _short_to_char(s, EMS_Other.SM10collectorTemp); + rootSM10[SM10_BOTTOMTEMP] = _short_to_char(s, EMS_Other.SM10bottomTemp); + rootSM10[SM10_PUMPMODULATION] = _int_to_char(s, EMS_Other.SM10pumpModulation); + rootSM10[SM10_PUMP] = _bool_to_char(s, EMS_Other.SM10pump); + + data[0] = '\0'; // reset data for next package + serializeJson(doc, data, sizeof(data)); + + // calculate new CRC + crc.reset(); + for (size_t i = 0; i < measureJson(doc) - 1; i++) { + crc.update(data[i]); + } + fchecksum = crc.finalize(); + if ((previousOtherPublishCRC != fchecksum) || force) { + previousOtherPublishCRC = fchecksum; + myDebugLog("Publishing SM10 data via MQTT"); + + // send values via MQTT + myESP.mqttPublish(TOPIC_SM10_DATA, data); + } + } } // sets the shower timer on/off @@ -570,6 +723,61 @@ char * _readWord() { return word; } +// publish external dallas sensor temperature values to MQTT +void do_publishSensorValues() { + if (EMSESP_Status.dallas_sensors != 0) { + publishSensorValues(); + } +} + +// call PublishValues without forcing, so using CRC to see if we really need to publish +void do_publishValues() { + // don't publish if we're not connected to the EMS bus + if ((ems_getBusConnected()) && (!myESP.getUseSerial()) && myESP.isMQTTConnected()) { + publishValues(false); + } +} + +// callback to light up the LED, called via Ticker every second +// fast way is to use WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + (state ? 4 : 8), (1 << EMSESP_Status.led_gpio)); // 4 is on, 8 is off +void do_ledcheck() { + if (EMSESP_Status.led) { + if (ems_getBusConnected()) { + digitalWrite(EMSESP_Status.led_gpio, (EMSESP_Status.led_gpio == LED_BUILTIN) ? LOW : HIGH); // light on. For onboard LED high=off + } else { + int state = digitalRead(EMSESP_Status.led_gpio); + digitalWrite(EMSESP_Status.led_gpio, !state); + } + } +} + +// Thermostat scan +void do_scanThermostat() { + if ((ems_getBusConnected()) && (!myESP.getUseSerial())) { + myDebug("> Scanning thermostat message type #0x%02X...", scanThermostat_count); + ems_doReadCommand(scanThermostat_count, EMS_Thermostat.type_id); + scanThermostat_count++; + } +} + +// do a system health check every now and then to see if we all connections +void do_systemCheck() { + if ((!ems_getBusConnected()) && (!myESP.getUseSerial())) { + myDebug("Error! Unable to read from EMS bus. Retrying in %d seconds...", SYSTEMCHECK_TIME); + } +} + +// force calls to get data from EMS for the types that aren't sent as broadcasts +// only if we have a EMS connection +void do_regularUpdates() { + if ((ems_getBusConnected()) && (!myESP.getUseSerial())) { + myDebugLog("Calling scheduled data refresh from EMS devices..."); + ems_getThermostatValues(); + ems_getBoilerValues(); + ems_getOtherValues(); + } +} + // initiate a force scan by sending type read requests from 0 to FF to the thermostat // used to analyze responses for debugging void startThermostatScan(uint8_t start) { @@ -582,13 +790,34 @@ void startThermostatScan(uint8_t start) { scanThermostat.attach(SCANTHERMOSTAT_TIME, do_scanThermostat); } +// turn back on the hot water for the shower +void _showerColdShotStop() { + if (EMSESP_Shower.doingColdShot) { + myDebugLog("[Shower] finished shot of cold. hot water back on"); + ems_setWarmTapWaterActivated(true); + EMSESP_Shower.doingColdShot = false; + showerColdShotStopTimer.detach(); // disable the timer + } +} + +// turn off hot water to send a shot of cold +void _showerColdShotStart() { + if (EMSESP_Status.shower_alert) { + myDebugLog("[Shower] doing a shot of cold water"); + ems_setWarmTapWaterActivated(false); + EMSESP_Shower.doingColdShot = true; + // start the timer for n seconds which will reset the water back to hot + showerColdShotStopTimer.attach(SHOWER_COLDSHOT_DURATION, _showerColdShotStop); + } +} + // callback for loading/saving settings to the file system (SPIFFS) bool FSCallback(MYESP_FSACTION action, const JsonObject json) { + bool recreate_config = true; + if (action == MYESP_FSACTION_LOAD) { // led - if (!(EMSESP_Status.led_enabled = json["led"])) { - EMSESP_Status.led_enabled = LED_BUILTIN; // default value - } + EMSESP_Status.led = json["led"]; // led_gpio if (!(EMSESP_Status.led_gpio = json["led_gpio"])) { @@ -600,6 +829,11 @@ bool FSCallback(MYESP_FSACTION action, const JsonObject json) { EMSESP_Status.dallas_gpio = EMSESP_DALLAS_GPIO; // default value } + // dallas_parasite + if (!(EMSESP_Status.dallas_parasite = json["dallas_parasite"])) { + EMSESP_Status.dallas_parasite = EMSESP_DALLAS_PARASITE; // default value + } + // thermostat_type if (!(EMS_Thermostat.type_id = json["thermostat_type"])) { EMS_Thermostat.type_id = EMSESP_THERMOSTAT_TYPE; // set default @@ -610,21 +844,35 @@ bool FSCallback(MYESP_FSACTION action, const JsonObject json) { EMS_Boiler.type_id = EMSESP_BOILER_TYPE; // set default } - // test mode - if (!(EMSESP_Status.test_mode = json["test_mode"])) { - EMSESP_Status.test_mode = false; // default value + // silent mode + EMSESP_Status.silent_mode = json["silent_mode"]; + ems_setTxDisabled(EMSESP_Status.silent_mode); + + // shower_timer + EMSESP_Status.shower_timer = json["shower_timer"]; + + // shower_alert + EMSESP_Status.shower_alert = json["shower_alert"]; + + // publish_wait + if (!(EMSESP_Status.publish_wait = json["publish_wait"])) { + EMSESP_Status.publish_wait = DEFAULT_PUBLISHWAIT; // default value } - return false; // always save the settings + return recreate_config; // return false if some settings are missing and we need to rebuild the file } if (action == MYESP_FSACTION_SAVE) { - json["led"] = EMSESP_Status.led_enabled; + json["led"] = EMSESP_Status.led; json["led_gpio"] = EMSESP_Status.led_gpio; json["dallas_gpio"] = EMSESP_Status.dallas_gpio; + json["dallas_parasite"] = EMSESP_Status.dallas_parasite; json["thermostat_type"] = EMS_Thermostat.type_id; json["boiler_type"] = EMS_Boiler.type_id; - json["test_mode"] = EMSESP_Status.test_mode; + json["silent_mode"] = EMSESP_Status.silent_mode; + json["shower_timer"] = EMSESP_Status.shower_timer; + json["shower_alert"] = EMSESP_Status.shower_alert; + json["publish_wait"] = EMSESP_Status.publish_wait; return true; } @@ -632,9 +880,9 @@ bool FSCallback(MYESP_FSACTION action, const JsonObject json) { return false; } -// callback for custom settings when showing Stored Settings +// callback for custom settings when showing Stored Settings with the 'set' command // wc is number of arguments after the 'set' command -// returns true if the setting was recognized and changed +// returns true if the setting was recognized and changed and should be saved back to SPIFFs bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, const char * value) { bool ok = false; @@ -642,25 +890,32 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c // led if ((strcmp(setting, "led") == 0) && (wc == 2)) { if (strcmp(value, "on") == 0) { - EMSESP_Status.led_enabled = true; - ok = true; + EMSESP_Status.led = true; + ok = true; } else if (strcmp(value, "off") == 0) { - EMSESP_Status.led_enabled = false; - ok = true; - // let's make sure LED is really off - digitalWrite(EMSESP_Status.led_gpio, (EMSESP_Status.led_gpio == LED_BUILTIN) ? HIGH : LOW); // light off. For onboard high=off + EMSESP_Status.led = false; + ok = true; + // let's make sure LED is really off - For onboard high=off + digitalWrite(EMSESP_Status.led_gpio, (EMSESP_Status.led_gpio == LED_BUILTIN) ? HIGH : LOW); + } else { + myDebug("Error. Usage: set led "); } } // test mode - if ((strcmp(setting, "test_mode") == 0) && (wc == 2)) { + if ((strcmp(setting, "silent_mode") == 0) && (wc == 2)) { if (strcmp(value, "on") == 0) { - EMSESP_Status.test_mode = true; - ok = true; - myDebug("* Reboot to go into test mode."); + EMSESP_Status.silent_mode = true; + ok = true; + myDebug("* in Silent mode. All Tx is disabled."); + ems_setTxDisabled(true); } else if (strcmp(value, "off") == 0) { - EMSESP_Status.test_mode = false; - ok = true; + EMSESP_Status.silent_mode = false; + ok = true; + ems_setTxDisabled(false); + myDebug("* out of Silent mode. Tx is enabled."); + } else { + myDebug("Error. Usage: set silent_mode "); } } @@ -679,6 +934,19 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c ok = true; } + // dallas_parasite + if ((strcmp(setting, "dallas_parasite") == 0) && (wc == 2)) { + if (strcmp(value, "on") == 0) { + EMSESP_Status.dallas_parasite = true; + ok = true; + } else if (strcmp(value, "off") == 0) { + EMSESP_Status.dallas_parasite = false; + ok = true; + } else { + myDebug("Error. Usage: set dallas_parasite "); + } + } + // thermostat_type if (strcmp(setting, "thermostat_type") == 0) { EMS_Thermostat.type_id = ((wc == 2) ? (uint8_t)strtol(value, 0, 16) : EMS_ID_NONE); @@ -690,13 +958,45 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c EMS_Boiler.type_id = ((wc == 2) ? (uint8_t)strtol(value, 0, 16) : EMS_ID_NONE); ok = true; } + + // shower timer + if ((strcmp(setting, "shower_timer") == 0) && (wc == 2)) { + if (strcmp(value, "on") == 0) { + EMSESP_Status.shower_timer = true; + ok = true; + } else if (strcmp(value, "off") == 0) { + EMSESP_Status.shower_timer = false; + ok = true; + } else { + myDebug("Error. Usage: set shower_timer "); + } + } + + // shower alert + if ((strcmp(setting, "shower_alert") == 0) && (wc == 2)) { + if (strcmp(value, "on") == 0) { + EMSESP_Status.shower_alert = true; + ok = true; + } else if (strcmp(value, "off") == 0) { + EMSESP_Status.shower_alert = false; + ok = true; + } else { + myDebug("Error. Usage: set shower_alert "); + } + } + + // publish_wait + if ((strcmp(setting, "publish_wait") == 0) && (wc == 2)) { + EMSESP_Status.publish_wait = atoi(value); + ok = true; + } } if (action == MYESP_FSACTION_LIST) { - myDebug(" test_mode=%s", EMSESP_Status.test_mode ? "on" : "off"); - myDebug(" led=%s", EMSESP_Status.led_enabled ? "on" : "off"); + myDebug(" led=%s", EMSESP_Status.led ? "on" : "off"); myDebug(" led_gpio=%d", EMSESP_Status.led_gpio); myDebug(" dallas_gpio=%d", EMSESP_Status.dallas_gpio); + myDebug(" dallas_parasite=%s", EMSESP_Status.dallas_parasite ? "on" : "off"); if (EMS_Thermostat.type_id == EMS_ID_NONE) { myDebug(" thermostat_type="); @@ -711,6 +1011,11 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c } else { myDebug(" boiler_type=%02X", EMS_Boiler.type_id); } + + myDebug(" silent_mode=%s", EMSESP_Status.silent_mode ? "on" : "off"); + myDebug(" shower_timer=%s", EMSESP_Status.shower_timer ? "on" : "off"); + myDebug(" shower_alert=%s", EMSESP_Status.shower_alert ? "on" : "off"); + myDebug(" publish_wait=%d", EMSESP_Status.publish_wait); } return ok; @@ -743,6 +1048,12 @@ void TelnetCommandCallback(uint8_t wc, const char * commandLine) { ok = true; } + if (strcmp(first_cmd, "refresh") == 0) { + myDebug("Fetching data from EMS devices..."); + do_regularUpdates(); + ok = true; + } + if (strcmp(first_cmd, "types") == 0) { ems_printAllTypes(); ok = true; @@ -758,84 +1069,93 @@ void TelnetCommandCallback(uint8_t wc, const char * commandLine) { ok = true; } + if (strcmp(first_cmd, "startup") == 0) { + ems_startupTelegrams(); + ok = true; + } + // shower settings - if (strcmp(first_cmd, "shower") == 0) { - if (wc == 2) { - char * second_cmd = _readWord(); - if (strcmp(second_cmd, "timer") == 0) { - EMSESP_Status.shower_timer = !EMSESP_Status.shower_timer; - myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Status.shower_timer ? "1" : "0"); - ok = true; - } else if (strcmp(second_cmd, "alert") == 0) { - EMSESP_Status.shower_alert = !EMSESP_Status.shower_alert; - myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Status.shower_alert ? "1" : "0"); - ok = true; - } + if ((strcmp(first_cmd, "shower") == 0) && (wc == 2)) { + char * second_cmd = _readWord(); + if (strcmp(second_cmd, "timer") == 0) { + EMSESP_Status.shower_timer = !EMSESP_Status.shower_timer; + myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Status.shower_timer ? "1" : "0"); + ok = true; + } else if (strcmp(second_cmd, "alert") == 0) { + EMSESP_Status.shower_alert = !EMSESP_Status.shower_alert; + myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Status.shower_alert ? "1" : "0"); + ok = true; } } // logging - if (strcmp(first_cmd, "log") == 0) { - if (wc == 2) { - char * second_cmd = _readWord(); - if (strcmp(second_cmd, "v") == 0) { - ems_setLogging(EMS_SYS_LOGGING_VERBOSE); - ok = true; - } else if (strcmp(second_cmd, "b") == 0) { - ems_setLogging(EMS_SYS_LOGGING_BASIC); - ok = true; - } else if (strcmp(second_cmd, "t") == 0) { - ems_setLogging(EMS_SYS_LOGGING_THERMOSTAT); - ok = true; - } else if (strcmp(second_cmd, "r") == 0) { - ems_setLogging(EMS_SYS_LOGGING_RAW); - ok = true; - } else if (strcmp(second_cmd, "n") == 0) { - ems_setLogging(EMS_SYS_LOGGING_NONE); - ok = true; - } + if ((strcmp(first_cmd, "log") == 0) && (wc == 2)) { + char * second_cmd = _readWord(); + if (strcmp(second_cmd, "v") == 0) { + ems_setLogging(EMS_SYS_LOGGING_VERBOSE); + ok = true; + } else if (strcmp(second_cmd, "b") == 0) { + ems_setLogging(EMS_SYS_LOGGING_BASIC); + ok = true; + } else if (strcmp(second_cmd, "t") == 0) { + ems_setLogging(EMS_SYS_LOGGING_THERMOSTAT); + ok = true; + } else if (strcmp(second_cmd, "r") == 0) { + ems_setLogging(EMS_SYS_LOGGING_RAW); + ok = true; + } else if (strcmp(second_cmd, "n") == 0) { + ems_setLogging(EMS_SYS_LOGGING_NONE); + ok = true; } } // thermostat commands - if (strcmp(first_cmd, "thermostat") == 0) { - if (wc == 3) { - char * second_cmd = _readWord(); - if (strcmp(second_cmd, "temp") == 0) { - ems_setThermostatTemp(_readFloatNumber()); - ok = true; - } else if (strcmp(second_cmd, "mode") == 0) { - ems_setThermostatMode(_readIntNumber()); - ok = true; - } else if (strcmp(second_cmd, "read") == 0) { - ems_doReadCommand(_readHexNumber(), EMS_Thermostat.type_id); - ok = true; - } else if (strcmp(second_cmd, "scan") == 0) { - startThermostatScan(_readIntNumber()); - ok = true; - } + if ((strcmp(first_cmd, "thermostat") == 0) && (wc == 3)) { + char * second_cmd = _readWord(); + if (strcmp(second_cmd, "temp") == 0) { + ems_setThermostatTemp(_readFloatNumber()); + ok = true; + } else if (strcmp(second_cmd, "mode") == 0) { + ems_setThermostatMode(_readIntNumber()); + ok = true; + } else if (strcmp(second_cmd, "read") == 0) { + ems_doReadCommand(_readHexNumber(), EMS_Thermostat.type_id); + ok = true; + } else if (strcmp(second_cmd, "scan") == 0) { + startThermostatScan(_readIntNumber()); + ok = true; } } // boiler commands - if (strcmp(first_cmd, "boiler") == 0) { - if (wc == 3) { - char * second_cmd = _readWord(); - if (strcmp(second_cmd, "wwtemp") == 0) { - ems_setWarmWaterTemp(_readIntNumber()); + if ((strcmp(first_cmd, "boiler") == 0) && (wc == 3)) { + char * second_cmd = _readWord(); + if (strcmp(second_cmd, "wwtemp") == 0) { + ems_setWarmWaterTemp(_readIntNumber()); + ok = true; + } else if (strcmp(second_cmd, "comfort") == 0) { + char * third_cmd = _readWord(); + if (strcmp(third_cmd, "hot") == 0) { + ems_setWarmWaterModeComfort(1); ok = true; - } else if (strcmp(second_cmd, "read") == 0) { - ems_doReadCommand(_readHexNumber(), EMS_Boiler.type_id); + } else if (strcmp(third_cmd, "eco") == 0) { + ems_setWarmWaterModeComfort(2); + ok = true; + } else if (strcmp(third_cmd, "intelligent") == 0) { + ems_setWarmWaterModeComfort(3); + ok = true; + } + } else if (strcmp(second_cmd, "read") == 0) { + ems_doReadCommand(_readHexNumber(), EMS_Boiler.type_id); + ok = true; + } else if (strcmp(second_cmd, "tapwater") == 0) { + char * third_cmd = _readWord(); + if (strcmp(third_cmd, "on") == 0) { + ems_setWarmTapWaterActivated(true); + ok = true; + } else if (strcmp(third_cmd, "off") == 0) { + ems_setWarmTapWaterActivated(false); ok = true; - } else if (strcmp(second_cmd, "tapwater") == 0) { - char * third_cmd = _readWord(); - if (strcmp(third_cmd, "on") == 0) { - ems_setWarmTapWaterActivated(true); - ok = true; - } else if (strcmp(third_cmd, "off") == 0) { - ems_setWarmTapWaterActivated(false); - ok = true; - } } } } @@ -854,10 +1174,16 @@ void TelnetCommandCallback(uint8_t wc, const char * commandLine) { // OTA callback when the OTA process starts // so we can disable the EMS to avoid any noise -void OTACallback() { +void OTACallback_pre() { emsuart_stop(); } +// OTA callback when the OTA process finishes +// so we can re-enable the UART +void OTACallback_post() { + emsuart_start(); +} + // MQTT Callback to handle incoming/outgoing changes void MQTTCallback(unsigned int type, const char * topic, const char * message) { // we're connected. lets subscribe to some topics @@ -866,6 +1192,7 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_MODE); myESP.mqttSubscribe(TOPIC_BOILER_WWACTIVATED); myESP.mqttSubscribe(TOPIC_BOILER_CMD_WWTEMP); + myESP.mqttSubscribe(TOPIC_BOILER_CMD_COMFORT); myESP.mqttSubscribe(TOPIC_SHOWER_TIMER); myESP.mqttSubscribe(TOPIC_SHOWER_ALERT); myESP.mqttSubscribe(TOPIC_SHOWER_COLDSHOT); @@ -920,13 +1247,25 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { // boiler wwtemp changes if (strcmp(topic, TOPIC_BOILER_CMD_WWTEMP) == 0) { - float f = strtof((char *)message, 0); - char s[10] = {0}; - myDebug("MQTT topic: boiler warm water temperature value %s", _float_to_char(s, f)); - ems_setWarmWaterTemp(f); + uint8_t t = atoi((char *)message); + myDebug("MQTT topic: boiler warm water temperature value %d", t); + ems_setWarmWaterTemp(t); publishValues(true); // publish back immediately } + // boiler ww comfort setting + if (strcmp(topic, TOPIC_BOILER_CMD_COMFORT) == 0) { + myDebug("MQTT topic: boiler warm water comfort value is %s", message); + if (strcmp((char *)message, "hot") == 0) { + ems_setWarmWaterModeComfort(1); + } else if (strcmp((char *)message, "comfort") == 0) { + ems_setWarmWaterModeComfort(2); + } else if (strcmp((char *)message, "intelligent") == 0) { + ems_setWarmWaterModeComfort(3); + } + // publishValues(true); // publish back immediately + } + // shower timer if (strcmp(topic, TOPIC_SHOWER_TIMER) == 0) { if (message[0] == '1') { @@ -960,22 +1299,26 @@ void WIFICallback() { // This is done after we have a WiFi signal to avoid any resource conflicts if (myESP.getUseSerial()) { - myDebug("Warning! EMS bus disabled when in Serial mode. Use 'set serial off' to enable."); + myDebug("Warning! EMS bus disabled when in Serial mode. Use 'set serial off' to start EMS."); } else { emsuart_init(); myDebug("[UART] Opened Rx/Tx connection"); - // go and find the boiler and thermostat types - ems_discoverModels(); + if (!EMSESP_Status.silent_mode) { + // go and find the boiler and thermostat types, if not in silent mode + ems_discoverModels(); + } } } // Initialize the boiler settings and shower settings +// Most of these will be overwritten after the SPIFFS config file is loaded void initEMSESP() { // general settings - EMSESP_Status.shower_timer = BOILER_SHOWER_TIMER; - EMSESP_Status.shower_alert = BOILER_SHOWER_ALERT; - EMSESP_Status.led_enabled = true; // LED is on by default - EMSESP_Status.test_mode = false; + EMSESP_Status.shower_timer = false; + EMSESP_Status.shower_alert = false; + EMSESP_Status.led = true; // LED is on by default + EMSESP_Status.silent_mode = false; + EMSESP_Status.publish_wait = DEFAULT_PUBLISHWAIT; EMSESP_Status.timestamp = millis(); EMSESP_Status.dallas_sensors = 0; @@ -990,74 +1333,6 @@ void initEMSESP() { EMSESP_Shower.doingColdShot = false; } -// call PublishValues without forcing, so using CRC to see if we really need to publish -void do_publishValues() { - // don't publish if we're not connected to the EMS bus - if ((ems_getBusConnected()) && (!myESP.getUseSerial())) { - publishValues(false); - } -} - -// callback to light up the LED, called via Ticker every second -// fast way is to use WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + (state ? 4 : 8), (1 << EMSESP_Status.led_gpio)); // 4 is on, 8 is off -void do_ledcheck() { - if (EMSESP_Status.led_enabled) { - if (ems_getBusConnected()) { - digitalWrite(EMSESP_Status.led_gpio, (EMSESP_Status.led_gpio == LED_BUILTIN) ? LOW : HIGH); // light on. For onboard LED high=off - } else { - int state = digitalRead(EMSESP_Status.led_gpio); - digitalWrite(EMSESP_Status.led_gpio, !state); - } - } -} - -// Thermostat scan -void do_scanThermostat() { - if ((ems_getBusConnected()) && (!myESP.getUseSerial())) { - myDebug("> Scanning thermostat message type #0x%02X..", scanThermostat_count); - ems_doReadCommand(scanThermostat_count, EMS_Thermostat.type_id); - scanThermostat_count++; - } -} - -// do a system health check every now and then to see if we all connections -void do_systemCheck() { - if ((!ems_getBusConnected()) && (!myESP.getUseSerial())) { - myDebug("Error! Unable to read from EMS bus. Retrying in %d seconds...", SYSTEMCHECK_TIME); - } -} - -// force calls to get data from EMS for the types that aren't sent as broadcasts -// only if we have a EMS connection -void do_regularUpdates() { - if ((ems_getBusConnected()) && (!myESP.getUseSerial())) { - myDebugLog("Calling scheduled data refresh from EMS devices.."); - ems_getThermostatValues(); - ems_getBoilerValues(); - } -} - -// turn off hot water to send a shot of cold -void _showerColdShotStart() { - if (EMSESP_Status.shower_alert) { - myDebugLog("[Shower] doing a shot of cold water"); - ems_setWarmTapWaterActivated(false); - EMSESP_Shower.doingColdShot = true; - // start the timer for n seconds which will reset the water back to hot - showerColdShotStopTimer.attach(SHOWER_COLDSHOT_DURATION, _showerColdShotStop); - } -} - -// turn back on the hot water for the shower -void _showerColdShotStop() { - if (EMSESP_Shower.doingColdShot) { - myDebugLog("[Shower] finished shot of cold. hot water back on"); - ems_setWarmTapWaterActivated(true); - EMSESP_Shower.doingColdShot = false; - showerColdShotStopTimer.detach(); // disable the timer - } -} - /* * Shower Logic */ @@ -1065,7 +1340,7 @@ void showerCheck() { // if already in cold mode, ignore all this logic until we're out of the cold blast if (!EMSESP_Shower.doingColdShot) { // is the hot water running? - if (EMS_Boiler.tapwaterActive) { + if (EMS_Boiler.tapwaterActive == 1) { // if heater was previously off, start the timer if (EMSESP_Shower.timerStart == 0) { // hot water just started... @@ -1157,8 +1432,8 @@ void setup() { MQTT_WILL_OFFLINE_PAYLOAD, MQTTCallback); - // OTA callback which is called when OTA is starting - myESP.setOTA(OTACallback); + // OTA callback which is called when OTA is starting and stopping + myESP.setOTA(OTACallback_pre, OTACallback_post); // custom settings in SPIFFS myESP.setSettings(FSCallback, SettingsCallback); @@ -1166,10 +1441,13 @@ void setup() { // start up all the services myESP.begin(APP_HOSTNAME, APP_NAME, APP_VERSION); + // at this point we have the settings from our internall SPIFFS config file + // enable regular checks if not in test mode - if (!EMSESP_Status.test_mode) { - publishValuesTimer.attach(PUBLISHVALUES_TIME, do_publishValues); // post MQTT values - regularUpdatesTimer.attach(REGULARUPDATES_TIME, do_regularUpdates); // regular reads from the EMS + if (!EMSESP_Status.silent_mode) { + publishValuesTimer.attach(EMSESP_Status.publish_wait, do_publishValues); // post MQTT EMS values + publishSensorValuesTimer.attach(EMSESP_Status.publish_wait, do_publishSensorValues); // post MQTT sensor values + regularUpdatesTimer.attach(REGULARUPDATES_TIME, do_regularUpdates); // regular reads from the EMS } // set pin for LED @@ -1180,7 +1458,7 @@ void setup() { } // check for Dallas sensors - EMSESP_Status.dallas_sensors = ds18.setup(EMSESP_Status.dallas_gpio); // returns #sensors + EMSESP_Status.dallas_sensors = ds18.setup(EMSESP_Status.dallas_gpio, EMSESP_Status.dallas_parasite); // returns #sensors } // @@ -1192,14 +1470,15 @@ void loop() { // the main loop myESP.loop(); - // check Dallas sensors + // check Dallas sensors, every 2 seconds + // these values are published to MQTT seperately via the timer publishSensorValuesTimer if (EMSESP_Status.dallas_sensors != 0) { ds18.loop(); } // publish the values to MQTT, only if the values have changed // although we don't want to publish when doing a deep scan of the thermostat - if (ems_getEmsRefreshed() && (scanThermostat_count == 0) && (!EMSESP_Status.test_mode)) { + if (ems_getEmsRefreshed() && (scanThermostat_count == 0) && (!EMSESP_Status.silent_mode)) { publishValues(false); ems_setEmsRefreshed(false); // reset } @@ -1208,4 +1487,8 @@ void loop() { if (EMSESP_Status.shower_timer) { showerCheck(); } + + if (EMSESP_DELAY != 0) { + delay(EMSESP_DELAY); // some time to WiFi and everything else to catch up, and prevent overheating + } } diff --git a/src/ems.cpp b/src/ems.cpp index 76b263e3a..2e640915c 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -14,49 +14,58 @@ #include #include // std::list -// myESP +// myESP for logging to telnet and serial #define myDebug(...) myESP.myDebug(__VA_ARGS__) _EMS_Sys_Status EMS_Sys_Status; // EMS Status CircularBuffer<_EMS_TxTelegram, EMS_TX_TELEGRAM_QUEUE_MAX> EMS_TxQueue; // FIFO queue for Tx send buffer -// callbacks per type +// +// process callbacks per type +// + +// macros used in the _process* functions +#define _toByte(i) (data[i]) +#define _toShort(i) ((data[i] << 8) + data[i + 1]) +#define _toLong(i) ((data[i] << 16) + (data[i + 1] << 8) + (data[i + 2])) +#define _bitRead(i, bit) (((data[i]) >> (bit)) & 0x01) // generic -void _process_Version(uint8_t type, uint8_t * data, uint8_t length); +void _process_Version(uint8_t src, uint8_t * data, uint8_t length); // Boiler and Buderus devices -void _process_UBAMonitorFast(uint8_t type, uint8_t * data, uint8_t length); -void _process_UBAMonitorSlow(uint8_t type, uint8_t * data, uint8_t length); -void _process_UBAMonitorWWMessage(uint8_t type, uint8_t * data, uint8_t length); -void _process_UBAParameterWW(uint8_t type, uint8_t * data, uint8_t length); -void _process_UBATotalUptimeMessage(uint8_t type, uint8_t * data, uint8_t length); -void _process_UBAParametersMessage(uint8_t type, uint8_t * data, uint8_t length); -void _process_SetPoints(uint8_t type, uint8_t * data, uint8_t length); +void _process_UBAMonitorFast(uint8_t src, uint8_t * data, uint8_t length); +void _process_UBAMonitorSlow(uint8_t src, uint8_t * data, uint8_t length); +void _process_UBAMonitorWWMessage(uint8_t src, uint8_t * data, uint8_t length); +void _process_UBAParameterWW(uint8_t src, uint8_t * data, uint8_t length); +void _process_UBATotalUptimeMessage(uint8_t src, uint8_t * data, uint8_t length); +void _process_UBAParametersMessage(uint8_t src, uint8_t * data, uint8_t length); +void _process_SetPoints(uint8_t src, uint8_t * data, uint8_t length); +void _process_SM10Monitor(uint8_t src, uint8_t * data, uint8_t length); // Common for most thermostats -void _process_RCTime(uint8_t type, uint8_t * data, uint8_t length); -void _process_RCOutdoorTempMessage(uint8_t type, uint8_t * data, uint8_t length); +void _process_RCTime(uint8_t src, uint8_t * data, uint8_t length); +void _process_RCOutdoorTempMessage(uint8_t src, uint8_t * data, uint8_t length); // RC10 -void _process_RC10Set(uint8_t type, uint8_t * data, uint8_t length); -void _process_RC10StatusMessage(uint8_t type, uint8_t * data, uint8_t length); +void _process_RC10Set(uint8_t src, uint8_t * data, uint8_t length); +void _process_RC10StatusMessage(uint8_t src, uint8_t * data, uint8_t length); // RC20 -void _process_RC20Set(uint8_t type, uint8_t * data, uint8_t length); -void _process_RC20StatusMessage(uint8_t type, uint8_t * data, uint8_t length); +void _process_RC20Set(uint8_t src, uint8_t * data, uint8_t length); +void _process_RC20StatusMessage(uint8_t src, uint8_t * data, uint8_t length); // RC30 -void _process_RC30Set(uint8_t type, uint8_t * data, uint8_t length); -void _process_RC30StatusMessage(uint8_t type, uint8_t * data, uint8_t length); +void _process_RC30Set(uint8_t src, uint8_t * data, uint8_t length); +void _process_RC30StatusMessage(uint8_t src, uint8_t * data, uint8_t length); // RC35 -void _process_RC35Set(uint8_t type, uint8_t * data, uint8_t length); -void _process_RC35StatusMessage(uint8_t type, uint8_t * data, uint8_t length); +void _process_RC35Set(uint8_t src, uint8_t * data, uint8_t length); +void _process_RC35StatusMessage(uint8_t src, uint8_t * data, uint8_t length); // Easy -void _process_EasyStatusMessage(uint8_t type, uint8_t * data, uint8_t length); +void _process_EasyStatusMessage(uint8_t src, uint8_t * data, uint8_t length); /* * Recognized EMS types and the functions they call to process the telegrams @@ -77,6 +86,9 @@ const _EMS_Type EMS_Types[] = { {EMS_MODEL_UBA, EMS_TYPE_UBAParametersMessage, "UBAParametersMessage", _process_UBAParametersMessage}, {EMS_MODEL_UBA, EMS_TYPE_UBASetPoints, "UBASetPoints", _process_SetPoints}, + // Other devices + {EMS_MODEL_OTHER, EMS_TYPE_SM10Monitor, "SM10Monitor", _process_SM10Monitor}, + // RC10 {EMS_MODEL_RC10, EMS_TYPE_RCTime, "RCTime", _process_RCTime}, {EMS_MODEL_RC10, EMS_TYPE_RC10Set, "RC10Set", _process_RC10Set}, @@ -115,17 +127,18 @@ const _EMS_Type EMS_Types[] = { {EMS_MODEL_EASY, EMS_TYPE_EasyStatusMessage, "EasyStatusMessage", _process_EasyStatusMessage}, {EMS_MODEL_BOSCHEASY, EMS_TYPE_EasyStatusMessage, "EasyStatusMessage", _process_EasyStatusMessage}, - }; -// calculate sizes of arrays +// calculate sizes of arrays at compile uint8_t _EMS_Types_max = ArraySize(EMS_Types); // number of defined types -uint8_t _Boiler_Types_max = ArraySize(Boiler_Types); // number of models +uint8_t _Boiler_Types_max = ArraySize(Boiler_Types); // number of boiler models +uint8_t _Other_Types_max = ArraySize(Other_Types); // number of other ems devices uint8_t _Thermostat_Types_max = ArraySize(Thermostat_Types); // number of defined thermostat types // these structs contain the data we store from the Boiler and Thermostat -_EMS_Boiler EMS_Boiler; -_EMS_Thermostat EMS_Thermostat; +_EMS_Boiler EMS_Boiler; // for boiler +_EMS_Thermostat EMS_Thermostat; // for thermostat +_EMS_Other EMS_Other; // for other known EMS devices // CRC lookup table with poly 12 for faster checking const uint8_t ems_crc_table[] = {0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, 0x22, @@ -162,12 +175,13 @@ void ems_init() { EMS_Sys_Status.emsBusConnected = false; EMS_Sys_Status.emsRxTimestamp = 0; EMS_Sys_Status.emsTxCapable = false; + EMS_Sys_Status.emsTxDisabled = false; EMS_Sys_Status.emsPollTimestamp = 0; EMS_Sys_Status.txRetryCount = 0; // thermostat - EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_FLOAT_NOTSET; - EMS_Thermostat.curr_roomTemp = EMS_VALUE_FLOAT_NOTSET; + EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_SHORT_NOTSET; + EMS_Thermostat.curr_roomTemp = EMS_VALUE_SHORT_NOTSET; EMS_Thermostat.hour = 0; EMS_Thermostat.minute = 0; EMS_Thermostat.second = 0; @@ -190,8 +204,8 @@ void ems_init() { // UBAMonitorFast EMS_Boiler.selFlowTemp = EMS_VALUE_INT_NOTSET; // Selected flow temperature - EMS_Boiler.curFlowTemp = EMS_VALUE_FLOAT_NOTSET; // Current flow temperature - EMS_Boiler.retTemp = EMS_VALUE_FLOAT_NOTSET; // Return temperature + EMS_Boiler.curFlowTemp = EMS_VALUE_SHORT_NOTSET; // Current flow temperature + EMS_Boiler.retTemp = EMS_VALUE_SHORT_NOTSET; // Return temperature EMS_Boiler.burnGas = EMS_VALUE_INT_NOTSET; // Gas on/off EMS_Boiler.fanWork = EMS_VALUE_INT_NOTSET; // Fan on/off EMS_Boiler.ignWork = EMS_VALUE_INT_NOTSET; // Ignition on/off @@ -200,20 +214,21 @@ void ems_init() { EMS_Boiler.wWCirc = EMS_VALUE_INT_NOTSET; // Circulation on/off EMS_Boiler.selBurnPow = EMS_VALUE_INT_NOTSET; // Burner max power EMS_Boiler.curBurnPow = EMS_VALUE_INT_NOTSET; // Burner current power - EMS_Boiler.flameCurr = EMS_VALUE_FLOAT_NOTSET; // Flame current in micro amps - EMS_Boiler.sysPress = EMS_VALUE_FLOAT_NOTSET; // System pressure + EMS_Boiler.flameCurr = EMS_VALUE_SHORT_NOTSET; // Flame current in micro amps + EMS_Boiler.sysPress = EMS_VALUE_INT_NOTSET; // System pressure strlcpy(EMS_Boiler.serviceCodeChar, "??", sizeof(EMS_Boiler.serviceCodeChar)); + EMS_Boiler.serviceCode = EMS_VALUE_SHORT_NOTSET; // UBAMonitorSlow - EMS_Boiler.extTemp = EMS_VALUE_FLOAT_NOTSET; // Outside temperature - EMS_Boiler.boilTemp = EMS_VALUE_FLOAT_NOTSET; // Boiler temperature + EMS_Boiler.extTemp = EMS_VALUE_SHORT_NOTSET; // Outside temperature + EMS_Boiler.boilTemp = EMS_VALUE_SHORT_NOTSET; // Boiler temperature EMS_Boiler.pumpMod = EMS_VALUE_INT_NOTSET; // Pump modulation EMS_Boiler.burnStarts = EMS_VALUE_LONG_NOTSET; // # burner restarts EMS_Boiler.burnWorkMin = EMS_VALUE_LONG_NOTSET; // Total burner operating time EMS_Boiler.heatWorkMin = EMS_VALUE_LONG_NOTSET; // Total heat operating time // UBAMonitorWWMessage - EMS_Boiler.wWCurTmp = EMS_VALUE_FLOAT_NOTSET; // Warm Water current temperature: + EMS_Boiler.wWCurTmp = EMS_VALUE_SHORT_NOTSET; // Warm Water current temperature: EMS_Boiler.wWStarts = EMS_VALUE_LONG_NOTSET; // Warm Water # starts EMS_Boiler.wWWorkM = EMS_VALUE_LONG_NOTSET; // Warm Water # minutes EMS_Boiler.wWOneTime = EMS_VALUE_INT_NOTSET; // Warm Water one time function on/off @@ -227,18 +242,27 @@ void ems_init() { EMS_Boiler.pump_mod_max = EMS_VALUE_INT_NOTSET; // Boiler circuit pump modulation max. power EMS_Boiler.pump_mod_min = EMS_VALUE_INT_NOTSET; // Boiler circuit pump modulation min. power + // Other EMS devices values + EMS_Other.SM10collectorTemp = EMS_VALUE_SHORT_NOTSET; // collector temp from SM10 + EMS_Other.SM10bottomTemp = EMS_VALUE_SHORT_NOTSET; // bottom temp from SM10 + EMS_Other.SM10pumpModulation = EMS_VALUE_INT_NOTSET; // modulation solar pump SM10 + EMS_Other.SM10pump = EMS_VALUE_INT_NOTSET; // pump active + // calculated values EMS_Boiler.tapwaterActive = EMS_VALUE_INT_NOTSET; // Hot tap water is on/off EMS_Boiler.heatingActive = EMS_VALUE_INT_NOTSET; // Central heating is on/off // set boiler type EMS_Boiler.product_id = 0; - strlcpy(EMS_Boiler.version, "not set", sizeof(EMS_Boiler.version)); + strlcpy(EMS_Boiler.version, "?", sizeof(EMS_Boiler.version)); // set thermostat model EMS_Thermostat.model_id = EMS_MODEL_NONE; EMS_Thermostat.product_id = 0; - strlcpy(EMS_Thermostat.version, "not set", sizeof(EMS_Thermostat.version)); + strlcpy(EMS_Thermostat.version, "?", sizeof(EMS_Thermostat.version)); + + // set other types + EMS_Other.SM10 = false; // default logging is none ems_setLogging(EMS_SYS_LOGGING_DEFAULT); @@ -274,6 +298,10 @@ uint8_t ems_getThermostatModel() { return (EMS_Thermostat.model_id); } +void ems_setTxDisabled(bool b) { + EMS_Sys_Status.emsTxDisabled = b; +} + bool ems_getTxCapable() { if ((millis() - EMS_Sys_Status.emsPollTimestamp) > EMS_POLL_TIMEOUT) { EMS_Sys_Status.emsTxCapable = false; @@ -326,52 +354,7 @@ uint8_t _crcCalculator(uint8_t * data, uint8_t len) { return crc; } -/** - * function to turn a telegram int (2 bytes) to a float. The source is *10 - * negative values are stored as 1-compliment (https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c) - */ -float _toFloat(uint8_t i, uint8_t * data) { - // if the MSB is set, it's a negative number or an error - if ((data[i] & 0x80) == 0x80) { - // check if its an invalid number - // 0x8000 is used when sensor is missing - if ((data[i] == 0x80) && (data[i + 1] == 0)) { - return (float)EMS_VALUE_FLOAT_NOTSET; // return -1 to indicate that is unknown - } - // its definitely a negative number - // assume its 1-compliment, otherwise we need add 1 to the total for 2-compliment - int16_t x = (data[i] << 8) + data[i + 1]; - return ((float)(x)) / 10; - } else { - // ...a positive number - return ((float)(((data[i] << 8) + data[i + 1]))) / 10; - } -} - -// function to turn a telegram long (3 bytes) to a long int -uint32_t _toLong(uint8_t i, uint8_t * data) { - return (((data[i]) << 16) + ((data[i + 1]) << 8) + (data[i + 2])); -} - -/** - * Find the pointer to the EMS_Types array for a given type ID - */ -int _ems_findType(uint8_t type) { - uint8_t i = 0; - bool typeFound = false; - // scan through known ID types - while (i < _EMS_Types_max) { - if (EMS_Types[i].type == type) { - typeFound = true; // we have a match - break; - } - i++; - } - - return (typeFound ? i : -1); -} - -// like itoa but for hex, and quick +// like itoa but for hex, and quicker char * _hextoa(uint8_t value, char * buffer) { char * p = buffer; byte nib1 = (value >> 4) & 0x0F; @@ -390,25 +373,58 @@ char * _smallitoa(uint8_t value, char * buffer) { return buffer; } +/* for decimals 0 to 999, printed as a string + * From @nomis + */ +char * _smallitoa3(uint16_t value, char * buffer) { + buffer[0] = ((value / 100) == 0) ? '0' : (value / 100) + '0'; + buffer[1] = (((value % 100) / 10) == 0) ? '0' : ((value % 100) / 10) + '0'; + buffer[2] = (value % 10) + '0'; + buffer[3] = '\0'; + return buffer; +} + +/** + * Find the pointer to the EMS_Types array for a given type ID + * or -1 if not found + */ +int _ems_findType(uint8_t type) { + uint8_t i = 0; + bool typeFound = false; + // scan through known ID types + while (i < _EMS_Types_max) { + if (EMS_Types[i].type == type) { + typeFound = true; // we have a match + break; + } + i++; + } + + return (typeFound ? i : -1); +} + /** * debug print a telegram to telnet/serial including the CRC * len is length in bytes including the CRC */ -void _debugPrintTelegram(const char * prefix, uint8_t * data, uint8_t len, const char * color) { +void _debugPrintTelegram(const char * prefix, _EMS_RxTelegram * EMS_RxTelegram, const char * color) { if (EMS_Sys_Status.emsLogging <= EMS_SYS_LOGGING_BASIC) return; - char output_str[300] = {0}; // roughly EMS_MAX_TELEGRAM_LENGTH*3 + 20 - char buffer[16] = {0}; + char output_str[200] = {0}; + char buffer[16] = {0}; + uint8_t len = EMS_RxTelegram->length; + uint8_t * data = EMS_RxTelegram->telegram; - unsigned long upt = millis(); strlcpy(output_str, "(", sizeof(output_str)); strlcat(output_str, COLOR_CYAN, sizeof(output_str)); - strlcat(output_str, _smallitoa((uint8_t)((upt / 3600000) % 24), buffer), sizeof(output_str)); + strlcat(output_str, _smallitoa((uint8_t)((EMS_RxTelegram->timestamp / 3600000) % 24), buffer), sizeof(output_str)); strlcat(output_str, ":", sizeof(output_str)); - strlcat(output_str, _smallitoa((uint8_t)((upt / 60000) % 60), buffer), sizeof(output_str)); + strlcat(output_str, _smallitoa((uint8_t)((EMS_RxTelegram->timestamp / 60000) % 60), buffer), sizeof(output_str)); strlcat(output_str, ":", sizeof(output_str)); - strlcat(output_str, _smallitoa((uint8_t)((upt / 1000) % 60), buffer), sizeof(output_str)); + strlcat(output_str, _smallitoa((uint8_t)((EMS_RxTelegram->timestamp / 1000) % 60), buffer), sizeof(output_str)); + strlcat(output_str, ".", sizeof(output_str)); + strlcat(output_str, _smallitoa3(EMS_RxTelegram->timestamp % 1000, buffer), sizeof(output_str)); strlcat(output_str, COLOR_RESET, sizeof(output_str)); strlcat(output_str, ") ", sizeof(output_str)); @@ -450,21 +466,26 @@ void _ems_sendTelegram() { // we don't remove from the queue yet _EMS_TxTelegram EMS_TxTelegram = EMS_TxQueue.first(); - // if we're in raw mode just fire and forget - if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_RAW) { - EMS_TxTelegram.data[EMS_TxTelegram.length - 1] = _crcCalculator(EMS_TxTelegram.data, EMS_TxTelegram.length); // add the CRC - _debugPrintTelegram("Sending raw", EMS_TxTelegram.data, EMS_TxTelegram.length, COLOR_CYAN); // always show - emsuart_tx_buffer(EMS_TxTelegram.data, EMS_TxTelegram.length); // send the telegram to the UART Tx - EMS_TxQueue.shift(); // remove from queue - return; - } - // if there is no destination, also delete it from the queue if (EMS_TxTelegram.dest == EMS_ID_NONE) { EMS_TxQueue.shift(); // remove from queue return; } + + // if we're in raw mode just fire and forget + if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_RAW) { + EMS_TxTelegram.data[EMS_TxTelegram.length - 1] = _crcCalculator(EMS_TxTelegram.data, EMS_TxTelegram.length); // add the CRC + _EMS_RxTelegram EMS_RxTelegram; + EMS_RxTelegram.length = EMS_TxTelegram.length; + EMS_RxTelegram.telegram = EMS_TxTelegram.data; + EMS_RxTelegram.timestamp = millis(); // now + _debugPrintTelegram("Sending raw", &EMS_RxTelegram, COLOR_CYAN); // always show + emsuart_tx_buffer(EMS_TxTelegram.data, EMS_TxTelegram.length); // send the telegram to the UART Tx + EMS_TxQueue.shift(); // remove from queue + return; + } + // create header EMS_TxTelegram.data[0] = EMS_ID_ME; // src // dest @@ -499,7 +520,11 @@ void _ems_sendTelegram() { snprintf(s, sizeof(s), "Sending validate of type 0x%02X to 0x%02X:", EMS_TxTelegram.type, EMS_TxTelegram.dest & 0x7F); } - _debugPrintTelegram(s, EMS_TxTelegram.data, EMS_TxTelegram.length, COLOR_CYAN); + _EMS_RxTelegram EMS_RxTelegram; + EMS_RxTelegram.length = EMS_TxTelegram.length; + EMS_RxTelegram.telegram = EMS_TxTelegram.data; + EMS_RxTelegram.timestamp = millis(); // now + _debugPrintTelegram(s, &EMS_RxTelegram, COLOR_CYAN); } // send the telegram to the UART Tx @@ -552,25 +577,46 @@ void _createValidate() { EMS_TxQueue.unshift(new_EMS_TxTelegram); // add back to queue making it first to be picked up next (FIFO) } -/** - * the main logic that parses the telegram message, triggered by an interrupt in emsuart.cpp + +/* + * Entry point triggered by an interrupt in emsuart.cpp * length is only data bytes, excluding the BRK * Read commands are asynchronous as they're handled by the interrupt - * When we receive a Poll Request we need to send any Tx packages quickly within a 200ms window + * When a telegram is processed we forcefully erase it from the stack to prevent overflow */ void ems_parseTelegram(uint8_t * telegram, uint8_t length) { + if ((length != 0) && (telegram[0] != 0x00)) { + _ems_readTelegram(telegram, length); + } + // now clear the Rx buffer just be safe and prevent duplicates + for (uint8_t i = 0; i < EMS_MAXBUFFERSIZE; telegram[i++] = 0x00) + ; +} + +/** + * the main logic that parses the telegram message + * When we receive a Poll Request we need to send any Tx packages quickly within a 200ms window + */ +void _ems_readTelegram(uint8_t * telegram, uint8_t length) { + // create the Rx package + static _EMS_RxTelegram EMS_RxTelegram; + EMS_RxTelegram.length = length; + EMS_RxTelegram.telegram = telegram; + EMS_RxTelegram.timestamp = millis(); + // check if we just received a single byte - // it could well be a Poll request from the boiler to us which will have a value of 0x8B (0x0B | 0x80) + // it could well be a Poll request from the boiler for us, which will have a value of 0x8B (0x0B | 0x80) // or either a return code like 0x01 or 0x04 from the last Write command if (length == 1) { uint8_t value = telegram[0]; // 1st byte of data package // check first for a Poll for us if (value == (EMS_ID_ME | 0x80)) { - EMS_Sys_Status.emsPollTimestamp = millis(); // store when we received a last poll + EMS_Sys_Status.emsPollTimestamp = EMS_RxTelegram.timestamp; // store when we received a last poll EMS_Sys_Status.emsTxCapable = true; - // do we have something to send thats waiting in the Tx queue? if so send it if the Queue is not in a wait state + // do we have something to send thats waiting in the Tx queue? + // if so send it if the Queue is not in a wait state if ((!EMS_TxQueue.isEmpty()) && (EMS_Sys_Status.emsTxStatus == EMS_TX_STATUS_IDLE)) { _ems_sendTelegram(); // perform the read/write command immediately } else { @@ -602,7 +648,7 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) { // ignore anything that doesn't resemble a proper telegram package // minimal is 5 bytes, excluding CRC at the end if (length <= 4) { - //_debugPrintTelegram("Noisy data:", telegram, length, COLOR_RED); + //_debugPrintTelegram("Noisy data:", &EMS_RxTelegram COLOR_RED); return; } @@ -612,7 +658,7 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) { if (telegram[length - 1] != crc) { EMS_Sys_Status.emxCrcErr++; if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) { - _debugPrintTelegram("Corrupt telegram:", telegram, length, COLOR_RED); + _debugPrintTelegram("Corrupt telegram:", &EMS_RxTelegram, COLOR_RED); } return; } @@ -631,28 +677,29 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) { // here we know its a valid incoming telegram of at least 6 bytes // we use this to see if we always have a connection to the boiler, in case of drop outs - EMS_Sys_Status.emsRxTimestamp = millis(); // timestamp of last read + EMS_Sys_Status.emsRxTimestamp = EMS_RxTelegram.timestamp; // timestamp of last read EMS_Sys_Status.emsBusConnected = true; // now lets process it and see what to do next - _processType(telegram, length); + _processType(&EMS_RxTelegram); } /** * print detailed telegram * and then call its callback if there is one defined */ -void _ems_processTelegram(uint8_t * telegram, uint8_t length) { +void _ems_processTelegram(_EMS_RxTelegram * EMS_RxTelegram) { // header - uint8_t src = telegram[0] & 0x7F; - uint8_t dest = telegram[1] & 0x7F; // remove 8th bit to handle both reads and writes - uint8_t type = telegram[2]; - uint8_t offset = telegram[3]; - uint8_t * data = telegram + 4; // data block starts at position 5 + uint8_t * telegram = EMS_RxTelegram->telegram; + uint8_t src = telegram[0] & 0x7F; + uint8_t dest = telegram[1] & 0x7F; // remove 8th bit to handle both reads and writes + uint8_t type = telegram[2]; + uint8_t offset = telegram[3]; + uint8_t * data = telegram + 4; // data block starts at position 5 // print detailed telegram data if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_THERMOSTAT) { - char output_str[300] = {0}; // roughly EMS_MAX_TELEGRAM_LENGTH*3 + 20 + char output_str[200] = {0}; char buffer[16] = {0}; char color_s[20] = {0}; @@ -661,6 +708,8 @@ void _ems_processTelegram(uint8_t * telegram, uint8_t length) { strlcpy(output_str, "Boiler", sizeof(output_str)); } else if (src == EMS_Thermostat.type_id) { strlcpy(output_str, "Thermostat", sizeof(output_str)); + } else if (src == EMS_ID_SM10) { + strlcpy(output_str, "SM10", sizeof(output_str)); } else { strlcpy(output_str, "0x", sizeof(output_str)); strlcat(output_str, _hextoa(src, buffer), sizeof(output_str)); @@ -678,6 +727,9 @@ void _ems_processTelegram(uint8_t * telegram, uint8_t length) { } else if (dest == EMS_Boiler.type_id) { strlcat(output_str, "Boiler", sizeof(output_str)); strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s)); + } else if (dest == EMS_ID_SM10) { + strlcat(output_str, "SM10", sizeof(output_str)); + strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s)); } else if (dest == EMS_Thermostat.type_id) { strlcat(output_str, "Thermostat", sizeof(output_str)); strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s)); @@ -694,43 +746,43 @@ void _ems_processTelegram(uint8_t * telegram, uint8_t length) { if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_THERMOSTAT) { // only print ones to/from thermostat if logging is set to thermostat only if ((src == EMS_Thermostat.type_id) || (dest == EMS_Thermostat.type_id)) { - _debugPrintTelegram(output_str, telegram, length, color_s); + _debugPrintTelegram(output_str, EMS_RxTelegram, color_s); } } else { // always print - _debugPrintTelegram(output_str, telegram, length, color_s); + _debugPrintTelegram(output_str, EMS_RxTelegram, color_s); } } // see if we recognize the type first by scanning our known EMS types list - // trying to match the type ID - bool commonType = false; - bool typeFound = false; - bool forUs = false; - int i = 0; + bool typeFound = false; + uint8_t i = 0; while (i < _EMS_Types_max) { if (EMS_Types[i].type == type) { - typeFound = true; - commonType = (EMS_Types[i].model_id == EMS_MODEL_ALL); // is it common type for everyone? - forUs = (src == EMS_Boiler.type_id) || (src == EMS_Thermostat.type_id); // is it for us? So the src must match - break; + // is it common type for everyone? + // is it for us? So the src must match with either the boiler, thermostat or other devices + if ((EMS_Types[i].model_id == EMS_MODEL_ALL) + || ((src == EMS_Boiler.type_id) || (src == EMS_Thermostat.type_id) || (src == EMS_ID_SM10))) { + typeFound = true; + break; + } } i++; } // if it's a common type (across ems devices) or something specifically for us process it. // dest will be EMS_ID_NONE and offset 0x00 for a broadcast message - if (typeFound && (commonType || forUs)) { + if (typeFound) { if ((EMS_Types[i].processType_cb) != (void *)NULL) { // print non-verbose message - if ((EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_BASIC) || (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE)) { + if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_BASIC) { myDebug("<--- %s(0x%02X) received", EMS_Types[i].typeString, type); } // call callback function to process it // as we only handle complete telegrams (not partial) check that the offset is 0 - if (offset == EMS_ID_NONE) { - (void)EMS_Types[i].processType_cb(type, data, length - 5); + if (offset == 0) { + (void)EMS_Types[i].processType_cb(src, data, EMS_RxTelegram->length - 5); } } } @@ -751,19 +803,21 @@ void _removeTxQueue() { * length is only data bytes, excluding the BRK * We only remove from the Tx queue if the read or write was successful */ -void _processType(uint8_t * telegram, uint8_t length) { +void _processType(_EMS_RxTelegram * EMS_RxTelegram) { + uint8_t * telegram = EMS_RxTelegram->telegram; + // header uint8_t src = telegram[0] & 0x7F; // removing 8th bit as we deal with both reads and writes here // if its an echo of ourselves from the master UBA, ignore if (src == EMS_ID_ME) { - //_debugPrintTelegram("Telegram echo:", telegram, length, COLOR_BLUE); + // _debugPrintTelegram("echo:", EMS_RxTelegram, COLOR_WHITE); return; } // if its a broadcast and we didn't just send anything, process it and exit if (EMS_Sys_Status.emsTxStatus == EMS_TX_STATUS_IDLE) { - _ems_processTelegram(telegram, length); + _ems_processTelegram(EMS_RxTelegram); return; } @@ -775,13 +829,13 @@ void _processType(uint8_t * telegram, uint8_t length) { // and if not we probably didn't get any response so remove the last Tx from the queue and process the telegram anyway if ((telegram[1] & 0x7F) != EMS_ID_ME) { _removeTxQueue(); - _ems_processTelegram(telegram, length); + _ems_processTelegram(EMS_RxTelegram); return; } // first double check we actually have something in the queue if (EMS_TxQueue.isEmpty()) { - _ems_processTelegram(telegram, length); + _ems_processTelegram(EMS_RxTelegram); return; } @@ -816,7 +870,7 @@ void _processType(uint8_t * telegram, uint8_t length) { } } } - _ems_processTelegram(telegram, length); // process it always + _ems_processTelegram(EMS_RxTelegram); // process it always } if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_WRITE) { @@ -871,23 +925,27 @@ void _processType(uint8_t * telegram, uint8_t length) { * using a quick hack for checking the heating. Selected Flow Temp >= 70 */ void _checkActive() { - // hot tap water, using flow to check insread of the burner power - EMS_Boiler.tapwaterActive = ((EMS_Boiler.wWCurFlow != 0) && (EMS_Boiler.burnGas == EMS_VALUE_INT_ON)); + // hot tap water, using flow to check instead of the burner power + if (EMS_Boiler.wWCurFlow != EMS_VALUE_INT_NOTSET && EMS_Boiler.burnGas != EMS_VALUE_INT_NOTSET) { + EMS_Boiler.tapwaterActive = ((EMS_Boiler.wWCurFlow != 0) && (EMS_Boiler.burnGas == EMS_VALUE_INT_ON)); + } // heating - EMS_Boiler.heatingActive = ((EMS_Boiler.selFlowTemp >= EMS_BOILER_SELFLOWTEMP_HEATING) && (EMS_Boiler.burnGas == EMS_VALUE_INT_ON)); + if (EMS_Boiler.selFlowTemp != EMS_VALUE_INT_NOTSET && EMS_Boiler.burnGas != EMS_VALUE_INT_NOTSET) { + EMS_Boiler.heatingActive = ((EMS_Boiler.selFlowTemp >= EMS_BOILER_SELFLOWTEMP_HEATING) && (EMS_Boiler.burnGas == EMS_VALUE_INT_ON)); + } } /** * UBAParameterWW - type 0x33 - warm water parameters * received only after requested (not broadcasted) */ -void _process_UBAParameterWW(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Boiler.wWActivated = (data[1] == 0xFF); // 0xFF means on - EMS_Boiler.wWSelTemp = data[2]; - EMS_Boiler.wWCircPump = (data[6] == 0xFF); // 0xFF means on - EMS_Boiler.wWDesiredTemp = data[8]; - EMS_Boiler.wWComfort = (data[EMS_OFFSET_UBAParameterWW_wwComfort] == 0x00); +void _process_UBAParameterWW(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Boiler.wWActivated = (_toByte(1) == 0xFF); // 0xFF means on + EMS_Boiler.wWSelTemp = _toByte(2); + EMS_Boiler.wWCircPump = (_toByte(6) == 0xFF); // 0xFF means on + EMS_Boiler.wWDesiredTemp = _toByte(8); + EMS_Boiler.wWComfort = _toByte(EMS_OFFSET_UBAParameterWW_wwComfort); EMS_Sys_Status.emsRefreshed = true; // when we receieve this, lets force an MQTT publish } @@ -896,63 +954,63 @@ void _process_UBAParameterWW(uint8_t type, uint8_t * data, uint8_t length) { * UBATotalUptimeMessage - type 0x14 - total uptime * received only after requested (not broadcasted) */ -void _process_UBATotalUptimeMessage(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Boiler.UBAuptime = _toLong(0, data); +void _process_UBATotalUptimeMessage(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Boiler.UBAuptime = _toLong(0); EMS_Sys_Status.emsRefreshed = true; // when we receieve this, lets force an MQTT publish } /* * UBAParametersMessage - type 0x16 */ -void _process_UBAParametersMessage(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Boiler.heating_temp = data[1]; - EMS_Boiler.pump_mod_max = data[9]; - EMS_Boiler.pump_mod_min = data[10]; +void _process_UBAParametersMessage(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Boiler.heating_temp = _toByte(1); + EMS_Boiler.pump_mod_max = _toByte(9); + EMS_Boiler.pump_mod_min = _toByte(10); } /** * UBAMonitorWWMessage - type 0x34 - warm water monitor. 19 bytes long * received every 10 seconds */ -void _process_UBAMonitorWWMessage(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Boiler.wWCurTmp = _toFloat(1, data); - EMS_Boiler.wWStarts = _toLong(13, data); - EMS_Boiler.wWWorkM = _toLong(10, data); - EMS_Boiler.wWOneTime = bitRead(data[5], 1); - EMS_Boiler.wWCurFlow = data[9]; +void _process_UBAMonitorWWMessage(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Boiler.wWCurTmp = _toShort(1); + EMS_Boiler.wWStarts = _toLong(13); + EMS_Boiler.wWWorkM = _toLong(10); + EMS_Boiler.wWOneTime = _bitRead(5, 1); + EMS_Boiler.wWCurFlow = _toByte(9); } /** * UBAMonitorFast - type 0x18 - central heating monitor part 1 (25 bytes long) * received every 10 seconds */ -void _process_UBAMonitorFast(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Boiler.selFlowTemp = data[0]; - EMS_Boiler.curFlowTemp = _toFloat(1, data); - EMS_Boiler.retTemp = _toFloat(13, data); +void _process_UBAMonitorFast(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Boiler.selFlowTemp = _toByte(0); + EMS_Boiler.curFlowTemp = _toShort(1); + EMS_Boiler.retTemp = _toShort(13); - uint8_t v = data[7]; - EMS_Boiler.burnGas = bitRead(v, 0); - EMS_Boiler.fanWork = bitRead(v, 2); - EMS_Boiler.ignWork = bitRead(v, 3); - EMS_Boiler.heatPmp = bitRead(v, 5); - EMS_Boiler.wWHeat = bitRead(v, 6); - EMS_Boiler.wWCirc = bitRead(v, 7); + EMS_Boiler.burnGas = _bitRead(7, 0); + EMS_Boiler.fanWork = _bitRead(7, 2); + EMS_Boiler.ignWork = _bitRead(7, 3); + EMS_Boiler.heatPmp = _bitRead(7, 5); + EMS_Boiler.wWHeat = _bitRead(7, 6); + EMS_Boiler.wWCirc = _bitRead(7, 7); - EMS_Boiler.curBurnPow = data[4]; - EMS_Boiler.selBurnPow = data[3]; // burn power max setting + EMS_Boiler.curBurnPow = _toByte(4); + EMS_Boiler.selBurnPow = _toByte(3); // burn power max setting - EMS_Boiler.flameCurr = _toFloat(15, data); + EMS_Boiler.flameCurr = _toShort(15); // read the service code / installation status as appears on the display - EMS_Boiler.serviceCodeChar[0] = char(data[18]); // ascii character 1 - EMS_Boiler.serviceCodeChar[1] = char(data[19]); // ascii character 2 + EMS_Boiler.serviceCodeChar[0] = char(_toByte(18)); // ascii character 1 + EMS_Boiler.serviceCodeChar[1] = char(_toByte(19)); // ascii character 2 + EMS_Boiler.serviceCodeChar[2] = '\0'; // null terminate string - if (data[17] == 0xFF) { // missing value for system pressure - EMS_Boiler.sysPress = 0; - } else { - EMS_Boiler.sysPress = (((float)data[17]) / (float)10); - } + // read error code + EMS_Boiler.serviceCode = _toShort(20); + + // system pressure. FF means missing + EMS_Boiler.sysPress = _toByte(17); // this is *10 // at this point do a quick check to see if the hot water or heating is active _checkActive(); @@ -962,24 +1020,24 @@ void _process_UBAMonitorFast(uint8_t type, uint8_t * data, uint8_t length) { * UBAMonitorSlow - type 0x19 - central heating monitor part 2 (27 bytes long) * received every 60 seconds */ -void _process_UBAMonitorSlow(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Boiler.extTemp = _toFloat(0, data); // 0x8000 if not available - EMS_Boiler.boilTemp = _toFloat(2, data); // 0x8000 if not available - EMS_Boiler.pumpMod = data[9]; - EMS_Boiler.burnStarts = _toLong(10, data); - EMS_Boiler.burnWorkMin = _toLong(13, data); - EMS_Boiler.heatWorkMin = _toLong(19, data); +void _process_UBAMonitorSlow(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Boiler.extTemp = _toShort(0); // 0x8000 if not available + EMS_Boiler.boilTemp = _toShort(2); // 0x8000 if not available + EMS_Boiler.pumpMod = _toByte(9); + EMS_Boiler.burnStarts = _toLong(10); + EMS_Boiler.burnWorkMin = _toLong(13); + EMS_Boiler.heatWorkMin = _toLong(19); } - /** * type 0xB1 - data from the RC10 thermostat (0x17) * For reading the temp values only * received every 60 seconds + * e.g. 17 0B 91 00 80 1E 00 CB 27 00 00 00 00 05 01 00 CB 00 (CRC=47), #data=14 */ -void _process_RC10StatusMessage(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Thermostat.setpoint_roomTemp = ((float)data[EMS_TYPE_RC10StatusMessage_setpoint]) / (float)2; - EMS_Thermostat.curr_roomTemp = ((float)data[EMS_TYPE_RC10StatusMessage_curr]) / (float)10; +void _process_RC10StatusMessage(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Thermostat.setpoint_roomTemp = _toByte(EMS_TYPE_RC10StatusMessage_setpoint); // is * 2 + EMS_Thermostat.curr_roomTemp = _toByte(EMS_TYPE_RC10StatusMessage_curr); // is * 10 EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT } @@ -989,9 +1047,9 @@ void _process_RC10StatusMessage(uint8_t type, uint8_t * data, uint8_t length) { * For reading the temp values only * received every 60 seconds */ -void _process_RC20StatusMessage(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Thermostat.setpoint_roomTemp = ((float)data[EMS_TYPE_RC20StatusMessage_setpoint]) / (float)2; - EMS_Thermostat.curr_roomTemp = _toFloat(EMS_TYPE_RC20StatusMessage_curr, data); +void _process_RC20StatusMessage(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Thermostat.setpoint_roomTemp = _toByte(EMS_TYPE_RC20StatusMessage_setpoint); // is * 2 + EMS_Thermostat.curr_roomTemp = _toShort(EMS_TYPE_RC20StatusMessage_curr); // is * 10 EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT } @@ -1001,9 +1059,9 @@ void _process_RC20StatusMessage(uint8_t type, uint8_t * data, uint8_t length) { * For reading the temp values only * received every 60 seconds */ -void _process_RC30StatusMessage(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Thermostat.setpoint_roomTemp = ((float)data[EMS_TYPE_RC30StatusMessage_setpoint]) / (float)2; - EMS_Thermostat.curr_roomTemp = _toFloat(EMS_TYPE_RC30StatusMessage_curr, data); +void _process_RC30StatusMessage(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Thermostat.setpoint_roomTemp = _toByte(EMS_TYPE_RC30StatusMessage_setpoint); // is * 2 + EMS_Thermostat.curr_roomTemp = _toShort(EMS_TYPE_RC30StatusMessage_curr); // note, its 2 bytes here EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT } @@ -1013,14 +1071,14 @@ void _process_RC30StatusMessage(uint8_t type, uint8_t * data, uint8_t length) { * For reading the temp values only * received every 60 seconds */ -void _process_RC35StatusMessage(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Thermostat.setpoint_roomTemp = ((float)data[EMS_TYPE_RC35StatusMessage_setpoint]) / (float)2; +void _process_RC35StatusMessage(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Thermostat.setpoint_roomTemp = _toByte(EMS_TYPE_RC35StatusMessage_setpoint); // is * 2 // check if temp sensor is unavailable if ((data[0] == 0x7D) && (data[1] = 0x00)) { - EMS_Thermostat.curr_roomTemp = EMS_VALUE_FLOAT_NOTSET; + EMS_Thermostat.curr_roomTemp = EMS_VALUE_SHORT_NOTSET; } else { - EMS_Thermostat.curr_roomTemp = _toFloat(EMS_TYPE_RC35StatusMessage_curr, data); + EMS_Thermostat.curr_roomTemp = _toShort(EMS_TYPE_RC35StatusMessage_curr); } EMS_Thermostat.day_mode = bitRead(data[EMS_OFFSET_RC35Get_mode_day], 1); //get day mode flag EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT @@ -1028,11 +1086,11 @@ void _process_RC35StatusMessage(uint8_t type, uint8_t * data, uint8_t length) { /** * type 0x0A - data from the Nefit Easy/TC100 thermostat (0x18) - 31 bytes long - * The Easy has a digital precision of its floats to 2 decimal places, so values is divided by 100 + * The Easy has a digital precision of its floats to 2 decimal places, so values must be divided by 100 */ -void _process_EasyStatusMessage(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Thermostat.curr_roomTemp = ((float)(((data[EMS_TYPE_EasyStatusMessage_curr] << 8) + data[9]))) / 100; - EMS_Thermostat.setpoint_roomTemp = ((float)(((data[EMS_TYPE_EasyStatusMessage_setpoint] << 8) + data[11]))) / 100; +void _process_EasyStatusMessage(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Thermostat.curr_roomTemp = _toShort(EMS_TYPE_EasyStatusMessage_curr); // is *100 + EMS_Thermostat.setpoint_roomTemp = _toShort(EMS_TYPE_EasyStatusMessage_setpoint); // is *100 EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT } @@ -1041,7 +1099,7 @@ void _process_EasyStatusMessage(uint8_t type, uint8_t * data, uint8_t length) { * type 0xB0 - for reading the mode from the RC10 thermostat (0x17) * received only after requested */ -void _process_RC10Set(uint8_t type, uint8_t * data, uint8_t length) { +void _process_RC10Set(uint8_t src, uint8_t * data, uint8_t length) { // mode not implemented yet } @@ -1049,16 +1107,16 @@ void _process_RC10Set(uint8_t type, uint8_t * data, uint8_t length) { * type 0xA8 - for reading the mode from the RC20 thermostat (0x17) * received only after requested */ -void _process_RC20Set(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Thermostat.mode = data[EMS_OFFSET_RC20Set_mode]; +void _process_RC20Set(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Thermostat.mode = _toByte(EMS_OFFSET_RC20Set_mode); } /** * type 0xA7 - for reading the mode from the RC30 thermostat (0x10) * received only after requested */ -void _process_RC30Set(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Thermostat.mode = data[EMS_OFFSET_RC30Set_mode]; +void _process_RC30Set(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Thermostat.mode = _toByte(EMS_OFFSET_RC30Set_mode); } /** @@ -1066,31 +1124,75 @@ void _process_RC30Set(uint8_t type, uint8_t * data, uint8_t length) { * Working Mode Heating Circuit 1 (HC1) * received only after requested */ -void _process_RC35Set(uint8_t type, uint8_t * data, uint8_t length) { - EMS_Thermostat.mode = data[EMS_OFFSET_RC35Set_mode]; +void _process_RC35Set(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Thermostat.mode = _toByte(EMS_OFFSET_RC35Set_mode); } /** * type 0xA3 - for external temp settings from the the RC* thermostats */ -void _process_RCOutdoorTempMessage(uint8_t type, uint8_t * data, uint8_t length) { +void _process_RCOutdoorTempMessage(uint8_t src, uint8_t * data, uint8_t length) { // add support here if you're reading external sensors } +/* + * SM10Monitor - type 0x97 + */ +void _process_SM10Monitor(uint8_t src, uint8_t * data, uint8_t length) { + EMS_Other.SM10collectorTemp = _toShort(2); // collector temp from SM10, is *10 + EMS_Other.SM10bottomTemp = _toShort(5); // bottom temp from SM10, is *10 + EMS_Other.SM10pumpModulation = _toByte(4); // modulation solar pump + EMS_Other.SM10pump = _bitRead(7, 1); // active if bit 1 is set + + EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT +} + +/** + * UBASetPoint 0x1A + */ +void _process_SetPoints(uint8_t src, uint8_t * data, uint8_t length) { + /* + if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) { + if (length != 0) { + uint8_t setpoint = data[0]; + uint8_t hk_power = data[1]; + uint8_t ww_power = data[2]; + myDebug(" SetPoint=%d, hk_power=%d, ww_power=%d", setpoint, hk_power, ww_power); + } + } + */ +} + +/** + * process_RCTime - type 0x06 - date and time from a thermostat - 14 bytes long + * common for all thermostats + */ +void _process_RCTime(uint8_t src, uint8_t * data, uint8_t length) { + if ((EMS_Thermostat.model_id == EMS_MODEL_EASY) || (EMS_Thermostat.model_id == EMS_MODEL_BOSCHEASY)) { + return; // not supported + } + + EMS_Thermostat.hour = _toByte(2); + EMS_Thermostat.minute = _toByte(4); + EMS_Thermostat.second = _toByte(5); + EMS_Thermostat.day = _toByte(3); + EMS_Thermostat.month = _toByte(1); + EMS_Thermostat.year = _toByte(0); +} + /** * type 0x02 - get the firmware version and type of an EMS device * look up known devices via the product id and setup if not already set */ -void _process_Version(uint8_t type, uint8_t * data, uint8_t length) { +void _process_Version(uint8_t src, uint8_t * data, uint8_t length) { // ignore short messages that we can't interpret if (length < 3) { return; } - bool do_save = false; - uint8_t product_id = data[0]; + uint8_t product_id = _toByte(0); char version[10] = {0}; - snprintf(version, sizeof(version), "%02d.%02d", data[1], data[2]); + snprintf(version, sizeof(version), "%02d.%02d", _toByte(1), _toByte(2)); // see if its a known boiler int i = 0; @@ -1105,7 +1207,7 @@ void _process_Version(uint8_t type, uint8_t * data, uint8_t length) { if (typeFound) { // its a boiler - myDebug("Boiler type device found. Model %s with TypeID 0x%02X, Product ID %d, Version %s", + myDebug("Boiler found. Model %s with TypeID 0x%02X, ProductID %d, Version %s", Boiler_Types[i].model_string, Boiler_Types[i].type_id, product_id, @@ -1114,7 +1216,7 @@ void _process_Version(uint8_t type, uint8_t * data, uint8_t length) { // if its a boiler set it // it will take the first one found in the list if ((EMS_Boiler.type_id == EMS_ID_NONE) || (EMS_Boiler.type_id == Boiler_Types[i].type_id)) { - myDebug("* Setting Boiler type to Model %s, TypeID 0x%02X, Product ID %d, Version %s", + myDebug("* Setting Boiler type to Model %s, TypeID 0x%02X, ProductID %d, Version %s", Boiler_Types[i].model_string, Boiler_Types[i].type_id, product_id, @@ -1124,7 +1226,7 @@ void _process_Version(uint8_t type, uint8_t * data, uint8_t length) { EMS_Boiler.product_id = Boiler_Types[i].product_id; strlcpy(EMS_Boiler.version, version, sizeof(EMS_Boiler.version)); - do_save = true; + myESP.fs_saveConfig(); // save config to SPIFFS ems_getBoilerValues(); // get Boiler values that we would usually have to wait for } @@ -1144,7 +1246,7 @@ void _process_Version(uint8_t type, uint8_t * data, uint8_t length) { if (typeFound) { // its a known thermostat if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) { - myDebug("Thermostat found. Model %s with TypeID 0x%02X, Product ID %d, Version %s", + myDebug("Thermostat found. Model %s with TypeID 0x%02X, ProductID %d, Version %s", Thermostat_Types[i].model_string, Thermostat_Types[i].type_id, product_id, @@ -1154,7 +1256,7 @@ void _process_Version(uint8_t type, uint8_t * data, uint8_t length) { // if we don't have a thermostat set, use this one if ((EMS_Thermostat.type_id == EMS_ID_NONE) || (EMS_Thermostat.model_id == EMS_MODEL_NONE) || (EMS_Thermostat.type_id == Thermostat_Types[i].type_id)) { - myDebug("* Setting Thermostat type to Model %s, TypeID 0x%02X, Product ID %d, Version %s", + myDebug("* Setting Thermostat type to Model %s, TypeID 0x%02X, ProductID %d, Version %s", Thermostat_Types[i].model_string, Thermostat_Types[i].type_id, product_id, @@ -1167,18 +1269,43 @@ void _process_Version(uint8_t type, uint8_t * data, uint8_t length) { EMS_Thermostat.product_id = product_id; strlcpy(EMS_Thermostat.version, version, sizeof(EMS_Thermostat.version)); - do_save = true; + myESP.fs_saveConfig(); // save config to SPIFFS // get Thermostat values (if supported) ems_getThermostatValues(); } - } else { - myDebug("Unrecognized device found. TypeID 0x%02X, Product ID %d, Version %s", type, product_id, version); + return; } - // if the boiler or thermostat values have changed, save them to SPIFFS - if (do_save) { - myESP.fs_saveConfig(); + // finally look for the other EMS devices + i = 0; + while (i < _Other_Types_max) { + if (Other_Types[i].product_id == product_id) { + typeFound = true; // we have a matching product id. i is the index. + break; + } + i++; + } + + if (typeFound) { + myDebug("Device found. Model %s with TypeID 0x%02X, ProductID %d, Version %s", + Other_Types[i].model_string, + Other_Types[i].type_id, + product_id, + version); + + // see if this is a Solar Module SM10 + if (Other_Types[i].type_id == EMS_ID_SM10) { + EMS_Other.SM10 = true; // we have detected a SM10 + myDebug("SM10 Solar Module support enabled."); + } + + // fetch other values + ems_getOtherValues(); + return; + + } else { + myDebug("Unrecognized device found. TypeID 0x%02X, ProductID %d, Version %s", src, product_id, version); } } @@ -1189,6 +1316,9 @@ void ems_discoverModels() { // boiler ems_doReadCommand(EMS_TYPE_Version, EMS_Boiler.type_id); // get version details of boiler + // solar module + ems_doReadCommand(EMS_TYPE_Version, EMS_ID_SM10); // check if there is Solar Module available + // thermostat // if it hasn't been set, auto discover it if (EMS_Thermostat.type_id == EMS_ID_NONE) { @@ -1225,7 +1355,7 @@ void _ems_setThermostatModel(uint8_t thermostat_modelid) { // set the thermostat if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) { - myDebug("Setting Thermostat. Model %s with TypeID 0x%02X, Product ID %d", + myDebug("Setting Thermostat. Model %s with TypeID 0x%02X, ProductID %d", thermostat_type->model_string, thermostat_type->type_id, thermostat_type->product_id); @@ -1238,39 +1368,6 @@ void _ems_setThermostatModel(uint8_t thermostat_modelid) { EMS_Thermostat.write_supported = thermostat_type->write_supported; } -/** - * UBASetPoint 0x1A - */ -void _process_SetPoints(uint8_t type, uint8_t * data, uint8_t length) { - /* - if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) { - if (length != 0) { - uint8_t setpoint = data[0]; - uint8_t hk_power = data[1]; - uint8_t ww_power = data[2]; - myDebug(" SetPoint=%d, hk_power=%d, ww_power=%d", setpoint, hk_power, ww_power); - } - } - */ -} - -/** - * process_RCTime - type 0x06 - date and time from a thermostat - 14 bytes long - * common for all thermostats - */ -void _process_RCTime(uint8_t type, uint8_t * data, uint8_t length) { - if ((EMS_Thermostat.model_id == EMS_MODEL_EASY) || (EMS_Thermostat.model_id == EMS_MODEL_BOSCHEASY)) { - return; // not supported - } - - EMS_Thermostat.hour = data[2]; - EMS_Thermostat.minute = data[4]; - EMS_Thermostat.second = data[5]; - EMS_Thermostat.day = data[3]; - EMS_Thermostat.month = data[1]; - EMS_Thermostat.year = data[0]; -} - /** * Print the Tx queue - for debugging */ @@ -1367,6 +1464,15 @@ void ems_getBoilerValues() { ems_doReadCommand(EMS_TYPE_UBATotalUptimeMessage, EMS_Boiler.type_id); // get uptime from boiler } +/* + * Get other values from EMS devices + */ +void ems_getOtherValues() { + if (EMS_Other.SM10) { + ems_doReadCommand(EMS_TYPE_SM10Monitor, EMS_ID_SM10); // fetch all from SM10Monitor, e.g. 0B B0 97 00 16 + } +} + /** * returns current thermostat type as a string */ @@ -1375,9 +1481,9 @@ char * ems_getThermostatDescription(char * buffer) { if (!ems_getThermostatEnabled()) { strlcpy(buffer, "", size); } else { - // find the boiler details - int i = 0; - bool found = false; + int i = 0; + bool found = false; + char tmp[6] = {0}; // scan through known ID types while (i < _Thermostat_Types_max) { @@ -1387,19 +1493,19 @@ char * ems_getThermostatDescription(char * buffer) { } i++; } + if (found) { strlcpy(buffer, Thermostat_Types[i].model_string, size); } else { - strlcpy(buffer, "Generic Type", size); + strlcpy(buffer, "TypeID: 0x", size); + strlcat(buffer, _hextoa(EMS_Thermostat.type_id, tmp), size); } - char tmp[6] = {0}; - strlcat(buffer, " [Type ID: 0x", size); - strlcat(buffer, _hextoa(EMS_Thermostat.type_id, tmp), size); - strlcat(buffer, "] Product ID:", size); + strlcat(buffer, " (ProductID:", size); strlcat(buffer, itoa(EMS_Thermostat.product_id, tmp, 10), size); strlcat(buffer, " Version:", size); strlcat(buffer, EMS_Thermostat.version, size); + strlcat(buffer, ")", size); } return buffer; @@ -1413,9 +1519,9 @@ char * ems_getBoilerDescription(char * buffer) { if (!ems_getBoilerEnabled()) { strlcpy(buffer, "", size); } else { - // find the boiler details - int i = 0; - bool found = false; + int i = 0; + bool found = false; + char tmp[6] = {0}; // scan through known ID types while (i < _Boiler_Types_max) { @@ -1428,16 +1534,15 @@ char * ems_getBoilerDescription(char * buffer) { if (found) { strlcpy(buffer, Boiler_Types[i].model_string, size); } else { - strlcpy(buffer, "Generic Type", size); + strlcpy(buffer, "TypeID: 0x", size); + strlcat(buffer, _hextoa(EMS_Boiler.type_id, tmp), size); } - char tmp[6] = {0}; - strlcat(buffer, " [Type ID: 0x", size); - strlcat(buffer, _hextoa(EMS_Boiler.type_id, tmp), size); - strlcat(buffer, "] Product ID:", size); + strlcat(buffer, " (ProductID:", size); strlcat(buffer, itoa(EMS_Boiler.product_id, tmp, 10), size); strlcat(buffer, " Version:", size); strlcat(buffer, EMS_Boiler.version, size); + strlcat(buffer, ")", size); } return buffer; @@ -1449,7 +1554,7 @@ char * ems_getBoilerDescription(char * buffer) { void ems_scanDevices() { myDebug("Started scan of EMS bus for known devices"); - std::list Device_Ids; // new list + std::list Device_Ids; // create a new list // copy over boilers for (_Boiler_Type bt : Boiler_Types) { @@ -1460,6 +1565,12 @@ void ems_scanDevices() { for (_Thermostat_Type tt : Thermostat_Types) { Device_Ids.push_back(tt.type_id); } + + // copy over others + for (_Other_Type ot : Other_Types) { + Device_Ids.push_back(ot.type_id); + } + // remove duplicates and reserved IDs (like our own device) Device_Ids.sort(); Device_Ids.unique(); @@ -1478,13 +1589,16 @@ void ems_printAllTypes() { uint8_t i; myDebug("\nThese %d boiler type devices are in the library:", _Boiler_Types_max); - for (i = 0; i < _Boiler_Types_max; i++) { - myDebug(" %s, type ID:0x%02X Product ID:%d", Boiler_Types[i].model_string, Boiler_Types[i].type_id, Boiler_Types[i].product_id); + myDebug(" %s, type ID:0x%02X ProductID:%d", Boiler_Types[i].model_string, Boiler_Types[i].type_id, Boiler_Types[i].product_id); + } + + myDebug("\nThese %d EMS devices are in the library:", _Other_Types_max); + for (i = 0; i < _Other_Types_max; i++) { + myDebug(" %s, type ID:0x%02X ProductID:%d", Other_Types[i].model_string, Other_Types[i].type_id, Other_Types[i].product_id); } myDebug("\nThese telegram type IDs are recognized for the selected boiler:"); - for (i = 0; i < _EMS_Types_max; i++) { if ((EMS_Types[i].model_id == EMS_MODEL_ALL) || (EMS_Types[i].model_id == EMS_MODEL_UBA)) { myDebug(" type %02X (%s)", EMS_Types[i].type, EMS_Types[i].typeString); @@ -1493,7 +1607,7 @@ void ems_printAllTypes() { myDebug("\nThese %d thermostats models are supported:", _Thermostat_Types_max); for (i = 0; i < _Thermostat_Types_max; i++) { - myDebug(" %s, type ID:0x%02X Product ID:%d Read/Write support:%c%c", + myDebug(" %s, type ID:0x%02X ProductID:%d Read/Write support:%c%c", Thermostat_Types[i].model_string, Thermostat_Types[i].type_id, Thermostat_Types[i].product_id, @@ -1508,7 +1622,13 @@ void ems_printAllTypes() { */ void ems_doReadCommand(uint8_t type, uint8_t dest, bool forceRefresh) { // if not a valid type of boiler is not accessible then quits - if (type == EMS_ID_NONE) { + if ((type == EMS_ID_NONE) || (dest == EMS_ID_NONE)) { + return; + } + + // if we're preventing all outbound traffic, quit + if (EMS_Sys_Status.emsTxDisabled) { + myDebug("in Silent Mode. All Tx is disabled."); return; } @@ -1550,18 +1670,22 @@ void ems_sendRawTelegram(char * telegram) { char * p; char value[10] = {0}; + if (EMS_Sys_Status.emsTxDisabled) { + return; // user has disabled all Tx + } + _EMS_TxTelegram EMS_TxTelegram = EMS_TX_TELEGRAM_NEW; // create new Tx EMS_TxTelegram.timestamp = millis(); // set timestamp EMS_Sys_Status.txRetryCount = 0; // reset retry counter // get first value, which should be the src - if ( (p = strtok(telegram, " ,")) ) { // delimiter + if ((p = strtok(telegram, " ,"))) { // delimiter strlcpy(value, p, sizeof(value)); EMS_TxTelegram.data[0] = (uint8_t)strtol(value, 0, 16); } // and interate until end while (p != 0) { - if ( (p = strtok(NULL, " ,")) ) { + if ((p = strtok(NULL, " ,"))) { strlcpy(value, p, sizeof(value)); uint8_t val = (uint8_t)strtol(value, 0, 16); EMS_TxTelegram.data[++count] = val; @@ -1575,6 +1699,10 @@ void ems_sendRawTelegram(char * telegram) { } } + if (count == 0) { + return; // nothing to send + } + // calculate length including header and CRC EMS_TxTelegram.length = count + 2; EMS_TxTelegram.type_validate = EMS_ID_NONE; @@ -1726,22 +1854,32 @@ void ems_setWarmWaterTemp(uint8_t temperature) { /** * Set the warm water mode to comfort to Eco/Comfort + * 1 = Hot, 2 = Eco, 3 = Intelligent */ -void ems_setWarmWaterModeComfort(bool comfort) { - myDebug("Setting boiler warm water to comfort mode %s\n", comfort ? "Comfort" : "Eco"); - +void ems_setWarmWaterModeComfort(uint8_t comfort) { _EMS_TxTelegram EMS_TxTelegram = EMS_TX_TELEGRAM_NEW; // create new Tx EMS_TxTelegram.timestamp = millis(); // set timestamp EMS_Sys_Status.txRetryCount = 0; // reset retry counter + if (comfort == 1) { + myDebug("Setting boiler warm water comfort mode to Hot"); + EMS_TxTelegram.dataValue = EMS_VALUE_UBAParameterWW_wwComfort_Hot; + } else if (comfort == 2) { + myDebug("Setting boiler warm water comfort mode to Eco"); + EMS_TxTelegram.dataValue = EMS_VALUE_UBAParameterWW_wwComfort_Eco; + } else if (comfort == 3) { + myDebug("Setting boiler warm water comfort mode to Intelligent"); + EMS_TxTelegram.dataValue = EMS_VALUE_UBAParameterWW_wwComfort_Intelligent; + } else { + return; // invalid comfort value + } + EMS_TxTelegram.action = EMS_TX_TELEGRAM_WRITE; EMS_TxTelegram.dest = EMS_Boiler.type_id; EMS_TxTelegram.type = EMS_TYPE_UBAParameterWW; EMS_TxTelegram.offset = EMS_OFFSET_UBAParameterWW_wwComfort; EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH; EMS_TxTelegram.type_validate = EMS_ID_NONE; // don't validate - EMS_TxTelegram.dataValue = - (comfort ? EMS_VALUE_UBAParameterWW_wwComfort_Comfort : EMS_VALUE_UBAParameterWW_wwComfort_Eco); // 0x00 is on, 0xD8 is off EMS_TxQueue.push(EMS_TxTelegram); } @@ -1816,3 +1954,26 @@ void ems_setWarmTapWaterActivated(bool activated) { EMS_TxQueue.push(EMS_TxTelegram); // add to queue } + +/* + * Start up sequence for UBA Master, hopefully to initialize a handshake + * Still experimental + */ +void ems_startupTelegrams() { + if ((EMS_Sys_Status.emsTxDisabled) || (!EMS_Sys_Status.emsBusConnected)) { + myDebug("Unable to send startup sequence when in silent mode or bus is disabled"); + } + + myDebug("Sending startup sequence..."); + char s[20] = {0}; + + // (00:07:27.512) Telegram echo: telegram: 0B 08 1D 00 00 (CRC=84), #data=1 + // Write type 0x1D to get out of function test mode + snprintf(s, sizeof(s), "%02X %02X 1D 00 00", EMS_ID_ME, EMS_Boiler.type_id); + ems_sendRawTelegram(s); + + // (00:07:35.555) Telegram echo: telegram: 0B 88 01 00 1B (CRC=8B), #data=1 + // Read type 0x01 + snprintf(s, sizeof(s), "%02X %02X 01 00 1B", EMS_ID_ME, EMS_Boiler.type_id | 0x80); + ems_sendRawTelegram(s); +} diff --git a/src/ems.h b/src/ems.h index 5147223b0..e1e97f33a 100644 --- a/src/ems.h +++ b/src/ems.h @@ -16,18 +16,19 @@ #define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs #define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as the "Service Key" #define EMS_ID_DEFAULT_BOILER 0x08 +#define EMS_ID_SM10 0x30 // Solar Module SM10 #define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC // max length of a telegram, including CRC, for Rx and Tx. -#define EMS_MAX_TELEGRAM_LENGTH 99 +#define EMS_MAX_TELEGRAM_LENGTH 32 // default values #define EMS_VALUE_INT_ON 1 // boolean true #define EMS_VALUE_INT_OFF 0 // boolean false #define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit ints +#define EMS_VALUE_SHORT_NOTSET 0x8000 // for 2-byte shorts #define EMS_VALUE_LONG_NOTSET 0xFFFFFF // for 3-byte longs -#define EMS_VALUE_FLOAT_NOTSET -255 // float #define EMS_THERMOSTAT_READ_YES true #define EMS_THERMOSTAT_READ_NO false @@ -91,6 +92,7 @@ typedef struct { unsigned long emsRxTimestamp; // timestamp of last EMS message received unsigned long emsPollTimestamp; // timestamp of last EMS poll sent to us bool emsTxCapable; // able to send via Tx + bool emsTxDisabled; // true to prevent all Tx uint8_t txRetryCount; // # times the last Tx was re-sent } _EMS_Sys_Status; @@ -111,6 +113,12 @@ typedef struct { uint8_t data[EMS_MAX_TELEGRAM_LENGTH]; } _EMS_TxTelegram; +// The Rx receive package +typedef struct { + uint32_t timestamp; // timestamp from millis() + uint8_t * telegram; // the full data package + uint8_t length; // length in bytes +} _EMS_RxTelegram; // default empty Tx @@ -137,6 +145,13 @@ typedef struct { char model_string[50]; } _Boiler_Type; +typedef struct { + uint8_t model_id; + uint8_t product_id; + uint8_t type_id; + char model_string[50]; +} _Other_Type; + // Definition for thermostat type typedef struct { uint8_t model_id; @@ -158,31 +173,32 @@ typedef struct { // UBAParameterWW uint8_t wWComfort; // Warm water comfort or ECO mode // UBAMonitorFast - uint8_t selFlowTemp; // Selected flow temperature - float curFlowTemp; // Current flow temperature - float retTemp; // Return temperature - uint8_t burnGas; // Gas on/off - uint8_t fanWork; // Fan on/off - uint8_t ignWork; // Ignition on/off - uint8_t heatPmp; // Circulating pump on/off - uint8_t wWHeat; // 3-way valve on WW - uint8_t wWCirc; // Circulation on/off - uint8_t selBurnPow; // Burner max power - uint8_t curBurnPow; // Burner current power - float flameCurr; // Flame current in micro amps - float sysPress; // System pressure - char serviceCodeChar[2]; // 2 character status/service code + uint8_t selFlowTemp; // Selected flow temperature + int16_t curFlowTemp; // Current flow temperature + int16_t retTemp; // Return temperature + uint8_t burnGas; // Gas on/off + uint8_t fanWork; // Fan on/off + uint8_t ignWork; // Ignition on/off + uint8_t heatPmp; // Circulating pump on/off + uint8_t wWHeat; // 3-way valve on WW + uint8_t wWCirc; // Circulation on/off + uint8_t selBurnPow; // Burner max power + uint8_t curBurnPow; // Burner current power + uint16_t flameCurr; // Flame current in micro amps + uint8_t sysPress; // System pressure + char serviceCodeChar[3]; // 2 character status/service code + uint16_t serviceCode; // error/service code // UBAMonitorSlow - float extTemp; // Outside temperature - float boilTemp; // Boiler temperature + int16_t extTemp; // Outside temperature + int16_t boilTemp; // Boiler temperature uint8_t pumpMod; // Pump modulation - uint32_t burnStarts; // # burner restarts + uint32_t burnStarts; // # burner starts uint32_t burnWorkMin; // Total burner operating time uint32_t heatWorkMin; // Total heat operating time // UBAMonitorWWMessage - float wWCurTmp; // Warm Water current temperature: + int16_t wWCurTmp; // Warm Water current temperature: uint32_t wWStarts; // Warm Water # starts uint32_t wWWorkM; // Warm Water # minutes uint8_t wWOneTime; // Warm Water one time function on/off @@ -206,6 +222,18 @@ typedef struct { // UBAParameterWW uint8_t product_id; } _EMS_Boiler; +/* + * Telegram package defintions for Other EMS devices + */ +typedef struct { + // SM10 Solar Module - SM10Monitor + bool SM10; // set true if there is a SM10 available + int16_t SM10collectorTemp; // collector temp from SM10 + int16_t SM10bottomTemp; // bottom temp from SM10 + uint8_t SM10pumpModulation; // modulation solar pump + uint8_t SM10pump; // pump active +} _EMS_Other; + // Thermostat data typedef struct { uint8_t type_id; // the type ID of the thermostat @@ -214,8 +242,8 @@ typedef struct { bool read_supported; bool write_supported; char version[10]; - float setpoint_roomTemp; // current set temp - float curr_roomTemp; // current room temp + int16_t setpoint_roomTemp; // current set temp + int16_t curr_roomTemp; // current room temp uint8_t mode; // 0=low, 1=manual, 2=auto bool day_mode; // 0=night, 1=day uint8_t hour; @@ -227,7 +255,7 @@ typedef struct { } _EMS_Thermostat; // call back function signature for processing telegram types -typedef void (*EMS_processType_cb)(uint8_t type, uint8_t * data, uint8_t length); +typedef void (*EMS_processType_cb)(uint8_t src, uint8_t * data, uint8_t length); // Definition for each EMS type, including the relative callback function typedef struct { @@ -249,15 +277,16 @@ void ems_setWarmWaterTemp(uint8_t temperature); void ems_setWarmWaterActivated(bool activated); void ems_setWarmTapWaterActivated(bool activated); void ems_setPoll(bool b); -void ems_setTxEnabled(bool b); void ems_setLogging(_EMS_SYS_LOGGING loglevel); void ems_setEmsRefreshed(bool b); -void ems_setWarmWaterModeComfort(bool comfort); +void ems_setWarmWaterModeComfort(uint8_t comfort); bool ems_checkEMSBUSAlive(); void ems_setModels(); +void ems_setTxDisabled(bool b); void ems_getThermostatValues(); void ems_getBoilerValues(); +void ems_getOtherValues(); bool ems_getPoll(); bool ems_getTxEnabled(); bool ems_getThermostatEnabled(); @@ -275,17 +304,21 @@ char * ems_getThermostatDescription(char * buffer); void ems_printTxQueue(); char * ems_getBoilerDescription(char * buffer); +void ems_startupTelegrams(); + // private functions uint8_t _crcCalculator(uint8_t * data, uint8_t len); -void _processType(uint8_t * telegram, uint8_t length); -void _debugPrintPackage(const char * prefix, uint8_t * data, uint8_t len, const char * color); +void _processType(_EMS_RxTelegram * EMS_RxTelegram); +void _debugPrintPackage(const char * prefix, _EMS_RxTelegram * EMS_RxTelegram, const char * color); void _ems_clearTxData(); int _ems_findBoilerModel(uint8_t model_id); bool _ems_setModel(uint8_t model_id); void _ems_setThermostatModel(uint8_t thermostat_modelid); void _removeTxQueue(); +void _ems_readTelegram(uint8_t * telegram, uint8_t length); // global so can referenced in other classes extern _EMS_Sys_Status EMS_Sys_Status; extern _EMS_Boiler EMS_Boiler; extern _EMS_Thermostat EMS_Thermostat; +extern _EMS_Other EMS_Other; diff --git a/src/ems_devices.h b/src/ems_devices.h index 4ac8ab238..e959895a0 100644 --- a/src/ems_devices.h +++ b/src/ems_devices.h @@ -32,11 +32,15 @@ #define EMS_TYPE_UBASetPoints 0x1A #define EMS_TYPE_UBAFunctionTest 0x1D -#define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature -#define EMS_OFFSET_UBAParameterWW_wwactivated 1 // WW Activated -#define EMS_OFFSET_UBAParameterWW_wwComfort 9 // WW is in comfort or eco mode -#define EMS_VALUE_UBAParameterWW_wwComfort_Comfort 0x00 // the value for comfort -#define EMS_VALUE_UBAParameterWW_wwComfort_Eco 0xD8 // the value for eco +#define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature +#define EMS_OFFSET_UBAParameterWW_wwactivated 1 // WW Activated +#define EMS_OFFSET_UBAParameterWW_wwComfort 9 // WW is in comfort or eco mode +#define EMS_VALUE_UBAParameterWW_wwComfort_Hot 0x00 // the value for hot +#define EMS_VALUE_UBAParameterWW_wwComfort_Eco 0xD8 // the value for eco +#define EMS_VALUE_UBAParameterWW_wwComfort_Intelligent 0xEC // the value for intelligent + +// Other +#define EMS_TYPE_SM10Monitor 0x97 // SM10Monitor /* * Thermostats... @@ -92,7 +96,10 @@ typedef enum { // generic ID for the boiler EMS_MODEL_UBA, - // thermostats + // generic ID for all the other weird devices + EMS_MODEL_OTHER, + + // and finaly the thermostats EMS_MODEL_ES73, EMS_MODEL_RC10, EMS_MODEL_RC20, @@ -111,18 +118,26 @@ typedef enum { // format is MODEL_ID, PRODUCT ID, TYPE_ID, DESCRIPTION const _Boiler_Type Boiler_Types[] = { - {EMS_MODEL_UBA, 72, 0x08, "MC10"}, + {EMS_MODEL_UBA, 72, 0x08, "MC10 Module"}, {EMS_MODEL_UBA, 123, 0x08, "Buderus GB172/Nefit Trendline"}, {EMS_MODEL_UBA, 115, 0x08, "Nefit Topline Compact"}, {EMS_MODEL_UBA, 203, 0x08, "Buderus Logamax U122"}, {EMS_MODEL_UBA, 64, 0x08, "Sieger BK15 Boiler/Nefit Smartline"}, - {EMS_MODEL_UBA, 190, 0x09, "BC10 Base Controller"}, - {EMS_MODEL_UBA, 114, 0x09, "BC10 Base Controller"}, - {EMS_MODEL_UBA, 125, 0x09, "BC25 Base Controller"}, - {EMS_MODEL_UBA, 68, 0x09, "RFM20 Receiver"}, - {EMS_MODEL_UBA, 95, 0x08, "Bosch Condens 2500"}, - {EMS_MODEL_UBA, 251, 0x21, "MM10 Mixer Module"}, // warning, fake product id! - {EMS_MODEL_UBA, 250, 0x11, "WM10 Switch Module"}, // warning, fake product id! + {EMS_MODEL_UBA, 95, 0x08, "Bosch Condens 2500"} + +}; + +// Other EMS devices which are not considered boilers or thermostats +const _Other_Type Other_Types[] = { + + {EMS_MODEL_OTHER, 251, 0x21, "MM10 Mixer Module"}, // warning, fake product id! + {EMS_MODEL_OTHER, 250, 0x11, "WM10 Switch Module"}, // warning, fake product id! + {EMS_MODEL_OTHER, 68, 0x09, "RFM20 Receiver"}, + {EMS_MODEL_OTHER, 190, 0x09, "BC10 Base Controller"}, + {EMS_MODEL_OTHER, 114, 0x09, "BC10 Base Controller"}, + {EMS_MODEL_OTHER, 125, 0x09, "BC25 Base Controller"}, + {EMS_MODEL_OTHER, 205, 0x02, "Nefit Moduline Easy Connect"}, + {EMS_MODEL_OTHER, 73, EMS_ID_SM10, "SM10 Solar Module"} }; @@ -132,15 +147,16 @@ const _Boiler_Type Boiler_Types[] = { const _Thermostat_Type Thermostat_Types[] = { {EMS_MODEL_ES73, 76, 0x10, "Sieger ES73", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}, - {EMS_MODEL_RC10, 79, 0x17, "RC10/Nefit Moduline 100)", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}, - {EMS_MODEL_RC20, 77, 0x17, "RC20/Nefit Moduline 300)", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}, + {EMS_MODEL_RC10, 79, 0x17, "RC10/Nefit Moduline 100", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}, + {EMS_MODEL_RC20, 77, 0x17, "RC20/Nefit Moduline 300", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}, {EMS_MODEL_RC20F, 93, 0x18, "RC20F", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}, - {EMS_MODEL_RC30, 78, 0x10, "RC30/Nefit Moduline 400)", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}, + {EMS_MODEL_RC30, 78, 0x10, "RC30/Nefit Moduline 400", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}, {EMS_MODEL_RC35, 86, 0x10, "RC35", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}, {EMS_MODEL_EASY, 202, 0x18, "TC100/Nefit Easy", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_BOSCHEASY, 206, 0x02, "Bosch Easy", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_RC310, 158, 0x10, "RC310", EMS_THERMOSTAT_READ_NO, EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_CW100, 255, 0x18, "Bosch CW100", EMS_THERMOSTAT_READ_NO, EMS_THERMOSTAT_WRITE_NO}, - {EMS_MODEL_OT, 171, 0x02, "EMS-OT OpenTherm converter", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES} + {EMS_MODEL_OT, 171, 0x02, "EMS-OT OpenTherm converter", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}, + {EMS_MODEL_RC10, 165, 0x02, "RC10/Nefit Moduline 1010", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES} }; diff --git a/src/emsuart.cpp b/src/emsuart.cpp index 454ecb0fd..65b479b0b 100644 --- a/src/emsuart.cpp +++ b/src/emsuart.cpp @@ -24,8 +24,8 @@ os_event_t recvTaskQueue[EMSUART_recvTaskQueueLen]; // our Rx queue // Important: do not use ICACHE_FLASH_ATTR ! // static void emsuart_rx_intr_handler(void * para) { - static uint16_t length; - static uint8_t uart_buffer[EMS_MAXBUFFERSIZE]; + static uint8_t length; + static uint8_t uart_buffer[EMS_MAXBUFFERSIZE]; // is a new buffer? if so init the thing for a new telegram if (EMS_Sys_Status.emsRxStatus == EMS_RX_STATUS_IDLE) { @@ -67,18 +67,13 @@ static void emsuart_rx_intr_handler(void * para) { /* * system task triggered on BRK interrupt - * Read commands are all asynchronous - * When a buffer is full it is sent to the ems_parseTelegram() function in ems.cpp. This is the hook + * incoming received messages are always asynchronous + * The full buffer is sent to the ems_parseTelegram() function in ems.cpp. */ static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events) { - // get next free EMS Receive buffer _EMSRxBuf * pCurrent = pEMSRxBuf; - pEMSRxBuf = paEMSRxBuf[++emsRxBufIdx % EMS_MAXBUFFERS]; - - // transmit EMS buffer, excluding the BRK - if (pCurrent->writePtr > 1) { - ems_parseTelegram((uint8_t *)pCurrent->buffer, (pCurrent->writePtr) - 1); - } + ems_parseTelegram((uint8_t *)pCurrent->buffer, (pCurrent->writePtr) - 1); // transmit EMS buffer, excluding the BRK + pEMSRxBuf = paEMSRxBuf[++emsRxBufIdx % EMS_MAXBUFFERS]; // next free EMS Receive buffer } /* @@ -97,12 +92,12 @@ void ICACHE_FLASH_ATTR emsuart_init() { // pin settings PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U); - PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0RXD); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD); PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0RXD_U); PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD); // set 9600, 8 bits, no parity check, 1 stop bit - USD(EMSUART_UART) = (ESP8266_CLOCK / EMSUART_BAUD); + USD(EMSUART_UART) = (UART_CLK_FREQ / EMSUART_BAUD); USC0(EMSUART_UART) = EMSUART_CONFIG; // 8N1 // flush everything left over in buffer, this clears both rx and tx FIFOs @@ -129,13 +124,13 @@ void ICACHE_FLASH_ATTR emsuart_init() { system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen); // disable esp debug which will go to Tx and mess up the line - // system_set_os_print(0); // https://github.com/espruino/Espruino/issues/655 - - ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, NULL); - ETS_UART_INTR_ENABLE(); + system_set_os_print(0); // https://github.com/espruino/Espruino/issues/655 // swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively system_uart_swap(); + + ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, NULL); + ETS_UART_INTR_ENABLE(); } /* @@ -143,7 +138,17 @@ void ICACHE_FLASH_ATTR emsuart_init() { */ void ICACHE_FLASH_ATTR emsuart_stop() { ETS_UART_INTR_DISABLE(); - ETS_UART_INTR_ATTACH(NULL, NULL); + //ETS_UART_INTR_ATTACH(NULL, NULL); + //system_uart_swap(); // to be sure, swap Tx/Rx back. + //detachInterrupt(digitalPinToInterrupt(D7)); + //noInterrupts(); +} + +/* + * re-start UART0 driver + */ +void ICACHE_FLASH_ATTR emsuart_start() { + ETS_UART_INTR_ENABLE(); } /* diff --git a/src/emsuart.h b/src/emsuart.h index f5053bda3..e1fc9498c 100644 --- a/src/emsuart.h +++ b/src/emsuart.h @@ -10,15 +10,15 @@ #include #define EMSUART_UART 0 // UART 0 -#define EMSUART_CONFIG 0x1c // 8N1 (8 bits, no stop bits, 1 parity) +#define EMSUART_CONFIG 0x1C // 8N1 (8 bits, no stop bits, 1 parity) #define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit -#define EMS_MAXBUFFERS 4 // 4 buffers for circular filling to avoid collisions -#define EMS_MAXBUFFERSIZE 128 // max size of the buffer. packets are max 32 bytes +#define EMS_MAXBUFFERS 10 // 4 buffers for circular filling to avoid collisions +#define EMS_MAXBUFFERSIZE 32 // max size of the buffer. packets are max 32 bytes -// this is how long we drop the Tx signal to create a 11-bit Break of zeros +// this is how long we drop the Tx signal to create a 11-bit Break of zeros (BRK) // At 9600 baud, 11 bits will be 1144 microseconds -// the BRK from Boiler is roughly 1.039ms, so accounting for hardware lag using around 2078 (for half-duplex) - 8 (lag) +// the BRK from Boiler master is roughly 1.039ms, so accounting for hardware lag using around 2078 (for half-duplex) - 8 (lag) #define EMS_TX_BRK_WAIT 2070 #define EMSUART_recvTaskPrio 1 @@ -31,6 +31,7 @@ typedef struct { void ICACHE_FLASH_ATTR emsuart_init(); void ICACHE_FLASH_ATTR emsuart_stop(); +void ICACHE_FLASH_ATTR emsuart_start(); void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len); void ICACHE_FLASH_ATTR emsaurt_tx_poll(); void ICACHE_FLASH_ATTR emsuart_tx_brk(); diff --git a/src/my_config.h b/src/my_config.h index 2c23d611b..8f55c891f 100644 --- a/src/my_config.h +++ b/src/my_config.h @@ -37,6 +37,14 @@ #define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on #define TOPIC_BOILER_WWACTIVATED "wwactivated" // for receiving MQTT message to change water on/off #define TOPIC_BOILER_CMD_WWTEMP "boiler_cmd_wwtemp" // for received boiler wwtemp changes via MQTT +#define TOPIC_BOILER_CMD_COMFORT "boiler_cmd_comfort" // for received boiler ww comfort setting via MQTT + +// MQTT for SM10 Solar Module +#define TOPIC_SM10_DATA "sm10_data" // topic name +#define SM10_COLLECTORTEMP "temp" // collector temp +#define SM10_BOTTOMTEMP "bottomtemp" // bottom temp +#define SM10_PUMPMODULATION "pumpmodulation" // pump modulation +#define SM10_PUMP "pump" // pump active // shower time #define TOPIC_SHOWERTIME "showertime" // for sending shower time results @@ -44,28 +52,26 @@ #define TOPIC_SHOWER_ALERT "shower_alert" // toggle switch for enabling the shower alarm logic #define TOPIC_SHOWER_COLDSHOT "shower_coldshot" // used to trigger a coldshot from an MQTT command -// default values for shower logic on/off -#define BOILER_SHOWER_TIMER 1 // enable (1) to monitor shower time -#define BOILER_SHOWER_ALERT 0 // enable (1) to send alert of cold water when shower time limit has exceeded -#define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water +// MQTT for EXTERNAL SENSORS +#define TOPIC_EXTERNAL_SENSORS "sensors" // for sending sensor values to MQTT +#define PAYLOAD_EXTERNAL_SENSORS "temp_%d" // for formatting the payload for each external dallas sensor + //////////////////////////////////////////////////////////////////////////////////////////////////// // THESE DEFAULT VALUES CAN ALSO BE SET AND STORED WITHTIN THE APPLICATION (see 'set' command) // -// ALTHOUGH YOU MAY ALSO HARDCODE THEM HERE BUT THEY WILL BE OVERWRITTEN WITH NEW RELEASE UPDATES // //////////////////////////////////////////////////////////////////////////////////////////////////// -// Set LED pin used for showing ems bus connection status. Solid is connected, Flashing is error -// can be either the onboard LED on the ESP8266 (LED_BULLETIN) or external via an external pull-up LED -// (e.g. D1 on a bbqkees' board -// can be enabled and disabled via the 'set led' -// pin can be set by 'set led_gpio' +// Set LED pin used for showing the EMS bus connection status. Solid means EMS bus working, flashing is an error +// can be either the onboard LED on the ESP8266 (LED_BULLETIN) or external via an external pull-up LED (e.g. D1 on a bbqkees' board) +// can be enabled and disabled via the 'set led' command and pin set by 'set led_gpio' #define EMSESP_LED_GPIO LED_BUILTIN // set this if using an external temperature sensor like a DS18B20 -// D5 is the default on bbqkees' board +// D5 is the default on a bbqkees board #define EMSESP_DALLAS_GPIO D5 +#define EMSESP_DALLAS_PARASITE false -// By default the EMS bus will be scanned for known devices based on product ids in ems_devices.h +// By default the EMS bus will be scanned for known devices based on the product ids in ems_devices.h // You can override the Thermostat and Boiler types here #define EMSESP_BOILER_TYPE EMS_ID_NONE #define EMSESP_THERMOSTAT_TYPE EMS_ID_NONE diff --git a/src/version.h b/src/version.h index c078e0432..d4e4d8391 100644 --- a/src/version.h +++ b/src/version.h @@ -6,5 +6,5 @@ #pragma once #define APP_NAME "EMS-ESP" -#define APP_VERSION "1.5.6" +#define APP_VERSION "1.6.0" #define APP_HOSTNAME "ems-esp"