Merge pull request #78 from gl3nni3/master

Ems plus support for the nefit 1010
This commit is contained in:
Proddy
2019-04-05 12:48:19 +02:00
committed by GitHub
22 changed files with 1984 additions and 432 deletions

35
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,35 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
*Before creating a new issue please check that you have:*
* *searched the existing [issues](https://github.com/proddy/EMS-ESP/issues) (both open and closed)*
* *searched the [doc](https://github.com/proddy/EMS-ESP/blob/master/README.md)*
*Fulfilling this template will help developers and contributors to address the issue. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.*
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the issue (for instance, the screenshots) then you can delete them.*
**Bug description**
*A clear and concise description of what the bug is.*
**Steps to reproduce**
*Steps to reproduce the behavior.*
**Expected behavior**
*A clear and concise description of what you expected to happen.*
**Screenshots**
*If applicable, add screenshots to help explain your problem.*
**Device information**
*Copy-paste here the information as it is outputted by the device. You can get this information by from the telnet session with the logging set to Verbose mode.*
**Additional context**
*Add any other context about the problem here.*

View File

@@ -0,0 +1,26 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
*Before creating a new feature request please check that you have searched the existing [issues](https://github.com/proddy/EMS-ESP/issues) (both open and closed)*
*Fulfilling this template will help developers and contributors evaluating the feature. If the information provided is not enough the issue will likely be closed.*
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the request then you can delete them.*
**Is your feature request related to a problem? Please describe.**
*A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]*
**Describe the solution you'd like**
*A clear and concise description of what you want to happen.*
**Describe alternatives you've considered**
*A clear and concise description of any alternative solutions or features you've considered.*
**Additional context**
*Add any other context or screenshots about the feature request here.*

View File

@@ -0,0 +1,30 @@
---
name: Questions & troubleshooting
about: Anything not a bug or feature request
title: ''
labels: question
assignees: ''
---
*Before creating a new issue please check that you have:*
* *searched the existing [issues](https://github.com/proddy/EMS-ESP/issues) (both open and closed)*
* *searched the [doc](https://github.com/proddy/EMS-ESP/blob/master/README.md)*
*Fulfilling this template will help developers and contributors help you. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.*
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the issue (for instance, the screenshots) then you can delete them.*
**Question**
*A clear and concise description of what the problem/doubt is.*
**Screenshots**
*If applicable, add screenshots to help explain your problem.*
**Device information**
*Copy-paste here the information as it is outputted by the device. You can get this information by from the telnet session with the logging set to Verbose mode.*
**Additional context**
*Add any other context about the problem here.*

10
.github/contribute.md vendored Normal file
View File

@@ -0,0 +1,10 @@
Do you want to do a pull request?
Excellent! Thanks for contributing!
Please do keep in mind these basic rules:
## Pull request ##
* Do the pull request against the **`dev` branch**
* **Only touch relevant files** (beware if your editor has auto-formatting feature enabled)

41
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale Issue or Pull Request is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- enhancement
- bug
- staged for release
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed in 7 days if no further activity occurs.
Thank you for your contributions.
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue will be auto-closed because there hasn't been any activity for two months. Feel free to open a new one if you still experience this problem.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
only: issues

2
.gitignore vendored
View File

@@ -7,4 +7,4 @@ platformio.ini
lib/readme.txt
.travis.yml
stackdmp.txt
*.jar
*.jar

View File

@@ -21,6 +21,9 @@ There are 3 parts to this project, first the design of the circuit, secondly the
- [EMS Polling](#ems-polling)
- [EMS Broadcasting](#ems-broadcasting)
- [EMS Reading and Writing](#ems-reading-and-writing)
- [EMS Plus](#ems-plus)
- [Message layout](#message-layout)
- [Message types](#message-types)
- [The ESP8266 Source Code](#the-esp8266-source-code)
- [Special EMS Types](#special-ems-types)
- [Which thermostats are supported?](#which-thermostats-are-supported)
@@ -43,11 +46,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:
**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
@@ -63,6 +66,31 @@ 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.
<<<<<<< HEAD
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.
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.
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).
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.
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.
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.
11. Hook up the ESP to the EMS board as follows:
| EMS board | ESP8266 dev board |
| ----------- | ----------------- |
| Ground/G/J2 | GND/G |
| Rx/J2 | D7 |
| Tx/J2 | D8 |
| VC/J2 | 3v3 or 5v |
13. Connect the EMS lines to the ESP. This can be done via the two EMS wires or via the 3.5" service jack if you have an bbqkees board.
14. Reboot the ESP, either by the reset switch or pulling the power.
15. The ESP will first perform an autodetect to try and discover the EMS devices attached. If your boiler and thermostat are recognized it will set these types and store them for ever and ever. You can trace the output by telnet'ing to the board `telnet ems-esp.local`. Also type `info` to check what happened.
16. If your boiler/thermostat is not discovered create a GitHub issue stating the type and product ID. These will be added to the file `ems_devices.h` in a future release.
17. If all is well and there is traffic on the EMS bus the onboard LED will stop blinking and be permanently on. If this is annoying you can disable with `set led off`. To see the EMS messages type `set log v` for verbose logging.
18. And all is not well, check the wiring, make sure serial is off and look at the telnet session for errors. If in doubt, wipe the ESP with `pio run -t erase` and start again with step #3
=======
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.
@@ -85,6 +113,7 @@ The code and circuit has been tested with a few ESP8266 development boards such
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
>>>>>>> upstream/dev
## Monitoring The Output
@@ -114,7 +143,7 @@ The schematic used:
![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**:
@@ -129,8 +158,8 @@ The EMS circuit will work with both 3.3V and 5V. It's easiest though to power di
- 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 |
| ------------------------------------------ |
| With Power Circuit |
| --------------------------------------------------------------- |
| ![Power circuit](doc/schematics/Schematic_EMS-ESP-supercap.png) |
## Adding external temperature sensors
@@ -143,9 +172,9 @@ 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:
``[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
@@ -196,6 +225,24 @@ 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.
## Ems Plus
In this chapter we will report our findings on the ems plus.
### Message layout
| 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 |
### Message types
| Message type | Definition |
| ------------ | --------------- |
| 03 | Set temperature |
| 00 | Status message |
## The ESP8266 Source Code
`emsuart.cpp` handles the low level UART read and write logic to the bus. You shouldn't need to touch this. All receive commands from the EMS bus are handled asynchronously using a circular buffer via an interrupt. A separate function processes the buffer and extracts the telegrams.
@@ -223,7 +270,7 @@ Every telegram sent is echo'd back to Rx, along the same Bus used for all Rx/Tx
| Boiler (0x08) | 0x15 | UBAMaintenanceSettingsMessage | |
| 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?
@@ -296,6 +343,7 @@ You can find the .yaml configuration files under `doc/ha`. See also this [HA for
**On Linux (e.g. Ubuntu under Windows 10):**
Make sure Python 2.7 is installed, then...
```python
% pip install -U platformio
% sudo platformio upgrade
@@ -306,7 +354,9 @@ Make sure Python 2.7 is installed, then...
% cd EMS-ESP
% cp platformio.ini-example platformio.ini
```
edit `platformio.ini` to set `env_default` to your board type, then
```c
% platformio run -t upload
```

View File

@@ -1,26 +0,0 @@
#!/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'])

View File

@@ -1,14 +0,0 @@
#!/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)

View File

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

View File

@@ -1,307 +0,0 @@
#!/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)

View File

@@ -73,6 +73,14 @@ void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
#define COLOR_MAGENTA "\x1B[0;35m"
#define COLOR_CYAN "\x1B[0;36m"
#define COLOR_WHITE "\x1B[0;37m"
#define COLOR_BRIGHT_BLACK "\x1B[0;90m"
#define COLOR_BRIGHT_RED "\x1B[0;91m"
#define COLOR_BRIGHT_GREEN "\x1B[0;92m"
#define COLOR_BRIGHT_YELLOW "\x1B[0;99m"
#define COLOR_BRIGHT_BLUE "\x1B[0;94m"
#define COLOR_BRIGHT_MAGENTA "\x1B[0;95m"
#define COLOR_BRIGHT_CYAN "\x1B[0;96m"
#define COLOR_BRIGHT_WHITE "\x1B[0;97m"
#define COLOR_BOLD_ON "\x1B[1m"
#define COLOR_BOLD_OFF "\x1B[22m" // fix by Scott Arlott to support Linux
@@ -80,6 +88,10 @@ void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
#define SPIFFS_MAXSIZE 600 // https://arduinojson.org/v6/assistant/
// CRASH
<<<<<<< HEAD
#define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data
#define SAVE_CRASH_EEPROM_SIZE 0x0200 // size
=======
/**
* Structure of the single crash data set
*
@@ -97,6 +109,7 @@ void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
* ...
*/
#define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data
>>>>>>> upstream/dev
#define SAVE_CRASH_CRASH_TIME 0x00 // 4 bytes
#define SAVE_CRASH_RESTART_REASON 0x04 // 1 byte
#define SAVE_CRASH_EXCEPTION_CAUSE 0x05 // 1 byte

1183
lib/myESP/MyESP.cpp Normal file

File diff suppressed because it is too large Load Diff

238
lib/myESP/MyESP.h Normal file
View File

@@ -0,0 +1,238 @@
/*
* MyESP.h
*
* Paul Derbyshire - December 2018
*/
#pragma once
#ifndef MyEMS_h
#define MyEMS_h
#define MYESP_VERSION "1.1.5"
#include <ArduinoJson.h>
#include <ArduinoOTA.h>
#include <AsyncMqttClient.h> // https://github.com/marvinroger/async-mqtt-client and for ESP32 see https://github.com/marvinroger/async-mqtt-client/issues/127
#include <DNSServer.h>
#include <FS.h>
#include <JustWifi.h> // https://github.com/xoseperez/justwifi
#include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy
#if defined(ARDUINO_ARCH_ESP32)
#include <ESPmDNS.h>
#include <SPIFFS.h> // added for ESP32
#define ets_vsnprintf vsnprintf // added for ESP32
#define OTA_PORT 8266
#else
#include <ESP8266HTTPUpdateServer.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESPAsyncTCP.h>
#define OTA_PORT 8266
#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_BRIGHT_BLACK "\x1B[0;90m"
#define COLOR_BRIGHT_RED "\x1B[0;91m"
#define COLOR_BRIGHT_GREEN "\x1B[0;92m"
#define COLOR_BRIGHT_YELLOW "\x1B[0;99m"
#define COLOR_BRIGHT_BLUE "\x1B[0;94m"
#define COLOR_BRIGHT_MAGENTA "\x1B[0;95m"
#define COLOR_BRIGHT_CYAN "\x1B[0;96m"
#define COLOR_BRIGHT_WHITE "\x1B[0;97m"
#define COLOR_BOLD_ON "\x1B[1m"
#define COLOR_BOLD_OFF "\x1B[22m" // fixed by Scott Arlott
// SPIFFS
#define SPIFFS_MAXSIZE 500 // https://arduinojson.org/v5/assistant/
typedef struct {
char key[40];
char description[100];
} command_t;
typedef enum { MYESP_FSACTION_SET, MYESP_FSACTION_LIST, MYESP_FSACTION_SAVE, MYESP_FSACTION_LOAD } MYESP_FSACTION;
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
typedef std::function<void()> wifi_callback_f;
typedef std::function<void()> ota_callback_f;
typedef std::function<void(uint8_t, const char *)> telnetcommand_callback_f;
typedef std::function<void(uint8_t)> telnet_callback_f;
typedef std::function<bool(MYESP_FSACTION, const JsonObject json)> fs_callback_f;
typedef std::function<bool(MYESP_FSACTION, uint8_t, const char *, const char *)> fs_settings_callback_f;
// calculates size of an 2d array at compile time
template <typename T, size_t N>
constexpr size_t ArraySize(T (&)[N]) {
return N;
}
// class definition
class MyESP {
public:
MyESP();
~MyESP();
// wifi
void setWIFICallback(void (*callback)());
void setWIFI(const char * wifi_ssid, const char * wifi_password, wifi_callback_f callback);
bool isWifiConnected();
// mqtt
void mqttSubscribe(const char * topic);
void mqttUnsubscribe(const char * topic);
void mqttPublish(const char * topic, const char * payload);
void setMQTT(const char * mqtt_host,
const char * mqtt_username,
const char * mqtt_password,
const char * mqtt_base,
unsigned long mqtt_keepalive,
unsigned char mqtt_qos,
bool mqtt_retain,
const char * mqtt_will_topic,
const char * mqtt_will_online_payload,
const char * mqtt_will_offline_payload,
mqtt_callback_f callback);
// OTA
void setOTA(ota_callback_f OTACallback);
// debug & telnet
void myDebug(const char * format, ...);
void myDebug_P(PGM_P format_P, ...);
void setTelnet(command_t * cmds, uint8_t count, telnetcommand_callback_f callback_cmd, telnet_callback_f callback);
bool getUseSerial();
// FS
void setSettings(fs_callback_f callback, fs_settings_callback_f fs_settings_callback);
bool fs_saveConfig();
// general
void end();
void loop();
void begin(const char * app_hostname, const char * app_name, const char * app_version);
void setBoottime(const char * boottime);
void resetESP();
uint16_t getSystemLoadAverage();
int getWifiQuality();
private:
// mqtt
AsyncMqttClient mqttClient;
unsigned long _mqtt_reconnect_delay;
void _mqttOnMessage(char * topic, char * payload, size_t len);
void _mqttConnect();
void _mqtt_setup();
mqtt_callback_f _mqtt_callback;
void _mqttOnConnect();
void _sendStart();
char * _mqttTopic(const char * topic);
char * _mqtt_host;
char * _mqtt_username;
char * _mqtt_password;
char * _mqtt_base;
unsigned long _mqtt_keepalive;
unsigned char _mqtt_qos;
bool _mqtt_retain;
char * _mqtt_will_topic;
char * _mqtt_will_online_payload;
char * _mqtt_will_offline_payload;
char * _mqtt_topic;
unsigned long _mqtt_last_connection;
bool _mqtt_connecting;
// wifi
DNSServer dnsServer; // For Access Point (AP) support
void _wifiCallback(justwifi_messages_t code, char * parameter);
void _wifi_setup();
wifi_callback_f _wifi_callback;
char * _wifi_ssid;
char * _wifi_password;
bool _wifi_connected;
// ota
ota_callback_f _ota_callback;
void _ota_setup();
void _OTACallback();
// telnet & debug
TelnetSpy SerialAndTelnet;
void _telnetConnected();
void _telnetDisconnected();
void _telnetHandle();
void _telnetCommand(char * commandLine);
char * _telnet_readWord();
void _telnet_setup();
char _command[TELNET_MAX_COMMAND_LENGTH]; // the input command from either Serial or Telnet
command_t * _helpProjectCmds; // Help of commands setted by project
uint8_t _helpProjectCmds_count; // # available commands
void _consoleShowHelp();
telnetcommand_callback_f _telnetcommand_callback; // Callable for projects commands
telnet_callback_f _telnet_callback; // callback for connect/disconnect
void _changeSetting(uint8_t wc, const char * setting, const char * value);
void _changeSetting2(const char * setting, const char * value1, const char * value2);
// fs
void _fs_setup();
bool _fs_loadConfig();
void _fs_printConfig();
void _fs_eraseConfig();
fs_callback_f _fs_callback;
fs_settings_callback_f _fs_settings_callback;
// general
char * _app_hostname;
char * _app_name;
char * _app_version;
char * _boottime;
bool _suspendOutput;
bool _use_serial;
void _printBuildTime(unsigned long unix);
// load average (0..100)
void _calculateLoad();
unsigned short int _load_average;
};
extern MyESP myESP;
#endif

View File

@@ -34,7 +34,11 @@ upload_speed = 921600
monitor_speed = 115200
; for OTA comment out these sections
;upload_protocol = espota
<<<<<<< HEAD
;upload_port = <the IP address...>
=======
;upload_port = ems-esp.local
;upload_port = <add here your ip of the device>
>>>>>>> upstream/dev

View File

@@ -1,8 +0,0 @@
#!/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

@@ -1346,6 +1346,77 @@ void initEMSESP() {
EMSESP_Shower.doingColdShot = false;
}
<<<<<<< HEAD:src/ems-esp.ino
// 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_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
}
}
=======
>>>>>>> upstream/dev:src/ems-esp.cpp
/*
* Shower Logic
*/

View File

@@ -32,7 +32,12 @@ CircularBuffer<_EMS_TxTelegram, EMS_TX_TELEGRAM_QUEUE_MAX> EMS_TxQueue; // FIFO
#define _bitRead(i, bit) (((data[i]) >> (bit)) & 0x01)
// generic
<<<<<<< HEAD
void _process_Version(uint8_t type, uint8_t * data, uint8_t length);
void _printMessage(uint8_t * telegram, uint8_t length);
=======
void _process_Version(uint8_t src, uint8_t * data, uint8_t length);
>>>>>>> upstream/dev
// Boiler and Buderus devices
void _process_UBAMonitorFast(uint8_t src, uint8_t * data, uint8_t length);
@@ -65,7 +70,14 @@ 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
<<<<<<< HEAD
void _process_EasyStatusMessage(uint8_t type, uint8_t * data, uint8_t length);
//RC1010
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_EasyStatusMessage(uint8_t src, uint8_t * data, uint8_t length);
>>>>>>> upstream/dev
/*
* Recognized EMS types and the functions they call to process the telegrams
@@ -74,60 +86,69 @@ void _process_EasyStatusMessage(uint8_t src, uint8_t * data, uint8_t length);
const _EMS_Type EMS_Types[] = {
// common
{EMS_MODEL_ALL, EMS_TYPE_Version, "Version", _process_Version},
{EMS_MODEL_ALL, EMS_TYPE_Version, "Version", _process_Version, false},
// Boiler commands
{EMS_MODEL_UBA, EMS_TYPE_UBAMonitorFast, "UBAMonitorFast", _process_UBAMonitorFast},
{EMS_MODEL_UBA, EMS_TYPE_UBAMonitorSlow, "UBAMonitorSlow", _process_UBAMonitorSlow},
{EMS_MODEL_UBA, EMS_TYPE_UBAMonitorWWMessage, "UBAMonitorWWMessage", _process_UBAMonitorWWMessage},
{EMS_MODEL_UBA, EMS_TYPE_UBAParameterWW, "UBAParameterWW", _process_UBAParameterWW},
{EMS_MODEL_UBA, EMS_TYPE_UBATotalUptimeMessage, "UBATotalUptimeMessage", _process_UBATotalUptimeMessage},
{EMS_MODEL_UBA, EMS_TYPE_UBAMaintenanceSettingsMessage, "UBAMaintenanceSettingsMessage", NULL},
{EMS_MODEL_UBA, EMS_TYPE_UBAParametersMessage, "UBAParametersMessage", _process_UBAParametersMessage},
{EMS_MODEL_UBA, EMS_TYPE_UBASetPoints, "UBASetPoints", _process_SetPoints},
{EMS_MODEL_UBA, EMS_TYPE_UBAMonitorFast, "UBAMonitorFast", _process_UBAMonitorFast, false},
{EMS_MODEL_UBA, EMS_TYPE_UBAMonitorSlow, "UBAMonitorSlow", _process_UBAMonitorSlow, false},
{EMS_MODEL_UBA, EMS_TYPE_UBAMonitorWWMessage, "UBAMonitorWWMessage", _process_UBAMonitorWWMessage, false},
{EMS_MODEL_UBA, EMS_TYPE_UBAParameterWW, "UBAParameterWW", _process_UBAParameterWW, false},
{EMS_MODEL_UBA, EMS_TYPE_UBATotalUptimeMessage, "UBATotalUptimeMessage", _process_UBATotalUptimeMessage, false},
{EMS_MODEL_UBA, EMS_TYPE_UBAMaintenanceSettingsMessage, "UBAMaintenanceSettingsMessage", NULL, false},
{EMS_MODEL_UBA, EMS_TYPE_UBAParametersMessage, "UBAParametersMessage", _process_UBAParametersMessage, false},
{EMS_MODEL_UBA, EMS_TYPE_UBASetPoints, "UBASetPoints", _process_SetPoints, false},
// 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},
{EMS_MODEL_RC10, EMS_TYPE_RC10StatusMessage, "RC10StatusMessage", _process_RC10StatusMessage},
{EMS_MODEL_RC10, EMS_TYPE_RCTime, "RCTime", _process_RCTime, false},
{EMS_MODEL_RC10, EMS_TYPE_RC10Set, "RC10Set", _process_RC10Set, false},
{EMS_MODEL_RC10, EMS_TYPE_RC10StatusMessage, "RC10StatusMessage", _process_RC10StatusMessage, false},
// RC20 and RC20F
{EMS_MODEL_RC20, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage},
{EMS_MODEL_RC20, EMS_TYPE_RCTime, "RCTime", _process_RCTime},
{EMS_MODEL_RC20, EMS_TYPE_RC20Set, "RC20Set", _process_RC20Set},
{EMS_MODEL_RC20, EMS_TYPE_RC20StatusMessage, "RC20StatusMessage", _process_RC20StatusMessage},
{EMS_MODEL_RC20, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage, false},
{EMS_MODEL_RC20, EMS_TYPE_RCTime, "RCTime", _process_RCTime, false},
{EMS_MODEL_RC20, EMS_TYPE_RC20Set, "RC20Set", _process_RC20Set, false},
{EMS_MODEL_RC20, EMS_TYPE_RC20StatusMessage, "RC20StatusMessage", _process_RC20StatusMessage, false},
{EMS_MODEL_RC20F, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage},
{EMS_MODEL_RC20F, EMS_TYPE_RCTime, "RCTime", _process_RCTime},
{EMS_MODEL_RC20F, EMS_TYPE_RC20Set, "RC20Set", _process_RC20Set},
{EMS_MODEL_RC20F, EMS_TYPE_RC20StatusMessage, "RC20StatusMessage", _process_RC20StatusMessage},
{EMS_MODEL_RC20F, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage, false},
{EMS_MODEL_RC20F, EMS_TYPE_RCTime, "RCTime", _process_RCTime, false},
{EMS_MODEL_RC20F, EMS_TYPE_RC20Set, "RC20Set", _process_RC20Set, false},
{EMS_MODEL_RC20F, EMS_TYPE_RC20StatusMessage, "RC20StatusMessage", _process_RC20StatusMessage, false},
// RC30
{EMS_MODEL_RC30, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage},
{EMS_MODEL_RC30, EMS_TYPE_RCTime, "RCTime", _process_RCTime},
{EMS_MODEL_RC30, EMS_TYPE_RC30Set, "RC30Set", _process_RC30Set},
{EMS_MODEL_RC30, EMS_TYPE_RC30StatusMessage, "RC30StatusMessage", _process_RC30StatusMessage},
{EMS_MODEL_RC30, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage, false},
{EMS_MODEL_RC30, EMS_TYPE_RCTime, "RCTime", _process_RCTime, false},
{EMS_MODEL_RC30, EMS_TYPE_RC30Set, "RC30Set", _process_RC30Set, false},
{EMS_MODEL_RC30, EMS_TYPE_RC30StatusMessage, "RC30StatusMessage", _process_RC30StatusMessage, false},
// RC35
{EMS_MODEL_RC35, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage},
{EMS_MODEL_RC35, EMS_TYPE_RCTime, "RCTime", _process_RCTime},
{EMS_MODEL_RC35, EMS_TYPE_RC35Set, "RC35Set", _process_RC35Set},
{EMS_MODEL_RC35, EMS_TYPE_RC35StatusMessage, "RC35StatusMessage", _process_RC35StatusMessage},
{EMS_MODEL_RC35, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage, false},
{EMS_MODEL_RC35, EMS_TYPE_RCTime, "RCTime", _process_RCTime, false},
{EMS_MODEL_RC35, EMS_TYPE_RC35Set, "RC35Set", _process_RC35Set, false},
{EMS_MODEL_RC35, EMS_TYPE_RC35StatusMessage, "RC35StatusMessage", _process_RC35StatusMessage, false},
// ES73
{EMS_MODEL_ES73, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage},
{EMS_MODEL_ES73, EMS_TYPE_RCTime, "RCTime", _process_RCTime},
{EMS_MODEL_ES73, EMS_TYPE_RC35Set, "RC35Set", _process_RC35Set},
{EMS_MODEL_ES73, EMS_TYPE_RC35StatusMessage, "RC35StatusMessage", _process_RC35StatusMessage},
{EMS_MODEL_ES73, EMS_TYPE_RCOutdoorTempMessage, "RCOutdoorTempMessage", _process_RCOutdoorTempMessage, false},
{EMS_MODEL_ES73, EMS_TYPE_RCTime, "RCTime", _process_RCTime, false},
{EMS_MODEL_ES73, EMS_TYPE_RC35Set, "RC35Set", _process_RC35Set, false},
{EMS_MODEL_ES73, EMS_TYPE_RC35StatusMessage, "RC35StatusMessage", _process_RC35StatusMessage, false},
// Easy
<<<<<<< HEAD
{EMS_MODEL_EASY, EMS_TYPE_EasyStatusMessage, "EasyStatusMessage", _process_EasyStatusMessage, false},
{EMS_MODEL_BOSCHEASY, EMS_TYPE_EasyStatusMessage, "EasyStatusMessage", _process_EasyStatusMessage, false},
//Ems plus
//Nefit 1010
{EMS_MODEL_RC1010, EMS_TYPE_RC1010StatusMessage, "RC1010StatusMessage", _process_RC1010StatusMessage, true},
{EMS_MODEL_RC1010, EMS_TYPE_RC1010Set, "RC1010SetMessage", _process_RC1010SetMessage, true}};
=======
{EMS_MODEL_EASY, EMS_TYPE_EasyStatusMessage, "EasyStatusMessage", _process_EasyStatusMessage},
{EMS_MODEL_BOSCHEASY, EMS_TYPE_EasyStatusMessage, "EasyStatusMessage", _process_EasyStatusMessage},
};
>>>>>>> upstream/dev
// calculate sizes of arrays at compile
uint8_t _EMS_Types_max = ArraySize(EMS_Types); // number of defined types
@@ -696,6 +717,69 @@ void _ems_readTelegram(uint8_t * telegram, uint8_t length) {
*/
void _ems_processTelegram(_EMS_RxTelegram * EMS_RxTelegram) {
// header
<<<<<<< HEAD
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 4
uint8_t ptype = telegram[3];
uint8_t poffset = telegram[4];
uint8_t * pdata = &telegram[5 + poffset]; // data block starts at position 5 plus the offset
_printMessage(telegram, length);
// 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) || (EMS_Types[i].emsplus && type >= 240 && EMS_Types[i].type == ptype)) {
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)) {
if (EMS_Types[i].emsplus)
myDebug("<--- %s(0x%02X) received", EMS_Types[i].typeString, ptype);
else
myDebug("<--- %s(0x%02X) received", EMS_Types[i].typeString, type);
}
// call callback function to process it
if (EMS_Types[i].emsplus && poffset == EMS_PLUS_ID_NONE)
(void)EMS_Types[i].processType_cb(ptype, pdata, length - 6 - poffset);
// as we only handle complete telegrams (not partial) check that the offset is 0
else if (offset == EMS_ID_NONE && !EMS_Types[i].emsplus) {
(void)EMS_Types[i].processType_cb(type, data, length - 5);
}
}
}
}
void _printMessage(uint8_t * telegram, uint8_t length) {
bool emsp = false;
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
if (type >= 240) {
type = telegram[3];
offset = telegram[4];
data = &telegram[5 + offset];
emsp = true;
}
=======
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
@@ -703,6 +787,7 @@ void _ems_processTelegram(_EMS_RxTelegram * EMS_RxTelegram) {
uint8_t offset = telegram[3];
uint8_t * data = telegram + 4; // data block starts at position 5
>>>>>>> upstream/dev
// print detailed telegram data
if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_THERMOSTAT) {
char output_str[200] = {0};
@@ -713,9 +798,16 @@ void _ems_processTelegram(_EMS_RxTelegram * EMS_RxTelegram) {
if (src == EMS_Boiler.type_id) {
strlcpy(output_str, "Boiler", sizeof(output_str));
} else if (src == EMS_Thermostat.type_id) {
<<<<<<< HEAD
if (emsp)
strlcpy(output_str, "Thermostat+", sizeof(output_str));
else
strlcpy(output_str, "Thermostat", sizeof(output_str));
=======
strlcpy(output_str, "Thermostat", sizeof(output_str));
} else if (src == EMS_ID_SM10) {
strlcpy(output_str, "SM10", sizeof(output_str));
>>>>>>> upstream/dev
} else {
strlcpy(output_str, "0x", sizeof(output_str));
strlcat(output_str, _hextoa(src, buffer), sizeof(output_str));
@@ -725,24 +817,55 @@ void _ems_processTelegram(_EMS_RxTelegram * EMS_RxTelegram) {
// destination
if (dest == EMS_ID_ME) {
strlcat(output_str, "me", sizeof(output_str));
strlcpy(color_s, COLOR_YELLOW, sizeof(color_s));
if (emsp) {
strlcat(output_str, "me", sizeof(output_str));
strlcpy(color_s, COLOR_BRIGHT_YELLOW, sizeof(color_s));
} else {
strlcat(output_str, "me", sizeof(output_str));
strlcpy(color_s, COLOR_YELLOW, sizeof(color_s));
}
} else if (dest == EMS_ID_NONE) {
strlcat(output_str, "all", sizeof(output_str));
strlcpy(color_s, COLOR_GREEN, sizeof(color_s));
if (emsp) {
strlcat(output_str, "all", sizeof(output_str));
strlcpy(color_s, COLOR_BRIGHT_GREEN, sizeof(color_s));
} else {
strlcat(output_str, "all", sizeof(output_str));
strlcpy(color_s, COLOR_GREEN, sizeof(color_s));
}
} else if (dest == EMS_Boiler.type_id) {
<<<<<<< HEAD
if (emsp) {
strlcat(output_str, "Boiler", sizeof(output_str));
strlcpy(color_s, COLOR_BRIGHT_MAGENTA, sizeof(color_s));
} else {
strlcat(output_str, "Boiler", sizeof(output_str));
strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s));
}
=======
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));
>>>>>>> upstream/dev
} else if (dest == EMS_Thermostat.type_id) {
strlcat(output_str, "Thermostat", sizeof(output_str));
strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s));
if (emsp) {
strlcat(output_str, "Thermostat", sizeof(output_str));
strlcpy(color_s, COLOR_BRIGHT_MAGENTA, sizeof(color_s));
} else {
strlcat(output_str, "Thermostat", sizeof(output_str));
strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s));
}
} else {
strlcat(output_str, "0x", sizeof(output_str));
strlcat(output_str, _hextoa(dest, buffer), sizeof(output_str));
strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s));
if (emsp) {
strlcat(output_str, "0x", sizeof(output_str));
strlcat(output_str, _hextoa(dest, buffer), sizeof(output_str));
strlcpy(color_s, COLOR_BRIGHT_MAGENTA, sizeof(color_s));
} else {
strlcat(output_str, "0x", sizeof(output_str));
strlcat(output_str, _hextoa(dest, buffer), sizeof(output_str));
strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s));
}
}
// type
@@ -792,6 +915,7 @@ void _ems_processTelegram(_EMS_RxTelegram * EMS_RxTelegram) {
}
}
}
EMS_Sys_Status.emsTxStatus = EMS_TX_STATUS_IDLE;
}
/**
@@ -1061,6 +1185,13 @@ void _process_RC20StatusMessage(uint8_t src, uint8_t * data, uint8_t length) {
}
/**
<<<<<<< HEAD
*type 0x41 - data from the RC30 thermostat(0x10) - 14 bytes long * 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);
=======
* type 0x41 - data from the RC30 thermostat (0x10) - 14 bytes long
* For reading the temp values only
* received every 60 seconds
@@ -1068,6 +1199,7 @@ void _process_RC20StatusMessage(uint8_t src, uint8_t * data, uint8_t length) {
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
>>>>>>> upstream/dev
EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT
}
@@ -1094,13 +1226,32 @@ void _process_RC35StatusMessage(uint8_t src, 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 must be divided by 100
*/
<<<<<<< HEAD
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;
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
* The 1010 has a digital precision of its floats to 1 decimal places for the current temperature, so values is divided by 10
* The 1010 has a digital precision of its floats to 1 decimal places for the set temperature, so values is divided by 2
*/
void _process_RC1010StatusMessage(uint8_t type, uint8_t * data, uint8_t length) {
EMS_Thermostat.curr_roomTemp = ((float)data[EMS_TYPE_RC1010StatusMessage_curr]) / (float)10;
EMS_Thermostat.setpoint_roomTemp = ((float)data[EMS_TYPE_RC1010StatusMessage_set]) / (float)2;
=======
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
>>>>>>> upstream/dev
EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT
}
void _process_RC1010SetMessage(uint8_t type, uint8_t * data, uint8_t length) {
EMS_Thermostat.setpoint_roomTemp = ((float)data[EMS_TYPE_RC1010Set]) / (float)2;
EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT
}
/**
* type 0xB0 - for reading the mode from the RC10 thermostat (0x17)
* received only after requested
@@ -1375,6 +1526,43 @@ void _ems_setThermostatModel(uint8_t thermostat_modelid) {
}
/**
<<<<<<< HEAD
* 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
}
if (length > 5) {
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];
}
}
/**
=======
>>>>>>> upstream/dev
* Print the Tx queue - for debugging
*/
void ems_printTxQueue() {
@@ -1629,12 +1817,15 @@ 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) || (dest == EMS_ID_NONE)) {
<<<<<<< HEAD
=======
return;
}
// if we're preventing all outbound traffic, quit
if (EMS_Sys_Status.emsTxDisabled) {
myDebug("in Silent Mode. All Tx is disabled.");
>>>>>>> upstream/dev
return;
}

