Merged everything

This commit is contained in:
Glenn Arens
2019-03-20 20:23:34 +01:00
14 changed files with 2070 additions and 65 deletions

View File

@@ -47,11 +47,11 @@ The original intention for this home project was to build a custom smart thermos
Acknowledgments and kudos to the following people who have open-sourced their projects: Acknowledgments and kudos to the following people who have open-sourced their projects:
**susisstrolch** - One of the first working versions of the EMS bridge circuit I found designed for specifically for the ESP8266. I borrowed Juergen's [schematic](https://github.com/susisstrolch/EMS-ESP12) and parts of his code ideas for reading telegrams. **susisstrolch** - One of the first working versions of the EMS bridge circuit I found designed for specifically for the ESP8266. I borrowed Juergen's [schematic](https://github.com/susisstrolch/EMS-ESP12) and parts of his code ideas for reading telegrams.
**bbqkees** - Kees built a working [circuit](https://shop.hotgoodies.nl/ems/) and his SMD board is available for purchase on his website. **bbqkees** - Kees built a working [circuit](https://shop.hotgoodies.nl/ems/) and his SMD board is available for purchase on his website.
**EMS Wiki** - A comprehensive [reference](https://emswiki.thefischer.net/doku.php?id=wiki:ems:telegramme) (in German) for the EMS bus which is a little outdated, not always 100% accurate and sadly no longer maintained. **EMS Wiki** - A comprehensive [reference](https://emswiki.thefischer.net/doku.php?id=wiki:ems:telegramme) (in German) for the EMS bus which is a little outdated, not always 100% accurate and sadly no longer maintained.
## Supported EMS Devices ## Supported EMS Devices
@@ -70,25 +70,26 @@ The code and circuit has been tested with a few ESP8266 development boards such
3. Optionally add external Dallas temperature sensors and an external LED. The default pins for these are D1 and D5 respectively. 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. 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. 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. 6. 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.
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). 7. 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. 8. Next is to change some of the settings. Type `set` to list the current stored settings. Use `set wifi` to add your wifi credentials and if you're using MQTT set the host, username and password. There is no need to reboot the device.
10. The `led_gpio` will default to the onboard LED (which is probably blinking now). Ignore `thermostat_type` and `boiler_type` as these will be auto-detected hopefully later on. 9. 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. 10. **Important**: If `serial` is set to `on` set it to `off` using `set serial off`. The EMS bus is disabled when the serial is on. This mode is only used for setting up a new board or debugging startup issues.
12. Hook up the ESP to the EMS board as follows: 11. Hook up the ESP to the EMS board as follows:
| EMS board | ESP8266 dev board | | EMS board | ESP8266 dev board |
| ----------|------------------ | | ----------- | ----------------- |
| Ground/G/J2| GND/G | | Ground/G/J2 | GND/G |
| Rx/J2 | D7 | | Rx/J2 | D7 |
| Tx/J2 | D8 | | Tx/J2 | D8 |
| VC/J2 | 3v3 or 5v | | VC/J2 | 3v3 or 5v |
13. Connect the EMS lines to the ESP. This can be done via the two EMS wires or via the 3.5" service jack if you have an bbqkees board.
14. Reboot the ESP, either by the reset switch or pulling the power. 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.
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. 14. Reboot the ESP, either by the reset switch or pulling the power.
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 type `info` to check what happened.
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. 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.
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 17. If all is well and there is traffic on the EMS bus the onboard LED will stop blinking and be permanently on. If this is annoying you can disable with `set led off`. To see the EMS messages type `set log v` for verbose logging.
18. And all is not well, check the wiring, make sure serial is off and look at the telnet session for errors. If in doubt, wipe the ESP with `pio run -t erase` and start again with step #3
## Monitoring The Output ## Monitoring The Output
@@ -118,7 +119,7 @@ The schematic used:
![Schematic](doc/schematics/circuit.png) ![Schematic](doc/schematics/circuit.png)
*Optionally I've also added 2 0.5A/72V polyfuses between the EMS and the two inductors L1 and L2 for extra protection.* _Optionally I've also added 2 0.5A/72V polyfuses between the EMS and the two inductors L1 and L2 for extra protection._
And here's a version using an early prototype board from **bbqkees**: And here's a version using an early prototype board from **bbqkees**:
@@ -133,8 +134,8 @@ The EMS circuit will work with both 3.3V and 5V. It's easiest though to power di
- 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 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 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.
| With Power Circuit | | With Power Circuit |
| ------------------------------------------ | | --------------------------------------------------------------- |
| ![Power circuit](doc/schematics/Schematic_EMS-ESP-supercap.png) | | ![Power circuit](doc/schematics/Schematic_EMS-ESP-supercap.png) |
## Adding external temperature sensors ## Adding external temperature sensors
@@ -147,9 +148,10 @@ Packages are streamed to the EMS "bus" from any other compatible connected devic
A package can be a single byte (see Polling below) or a string of 6 or more bytes making up an actual data telegram. A telegram is always in the format: A package can be a single byte (see Polling below) or a string of 6 or more bytes making up an actual data telegram. A telegram is always in the format:
``[src] [dest] [type] [offset] [data] [crc] <BRK>`` `[src] [dest] [type] [offset] [data] [crc] <BRK>`
The first 4 bytes is referenced as the _header_ in this document.
The first 4 bytes is referenced as the *header* in this document.
### EMS IDs ### EMS IDs
Each device has a unique ID. Each device has a unique ID.
@@ -200,18 +202,22 @@ Following a write request, the `[dest]` doesn't have the 8th bit set and after t
Every telegram sent is echo'd back to Rx, along the same Bus used for all Rx/Tx transmissions. Every telegram sent is echo'd back to Rx, along the same Bus used for all Rx/Tx transmissions.
## Ems Plus ## Ems Plus
In this chapter we will report our findings on the ems plus. In this chapter we will report our findings on the ems plus.
### Message layout ### Message layout
| 0 | 1 | 2 | 3 | 4 | 5 | n....n-1 | n |
| - | - | - | - | - | - | - | - | | 0 | 1 | 2 | 3 | 4 | 5 | n....n-1 | n |
|transmitter| receiver | ems plus mark | message type | offset | device intended for | data | cnc | ----------- | -------- | ------------- | ------------ | ------ | ------------------- | -------- | --- |
| 18 | 00 | FF | 03 | 01 | A5 | 28 | 46| | transmitter | receiver | ems plus mark | message type | offset | device intended for | data | cnc |
| 18 | 00 | FF | 03 | 01 | A5 | 28 | 46 |
### Message types ### Message types
| Message type | Definition |
|--------------|---------------------| | Message type | Definition |
| 03 | Set temperature | | ------------ | --------------- |
| 00 | Status message | | 03 | Set temperature |
| 00 | Status message |
## The ESP8266 Source Code ## The ESP8266 Source Code
@@ -238,7 +244,7 @@ In this chapter we will report our findings on the ems plus.
| Boiler (0x08) | 0x15 | UBAMaintenanceSettingsMessage | | | Boiler (0x08) | 0x15 | UBAMaintenanceSettingsMessage | |
| Boiler (0x08) | 0x16 | UBAParametersMessage | | | Boiler (0x08) | 0x16 | UBAParametersMessage | |
In `ems.cpp` you can add scheduled calls to specific EMS types in the functions `ems_getThermostatValues()` and `ems_getBoilerValues()`. In `ems.cpp` you can add scheduled calls to specific EMS types in the functions `ems_getThermostatValues()` and `ems_getBoilerValues()`.
### Which thermostats are supported? ### Which thermostats are supported?
@@ -314,6 +320,7 @@ You can find the .yaml configuration files under `doc/ha`. See also this [HA for
**On Linux (e.g. Ubuntu under Windows 10):** **On Linux (e.g. Ubuntu under Windows 10):**
Make sure Python 2.7 is installed, then... Make sure Python 2.7 is installed, then...
```python ```python
% pip install -U platformio % pip install -U platformio
% sudo platformio upgrade % sudo platformio upgrade
@@ -323,7 +330,9 @@ Make sure Python 2.7 is installed, then...
% cd EMS-ESP % cd EMS-ESP
% cp platformio.ini-example platformio.ini % cp platformio.ini-example platformio.ini
``` ```
edit `platformio.ini` to set `env_default` to your board type, then edit `platformio.ini` to set `env_default` to your board type, then
```c ```c
% platformio run -t upload % platformio run -t upload
``` ```

26
checkcode.py Normal file
View File

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

14
clean_fw.py Normal file
View File

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

22
debug.py Normal file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python
from subprocess import call
import os
# python decoder.py -p ESP8266 -t C:\Users\Paul\.platformio\packages\toolchain-xtensa -e .pioenvs/nodemcuv2/firmware.elf stackdmp.txt
# java -jar .\EspStackTraceDecoder.jar C:\Users\Paul\.platformio\packages\toolchain-xtensa\bin\xtensa-lx106-elf-addr2line.exe .pioenvs/nodemcuv2/firmware.elf stackdmp.txt
# 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
# <<<stack<<<
call(['python', 'decoder.py ', '-s', '-e', os.getcwd()+"/.pioenvs/d1_mini/firmware_d1_mini.elf", 'stackdmp.txt'])

307
decoder.py Normal file
View File

@@ -0,0 +1,307 @@
#!/usr/bin/env python3
"""ESP Exception Decoder
github: https://github.com/janLo/EspArduinoExceptionDecoder
license: GPL v3
author: Jan Losinski
"""
import argparse
import re
import subprocess
from collections import namedtuple
import sys
import os
EXCEPTIONS = [
"Illegal instruction",
"SYSCALL instruction",
"InstructionFetchError: Processor internal physical address or data error during instruction fetch",
"LoadStoreError: Processor internal physical address or data error during load or store",
"Level1Interrupt: Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register",
"Alloca: MOVSP instruction, if caller's registers are not in the register file",
"IntegerDivideByZero: QUOS, QUOU, REMS, or REMU divisor operand is zero",
"reserved",
"Privileged: Attempt to execute a privileged operation when CRING ? 0",
"LoadStoreAlignmentCause: Load or store to an unaligned address",
"reserved",
"reserved",
"InstrPIFDataError: PIF data error during instruction fetch",
"LoadStorePIFDataError: Synchronous PIF data error during LoadStore access",
"InstrPIFAddrError: PIF address error during instruction fetch",
"LoadStorePIFAddrError: Synchronous PIF address error during LoadStore access",
"InstTLBMiss: Error during Instruction TLB refill",
"InstTLBMultiHit: Multiple instruction TLB entries matched",
"InstFetchPrivilege: An instruction fetch referenced a virtual address at a ring level less than CRING",
"reserved",
"InstFetchProhibited: An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch",
"reserved",
"reserved",
"reserved",
"LoadStoreTLBMiss: Error during TLB refill for a load or store",
"LoadStoreTLBMultiHit: Multiple TLB entries matched for a load or store",
"LoadStorePrivilege: A load or store referenced a virtual address at a ring level less than CRING",
"reserved",
"LoadProhibited: A load referenced a page mapped with an attribute that does not permit loads",
"StoreProhibited: A store referenced a page mapped with an attribute that does not permit stores"
]
PLATFORMS = {
"ESP8266": "lx106",
"ESP32": "esp32"
}
EXCEPTION_REGEX = re.compile("^Exception \\((?P<exc>[0-9]*)\\):$")
COUNTER_REGEX = re.compile('^epc1=(?P<epc1>0x[0-9a-f]+) epc2=(?P<epc2>0x[0-9a-f]+) epc3=(?P<epc3>0x[0-9a-f]+) '
'excvaddr=(?P<excvaddr>0x[0-9a-f]+) depc=(?P<depc>0x[0-9a-f]+)$')
CTX_REGEX = re.compile("^ctx: (?P<ctx>.+)$")
POINTER_REGEX = re.compile('^sp: (?P<sp>[0-9a-f]+) end: (?P<end>[0-9a-f]+) offset: (?P<offset>[0-9a-f]+)$')
STACK_BEGIN = '>>>stack>>>'
STACK_END = '<<<stack<<<'
STACK_REGEX = re.compile(
'^(?P<off>[0-9a-f]+):\W+(?P<c1>[0-9a-f]+) (?P<c2>[0-9a-f]+) (?P<c3>[0-9a-f]+) (?P<c4>[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<addr>[0-9a-fx]+): (?P<result>.+)$")
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)

1331
lib/MyESP/MyESP.cpp Normal file

File diff suppressed because it is too large Load Diff

254
lib/MyESP/MyESP.h Normal file
View File

@@ -0,0 +1,254 @@
/*
* MyESP.h
*
* Paul Derbyshire - December 2018
*/
#pragma once
#ifndef MyEMS_h
#define MyEMS_h
#define MYESP_VERSION "1.1.6b"
#include <ArduinoJson.h>
#include <ArduinoOTA.h>
#include <AsyncMqttClient.h> // https://github.com/marvinroger/async-mqtt-client and for ESP32 see https://github.com/marvinroger/async-mqtt-client/issues/127
#include <DNSServer.h>
#include <FS.h>
#include <JustWifi.h> // https://github.com/xoseperez/justwifi
#include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy
#include "EEPROM.h"
extern "C" {
void custom_crash_callback(struct rst_info*, uint32_t, uint32_t);
}
#if defined(ARDUINO_ARCH_ESP32)
//#include <ESPmDNS.h>
#include <SPIFFS.h> // added for ESP32
#define ets_vsnprintf vsnprintf // added for ESP32
#define OTA_PORT 8266
#else
//#include <ESP8266mDNS.h>
#include <ESPAsyncTCP.h>
#define OTA_PORT 3232
#endif
#define MYEMS_CONFIG_FILE "/config.json"
#define LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms)
// WIFI
#define WIFI_CONNECT_TIMEOUT 10000 // Connecting timeout for WIFI in ms
#define WIFI_RECONNECT_INTERVAL 60000 // If could not connect to WIFI, retry after this time in ms
// MQTT
#define MQTT_PORT 1883 // MQTT port
#define MQTT_RECONNECT_DELAY_MIN 2000 // Try to reconnect in 3 seconds upon disconnection
#define MQTT_RECONNECT_DELAY_STEP 3000 // Increase the reconnect delay in 3 seconds after each failed attempt
#define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most
#define MQTT_MAX_SIZE 600 // max length of MQTT message
#define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT message
// Internal MQTT events
#define MQTT_CONNECT_EVENT 0
#define MQTT_DISCONNECT_EVENT 1
#define MQTT_MESSAGE_EVENT 2
// Telnet
#define TELNET_SERIAL_BAUD 115200
#define TELNET_MAX_COMMAND_LENGTH 80 // length of a command
#define TELNET_EVENT_CONNECT 1
#define TELNET_EVENT_DISCONNECT 0
// ANSI Colors
#define COLOR_RESET "\x1B[0m"
#define COLOR_BLACK "\x1B[0;30m"
#define COLOR_RED "\x1B[0;31m"
#define COLOR_GREEN "\x1B[0;32m"
#define COLOR_YELLOW "\x1B[0;33m"
#define COLOR_BLUE "\x1B[0;34m"
#define COLOR_MAGENTA "\x1B[0;35m"
#define COLOR_CYAN "\x1B[0;36m"
#define COLOR_WHITE "\x1B[0;37m"
#define COLOR_BOLD_ON "\x1B[1m"
#define COLOR_BOLD_OFF "\x1B[22m" // fixed by Scott Arlott
// SPIFFS
#define SPIFFS_MAXSIZE 500 // https://arduinojson.org/v5/assistant/
// CRASH
#define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data
#define SAVE_CRASH_EEPROM_SIZE 0x0200 // size
#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];
char description[100];
} command_t;
typedef enum { MYESP_FSACTION_SET, MYESP_FSACTION_LIST, MYESP_FSACTION_SAVE, MYESP_FSACTION_LOAD } MYESP_FSACTION;
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
typedef std::function<void()> wifi_callback_f;
typedef std::function<void()> ota_callback_f;
typedef std::function<void(uint8_t, const char *)> telnetcommand_callback_f;
typedef std::function<void(uint8_t)> telnet_callback_f;
typedef std::function<bool(MYESP_FSACTION, const JsonObject json)> fs_callback_f;
typedef std::function<bool(MYESP_FSACTION, uint8_t, const char *, const char *)> fs_settings_callback_f;
// calculates size of an 2d array at compile time
template <typename T, size_t N>
constexpr size_t ArraySize(T (&)[N]) {
return N;
}
// class definition
class MyESP {
public:
MyESP();
~MyESP();
// wifi
void setWIFICallback(void (*callback)());
void setWIFI(const char * wifi_ssid, const char * wifi_password, wifi_callback_f callback);
bool isWifiConnected();
// mqtt
bool isMQTTConnected();
void mqttSubscribe(const char * topic);
void mqttUnsubscribe(const char * topic);
void mqttPublish(const char * topic, const char * payload);
void setMQTT(const char * mqtt_host,
const char * mqtt_username,
const char * mqtt_password,
const char * mqtt_base,
unsigned long mqtt_keepalive,
unsigned char mqtt_qos,
bool mqtt_retain,
const char * mqtt_will_topic,
const char * mqtt_will_online_payload,
const char * mqtt_will_offline_payload,
mqtt_callback_f callback);
// OTA
void setOTA(ota_callback_f OTACallback);
// debug & telnet
void myDebug(const char * format, ...);
void myDebug_P(PGM_P format_P, ...);
void setTelnet(command_t * cmds, uint8_t count, telnetcommand_callback_f callback_cmd, telnet_callback_f callback);
bool getUseSerial();
// FS
void setSettings(fs_callback_f callback, fs_settings_callback_f fs_settings_callback);
bool fs_saveConfig();
// CRASH
void crashClear();
void crashDump();
void crashTest(uint8_t t);
// general
void end();
void loop();
void begin(const char * app_hostname, const char * app_name, const char * app_version);
void setBoottime(const char * boottime);
void resetESP();
uint16_t getSystemLoadAverage();
int getWifiQuality();
private:
// mqtt
AsyncMqttClient mqttClient;
unsigned long _mqtt_reconnect_delay;
void _mqttOnMessage(char * topic, char * payload, size_t len);
void _mqttConnect();
void _mqtt_setup();
mqtt_callback_f _mqtt_callback;
void _mqttOnConnect();
void _sendStart();
char * _mqttTopic(const char * topic);
char * _mqtt_host;
char * _mqtt_username;
char * _mqtt_password;
char * _mqtt_base;
unsigned long _mqtt_keepalive;
unsigned char _mqtt_qos;
bool _mqtt_retain;
char * _mqtt_will_topic;
char * _mqtt_will_online_payload;
char * _mqtt_will_offline_payload;
char * _mqtt_topic;
unsigned long _mqtt_last_connection;
bool _mqtt_connecting;
// wifi
DNSServer dnsServer; // For Access Point (AP) support
void _wifiCallback(justwifi_messages_t code, char * parameter);
void _wifi_setup();
wifi_callback_f _wifi_callback;
char * _wifi_ssid;
char * _wifi_password;
bool _wifi_connected;
// ota
ota_callback_f _ota_callback;
void _ota_setup();
void _OTACallback();
// telnet & debug
TelnetSpy SerialAndTelnet;
void _telnetConnected();
void _telnetDisconnected();
void _telnetHandle();
void _telnetCommand(char * commandLine);
char * _telnet_readWord();
void _telnet_setup();
char _command[TELNET_MAX_COMMAND_LENGTH]; // the input command from either Serial or Telnet
command_t * _helpProjectCmds; // Help of commands setted by project
uint8_t _helpProjectCmds_count; // # available commands
void _consoleShowHelp();
telnetcommand_callback_f _telnetcommand_callback; // Callable for projects commands
telnet_callback_f _telnet_callback; // callback for connect/disconnect
void _changeSetting(uint8_t wc, const char * setting, const char * value);
void _changeSetting2(const char * setting, const char * value1, const char * value2);
// fs
void _fs_setup();
bool _fs_loadConfig();
void _fs_printConfig();
void _fs_eraseConfig();
fs_callback_f _fs_callback;
fs_settings_callback_f _fs_settings_callback;
// general
char * _app_hostname;
char * _app_name;
char * _app_version;
char * _boottime;
bool _suspendOutput;
bool _use_serial;
void _printBuildTime(unsigned long rawTime);
// load average (0..100)
void _calculateLoad();
unsigned short int _load_average;
};
extern MyESP myESP;
#endif

View File

@@ -4,8 +4,11 @@ env_default = d1_mini
[common] [common]
platform = espressif8266 platform = espressif8266
flash_mode = dout flash_mode = dout
build_flags = -g -w
;build_flags = -g -w -DBUILD_TIME=$UNIX_TIME build_flags_debug = -ggdb3 -Wall -Wextra -Werror -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-variable
;build_flags_prod = -Os -DBUILD_TIME=$UNIX_TIME
build_flags = ${common.build_flags_debug}
wifi_settings = wifi_settings =
; hard code if you prefer. Recommendation is to set from within the app when in Serial or AP mode ; hard code if you prefer. Recommendation is to set from within the app when in Serial or AP mode
@@ -17,7 +20,6 @@ lib_deps =
JustWifi JustWifi
AsyncMqttClient AsyncMqttClient
ArduinoJson ArduinoJson
; https://github.com/bblanchon/ArduinoJson#v5.13.5
OneWire OneWire
[env:d1_mini] [env:d1_mini]
@@ -32,6 +34,6 @@ monitor_speed = 115200
; for OTA comment out these sections ; for OTA comment out these sections
;upload_protocol = espota ;upload_protocol = espota
;upload_port = ems-esp.local ;upload_port = <the IP address...>

8
rename_fw.py Normal file
View File

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

View File

@@ -993,7 +993,7 @@ void initEMSESP() {
// call PublishValues without forcing, so using CRC to see if we really need to publish // call PublishValues without forcing, so using CRC to see if we really need to publish
void do_publishValues() { void do_publishValues() {
// don't publish if we're not connected to the EMS bus // don't publish if we're not connected to the EMS bus
if ((ems_getBusConnected()) && (!myESP.getUseSerial())) { if ((ems_getBusConnected()) && (!myESP.getUseSerial()) && myESP.isMQTTConnected()) {
publishValues(false); publishValues(false);
} }
} }

View File

@@ -25,6 +25,7 @@ CircularBuffer<_EMS_TxTelegram, EMS_TX_TELEGRAM_QUEUE_MAX> EMS_TxQueue; // FIFO
// generic // generic
void _process_Version(uint8_t type, uint8_t * data, uint8_t length); void _process_Version(uint8_t type, uint8_t * data, uint8_t length);
void _printMessage(uint8_t * telegram, uint8_t length);
// Boiler and Buderus devices // Boiler and Buderus devices
void _process_UBAMonitorFast(uint8_t type, uint8_t * data, uint8_t length); void _process_UBAMonitorFast(uint8_t type, uint8_t * data, uint8_t length);
@@ -61,8 +62,6 @@ void _process_EasyStatusMessage(uint8_t type, uint8_t * data, uint8_t length);
void _process_RC1010StatusMessage(uint8_t type, uint8_t * data, uint8_t length); void _process_RC1010StatusMessage(uint8_t type, uint8_t * data, uint8_t length);
void _process_RC1010SetMessage(uint8_t type, uint8_t * data, uint8_t length); void _process_RC1010SetMessage(uint8_t type, uint8_t * data, uint8_t length);
//debug messages
void _printMessage(uint8_t * telegram, uint8_t length);
/* /*
* Recognized EMS types and the functions they call to process the telegrams * Recognized EMS types and the functions they call to process the telegrams
* Format: MODEL ID, TYPE ID, Description, function * Format: MODEL ID, TYPE ID, Description, function
@@ -247,7 +246,7 @@ void ems_init() {
strlcpy(EMS_Thermostat.version, "not set", sizeof(EMS_Thermostat.version)); strlcpy(EMS_Thermostat.version, "not set", sizeof(EMS_Thermostat.version));
// default logging is none // default logging is none
ems_setLogging(EMS_SYS_LOGGING_THERMOSTAT); ems_setLogging(EMS_SYS_LOGGING_DEFAULT);
} }
// Getters and Setters for parameters // Getters and Setters for parameters
@@ -791,6 +790,40 @@ void _printMessage(uint8_t * telegram, uint8_t length) {
_debugPrintTelegram(output_str, telegram, length, color_s); _debugPrintTelegram(output_str, telegram, length, 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;
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;
}
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 ((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)) {
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);
}
}
}
EMS_Sys_Status.emsTxStatus = EMS_TX_STATUS_IDLE;
} }
/** /**
@@ -1054,10 +1087,8 @@ void _process_RC20StatusMessage(uint8_t type, uint8_t * data, uint8_t length) {
} }
/** /**
* type 0x41 - data from the RC30 thermostat (0x10) - 14 bytes long *type 0x41 - data from the RC30 thermostat(0x10) - 14 bytes long * For reading the temp values only * received every 60 seconds
* For reading the temp values only */
* received every 60 seconds
*/
void _process_RC30StatusMessage(uint8_t type, uint8_t * data, uint8_t length) { 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.setpoint_roomTemp = ((float)data[EMS_TYPE_RC30StatusMessage_setpoint]) / (float)2;
EMS_Thermostat.curr_roomTemp = _toFloat(EMS_TYPE_RC30StatusMessage_curr, data); EMS_Thermostat.curr_roomTemp = _toFloat(EMS_TYPE_RC30StatusMessage_curr, data);
@@ -1090,8 +1121,7 @@ void _process_RC35StatusMessage(uint8_t type, uint8_t * data, uint8_t length) {
void _process_EasyStatusMessage(uint8_t type, uint8_t * data, uint8_t length) { 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.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; EMS_Thermostat.setpoint_roomTemp = ((float)(((data[EMS_TYPE_EasyStatusMessage_setpoint] << 8) + data[11]))) / 100;
EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT
EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT
} }
/** /**
* type 0x00 - data from the Nefit RC1010 thermostat (0x18) - 24 bytes long * type 0x00 - data from the Nefit RC1010 thermostat (0x18) - 24 bytes long
@@ -1580,7 +1610,7 @@ void ems_printAllTypes() {
*/ */
void ems_doReadCommand(uint8_t type, uint8_t dest, bool forceRefresh) { void ems_doReadCommand(uint8_t type, uint8_t dest, bool forceRefresh) {
// if not a valid type of boiler is not accessible then quits // 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; return;
} }

View File

@@ -108,7 +108,8 @@ typedef enum {
EMS_MODEL_BOSCHEASY, EMS_MODEL_BOSCHEASY,
EMS_MODEL_RC310, EMS_MODEL_RC310,
EMS_MODEL_CW100, EMS_MODEL_CW100,
EMS_MODEL_RC1010 EMS_MODEL_RC1010,
EMS_MODEL_OT
} _EMS_MODEL_ID; } _EMS_MODEL_ID;
@@ -147,4 +148,5 @@ const _Thermostat_Type Thermostat_Types[] = {
{EMS_MODEL_RC310, 158, 0x10, "RC310", EMS_THERMOSTAT_READ_NO, 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_CW100, 255, 0x18, "Bosch CW100", EMS_THERMOSTAT_READ_NO, EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_RC1010, 165, 0x18, "RC1010/Nefit Moduline 1010)", EMS_THERMOSTAT_READ_NO, EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_RC1010, 165, 0x18, "RC1010/Nefit Moduline 1010)", 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},
}; };

View File

@@ -5,6 +5,6 @@
#pragma once #pragma once
#define APP_NAME "EMS-ESP" #define APP_NAME "EMS-HEERENVEEN-1"
#define APP_VERSION "1.5.6" #define APP_VERSION "1.5.7b"
#define APP_HOSTNAME "ems-esp" #define APP_HOSTNAME "ems-heerenveen-1"