View File

@@ -13,8 +13,9 @@
#include <Arduino.h>
// EMS IDs
#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs
#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as the "Service Key"
#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs
#define EMS_PLUS_ID_NONE 0x01 // 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
@@ -262,6 +263,7 @@ typedef struct {
uint8_t type;
const char typeString[50];
EMS_processType_cb processType_cb;
bool emsplus;
} _EMS_Type;
// function definitions

View File

@@ -87,7 +87,12 @@
#define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat
#define EMS_TYPE_EasyStatusMessage_setpoint 10 // setpoint temp
#define EMS_TYPE_EasyStatusMessage_curr 8 // current temp
// RC1010 specific
#define EMS_TYPE_RC1010StatusMessage 0x00 // is an automatic thermostat broadcast giving us temps
#define EMS_TYPE_RC1010StatusMessage_curr 1 // current temp
#define EMS_TYPE_RC1010StatusMessage_set 3 // setpoint temp
#define EMS_TYPE_RC1010Set 0x03 // setpoint temp message
#define EMS_TYPE_RC1010Set_set 0 // setpoint temp
// Known EMS types
typedef enum {
EMS_MODEL_NONE,
@@ -110,6 +115,7 @@ typedef enum {
EMS_MODEL_BOSCHEASY,
EMS_MODEL_RC310,
EMS_MODEL_CW100,
EMS_MODEL_RC1010,
EMS_MODEL_OT
} _EMS_MODEL_ID;
@@ -124,6 +130,16 @@ const _Boiler_Type Boiler_Types[] = {
{EMS_MODEL_UBA, 203, 0x08, "Buderus Logamax U122"},
{EMS_MODEL_UBA, 208, 0x08, "Buderus Logamax plus"},
{EMS_MODEL_UBA, 64, 0x08, "Sieger BK15 Boiler/Nefit Smartline"},
<<<<<<< HEAD
{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, 205, 0x08, "Nefit Moduline Easy Connect"},
{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"}
};
@@ -140,6 +156,7 @@ const _Other_Type Other_Types[] = {
{EMS_MODEL_OTHER, 205, 0x02, "Nefit Moduline Easy Connect"},
{EMS_MODEL_OTHER, 73, EMS_ID_SM10, "SM10 Solar Module"}
>>>>>>> upstream/dev
};
/*
@@ -157,7 +174,12 @@ const _Thermostat_Type Thermostat_Types[] = {
{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},
<<<<<<< HEAD
{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},
=======
{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}
>>>>>>> upstream/dev
};

View File

@@ -53,10 +53,17 @@
#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
<<<<<<< HEAD
// default values for shower logic on/off
#define BOILER_SHOWER_TIMER 0 // 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
>>>>>>> upstream/dev
////////////////////////////////////////////////////////////////////////////////////////////////////
// THESE DEFAULT VALUES CAN ALSO BE SET AND STORED WITHTIN THE APPLICATION (see 'set' command) //

View File

@@ -5,6 +5,12 @@
#pragma once
<<<<<<< HEAD
#define APP_NAME "EMS-HEERENVEEN-1"
#define APP_VERSION "1.5.7b"
#define APP_HOSTNAME "ems-heerenveen-1"
=======
#define APP_NAME "EMS-ESP"
#define APP_VERSION "1.6.1b1"
#define APP_HOSTNAME "ems-esp"
>>>>>>> upstream/dev