merged with dev 1.8.1

This commit is contained in:
Paul Derbyshire
2019-07-29 13:31:59 +02:00
20 changed files with 2280 additions and 785 deletions

2
.gitignore vendored
View File

@@ -3,7 +3,9 @@
.gcc-flags.json .gcc-flags.json
.vscode .vscode
.env .env
.DS_Store
platformio.ini platformio.ini
lib/readme.txt lib/readme.txt
.travis.yml .travis.yml
scripts/stackdmp.txt scripts/stackdmp.txt
*.bin

View File

@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.8.1dev] 2019-07-27
### Added
- Added back -DCRASH in Debug build target for capturing any ESP8266 stack dumps during crashes
- Web Interface, for checking stats and setting wifi credentials. See wiki for more details.
- reset firmware option. If the reset button on the ESP is pressed during boot up sequence (the LED is flashing very fast) all settings are erased and goes into AP mode.
- Added tx_mode back with options 0,1 and 2 until we've fixed option 2 that works for everyone and doesn't reset ESP
- More solar module data captured, thanks to @Vuego123
- Detect thermostat mode for EMS+ RC300/Moduline 3000
- MQTT message to set boiler flowtemp (`boiler_cmd_flowtemp`). See [wiki](https://github.com/proddy/EMS-ESP/wiki/MQTT).
### Fixed
- Detecting unset values in the SPIFFS and setting default values
- Bosch Easy Connect wrongly classified as a thermostat
- Correctly handle telegrams who's size are exactly 32 bytes (e.g. 0x19 MonitorSlow)
- Telnet also available when in AP mode
- Handling of thermostat temperatures that were single bytes and couldn't exceed 25.5 (0xFF) degrees!
### Changed
- Improved handling of Solar Modules (thanks @Vuego123)
- `publish_wait` renamed to `publish_time`, a value of 0 means disabling all MQTT sending
- How signed shorts are handled such as the current and setpoint temps on RC300s
- Stopped automatic refresh of web page, which causes crashes/memory loss after a short time
- Support HA 0.96 climate component changes
- -DDEFAULT_NO_SERIAL changed to -DFORCE_SERIAL
- some code cleanups, removing NULLS and moving some things fron heap to stack to prevent memory fragmentation
## [1.8.0] 2019-06-15 ## [1.8.0] 2019-06-15
### Added ### Added

View File

@@ -21,7 +21,7 @@
- platform: mqtt - platform: mqtt
name: boiler name: boiler
modes: modes:
- "on" - "auto"
- "off" - "off"
min_temp: 40 min_temp: 40
max_temp: 60 max_temp: 60
@@ -31,6 +31,7 @@
temperature_command_topic: "home/ems-esp/boiler_cmd_wwtemp" temperature_command_topic: "home/ems-esp/boiler_cmd_wwtemp"
current_temperature_template: "{{ value_json.wWCurTmp }}" current_temperature_template: "{{ value_json.wWCurTmp }}"
temperature_state_template: "{{ value_json.wWSelTemp }}" temperature_state_template: "{{ value_json.wWSelTemp }}"
mode_state_template: "{{ value_json.wWActivated }}" mode_state_template: "{% if value_json.wWActivated == 'off' %} off {% else %} auto {% endif %}"
mode_state_topic: "home/ems-esp/boiler_data" mode_state_topic: "home/ems-esp/boiler_data"
mode_command_topic: "home/ems-esp/wwactivated" mode_command_topic: "home/ems-esp/wwactivated"

File diff suppressed because it is too large Load Diff

View File

@@ -6,23 +6,26 @@
#pragma once #pragma once
#ifndef MyEMS_h #ifndef MyESP_h
#define MyEMS_h #define MyESP_h
#define MYESP_VERSION "1.1.16" #define MYESP_VERSION "1.1.24"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ArduinoOTA.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 <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 <ESP8266WebServer.h>
#include <FS.h> #include <FS.h>
#include <JustWifi.h> // https://github.com/xoseperez/justwifi #include <JustWifi.h> // https://github.com/xoseperez/justwifi
#include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy #include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy
#ifdef CRASH
#include <EEPROM_Rotate.h> #include <EEPROM_Rotate.h>
#endif
extern "C" { extern "C" {
#include "user_interface.h"
void custom_crash_callback(struct rst_info *, uint32_t, uint32_t); void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
#include "user_interface.h"
extern struct rst_info resetInfo; extern struct rst_info resetInfo;
} }
@@ -38,10 +41,10 @@ extern struct rst_info resetInfo;
#define MYEMS_CONFIG_FILE "/config.json" #define MYEMS_CONFIG_FILE "/config.json"
#define LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms) #define LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms) = 30 seconds
// WIFI // WIFI
#define WIFI_CONNECT_TIMEOUT 10000 // Connecting timeout for WIFI in ms #define WIFI_CONNECT_TIMEOUT 10000 // Connecting timeout for WIFI in ms (10 seconds)
#define WIFI_RECONNECT_INTERVAL 600000 // If could not connect to WIFI, retry after this time in ms. 10 minutes #define WIFI_RECONNECT_INTERVAL 600000 // If could not connect to WIFI, retry after this time in ms. 10 minutes
// MQTT // MQTT
@@ -65,6 +68,8 @@ extern struct rst_info resetInfo;
#define TELNET_MAX_COMMAND_LENGTH 80 // length of a command #define TELNET_MAX_COMMAND_LENGTH 80 // length of a command
#define TELNET_EVENT_CONNECT 1 #define TELNET_EVENT_CONNECT 1
#define TELNET_EVENT_DISCONNECT 0 #define TELNET_EVENT_DISCONNECT 0
#define TELNET_EVENT_SHOWCMD 10
#define TELNET_EVENT_SHOWSET 20
// ANSI Colors // ANSI Colors
#define COLOR_RESET "\x1B[0m" #define COLOR_RESET "\x1B[0m"
@@ -143,12 +148,11 @@ PROGMEM const char * const custom_reset_string[] = {custom_reset_hardware, cus
#define RTCMEM_OFFSET 32u #define RTCMEM_OFFSET 32u
#define RTCMEM_ADDR (RTCMEM_ADDR_BASE + (RTCMEM_OFFSET * 4u)) #define RTCMEM_ADDR (RTCMEM_ADDR_BASE + (RTCMEM_OFFSET * 4u))
#define RTCMEM_BLOCKS 96u #define RTCMEM_BLOCKS 96u
#define RTCMEM_MAGIC 0x45535075 #define RTCMEM_MAGIC 0x45535076
struct RtcmemData { struct RtcmemData {
uint32_t magic; // RTCMEM_MAGIC uint32_t magic; // RTCMEM_MAGIC
uint32_t sys; // system reset reason (1-4) uint32_t sys; // system details
uint32_t energy; // store energy count
}; };
static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big"); static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big");
@@ -165,6 +169,13 @@ typedef struct {
typedef enum { MYESP_FSACTION_SET, MYESP_FSACTION_LIST, MYESP_FSACTION_SAVE, MYESP_FSACTION_LOAD } MYESP_FSACTION; typedef enum { MYESP_FSACTION_SET, MYESP_FSACTION_LIST, MYESP_FSACTION_SAVE, MYESP_FSACTION_LOAD } MYESP_FSACTION;
typedef enum {
MYESP_BOOTSTATUS_POWERON = 0,
MYESP_BOOTSTATUS_BOOTED = 1,
MYESP_BOOTSTATUS_BOOTING = 2,
MYESP_BOOTSTATUS_RESETNEEDED = 3
} MYESP_BOOTSTATUS; // boot messages
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f; typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
typedef std::function<void()> wifi_callback_f; typedef std::function<void()> wifi_callback_f;
typedef std::function<void()> ota_callback_f; typedef std::function<void()> ota_callback_f;
@@ -172,6 +183,7 @@ typedef std::function<void(uint8_t, const char *)>
typedef std::function<void(uint8_t)> telnet_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, const JsonObject json)> fs_callback_f;
typedef std::function<bool(MYESP_FSACTION, uint8_t, const char *, const char *)> fs_settings_callback_f; typedef std::function<bool(MYESP_FSACTION, uint8_t, const char *, const char *)> fs_settings_callback_f;
typedef std::function<void(char *)> web_callback_f;
// calculates size of an 2d array at compile time // calculates size of an 2d array at compile time
template <typename T, size_t N> template <typename T, size_t N>
@@ -179,14 +191,55 @@ constexpr size_t ArraySize(T (&)[N]) {
return N; return N;
} }
template <typename T>
void PROGMEM_readAnything(const T * sce, T & dest) {
memcpy_P(&dest, sce, sizeof(T));
}
#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value #define UPTIME_OVERFLOW 4294967295 // Uptime overflow value
// web min and max length of wifi ssid and password
#define MAX_SSID_LEN 32
#define MAX_PWD_LEN 64
#define MYESP_BOOTUP_FLASHDELAY 50 // flash duration for LED at bootup sequence
#define MYESP_BOOTUP_DELAY 2000 // time before we open the window to reset. This is to stop resetting values when uploading firmware via USB
// max size of char buffer for storing web page
#define MYESP_MAXCHARBUFFER 800
// Holds the admin webpage in the program memory
const char webCommonPage_start[] = "<html>"
"<head>"
"<style>input {font-size: 1.2em; width: 100%; max-width: 350px; display: block; margin: 5px auto; }"
"body {background-color: #FFA500;font: normal 18px Verdana, Arial, sans-serif;} </style>";
const char webCommonPage_start_body[] = "</head><body>";
const char webCommonPage_end[] = "</body></html>";
const char webResetPage_form[] = "<form id='form' action='/reset' method='post'>"
"<input name='newssid' type='text' maxlength='32' placeholder='SSID'>"
"<input name='newpassword' id='password1' type='password' maxlength='64' placeholder='Password'>"
"<input type='submit' value='Save and reboot'>"
"</form>";
const char webResetPage_post[] =
"<p>New wifi credentials set. System is now rebooting. Please wait a few seconds and then reconnect via telnet or browser to its new IP given address.</p>";
const char webResetAllPage_form[] = "<form id='resetform' action='/resetall' method='post'>"
"<input name='confirm' type='text' minlength='3' maxlength='16' placeholder='yes'>"
"<input type='submit' value='Reset All'>"
"</form>";
// class definition // class definition
class MyESP { class MyESP {
public: public:
MyESP(); MyESP();
~MyESP(); ~MyESP();
ESP8266WebServer webServer; // Web server on port 80
// wifi // wifi
void setWIFICallback(void (*callback)()); void setWIFICallback(void (*callback)());
void setWIFI(const char * wifi_ssid, const char * wifi_password, wifi_callback_f callback); void setWIFI(const char * wifi_ssid, const char * wifi_password, wifi_callback_f callback);
@@ -216,7 +269,7 @@ class MyESP {
// debug & telnet // debug & telnet
void myDebug(const char * format, ...); void myDebug(const char * format, ...);
void myDebug_P(PGM_P format_P, ...); void myDebug_P(PGM_P format_P, ...);
void setTelnet(command_t * cmds, uint8_t count, telnetcommand_callback_f callback_cmd, telnet_callback_f callback); void setTelnet(telnetcommand_callback_f callback_cmd, telnet_callback_f callback);
bool getUseSerial(); bool getUseSerial();
void setUseSerial(bool toggle); void setUseSerial(bool toggle);
@@ -224,6 +277,9 @@ class MyESP {
void setSettings(fs_callback_f callback, fs_settings_callback_f fs_settings_callback); void setSettings(fs_callback_f callback, fs_settings_callback_f fs_settings_callback);
bool fs_saveConfig(); bool fs_saveConfig();
// Web
void setWeb(web_callback_f callback_web);
// Crash // Crash
void crashClear(); void crashClear();
void crashDump(); void crashDump();
@@ -239,10 +295,9 @@ class MyESP {
int getWifiQuality(); int getWifiQuality();
void showSystemStats(); void showSystemStats();
bool getHeartbeat(); bool getHeartbeat();
uint32_t getSystemLoadAverage();
// rtcmem and reset reason
bool rtcmemStatus();
uint32_t getSystemResetReason(); uint32_t getSystemResetReason();
uint8_t getSystemBootStatus();
private: private:
// mqtt // mqtt
@@ -271,7 +326,6 @@ class MyESP {
bool _rtcmem_status; bool _rtcmem_status;
// wifi // wifi
DNSServer dnsServer; // For Access Point (AP) support
void _wifiCallback(justwifi_messages_t code, char * parameter); void _wifiCallback(justwifi_messages_t code, char * parameter);
void _wifi_setup(); void _wifi_setup();
wifi_callback_f _wifi_callback; wifi_callback_f _wifi_callback;
@@ -285,6 +339,7 @@ class MyESP {
ota_callback_f _ota_post_callback; ota_callback_f _ota_post_callback;
void _ota_setup(); void _ota_setup();
void _OTACallback(); void _OTACallback();
bool _ota_doing_update;
// crash // crash
void _eeprom_setup(); void _eeprom_setup();
@@ -298,8 +353,6 @@ class MyESP {
char * _telnet_readWord(bool allow_all_chars); char * _telnet_readWord(bool allow_all_chars);
void _telnet_setup(); void _telnet_setup();
char _command[TELNET_MAX_COMMAND_LENGTH]; // the input command from either Serial or Telnet 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(); void _consoleShowHelp();
telnetcommand_callback_f _telnetcommand_callback; // Callable for projects commands telnetcommand_callback_f _telnetcommand_callback; // Callable for projects commands
telnet_callback_f _telnet_callback; // callback for connect/disconnect telnet_callback_f _telnet_callback; // callback for connect/disconnect
@@ -316,19 +369,25 @@ class MyESP {
fs_settings_callback_f _fs_settings_callback; fs_settings_callback_f _fs_settings_callback;
void _printSetCommands(); void _printSetCommands();
// web
web_callback_f _web_callback;
// general // general
char * _app_hostname; char * _app_hostname;
char * _app_name; char * _app_name;
char * _app_version; char * _app_version;
char * _boottime; char * _boottime;
bool _suspendOutput; bool _suspendOutput;
bool _use_serial; bool _serial;
bool _heartbeat; bool _heartbeat;
unsigned long _getUptime(); unsigned long _getUptime();
String _buildTime(); String _buildTime();
bool _firstInstall;
// reset reason and rtcmem // reset reason and rtcmem
bool _rtcmemStatus(); bool _rtcmemStatus();
bool _getRtcmemStatus();
void _rtcmemInit(); void _rtcmemInit();
void _rtcmemSetup(); void _rtcmemSetup();
@@ -337,27 +396,33 @@ class MyESP {
uint8_t _getSystemStabilityCounter(); uint8_t _getSystemStabilityCounter();
void _setSystemStabilityCounter(uint8_t counter); void _setSystemStabilityCounter(uint8_t counter);
uint8_t _getSystemResetReason();
void _setSystemResetReason(uint8_t reason); void _setSystemResetReason(uint8_t reason);
uint8_t _getCustomResetReason();
void _setCustomResetReason(uint8_t reason);
uint8_t _getSystemResetReason();
unsigned char _getCustomResetReason(); void _setSystemBootStatus(uint8_t status);
void _setCustomResetReason(unsigned char reason);
bool _systemStable; bool _systemStable;
void _bootupSequence();
bool getSystemCheck(); bool _getSystemCheck();
void _systemCheckLoop(); void _systemCheckLoop();
void _setSystemCheck(bool stable); void _setSystemCheck(bool stable);
// load average (0..100) and heap ram // load average (0..100) and heap ram
uint32_t getSystemLoadAverage();
void _calculateLoad(); void _calculateLoad();
uint32_t _load_average; uint32_t _load_average;
uint32_t getInitialFreeHeap(); uint32_t _getInitialFreeHeap();
uint32_t getUsedHeap(); uint32_t _getUsedHeap();
// heartbeat // heartbeat
void _heartbeatCheck(bool force); void _heartbeatCheck(bool force);
// webserver
void _webserver_setup();
void _webRootPage();
void _webResetPage();
void _webResetAllPage();
}; };
extern MyESP myESP; extern MyESP myESP;

View File

@@ -573,6 +573,7 @@ void TelnetSpy::handle() {
return; return;
} }
if (!listening) { if (!listening) {
if ((WiFi.status() == WL_DISCONNECTED) && (WiFi.getMode() & WIFI_AP)) { if ((WiFi.status() == WL_DISCONNECTED) && (WiFi.getMode() & WIFI_AP)) {
if (usedSer) { if (usedSer) {
usedSer->println("[TELNET] in AP mode"); // added by Proddy usedSer->println("[TELNET] in AP mode"); // added by Proddy

View File

@@ -1,6 +1,5 @@
; ;
; PlatformIO Project Configuration File for EMS-ESP ; PlatformIO Project Configuration File for EMS-ESP
; Uses PlatformIO 4.0
; ;
[platformio] [platformio]
@@ -8,24 +7,15 @@ default_envs = release
;default_envs = debug ;default_envs = debug
[common] [common]
debug_flags = -Wall -Wextra -Werror -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-variable -DTESTS ; -DMYESP_TIMESTAMP -DTESTS -DCRASH -DFORCE_SERIAL -DNO_GLOBAL_EEPROM -DLOGICANALYZER
general_flags = -g -w -DNO_GLOBAL_EEPROM extra_flags = -DNO_GLOBAL_EEPROM
arduino_core_2_3_0 = espressif8266@1.5.0
arduino_core_2_4_0 = espressif8266@1.6.0
arduino_core_2_4_1 = espressif8266@1.7.3
arduino_core_2_4_2 = espressif8266@1.8.0
arduino_core_2_5_0 = espressif8266@2.0.4
arduino_core_2_5_1 = espressif8266@2.1.1
arduino_core_2_5_2 = espressif8266@2.2.1
arduino_core_latest = espressif8266
[env] [env]
board = d1_mini board = d1_mini
; board = nodemcuv2 ; board = nodemcuv2
; board = d1_mini_pro ; board = d1_mini_pro
framework = arduino framework = arduino
platform = ${common.arduino_core_latest} platform = espressif8266
lib_deps = lib_deps =
CRC32 CRC32
CircularBuffer CircularBuffer
@@ -41,16 +31,17 @@ monitor_speed = 115200
;upload_port = ems-esp.local ;upload_port = ems-esp.local
[env:debug] [env:debug]
build_flags = ${common.general_flags} ${common.debug_flags} build_type = debug
build_flags = ${common.extra_flags} -DCRASH
extra_scripts = pre:scripts/rename_fw.py extra_scripts = pre:scripts/rename_fw.py
[env:clean] [env:clean]
extra_scripts = pre:scripts/clean_fw.py extra_scripts = pre:scripts/clean_fw.py
[env:release] [env:release]
build_flags = ${common.general_flags} build_flags = ${common.extra_flags}
extra_scripts = pre:scripts/rename_fw.py extra_scripts = pre:scripts/rename_fw.py
[env:checkcode] [env:checkcode]
build_flags = ${common.general_flags} build_flags = ${common.extra_flags}
extra_scripts = scripts/checkcode.py extra_scripts = scripts/checkcode.py

View File

@@ -17,4 +17,8 @@ import os
# 3fffffb0: feefeffe feefeffe 3ffe8558 40100b01 # 3fffffb0: feefeffe feefeffe 3ffe8558 40100b01
# <<<stack<<< # <<<stack<<<
call(['python', 'scripts/decoder.py', '-s', '-e', os.getcwd()+"/.pio/build/debug/firmware_d1_mini.elf", 'scripts/stackdmp.txt']) call(['python', 'scripts/decoder.py ', '-s', '-e', os.getcwd()+"/.pio/build/debug/firmware_d1_mini_debug.elf", 'scripts/stackdmp.txt'])
# example for linux:
# % cd EMS-ESP
# % python scripts/decoder_linux.py -s -e .pio/build/debug/firmware_d1_mini.elf scripts/stackdmp.txt

307
scripts/decoder_linux.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")
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

@@ -1,8 +1,26 @@
#!/usr/bin/env python #!/usr/bin/env python
from subprocess import call import re
import os
Import("env") Import("env")
# see http://docs.platformio.org/en/latest/projectconf/advanced_scripting.html#before-pre-and-after-post-actions bag = {}
# env.Replace(PROGNAME="firmware_%s" % defines.get("VERSION")) exprs = [
env.Replace(PROGNAME="firmware_%s" % env['BOARD']) (re.compile(r'^#define APP_VERSION\s+"(\S+)"'), 'app_version'),
(re.compile(r'^#define APP_NAME\s+"(\S+)"'), 'app_name'),
(re.compile(r'^#define APP_HOSTNAME\s+"(\S+)"'), 'app_hostname')
]
with open('./src/version.h', 'r') as f:
for l in f.readlines():
for expr, var in exprs:
m = expr.match(l)
if m and len(m.groups()) > 0:
bag[var] = m.group(1)
app_version = bag.get('app_version')
app_name = bag.get('app_name')
app_hostname = bag.get('app_hostname')
board = env['BOARD']
branch = env['PIOENV']
# build filename, replacing . with _ for the version
env.Replace(PROGNAME="firmware_%s" % branch + "_" + app_version.replace(".", "_"))

View File

@@ -11,7 +11,7 @@
std::vector<ds_device_t> _devices; std::vector<ds_device_t> _devices;
DS18::DS18() { DS18::DS18() {
_wire = NULL; _wire = nullptr;
_count = 0; _count = 0;
_gpio = GPIO_NONE; _gpio = GPIO_NONE;
_parasite = 0; _parasite = 0;

File diff suppressed because it is too large Load Diff

View File

@@ -63,6 +63,7 @@ void _process_SM100Energy(_EMS_RxTelegram * EMS_RxTelegram);
// ISM1 // ISM1
void _process_ISM1StatusMessage(_EMS_RxTelegram * EMS_RxTelegram); void _process_ISM1StatusMessage(_EMS_RxTelegram * EMS_RxTelegram);
void _process_ISM1Set(_EMS_RxTelegram * EMS_RxTelegram);
// HeatPump HP // HeatPump HP
void _process_HPMonitor1(_EMS_RxTelegram * EMS_RxTelegram); void _process_HPMonitor1(_EMS_RxTelegram * EMS_RxTelegram);
@@ -116,19 +117,22 @@ const _EMS_Type EMS_Types[] = {
{EMS_MODEL_UBA, EMS_TYPE_UBAMonitorWWMessage, "UBAMonitorWWMessage", _process_UBAMonitorWWMessage}, {EMS_MODEL_UBA, EMS_TYPE_UBAMonitorWWMessage, "UBAMonitorWWMessage", _process_UBAMonitorWWMessage},
{EMS_MODEL_UBA, EMS_TYPE_UBAParameterWW, "UBAParameterWW", _process_UBAParameterWW}, {EMS_MODEL_UBA, EMS_TYPE_UBAParameterWW, "UBAParameterWW", _process_UBAParameterWW},
{EMS_MODEL_UBA, EMS_TYPE_UBATotalUptimeMessage, "UBATotalUptimeMessage", _process_UBATotalUptimeMessage}, {EMS_MODEL_UBA, EMS_TYPE_UBATotalUptimeMessage, "UBATotalUptimeMessage", _process_UBATotalUptimeMessage},
{EMS_MODEL_UBA, EMS_TYPE_UBAMaintenanceSettingsMessage, "UBAMaintenanceSettingsMessage", NULL}, {EMS_MODEL_UBA, EMS_TYPE_UBAMaintenanceSettingsMessage, "UBAMaintenanceSettingsMessage", nullptr},
{EMS_MODEL_UBA, EMS_TYPE_UBAParametersMessage, "UBAParametersMessage", _process_UBAParametersMessage}, {EMS_MODEL_UBA, EMS_TYPE_UBAParametersMessage, "UBAParametersMessage", _process_UBAParametersMessage},
{EMS_MODEL_UBA, EMS_TYPE_UBASetPoints, "UBASetPoints", _process_SetPoints}, {EMS_MODEL_UBA, EMS_TYPE_UBASetPoints, "UBASetPoints", _process_SetPoints},
// Other devices // SM devices
{EMS_MODEL_OTHER, EMS_TYPE_SM10Monitor, "SM10Monitor", _process_SM10Monitor}, {EMS_MODEL_SM, EMS_TYPE_SM10Monitor, "SM10Monitor", _process_SM10Monitor},
{EMS_MODEL_OTHER, EMS_TYPE_SM100Monitor, "SM100Monitor", _process_SM100Monitor}, {EMS_MODEL_SM, EMS_TYPE_SM100Monitor, "SM100Monitor", _process_SM100Monitor},
{EMS_MODEL_OTHER, EMS_TYPE_SM100Status, "SM100Status", _process_SM100Status}, {EMS_MODEL_SM, EMS_TYPE_SM100Status, "SM100Status", _process_SM100Status},
{EMS_MODEL_OTHER, EMS_TYPE_SM100Status2, "SM100Status2", _process_SM100Status2}, {EMS_MODEL_SM, EMS_TYPE_SM100Status2, "SM100Status2", _process_SM100Status2},
{EMS_MODEL_OTHER, EMS_TYPE_SM100Energy, "SM100Energy", _process_SM100Energy}, {EMS_MODEL_SM, EMS_TYPE_SM100Energy, "SM100Energy", _process_SM100Energy},
{EMS_MODEL_OTHER, EMS_TYPE_HPMonitor1, "HeatPumpMonitor1", _process_HPMonitor1}, {EMS_MODEL_SM, EMS_TYPE_ISM1StatusMessage, "ISM1StatusMessage", _process_ISM1StatusMessage},
{EMS_MODEL_OTHER, EMS_TYPE_HPMonitor2, "HeatPumpMonitor2", _process_HPMonitor2}, {EMS_MODEL_SM, EMS_TYPE_ISM1Set, "ISM1Set", _process_ISM1Set},
{EMS_MODEL_OTHER, EMS_TYPE_ISM1StatusMessage, "ISM1StatusMessage", _process_ISM1StatusMessage},
// heatpunps
{EMS_MODEL_HP, EMS_TYPE_HPMonitor1, "HeatPumpMonitor1", _process_HPMonitor1},
{EMS_MODEL_HP, EMS_TYPE_HPMonitor2, "HeatPumpMonitor2", _process_HPMonitor2},
// RC10 // RC10
{EMS_MODEL_RC10, EMS_TYPE_RCTime, "RCTime", _process_RCTime}, {EMS_MODEL_RC10, EMS_TYPE_RCTime, "RCTime", _process_RCTime},
@@ -184,12 +188,16 @@ const _EMS_Type EMS_Types[] = {
// calculate sizes of arrays at compile // calculate sizes of arrays at compile
uint8_t _EMS_Types_max = ArraySize(EMS_Types); // number of defined types uint8_t _EMS_Types_max = ArraySize(EMS_Types); // number of defined types
uint8_t _Boiler_Types_max = ArraySize(Boiler_Types); // number of boiler models uint8_t _Boiler_Types_max = ArraySize(Boiler_Types); // number of boiler models
uint8_t _Solar_Module_Types_max = ArraySize(SolarModule_Types); // number of solar module types
uint8_t _Other_Types_max = ArraySize(Other_Types); // number of other ems devices uint8_t _Other_Types_max = ArraySize(Other_Types); // number of other ems devices
uint8_t _Thermostat_Types_max = ArraySize(Thermostat_Types); // number of defined thermostat types uint8_t _Thermostat_Types_max = ArraySize(Thermostat_Types); // number of defined thermostat types
uint8_t _HeatPump_Types_max = ArraySize(HeatPump_Types); // number of defined heatpuimp types
// these structs contain the data we store from the Boiler and Thermostat // these structs contain the data we store from the specific EMS devices
_EMS_Boiler EMS_Boiler; // for boiler _EMS_Boiler EMS_Boiler; // for boiler
_EMS_Thermostat EMS_Thermostat; // for thermostat _EMS_Thermostat EMS_Thermostat; // for thermostat
_EMS_SolarModule EMS_SolarModule; // for solar modules
_EMS_HeatPump EMS_HeatPump; // for heatpumps
_EMS_Other EMS_Other; // for other known EMS devices _EMS_Other EMS_Other; // for other known EMS devices
// CRC lookup table with poly 12 for faster checking // CRC lookup table with poly 12 for faster checking
@@ -212,7 +220,6 @@ const uint32_t EMS_BUS_TIMEOUT = 15000; // timeout in ms before recogni
const uint32_t EMS_POLL_TIMEOUT = 5000000; // timeout in microseconds before recognizing the ems bus is offline (5 seconds) const uint32_t EMS_POLL_TIMEOUT = 5000000; // timeout in microseconds before recognizing the ems bus is offline (5 seconds)
// init stats and counters and buffers // init stats and counters and buffers
// uses -255 or 255 for values that haven't been set yet (EMS_VALUE_INT_NOTSET and EMS_VALUE_FLOAT_NOTSET)
void ems_init() { void ems_init() {
// overall status // overall status
EMS_Sys_Status.emsRxPgks = 0; EMS_Sys_Status.emsRxPgks = 0;
@@ -229,6 +236,7 @@ void ems_init() {
EMS_Sys_Status.emsPollFrequency = 0; EMS_Sys_Status.emsPollFrequency = 0;
EMS_Sys_Status.txRetryCount = 0; EMS_Sys_Status.txRetryCount = 0;
EMS_Sys_Status.emsReverse = false; EMS_Sys_Status.emsReverse = false;
EMS_Sys_Status.emsTxMode = 0;
// thermostat // thermostat
EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_SHORT_NOTSET; EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_SHORT_NOTSET;
@@ -239,8 +247,8 @@ void ems_init() {
EMS_Thermostat.day = 0; EMS_Thermostat.day = 0;
EMS_Thermostat.month = 0; EMS_Thermostat.month = 0;
EMS_Thermostat.year = 0; EMS_Thermostat.year = 0;
EMS_Thermostat.mode = 255; // dummy value EMS_Thermostat.mode = EMS_VALUE_INT_NOTSET;
EMS_Thermostat.day_mode = 255; // dummy value EMS_Thermostat.day_mode = EMS_VALUE_INT_NOTSET;
EMS_Thermostat.device_id = EMS_ID_NONE; EMS_Thermostat.device_id = EMS_ID_NONE;
EMS_Thermostat.write_supported = false; EMS_Thermostat.write_supported = false;
EMS_Thermostat.hc = 1; // default heating circuit is 1 EMS_Thermostat.hc = 1; // default heating circuit is 1
@@ -260,8 +268,8 @@ void ems_init() {
// UBAMonitorFast // UBAMonitorFast
EMS_Boiler.selFlowTemp = EMS_VALUE_INT_NOTSET; // Selected flow temperature EMS_Boiler.selFlowTemp = EMS_VALUE_INT_NOTSET; // Selected flow temperature
EMS_Boiler.curFlowTemp = EMS_VALUE_SHORT_NOTSET; // Current flow temperature EMS_Boiler.curFlowTemp = EMS_VALUE_USHORT_NOTSET; // Current flow temperature
EMS_Boiler.retTemp = EMS_VALUE_SHORT_NOTSET; // Return temperature EMS_Boiler.retTemp = EMS_VALUE_USHORT_NOTSET; // Return temperature
EMS_Boiler.burnGas = EMS_VALUE_INT_NOTSET; // Gas on/off EMS_Boiler.burnGas = EMS_VALUE_INT_NOTSET; // Gas on/off
EMS_Boiler.fanWork = EMS_VALUE_INT_NOTSET; // Fan on/off EMS_Boiler.fanWork = EMS_VALUE_INT_NOTSET; // Fan on/off
EMS_Boiler.ignWork = EMS_VALUE_INT_NOTSET; // Ignition on/off EMS_Boiler.ignWork = EMS_VALUE_INT_NOTSET; // Ignition on/off
@@ -277,14 +285,14 @@ void ems_init() {
// UBAMonitorSlow // UBAMonitorSlow
EMS_Boiler.extTemp = EMS_VALUE_SHORT_NOTSET; // Outside temperature EMS_Boiler.extTemp = EMS_VALUE_SHORT_NOTSET; // Outside temperature
EMS_Boiler.boilTemp = EMS_VALUE_SHORT_NOTSET; // Boiler temperature EMS_Boiler.boilTemp = EMS_VALUE_USHORT_NOTSET; // Boiler temperature
EMS_Boiler.pumpMod = EMS_VALUE_INT_NOTSET; // Pump modulation EMS_Boiler.pumpMod = EMS_VALUE_INT_NOTSET; // Pump modulation
EMS_Boiler.burnStarts = EMS_VALUE_LONG_NOTSET; // # burner restarts EMS_Boiler.burnStarts = EMS_VALUE_LONG_NOTSET; // # burner restarts
EMS_Boiler.burnWorkMin = EMS_VALUE_LONG_NOTSET; // Total burner operating time EMS_Boiler.burnWorkMin = EMS_VALUE_LONG_NOTSET; // Total burner operating time
EMS_Boiler.heatWorkMin = EMS_VALUE_LONG_NOTSET; // Total heat operating time EMS_Boiler.heatWorkMin = EMS_VALUE_LONG_NOTSET; // Total heat operating time
// UBAMonitorWWMessage // UBAMonitorWWMessage
EMS_Boiler.wWCurTmp = EMS_VALUE_SHORT_NOTSET; // Warm Water current temperature EMS_Boiler.wWCurTmp = EMS_VALUE_USHORT_NOTSET; // Warm Water current temperature
EMS_Boiler.wWStarts = EMS_VALUE_LONG_NOTSET; // Warm Water # starts EMS_Boiler.wWStarts = EMS_VALUE_LONG_NOTSET; // Warm Water # starts
EMS_Boiler.wWWorkM = EMS_VALUE_LONG_NOTSET; // Warm Water # minutes EMS_Boiler.wWWorkM = EMS_VALUE_LONG_NOTSET; // Warm Water # minutes
EMS_Boiler.wWOneTime = EMS_VALUE_INT_NOTSET; // Warm Water one time function on/off EMS_Boiler.wWOneTime = EMS_VALUE_INT_NOTSET; // Warm Water one time function on/off
@@ -298,16 +306,26 @@ void ems_init() {
EMS_Boiler.pump_mod_max = EMS_VALUE_INT_NOTSET; // Boiler circuit pump modulation max. power EMS_Boiler.pump_mod_max = EMS_VALUE_INT_NOTSET; // Boiler circuit pump modulation max. power
EMS_Boiler.pump_mod_min = EMS_VALUE_INT_NOTSET; // Boiler circuit pump modulation min. power EMS_Boiler.pump_mod_min = EMS_VALUE_INT_NOTSET; // Boiler circuit pump modulation min. power
// Solar Module values
EMS_SolarModule.collectorTemp = EMS_VALUE_SHORT_NOTSET; // collector temp from SM10/SM100
EMS_SolarModule.bottomTemp = EMS_VALUE_SHORT_NOTSET; // bottom temp from SM10/SM100
EMS_SolarModule.pumpModulation = EMS_VALUE_INT_NOTSET; // modulation solar pump SM10/SM100
EMS_SolarModule.pump = EMS_VALUE_INT_NOTSET; // pump active
EMS_SolarModule.EnergyLastHour = EMS_VALUE_USHORT_NOTSET;
EMS_SolarModule.EnergyToday = EMS_VALUE_USHORT_NOTSET;
EMS_SolarModule.EnergyTotal = EMS_VALUE_USHORT_NOTSET;
EMS_SolarModule.device_id = EMS_ID_NONE;
EMS_SolarModule.model_id = EMS_MODEL_NONE;
EMS_SolarModule.product_id = EMS_ID_NONE;
EMS_SolarModule.pumpWorkMin = EMS_VALUE_LONG_NOTSET;
EMS_SolarModule.setpoint_maxBottomTemp = EMS_VALUE_SHORT_NOTSET;
// Other EMS devices values // Other EMS devices values
EMS_Other.SMcollectorTemp = EMS_VALUE_SHORT_NOTSET; // collector temp from SM10/SM100 EMS_HeatPump.HPModulation = EMS_VALUE_INT_NOTSET;
EMS_Other.SMbottomTemp = EMS_VALUE_SHORT_NOTSET; // bottom temp from SM10/SM100 EMS_HeatPump.HPSpeed = EMS_VALUE_INT_NOTSET;
EMS_Other.SMpumpModulation = EMS_VALUE_INT_NOTSET; // modulation solar pump SM10/SM100 EMS_HeatPump.device_id = EMS_ID_NONE;
EMS_Other.SMpump = EMS_VALUE_INT_NOTSET; // pump active EMS_HeatPump.model_id = EMS_MODEL_NONE;
EMS_Other.SMEnergyLastHour = EMS_VALUE_SHORT_NOTSET; EMS_HeatPump.product_id = EMS_ID_NONE;
EMS_Other.SMEnergyToday = EMS_VALUE_SHORT_NOTSET;
EMS_Other.SMEnergyTotal = EMS_VALUE_SHORT_NOTSET;
EMS_Other.HPModulation = EMS_VALUE_INT_NOTSET;
EMS_Other.HPSpeed = EMS_VALUE_INT_NOTSET;
// calculated values // calculated values
EMS_Boiler.tapwaterActive = EMS_VALUE_INT_NOTSET; // Hot tap water is on/off EMS_Boiler.tapwaterActive = EMS_VALUE_INT_NOTSET; // Hot tap water is on/off
@@ -322,9 +340,6 @@ void ems_init() {
EMS_Thermostat.product_id = EMS_ID_NONE; EMS_Thermostat.product_id = EMS_ID_NONE;
strlcpy(EMS_Thermostat.version, "?", sizeof(EMS_Thermostat.version)); strlcpy(EMS_Thermostat.version, "?", sizeof(EMS_Thermostat.version));
// set other types
EMS_Other.SM = false;
EMS_Other.HP = false;
// default logging is none // default logging is none
ems_setLogging(EMS_SYS_LOGGING_DEFAULT); ems_setLogging(EMS_SYS_LOGGING_DEFAULT);
@@ -340,6 +355,23 @@ bool ems_getPoll() {
return EMS_Sys_Status.emsPollEnabled; return EMS_Sys_Status.emsPollEnabled;
} }
void ems_setTxMode(uint8_t mode) {
EMS_Sys_Status.emsTxMode = mode;
// special case for Junkers. If tx_mode is 3 then set the reverse poll flag
// https://github.com/proddy/EMS-ESP/issues/103#issuecomment-495945850
if (mode == 3) {
EMS_Sys_Status.emsReverse = true;
myDebug_P(PSTR("Forcing emsReverse for Junkers"));
} else {
EMS_Sys_Status.emsReverse = false;
}
}
uint8_t ems_getTxMode() {
return EMS_Sys_Status.emsTxMode;
}
bool ems_getEmsRefreshed() { bool ems_getEmsRefreshed() {
return EMS_Sys_Status.emsRefreshed; return EMS_Sys_Status.emsRefreshed;
} }
@@ -360,14 +392,30 @@ bool ems_getThermostatEnabled() {
return (EMS_Thermostat.device_id != EMS_ID_NONE); return (EMS_Thermostat.device_id != EMS_ID_NONE);
} }
bool ems_getSolarModuleEnabled() {
return (EMS_SolarModule.device_id != EMS_ID_NONE);
}
bool ems_getHeatPumpEnabled() {
return (EMS_HeatPump.device_id != EMS_ID_NONE);
}
uint8_t ems_getThermostatModel() { uint8_t ems_getThermostatModel() {
return (EMS_Thermostat.model_id); return (EMS_Thermostat.model_id);
} }
uint8_t ems_getSolarModuleModel() {
return (EMS_SolarModule.model_id);
}
void ems_setTxDisabled(bool b) { void ems_setTxDisabled(bool b) {
EMS_Sys_Status.emsTxDisabled = b; EMS_Sys_Status.emsTxDisabled = b;
} }
bool ems_getTxDisabled() {
return (EMS_Sys_Status.emsTxDisabled);
}
uint32_t ems_getPollFrequency() { uint32_t ems_getPollFrequency() {
return EMS_Sys_Status.emsPollFrequency; return EMS_Sys_Status.emsPollFrequency;
} }
@@ -401,6 +449,8 @@ void ems_setLogging(_EMS_SYS_LOGGING loglevel) {
myDebug_P(PSTR("System Logging set to Verbose")); myDebug_P(PSTR("System Logging set to Verbose"));
} else if (loglevel == EMS_SYS_LOGGING_THERMOSTAT) { } else if (loglevel == EMS_SYS_LOGGING_THERMOSTAT) {
myDebug_P(PSTR("System Logging set to Thermostat only")); myDebug_P(PSTR("System Logging set to Thermostat only"));
} else if (loglevel == EMS_SYS_LOGGING_SOLARMODULE) {
myDebug_P(PSTR("System Logging set to Solar Module only"));
} else if (loglevel == EMS_SYS_LOGGING_RAW) { } else if (loglevel == EMS_SYS_LOGGING_RAW) {
myDebug_P(PSTR("System Logging set to Raw mode")); myDebug_P(PSTR("System Logging set to Raw mode"));
} }
@@ -457,7 +507,7 @@ char * _smallitoa3(uint16_t value, char * buffer) {
* Find the pointer to the EMS_Types array for a given type ID * Find the pointer to the EMS_Types array for a given type ID
* or -1 if not found * or -1 if not found
*/ */
int _ems_findType(uint8_t type) { int _ems_findType(uint16_t type) {
uint8_t i = 0; uint8_t i = 0;
bool typeFound = false; bool typeFound = false;
// scan through known ID types // scan through known ID types
@@ -580,19 +630,27 @@ void _ems_sendTelegram() {
// for a READ or VALIDATE // for a READ or VALIDATE
EMS_TxTelegram.data[1] = EMS_TxTelegram.dest | 0x80; // read has 8th bit set EMS_TxTelegram.data[1] = EMS_TxTelegram.dest | 0x80; // read has 8th bit set
} }
// complete the rest of the header depending on EMS or EMS+
if (EMS_TxTelegram.type > 0xFF) {
// EMS 2.0 / emsplus
EMS_TxTelegram.data[2] = 0xFF; // fixed value indicating an extended message
EMS_TxTelegram.data[3] = EMS_TxTelegram.offset;
EMS_TxTelegram.data[4] = EMS_TxTelegram.dataValue; // for read its #bytes to return, for write it the value to set
EMS_TxTelegram.data[5] = EMS_TxTelegram.type >> 8; // type, 1st byte
EMS_TxTelegram.data[6] = EMS_TxTelegram.type & 0xFF; // type, 2nd byte
EMS_TxTelegram.length += 2; // add 2 bytes to length to compensate the extra FF and byte for the type
} else {
// EMS 1.0
EMS_TxTelegram.data[2] = EMS_TxTelegram.type; // type EMS_TxTelegram.data[2] = EMS_TxTelegram.type; // type
EMS_TxTelegram.data[3] = EMS_TxTelegram.offset; // offset EMS_TxTelegram.data[3] = EMS_TxTelegram.offset; // offset
// see if it has data, add the single data value byte
// otherwise leave it alone and assume the data has been pre-populated
if (EMS_TxTelegram.length == EMS_MIN_TELEGRAM_LENGTH) { if (EMS_TxTelegram.length == EMS_MIN_TELEGRAM_LENGTH) {
// for reading this is #bytes we want to read (the size) EMS_TxTelegram.data[4] = EMS_TxTelegram.dataValue; // for read its #bytes to return, for write it the value to set
// for writing its the value we want to write
EMS_TxTelegram.data[4] = EMS_TxTelegram.dataValue;
} }
}
// finally calculate CRC and add it to the end // finally calculate CRC and add it to the end
uint8_t crc = _crcCalculator(EMS_TxTelegram.data, EMS_TxTelegram.length); EMS_TxTelegram.data[EMS_TxTelegram.length - 1] = _crcCalculator(EMS_TxTelegram.data, EMS_TxTelegram.length);
EMS_TxTelegram.data[EMS_TxTelegram.length - 1] = crc;
// print debug info // print debug info
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) { if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) {
@@ -669,17 +727,27 @@ void _createValidate() {
* When a telegram is processed we forcefully erase it from the stack to prevent overflow * When a telegram is processed we forcefully erase it from the stack to prevent overflow
*/ */
void ems_parseTelegram(uint8_t * telegram, uint8_t length) { void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
// create the Rx package
static _EMS_RxTelegram EMS_RxTelegram;
static uint32_t _last_emsPollFrequency = 0; static uint32_t _last_emsPollFrequency = 0;
EMS_RxTelegram.telegram = telegram; /*
EMS_RxTelegram.timestamp = millis(); * check if we just received a single byte
EMS_RxTelegram.length = length; * it could well be a Poll request from the boiler for us, which will have a value of 0x8B (0x0B | 0x80)
* or either a return code like 0x01 or 0x04 from the last Write command
* Roger Wilco: we have different types here:
* EMS_ID_ME && length == 1 && EMS_TX_STATUS_IDLE && EMS_RX_STATUS_IDLE: polling request
* EMS_ID_ME && length > 1 && EMS_TX_STATUS_IDLE && EMS_RX_STATUS_IDLE: direct telegram
* (EMS_TX_SUCCESS || EMS_TX_ERROR) && EMS_TX_STATUS_WAIT: response, free the EMS bus
*
* In addition, it may happen that we where interrupted (f.e. by WIFI activity) and the
* buffer isn't valid anymore, so we must not answer at all...
*/
if (EMS_Sys_Status.emsRxStatus != EMS_RX_STATUS_IDLE) {
if (EMS_Sys_Status.emsLogging > EMS_SYS_LOGGING_NONE) {
myDebug_P(PSTR("** [DEBUG MODE] We missed the bus - Rx non-idle!")); //TODO tidy up error logging
}
return;
}
// check if we just received a single byte
// it could well be a Poll request from the boiler for us, which will have a value of 0x8B (0x0B | 0x80)
// or either a return code like 0x01 or 0x04 from the last Write command
if (length == 1) { if (length == 1) {
uint8_t value = telegram[0]; // 1st byte of data package uint8_t value = telegram[0]; // 1st byte of data package
@@ -728,7 +796,11 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
return; return;
} }
// fill in the rest of the telegram static _EMS_RxTelegram EMS_RxTelegram; // create the Rx package
EMS_RxTelegram.telegram = telegram;
EMS_RxTelegram.timestamp = millis();
EMS_RxTelegram.length = length;
EMS_RxTelegram.src = telegram[0] & 0x7F; // removing 8th bit as we deal with both reads and writes here EMS_RxTelegram.src = telegram[0] & 0x7F; // removing 8th bit as we deal with both reads and writes here
EMS_RxTelegram.dest = telegram[1] & 0x7F; // remove 8th bit (don't care if read or write) EMS_RxTelegram.dest = telegram[1] & 0x7F; // remove 8th bit (don't care if read or write)
EMS_RxTelegram.offset = telegram[3]; // offset is always 4th byte EMS_RxTelegram.offset = telegram[3]; // offset is always 4th byte
@@ -766,9 +838,16 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
EMS_RxTelegram.data_length = length - 5; // remove 4 bytes header plus CRC EMS_RxTelegram.data_length = length - 5; // remove 4 bytes header plus CRC
} }
// if we are in raw logging mode then just print out the telegram as it is
// but still continue to process it
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_RAW) {
_debugPrintTelegram("", &EMS_RxTelegram, COLOR_WHITE, true);
}
// Assume at this point we have something that vaguely resembles a telegram in the format [src] [dest] [type] [offset] [data] [crc] // Assume at this point we have something that vaguely resembles a telegram in the format [src] [dest] [type] [offset] [data] [crc]
// validate the CRC, if it's bad ignore it // validate the CRC, if it's bad ignore it
if (telegram[length - 1] != _crcCalculator(telegram, length)) { if (telegram[length - 1] != _crcCalculator(telegram, length)) {
LA_PULSE(200);
EMS_Sys_Status.emxCrcErr++; EMS_Sys_Status.emxCrcErr++;
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) { if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) {
_debugPrintTelegram("Corrupt telegram: ", &EMS_RxTelegram, COLOR_RED, true); _debugPrintTelegram("Corrupt telegram: ", &EMS_RxTelegram, COLOR_RED, true);
@@ -776,12 +855,6 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
return; return;
} }
// if we are in raw logging mode then just print out the telegram as it is
// but still continue to process it
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_RAW) {
_debugPrintTelegram("", &EMS_RxTelegram, COLOR_WHITE, true);
}
// here we know its a valid incoming telegram of at least 6 bytes // here we know its a valid incoming telegram of at least 6 bytes
// we use this to see if we always have a connection to the boiler, in case of drop outs // we use this to see if we always have a connection to the boiler, in case of drop outs
EMS_Sys_Status.emsRxTimestamp = EMS_RxTelegram.timestamp; // timestamp of last read EMS_Sys_Status.emsRxTimestamp = EMS_RxTelegram.timestamp; // timestamp of last read
@@ -799,6 +872,7 @@ void _printMessage(_EMS_RxTelegram * EMS_RxTelegram) {
uint8_t src = EMS_RxTelegram->src; uint8_t src = EMS_RxTelegram->src;
uint8_t dest = EMS_RxTelegram->dest; uint8_t dest = EMS_RxTelegram->dest;
uint16_t type = EMS_RxTelegram->type; uint16_t type = EMS_RxTelegram->type;
uint8_t length = EMS_RxTelegram->data_length;
char output_str[200] = {0}; char output_str[200] = {0};
char buffer[16] = {0}; char buffer[16] = {0};
@@ -850,6 +924,7 @@ void _printMessage(_EMS_RxTelegram * EMS_RxTelegram) {
strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s)); strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s));
} }
if (length != 0) {
// type // type
strlcat(output_str, ", type 0x", sizeof(output_str)); strlcat(output_str, ", type 0x", sizeof(output_str));
@@ -859,18 +934,28 @@ void _printMessage(_EMS_RxTelegram * EMS_RxTelegram) {
} else { } else {
strlcat(output_str, _hextoa(type, buffer), sizeof(output_str)); strlcat(output_str, _hextoa(type, buffer), sizeof(output_str));
} }
}
strlcat(output_str, ", ", sizeof(output_str));
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_THERMOSTAT) { if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_THERMOSTAT) {
// only print ones to/from thermostat if logging is set to thermostat only // only print ones to/from thermostat if logging is set to thermostat only
if ((src == EMS_Thermostat.device_id) || (dest == EMS_Thermostat.device_id)) { if ((src == EMS_Thermostat.device_id) || (dest == EMS_Thermostat.device_id)) {
_debugPrintTelegram(output_str, EMS_RxTelegram, color_s); _debugPrintTelegram(output_str, EMS_RxTelegram, color_s);
} }
} else if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_SOLARMODULE) {
// only print ones to/from thermostat if logging is set to thermostat only
if ((src == EMS_SolarModule.device_id) || (dest == EMS_SolarModule.device_id)) {
_debugPrintTelegram(output_str, EMS_RxTelegram, color_s);
}
} else { } else {
// always print // always print
_debugPrintTelegram(output_str, EMS_RxTelegram, color_s); _debugPrintTelegram(output_str, EMS_RxTelegram, color_s);
} }
} }
/** /**
* print detailed telegram * print detailed telegram
* and then call its callback if there is one defined * and then call its callback if there is one defined
@@ -909,7 +994,7 @@ void _ems_processTelegram(_EMS_RxTelegram * EMS_RxTelegram) {
// if it's a common type (across ems devices) or something specifically for us process it. // 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 // dest will be EMS_ID_NONE and offset 0x00 for a broadcast message
if (typeFound) { if (typeFound) {
if ((EMS_Types[i].processType_cb) != (void *)NULL) { if ((EMS_Types[i].processType_cb) != nullptr) {
// print non-verbose message // print non-verbose message
if ((EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_BASIC) || (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE)) { if ((EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_BASIC) || (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE)) {
myDebug_P(PSTR("<--- %s(0x%02X)"), EMS_Types[i].typeString, type); myDebug_P(PSTR("<--- %s(0x%02X)"), EMS_Types[i].typeString, type);
@@ -990,10 +1075,15 @@ void _processType(_EMS_RxTelegram * EMS_RxTelegram) {
if (((EMS_RxTelegram->src & 0x7F) == (EMS_TxTelegram.dest & 0x7F)) && (EMS_RxTelegram->type == EMS_TxTelegram.type)) { if (((EMS_RxTelegram->src & 0x7F) == (EMS_TxTelegram.dest & 0x7F)) && (EMS_RxTelegram->type == EMS_TxTelegram.type)) {
// all checks out, read was successful, remove tx from queue and continue to process telegram // all checks out, read was successful, remove tx from queue and continue to process telegram
_removeTxQueue(); _removeTxQueue();
EMS_Sys_Status.emsRxPgks++; // increment counter EMS_Sys_Status.emsRxPgks++; // increment Rx happy counter
ems_setEmsRefreshed(EMS_TxTelegram.forceRefresh); // does mqtt need refreshing? ems_setEmsRefreshed(EMS_TxTelegram.forceRefresh); // does mqtt need refreshing?
} else { } else {
// read not OK, we didn't get back a telegram we expected // read not OK, we didn't get back a telegram we expected
// first see if we got a response back from the sender saying its an unknown command
if (EMS_RxTelegram->data_length == 0) {
_removeTxQueue();
} else {
// leave on queue and try again, but continue to process what we received as it may be important // leave on queue and try again, but continue to process what we received as it may be important
EMS_Sys_Status.txRetryCount++; EMS_Sys_Status.txRetryCount++;
// if retried too many times, give up and remove it // if retried too many times, give up and remove it
@@ -1008,6 +1098,7 @@ void _processType(_EMS_RxTelegram * EMS_RxTelegram) {
} }
} }
} }
}
_ems_processTelegram(EMS_RxTelegram); // process it always _ems_processTelegram(EMS_RxTelegram); // process it always
} }
@@ -1173,7 +1264,7 @@ void _process_UBAMonitorSlow(_EMS_RxTelegram * EMS_RxTelegram) {
*/ */
void _process_RC10StatusMessage(_EMS_RxTelegram * EMS_RxTelegram) { void _process_RC10StatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Thermostat.setpoint_roomTemp = _toByte(EMS_OFFSET_RC10StatusMessage_setpoint); // is * 2 EMS_Thermostat.setpoint_roomTemp = _toByte(EMS_OFFSET_RC10StatusMessage_setpoint); // is * 2
EMS_Thermostat.curr_roomTemp = _toByte(EMS_OFFSET_RC10StatusMessage_curr); // is * 10 EMS_Thermostat.curr_roomTemp = _toShort(EMS_OFFSET_RC10StatusMessage_curr); // is * 10
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
} }
@@ -1247,9 +1338,7 @@ void _process_RCPLUSStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Thermostat.day_mode = _bitRead(EMS_OFFSET_RCPLUSGet_mode_day, 1); // get day mode flag EMS_Thermostat.day_mode = _bitRead(EMS_OFFSET_RCPLUSGet_mode_day, 1); // get day mode flag
// room night setpoint is _toByte(2) (value is *2) EMS_Thermostat.mode = _bitRead(EMS_OFFSET_RCPLUSStatusMessage_mode, 0); // bit 1, mode (auto=1 or manual=0)
// boiler set temp is _toByte(4) (value is *2)
// day night is byte(8), 0x01 for night, 0x00 for day
} }
// actual set point // actual set point
@@ -1263,6 +1352,13 @@ void _process_RCPLUSStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
if (EMS_RxTelegram->offset == 6) { if (EMS_RxTelegram->offset == 6) {
// to add... // to add...
} }
// thermostat mode auto/manual, examples:
// manual : 10 00 FF 0A 01 A5 02 (CRC=16) #data=1
// auto : Thermostat -> all, type 0x01A5 telegram: 10 00 FF 0A 01 A5 03 (CRC=17) #data=1
if (EMS_RxTelegram->offset == EMS_OFFSET_RCPLUSStatusMessage_mode) {
EMS_Thermostat.mode = _bitRead(0, 0); // bit 0
}
} }
/** /**
@@ -1284,11 +1380,13 @@ void _process_RCPLUSStatusMode(_EMS_RxTelegram * EMS_RxTelegram) {
* FR10 Junkers - type x6F01 * FR10 Junkers - type x6F01
*/ */
void _process_JunkersStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) { void _process_JunkersStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
if (EMS_RxTelegram->offset == 0) {
// e.g. for FR10: 90 00 FF 00 00 6F 03 01 00 BE 00 BF // e.g. for FR10: 90 00 FF 00 00 6F 03 01 00 BE 00 BF
// e.g. for FW100: 90 00 FF 00 00 6F 03 02 00 D7 00 DA F3 34 00 C4 // e.g. for FW100: 90 00 FF 00 00 6F 03 02 00 D7 00 DA F3 34 00 C4
EMS_Thermostat.curr_roomTemp = _toShort(EMS_OFFSET_JunkersStatusMessage_curr); // value is * 10 EMS_Thermostat.curr_roomTemp = _toShort(EMS_OFFSET_JunkersStatusMessage_curr); // value is * 10
EMS_Thermostat.setpoint_roomTemp = _toShort(EMS_OFFSET_JunkersStatusMessage_setpoint); // value is * 10 EMS_Thermostat.setpoint_roomTemp = _toShort(EMS_OFFSET_JunkersStatusMessage_setpoint); // value is * 10
} }
}
/** /**
* to complete.... * to complete....
@@ -1348,12 +1446,11 @@ void _process_RCOutdoorTempMessage(_EMS_RxTelegram * EMS_RxTelegram) {
* SM10Monitor - type 0x97 * SM10Monitor - type 0x97
*/ */
void _process_SM10Monitor(_EMS_RxTelegram * EMS_RxTelegram) { void _process_SM10Monitor(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Other.SMcollectorTemp = _toShort(2); // collector temp from SM10, is *10 EMS_SolarModule.collectorTemp = _toShort(2); // collector temp from SM10, is *10
EMS_Other.SMbottomTemp = _toShort(5); // bottom temp from SM10, is *10 EMS_SolarModule.bottomTemp = _toShort(5); // bottom temp from SM10, is *10
EMS_Other.SMpumpModulation = _toByte(4); // modulation solar pump EMS_SolarModule.pumpModulation = _toByte(4); // modulation solar pump
EMS_Other.SMpump = _bitRead(7, 1); // active if bit 1 is set EMS_SolarModule.pump = _bitRead(7, 1); // active if bit 1 is set
EMS_Other.SM = true;
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
} }
@@ -1369,13 +1466,12 @@ void _process_SM100Monitor(_EMS_RxTelegram * EMS_RxTelegram) {
return; return;
} }
EMS_Other.SMcollectorTemp = _toShort(0); // collector temp from SM100, is *10 EMS_SolarModule.collectorTemp = _toShort(0); // collector temp from SM100, is *10
if (EMS_RxTelegram->data_length > 2) { if (EMS_RxTelegram->data_length > 2) {
EMS_Other.SMbottomTemp = _toShort(2); // bottom temp from SM100, is *10 EMS_SolarModule.bottomTemp = _toShort(2); // bottom temp from SM100, is *10
} }
EMS_Other.SM = true;
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
} }
@@ -1387,13 +1483,12 @@ void _process_SM100Monitor(_EMS_RxTelegram * EMS_RxTelegram) {
void _process_SM100Status(_EMS_RxTelegram * EMS_RxTelegram) { void _process_SM100Status(_EMS_RxTelegram * EMS_RxTelegram) {
// check for complete telegram // check for complete telegram
if (EMS_RxTelegram->offset == 0) { if (EMS_RxTelegram->offset == 0) {
EMS_Other.SMpumpModulation = _toByte(9); // modulation solar pump EMS_SolarModule.pumpModulation = _toByte(9); // modulation solar pump
} else if (EMS_RxTelegram->offset == 0x09) { } else if (EMS_RxTelegram->offset == 0x09) {
// or short telegram with a single byte with offset 09 // or short telegram with a single byte with offset 09
EMS_Other.SMpumpModulation = _toByte(0); // modulation solar pump EMS_SolarModule.pumpModulation = _toByte(0); // modulation solar pump
} }
EMS_Other.SM = true;
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
} }
@@ -1403,13 +1498,12 @@ void _process_SM100Status(_EMS_RxTelegram * EMS_RxTelegram) {
void _process_SM100Status2(_EMS_RxTelegram * EMS_RxTelegram) { void _process_SM100Status2(_EMS_RxTelegram * EMS_RxTelegram) {
// check for complete telegram // check for complete telegram
if (EMS_RxTelegram->offset == 0) { if (EMS_RxTelegram->offset == 0) {
EMS_Other.SMpump = _bitRead(10, 2); // 03=off 04=on at offset 10 which is byte 10 EMS_SolarModule.pump = _bitRead(10, 2); // 03=off 04=on at offset 10 which is byte 10
} else if (EMS_RxTelegram->offset == 0x0A) { } else if (EMS_RxTelegram->offset == 0x0A) {
// or short telegram with a single byte with offset 0A // or short telegram with a single byte with offset 0A
EMS_Other.SMpump = _bitRead(0, 2); // 03=off 04=on EMS_SolarModule.pump = _bitRead(0, 2); // 03=off 04=on
} }
EMS_Other.SM = true;
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
} }
@@ -1418,11 +1512,10 @@ void _process_SM100Status2(_EMS_RxTelegram * EMS_RxTelegram) {
* e.g. 30 00 FF 00 02 8E 00 00 00 00 00 00 06 C5 00 00 76 35 * e.g. 30 00 FF 00 02 8E 00 00 00 00 00 00 06 C5 00 00 76 35
*/ */
void _process_SM100Energy(_EMS_RxTelegram * EMS_RxTelegram) { void _process_SM100Energy(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Other.SMEnergyLastHour = _toShort(2); // last hour / 10 in Wh EMS_SolarModule.EnergyLastHour = _toShort(2); // last hour / 10 in Wh
EMS_Other.SMEnergyToday = _toShort(6); // todays in Wh EMS_SolarModule.EnergyToday = _toShort(6); // todays in Wh
EMS_Other.SMEnergyTotal = _toShort(10); // total / 10 in kWh EMS_SolarModule.EnergyTotal = _toShort(10); // total / 10 in kWh
EMS_Other.SM = true;
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
} }
@@ -1430,9 +1523,8 @@ void _process_SM100Energy(_EMS_RxTelegram * EMS_RxTelegram) {
* Type 0xE3 - HeatPump Monitor 1 * Type 0xE3 - HeatPump Monitor 1
*/ */
void _process_HPMonitor1(_EMS_RxTelegram * EMS_RxTelegram) { void _process_HPMonitor1(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Other.HPModulation = _toByte(14); // modulation % EMS_HeatPump.HPModulation = _toByte(14); // modulation %
EMS_Other.HP = true;
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
} }
@@ -1440,9 +1532,8 @@ void _process_HPMonitor1(_EMS_RxTelegram * EMS_RxTelegram) {
* Type 0xE5 - HeatPump Monitor 2 * Type 0xE5 - HeatPump Monitor 2
*/ */
void _process_HPMonitor2(_EMS_RxTelegram * EMS_RxTelegram) { void _process_HPMonitor2(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Other.HPSpeed = _toByte(25); // speed % EMS_HeatPump.HPSpeed = _toByte(25); // speed %
EMS_Other.HP = true;
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
} }
@@ -1450,10 +1541,31 @@ void _process_HPMonitor2(_EMS_RxTelegram * EMS_RxTelegram) {
* Junkers ISM1 Solar Module - type 0x0003 EMS+ for energy readings * Junkers ISM1 Solar Module - type 0x0003 EMS+ for energy readings
*/ */
void _process_ISM1StatusMessage(_EMS_RxTelegram * EMS_RxTelegram) { void _process_ISM1StatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
if (EMS_RxTelegram->offset == 0) {
// e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0 // e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0
EMS_Other.SMcollectorTemp = _toShort(4); // Collector Temperature EMS_SolarModule.collectorTemp = _toShort(4); // Collector Temperature
EMS_Other.SMbottomTemp = _toShort(6); // Temperature Bottom of Solar Boiler EMS_SolarModule.bottomTemp = _toShort(6); // Temperature Bottom of Solar Boiler
EMS_Other.SM = true; EMS_SolarModule.EnergyLastHour = _toShort(2); // Solar Energy produced in last hour - is * 10 and handled in ems-esp.cpp
EMS_SolarModule.pump = _bitRead(8, 0); // Solar pump on (1) or off (0)
EMS_SolarModule.pumpWorkMin = _toLong(10);
}
if (EMS_RxTelegram->offset == 4) {
// e.g. B0 00 FF 04 00 03 02 E5
EMS_SolarModule.collectorTemp = _toShort(0); // Collector Temperature
}
}
/*
* Junkers ISM1 Solar Module - type 0x0001 EMS+ for setting values
*/
void _process_ISM1Set(_EMS_RxTelegram * EMS_RxTelegram) {
if (EMS_RxTelegram->offset == 6) {
// e.g. 90 30 FF 06 00 01 50 (CRC=2C)
// to implement: change max solar boiler temperature
EMS_SolarModule.setpoint_maxBottomTemp = _toByte(0);
}
} }
/** /**
@@ -1508,7 +1620,6 @@ void ems_clearDeviceList() {
*/ */
void _addDevice(uint8_t product_id, uint8_t device_id, char * version, const char * model_string) { void _addDevice(uint8_t product_id, uint8_t device_id, char * version, const char * model_string) {
_Generic_Type device; _Generic_Type device;
// if its a duplicate don't add // if its a duplicate don't add
bool found = false; bool found = false;
for (std::list<_Generic_Type>::iterator it = Devices.begin(); it != Devices.end(); it++) { for (std::list<_Generic_Type>::iterator it = Devices.begin(); it != Devices.end(); it++) {
@@ -1516,7 +1627,6 @@ void _addDevice(uint8_t product_id, uint8_t device_id, char * version, const cha
found = true; found = true;
} }
} }
if (!found) { if (!found) {
device.product_id = product_id; device.product_id = product_id;
device.device_id = device_id; device.device_id = device_id;
@@ -1631,6 +1741,64 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
return; return;
} }
// look for Solar Modules
i = 0;
while (i < _Solar_Module_Types_max) {
if (SolarModule_Types[i].product_id == product_id) {
typeFound = true; // we have a matching product id. i is the index.
break;
}
i++;
}
if (typeFound) {
myDebug_P(PSTR("Solar Module found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"),
SolarModule_Types[i].model_string,
SolarModule_Types[i].device_id,
product_id,
version);
// add to list
_addDevice(product_id, SolarModule_Types[i].device_id, version, SolarModule_Types[i].model_string);
myDebug_P(PSTR("Solar Module support enabled."));
EMS_SolarModule.device_id = SolarModule_Types[i].device_id;
EMS_SolarModule.product_id = product_id;
strlcpy(EMS_SolarModule.version, version, sizeof(EMS_SolarModule.version));
// fetch Solar Module values
ems_getSolarModuleValues();
return;
}
// look for heatpumps
i = 0;
while (i < _HeatPump_Types_max) {
if (HeatPump_Types[i].product_id == product_id) {
typeFound = true; // we have a matching product id. i is the index.
break;
}
i++;
}
if (typeFound) {
myDebug_P(PSTR("Heat Pump found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"),
HeatPump_Types[i].model_string,
HeatPump_Types[i].device_id,
product_id,
version);
// add to list
_addDevice(product_id, HeatPump_Types[i].device_id, version, HeatPump_Types[i].model_string);
myDebug_P(PSTR("Heat Pump support enabled."));
EMS_HeatPump.device_id = SolarModule_Types[i].device_id;
EMS_HeatPump.product_id = product_id;
strlcpy(EMS_HeatPump.version, version, sizeof(EMS_HeatPump.version));
return;
}
// finally look for the other EMS devices // finally look for the other EMS devices
i = 0; i = 0;
while (i < _Other_Types_max) { while (i < _Other_Types_max) {
@@ -1646,26 +1814,9 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
// add to list // add to list
_addDevice(product_id, Other_Types[i].device_id, version, Other_Types[i].model_string); _addDevice(product_id, Other_Types[i].device_id, version, Other_Types[i].model_string);
// see if this is a Solar Module SM10
if (Other_Types[i].device_id == EMS_ID_SM) {
EMS_Other.SM = true; // we have detected a SM10
myDebug_P(PSTR("SM10 Solar Module support enabled."));
}
// see if this is a HeatPump
if (Other_Types[i].device_id == EMS_ID_HP) {
EMS_Other.HP = true; // we have detected a HP
myDebug_P(PSTR("HeatPump support enabled."));
}
// fetch other values
ems_getOtherValues();
return; return;
} else { } else {
myDebug_P(PSTR("Unrecognized device found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"), EMS_RxTelegram->src, product_id, version); myDebug_P(PSTR("Unrecognized device found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"), EMS_RxTelegram->src, product_id, version);
// add to list // add to list
_addDevice(product_id, EMS_RxTelegram->src, version, "unknown?"); _addDevice(product_id, EMS_RxTelegram->src, version, "unknown?");
} }
@@ -1774,22 +1925,32 @@ void ems_getThermostatValues() {
uint8_t type = EMS_Thermostat.device_id; uint8_t type = EMS_Thermostat.device_id;
uint8_t hc = EMS_Thermostat.hc; uint8_t hc = EMS_Thermostat.hc;
if (model_id == EMS_MODEL_RC20) { switch (model_id) {
ems_doReadCommand(EMS_TYPE_RC20StatusMessage, type); // to get the setpoint temp case EMS_MODEL_RC20:
ems_doReadCommand(EMS_TYPE_RC20StatusMessage, type); // to get the temps
ems_doReadCommand(EMS_TYPE_RC20Set, type); // to get the mode ems_doReadCommand(EMS_TYPE_RC20Set, type); // to get the mode
} else if (model_id == EMS_MODEL_RC30) { break;
ems_doReadCommand(EMS_TYPE_RC30StatusMessage, type); // to get the setpoint temp case EMS_MODEL_RC30:
ems_doReadCommand(EMS_TYPE_RC30StatusMessage, type); // to get the temps
ems_doReadCommand(EMS_TYPE_RC30Set, type); // to get the mode ems_doReadCommand(EMS_TYPE_RC30Set, type); // to get the mode
} else if ((model_id == EMS_MODEL_RC35) || (model_id == EMS_MODEL_ES73)) { break;
case EMS_MODEL_EASY:
ems_doReadCommand(EMS_TYPE_EasyStatusMessage, type);
break;
case EMS_MODEL_RC35:
case EMS_MODEL_ES73:
if (hc == 1) { if (hc == 1) {
ems_doReadCommand(EMS_TYPE_RC35StatusMessage_HC1, type); // to get the setpoint temp ems_doReadCommand(EMS_TYPE_RC35StatusMessage_HC1, type); // to get the temps
ems_doReadCommand(EMS_TYPE_RC35Set_HC1, type); // to get the mode ems_doReadCommand(EMS_TYPE_RC35Set_HC1, type); // to get the mode
} else if (hc == 2) { } else if (hc == 2) {
ems_doReadCommand(EMS_TYPE_RC35StatusMessage_HC2, type); // to get the setpoint temp ems_doReadCommand(EMS_TYPE_RC35StatusMessage_HC2, type); // to get the temps
ems_doReadCommand(EMS_TYPE_RC35Set_HC2, type); // to get the mode ems_doReadCommand(EMS_TYPE_RC35Set_HC2, type); // to get the mode
} }
} else if ((model_id == EMS_MODEL_EASY)) { break;
ems_doReadCommand(EMS_TYPE_EasyStatusMessage, type); case EMS_MODEL_RC300:
ems_doReadCommand(EMS_TYPE_RCPLUSStatusMessage, type);
default:
break;
} }
ems_doReadCommand(EMS_TYPE_RCTime, type); // get Thermostat time ems_doReadCommand(EMS_TYPE_RCTime, type); // get Thermostat time
@@ -1809,14 +1970,21 @@ void ems_getBoilerValues() {
/* /*
* Get other values from EMS devices * Get other values from EMS devices
*/ */
void ems_getOtherValues() { void ems_getSolarModuleValues() {
if (EMS_Other.SM) { uint8_t product_id = EMS_SolarModule.product_id;
if (ems_getSolarModuleEnabled()) {
if (product_id == EMS_PRODUCTID_SM10) {
ems_doReadCommand(EMS_TYPE_SM10Monitor, EMS_ID_SM); // fetch all from SM10Monitor ems_doReadCommand(EMS_TYPE_SM10Monitor, EMS_ID_SM); // fetch all from SM10Monitor
} else if (product_id == EMS_PRODUCTID_SM100) {
ems_doReadCommand(EMS_TYPE_SM100Monitor, EMS_ID_SM); // fetch all from SM100Monitor
}
} }
} }
/** /**
* returns current thermostat type as a string * returns current thermostat type as a string
* by looking up the product_id
*/ */
char * ems_getThermostatDescription(char * buffer) { char * ems_getThermostatDescription(char * buffer) {
uint8_t size = 128; uint8_t size = 128;
@@ -1898,6 +2066,88 @@ char * ems_getBoilerDescription(char * buffer) {
return buffer; return buffer;
} }
/**
* returns current Solar Module type as a string
*/
char * ems_getSolarModuleDescription(char * buffer) {
uint8_t size = 128;
if (!ems_getSolarModuleEnabled()) {
strlcpy(buffer, "<not enabled>", size);
} else {
int i = 0;
bool found = false;
char tmp[6] = {0};
// scan through known ID types
while (i < _Solar_Module_Types_max) {
if (SolarModule_Types[i].product_id == EMS_SolarModule.product_id) {
found = true; // we have a match
break;
}
i++;
}
if (found) {
strlcpy(buffer, SolarModule_Types[i].model_string, size);
} else {
strlcpy(buffer, "DeviceID: 0x", size);
strlcat(buffer, _hextoa(EMS_SolarModule.device_id, tmp), size);
}
strlcat(buffer, " (ProductID:", size);
if (EMS_SolarModule.product_id == EMS_ID_NONE) {
strlcat(buffer, "?", size);
} else {
strlcat(buffer, itoa(EMS_SolarModule.product_id, tmp, 10), size);
}
strlcat(buffer, " Version:", size);
strlcat(buffer, EMS_SolarModule.version, size);
strlcat(buffer, ")", size);
}
return buffer;
}
/**
* returns current Heat Pump type as a string
*/
char * ems_getHeatPumpDescription(char * buffer) {
uint8_t size = 128;
if (!ems_getHeatPumpEnabled()) {
strlcpy(buffer, "<not enabled>", size);
} else {
int i = 0;
bool found = false;
char tmp[6] = {0};
// scan through known ID types
while (i < _HeatPump_Types_max) {
if (HeatPump_Types[i].product_id == EMS_HeatPump.product_id) {
found = true; // we have a match
break;
}
i++;
}
if (found) {
strlcpy(buffer, HeatPump_Types[i].model_string, size);
} else {
strlcpy(buffer, "DeviceID: 0x", size);
strlcat(buffer, _hextoa(EMS_HeatPump.device_id, tmp), size);
}
strlcat(buffer, " (ProductID:", size);
if (EMS_HeatPump.product_id == EMS_ID_NONE) {
strlcat(buffer, "?", size);
} else {
strlcat(buffer, itoa(EMS_HeatPump.product_id, tmp, 10), size);
}
strlcat(buffer, " Version:", size);
strlcat(buffer, EMS_HeatPump.version, size);
strlcat(buffer, ")", size);
}
return buffer;
}
/** /**
* Find the versions of our connected devices * Find the versions of our connected devices
*/ */
@@ -1914,6 +2164,11 @@ void ems_scanDevices() {
Device_Ids.push_back(tt.device_id); Device_Ids.push_back(tt.device_id);
} }
// copy over solar modules
for (_SolarModule_Type sm : SolarModule_Types) {
Device_Ids.push_back(sm.device_id);
}
// copy over others // copy over others
for (_Other_Type ot : Other_Types) { for (_Other_Type ot : Other_Types) {
Device_Ids.push_back(ot.device_id); Device_Ids.push_back(ot.device_id);
@@ -1949,6 +2204,16 @@ void ems_printAllDevices() {
Boiler_Types[i].product_id); Boiler_Types[i].product_id);
} }
myDebug_P(PSTR("\nThese %d devices are supported as solar module devices:"), _Solar_Module_Types_max);
for (i = 0; i < _Solar_Module_Types_max; i++) {
myDebug_P(PSTR(" %s%s%s (DeviceID:0x%02X ProductID:%d)"),
COLOR_BOLD_ON,
SolarModule_Types[i].model_string,
COLOR_BOLD_OFF,
SolarModule_Types[i].device_id,
SolarModule_Types[i].product_id);
}
myDebug_P(PSTR("\nThese %d devices are supported as other known EMS devices:"), _Other_Types_max); myDebug_P(PSTR("\nThese %d devices are supported as other known EMS devices:"), _Other_Types_max);
for (i = 0; i < _Other_Types_max; i++) { for (i = 0; i < _Other_Types_max; i++) {
myDebug_P(PSTR(" %s%s%s (DeviceID:0x%02X ProductID:%d)"), myDebug_P(PSTR(" %s%s%s (DeviceID:0x%02X ProductID:%d)"),
@@ -2004,6 +2269,23 @@ void ems_printDevices() {
} }
} }
/*
* prints the device list to a string for html parsing
*/
uint8_t ems_printDevices_s(char * buffer, uint16_t len) {
if (Devices.size() == 0) {
return 0;
}
char s[100];
for (std::list<_Generic_Type>::iterator it = Devices.begin(); it != Devices.end(); it++) {
sprintf(s, "%s (DeviceID:0x%02X ProductID:%d Version:%s)<br>", (it)->model_string, (it)->device_id, (it)->product_id, (it)->version);
strlcat(buffer, s, len);
}
return Devices.size();
}
/** /**
* Send a command to UART Tx to Read from another device * Send a command to UART Tx to Read from another device
* Read commands when sent must respond by the destination (target) immediately (or within 10ms) * Read commands when sent must respond by the destination (target) immediately (or within 10ms)
@@ -2039,14 +2321,14 @@ void ems_doReadCommand(uint16_t type, uint8_t dest, bool forceRefresh) {
EMS_TxTelegram.action = EMS_TX_TELEGRAM_READ; // read command EMS_TxTelegram.action = EMS_TX_TELEGRAM_READ; // read command
EMS_TxTelegram.dest = dest; // 8th bit will be set to indicate a read EMS_TxTelegram.dest = dest; // 8th bit will be set to indicate a read
EMS_TxTelegram.offset = 0; // 0 for all data EMS_TxTelegram.offset = 0; // 0 for all data
EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH; // is always 6 bytes long (including CRC at end)
EMS_TxTelegram.type = type; EMS_TxTelegram.type = type;
EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH; // EMS 1.0: 6 bytes long (including CRC at end), EMS+ will add 2 bytes. includes CRC
EMS_TxTelegram.dataValue = EMS_MAX_TELEGRAM_LENGTH; // for a read this is the # bytes we want back EMS_TxTelegram.dataValue = EMS_MAX_TELEGRAM_LENGTH; // for a read this is the # bytes we want back
EMS_TxTelegram.type_validate = EMS_ID_NONE; EMS_TxTelegram.type_validate = EMS_ID_NONE;
EMS_TxTelegram.comparisonValue = 0; EMS_TxTelegram.comparisonValue = 0;
EMS_TxTelegram.comparisonOffset = 0; EMS_TxTelegram.comparisonOffset = 0;
EMS_TxTelegram.comparisonPostRead = EMS_ID_NONE; EMS_TxTelegram.comparisonPostRead = EMS_ID_NONE;
EMS_TxTelegram.forceRefresh = forceRefresh; // should we send to MQTT after a successful read? EMS_TxTelegram.forceRefresh = forceRefresh; // send to MQTT after a successful read
EMS_TxQueue.push(EMS_TxTelegram); EMS_TxQueue.push(EMS_TxTelegram);
} }
@@ -2075,7 +2357,7 @@ void ems_sendRawTelegram(char * telegram) {
} }
// and interate until end // and interate until end
while (p != 0) { while (p != 0) {
if ((p = strtok(NULL, " ,"))) { if ((p = strtok(nullptr, " ,"))) {
strlcpy(value, p, sizeof(value)); strlcpy(value, p, sizeof(value));
uint8_t val = (uint8_t)strtol(value, 0, 16); uint8_t val = (uint8_t)strtol(value, 0, 16);
EMS_TxTelegram.data[++count] = val; EMS_TxTelegram.data[++count] = val;
@@ -2454,7 +2736,7 @@ void ems_testTelegram(uint8_t test_num) {
// and interate until end // and interate until end
while (p != 0) { while (p != 0) {
if ((p = strtok(NULL, " ,"))) { if ((p = strtok(nullptr, " ,"))) {
strlcpy(value, p, sizeof(value)); strlcpy(value, p, sizeof(value));
uint8_t val = (uint8_t)strtol(value, 0, 16); uint8_t val = (uint8_t)strtol(value, 0, 16);
telegram[++length] = val; telegram[++length] = val;
@@ -2469,6 +2751,6 @@ void ems_testTelegram(uint8_t test_num) {
// go an parse it // go an parse it
ems_parseTelegram(telegram, length + 1); // include CRC in length ems_parseTelegram(telegram, length + 1); // include CRC in length
#else #else
myDebug_P(PSTR("Firmware not compiled with test data set")); myDebug_P(PSTR("Firmware not compiled with test data. Use -DTESTS"));
#endif #endif
} }

156
src/ems.h
View File

@@ -12,27 +12,90 @@
#include <Arduino.h> #include <Arduino.h>
/* debug helper for logic analyzer
* create marker puls on GPIOx
* ° for Rx, we use GPIO14
* ° for Tx, we use GPIO12
*/
// clang-format off
#ifdef LOGICANALYZER
#define RX_MARK_PIN 14
#define TX_MARK_PIN 12
#define RX_MARK_MASK (1 << RX_MARK_PIN)
#define TX_MARK_MASK (1 << TX_MARK_PIN)
#define MARKERS_MASK (RX_MARK_PIN | TX_MARK_PIN)
#define GPIO_H(mask) (GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, (mask)))
#define GPIO_L(mask) (GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, (mask)))
#define RX_PULSE(pulse) \
do { \
GPIO_H(RX_MARK_MASK); \
delayMicroseconds(pulse); \
GPIO_L(RX_MARK_MASK); \
} while (0)
#define TX_PULSE(pulse) \
do { \
GPIO_H(TX_MARK_MASK); \
delayMicroseconds(pulse); \
GPIO_L(TX_MARK_MASK); \
} while (0)
#define LA_PULSE(pulse) \
do { \
GPIO_H(MARKERS_MASK); \
delayMicroseconds(pulse); \
GPIO_L(MARKERS_MASK); \
} while (0)
#define INIT_MARKERS(void) \
do { \
pinMode(RX_MARK_PIN, OUTPUT); \
pinMode(TX_MARK_PIN, OUTPUT); \
GPIO_L(MARKERS_MASK); \
} while (0)
#else
#define RX_PULSE(pulse) \
{}
#define TX_PULSE(pulse) \
{}
#define LA_PULSE(pulse) \
{}
#define INIT_MARKERS(void) \
{}
#define RX_MARK_MASK
#define TX_MARK_MASK
#define GPIO_H(mask)
#define GPIO_L(mask)
#endif
// clang-format on
#define EMS_ID_NONE 0x00 // used as a dest in broadcast messages and empty device IDs #define EMS_ID_NONE 0x00 // used as a dest in broadcast messages and empty device IDs
// Fixed EMS IDs // Fixed EMS IDs
#define EMS_ID_ME 0x0B // our device, hardcoded as the "Service Key" #define EMS_ID_ME 0x0B // our device, hardcoded as the "Service Key"
#define EMS_ID_BOILER 0x08 // all UBA Boilers have 0x08 #define EMS_ID_BOILER 0x08 // all UBA Boilers have 0x08
#define EMS_ID_SM 0x30 // Solar Module SM10 and SM100 #define EMS_ID_SM 0x30 // Solar Module SM10, SM100 and ISM1
#define EMS_ID_HP 0x38 // HeatPump #define EMS_ID_HP 0x38 // HeatPump
#define EMS_ID_GATEWAY 0x48 // KM200 Web Gateway #define EMS_ID_GATEWAY 0x48 // KM200 Web Gateway
#define EMS_PRODUCTID_HEATRONICS 95 // ProductID for a Junkers Heatronic3 device #define EMS_PRODUCTID_HEATRONICS 95 // ProductID for a Junkers Heatronic3 device
#define EMS_PRODUCTID_SM10 73 // ProductID for SM10 solar module
#define EMS_PRODUCTID_SM100 163 // ProductID for SM10 solar module
#define EMS_PRODUCTID_ISM1 101 // ProductID for SM10 solar module
#define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC #define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC
// max length of a telegram, including CRC, for Rx and Tx. // max length of a telegram, including CRC, for Rx and Tx.
#define EMS_MAX_TELEGRAM_LENGTH 32 #define EMS_MAX_TELEGRAM_LENGTH 32
// default values // default values for null values
#define EMS_VALUE_INT_ON 1 // boolean true #define EMS_VALUE_INT_ON 1 // boolean true
#define EMS_VALUE_INT_OFF 0 // boolean false #define EMS_VALUE_INT_OFF 0 // boolean false
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit ints #define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit unsigned ints/bytes
#define EMS_VALUE_SHORT_NOTSET 0x8000 // for 2-byte signed shorts #define EMS_VALUE_SHORT_NOTSET -32768 // for 2-byte signed shorts
#define EMS_VALUE_USHORT_NOTSET 0x8000 // for 2-byte unsigned shorts
#define EMS_VALUE_LONG_NOTSET 0xFFFFFF // for 3-byte longs #define EMS_VALUE_LONG_NOTSET 0xFFFFFF // for 3-byte longs
#define EMS_THERMOSTAT_WRITE_YES true #define EMS_THERMOSTAT_WRITE_YES true
@@ -57,8 +120,11 @@ typedef enum {
} _EMS_RX_STATUS; } _EMS_RX_STATUS;
typedef enum { typedef enum {
EMS_TX_STATUS_OK,
EMS_TX_STATUS_IDLE, // ready EMS_TX_STATUS_IDLE, // ready
EMS_TX_STATUS_WAIT // waiting for response from last Tx EMS_TX_STATUS_WAIT, // waiting for response from last Tx
EMS_TX_WTD_TIMEOUT, // watchdog timeout during send
EMS_TX_BRK_DETECT // incoming BRK during Tx
} _EMS_TX_STATUS; } _EMS_TX_STATUS;
#define EMS_TX_SUCCESS 0x01 // EMS single byte after a Tx Write indicating a success #define EMS_TX_SUCCESS 0x01 // EMS single byte after a Tx Write indicating a success
@@ -78,6 +144,7 @@ typedef enum {
EMS_SYS_LOGGING_RAW, // raw data mode EMS_SYS_LOGGING_RAW, // raw data mode
EMS_SYS_LOGGING_BASIC, // only basic read/write messages EMS_SYS_LOGGING_BASIC, // only basic read/write messages
EMS_SYS_LOGGING_THERMOSTAT, // only telegrams sent from thermostat EMS_SYS_LOGGING_THERMOSTAT, // only telegrams sent from thermostat
EMS_SYS_LOGGING_SOLARMODULE, // only telegrams sent from thermostat
EMS_SYS_LOGGING_VERBOSE // everything EMS_SYS_LOGGING_VERBOSE // everything
} _EMS_SYS_LOGGING; } _EMS_SYS_LOGGING;
@@ -98,6 +165,7 @@ typedef struct {
bool emsTxDisabled; // true to prevent all Tx bool emsTxDisabled; // true to prevent all Tx
uint8_t txRetryCount; // # times the last Tx was re-sent uint8_t txRetryCount; // # times the last Tx was re-sent
bool emsReverse; // if true, poll logic is reversed bool emsReverse; // if true, poll logic is reversed
uint8_t emsTxMode; // handles Tx logic
} _EMS_Sys_Status; } _EMS_Sys_Status;
// The Tx send package // The Tx send package
@@ -148,20 +216,30 @@ const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
{0x00} // data {0x00} // data
}; };
// where defintions are stored
typedef struct { typedef struct {
uint8_t model_id;
uint8_t product_id; uint8_t product_id;
char model_string[50]; char model_string[50];
} _Boiler_Type; } _Boiler_Type;
typedef struct { typedef struct {
uint8_t model_id; uint8_t product_id;
uint8_t device_id;
char model_string[50];
} _SolarModule_Type;
typedef struct {
uint8_t product_id; uint8_t product_id;
uint8_t device_id; uint8_t device_id;
char model_string[50]; char model_string[50];
} _Other_Type; } _Other_Type;
// Definition for thermostat devices typedef struct {
uint8_t product_id;
uint8_t device_id;
char model_string[50];
} _HeatPump_Type;
typedef struct { typedef struct {
uint8_t model_id; uint8_t model_id;
uint8_t product_id; uint8_t product_id;
@@ -170,6 +248,7 @@ typedef struct {
bool write_supported; bool write_supported;
} _Thermostat_Type; } _Thermostat_Type;
// for consolidating all types
typedef struct { typedef struct {
uint8_t product_id; uint8_t product_id;
uint8_t device_id; uint8_t device_id;
@@ -189,8 +268,8 @@ typedef struct { // UBAParameterWW
// UBAMonitorFast // UBAMonitorFast
uint8_t selFlowTemp; // Selected flow temperature uint8_t selFlowTemp; // Selected flow temperature
int16_t curFlowTemp; // Current flow temperature uint16_t curFlowTemp; // Current flow temperature
int16_t retTemp; // Return temperature uint16_t retTemp; // Return temperature
uint8_t burnGas; // Gas on/off uint8_t burnGas; // Gas on/off
uint8_t fanWork; // Fan on/off uint8_t fanWork; // Fan on/off
uint8_t ignWork; // Ignition on/off uint8_t ignWork; // Ignition on/off
@@ -206,14 +285,14 @@ typedef struct { // UBAParameterWW
// UBAMonitorSlow // UBAMonitorSlow
int16_t extTemp; // Outside temperature int16_t extTemp; // Outside temperature
int16_t boilTemp; // Boiler temperature uint16_t boilTemp; // Boiler temperature
uint8_t pumpMod; // Pump modulation uint8_t pumpMod; // Pump modulation
uint32_t burnStarts; // # burner starts uint32_t burnStarts; // # burner starts
uint32_t burnWorkMin; // Total burner operating time uint32_t burnWorkMin; // Total burner operating time
uint32_t heatWorkMin; // Total heat operating time uint32_t heatWorkMin; // Total heat operating time
// UBAMonitorWWMessage // UBAMonitorWWMessage
int16_t wWCurTmp; // Warm Water current temperature: uint16_t wWCurTmp; // Warm Water current temperature
uint32_t wWStarts; // Warm Water # starts uint32_t wWStarts; // Warm Water # starts
uint32_t wWWorkM; // Warm Water # minutes uint32_t wWWorkM; // Warm Water # minutes
uint8_t wWOneTime; // Warm Water one time function on/off uint8_t wWOneTime; // Warm Water one time function on/off
@@ -241,21 +320,39 @@ typedef struct { // UBAParameterWW
* Telegram package defintions for Other EMS devices * Telegram package defintions for Other EMS devices
*/ */
// SM Solar Module - SM10Monitor/SM100Monitor
typedef struct { typedef struct {
bool SM; // set true if there is a Solar Module available
bool HP; // set true if there is a Heat Pump available
int16_t SMcollectorTemp; // collector temp
int16_t SMbottomTemp; // bottom temp
uint8_t SMpumpModulation; // modulation solar pump
uint8_t SMpump; // pump active
int16_t SMEnergyLastHour;
int16_t SMEnergyToday;
int16_t SMEnergyTotal;
uint8_t HPModulation; // heatpump modulation in % uint8_t HPModulation; // heatpump modulation in %
uint8_t HPSpeed; // speed 0-100 % uint8_t HPSpeed; // speed 0-100 %
uint8_t device_id; // the device ID of the Heat Pump (e.g. 0x30)
uint8_t model_id; // Solar Module / Heat Pump model (e.g. 3 > EMS_MODEL_OTHER )
uint8_t product_id;
char version[10];
} _EMS_HeatPump;
typedef struct {
uint8_t device_id;
uint8_t model_id;
uint8_t product_id;
char version[10];
} _EMS_Other; } _EMS_Other;
// SM Solar Module - SM10/SM100/ISM1
typedef struct {
int16_t collectorTemp; // collector temp
int16_t bottomTemp; // bottom temp
uint8_t pumpModulation; // modulation solar pump
uint8_t pump; // pump active
int16_t setpoint_maxBottomTemp; // setpoint for maximum collector temp
uint16_t EnergyLastHour;
uint16_t EnergyToday;
uint16_t EnergyTotal;
uint32_t pumpWorkMin; // Total solar pump operating time
uint8_t device_id; // the device ID of the Solar Module
uint8_t model_id; // Solar Module
uint8_t product_id;
char version[10];
} _EMS_SolarModule;
// Thermostat data // Thermostat data
typedef struct { typedef struct {
uint8_t device_id; // the device ID of the thermostat uint8_t device_id; // the device ID of the thermostat
@@ -300,11 +397,13 @@ void ems_sendRawTelegram(char * telegram);
void ems_scanDevices(); void ems_scanDevices();
void ems_printAllDevices(); void ems_printAllDevices();
void ems_printDevices(); void ems_printDevices();
uint8_t ems_printDevices_s(char * buffer, uint16_t len);
void ems_printTxQueue(); void ems_printTxQueue();
void ems_testTelegram(uint8_t test_num); void ems_testTelegram(uint8_t test_num);
void ems_startupTelegrams(); void ems_startupTelegrams();
bool ems_checkEMSBUSAlive(); bool ems_checkEMSBUSAlive();
void ems_clearDeviceList(); void ems_clearDeviceList();
void ems_setTxMode(uint8_t mode);
void ems_setThermostatTemp(float temperature, uint8_t temptype = 0); void ems_setThermostatTemp(float temperature, uint8_t temptype = 0);
void ems_setThermostatMode(uint8_t mode); void ems_setThermostatMode(uint8_t mode);
@@ -319,20 +418,27 @@ void ems_setEmsRefreshed(bool b);
void ems_setWarmWaterModeComfort(uint8_t comfort); void ems_setWarmWaterModeComfort(uint8_t comfort);
void ems_setModels(); void ems_setModels();
void ems_setTxDisabled(bool b); void ems_setTxDisabled(bool b);
bool ems_getTxDisabled();
uint8_t ems_getTxMode();
char * ems_getThermostatDescription(char * buffer); char * ems_getThermostatDescription(char * buffer);
char * ems_getBoilerDescription(char * buffer); char * ems_getBoilerDescription(char * buffer);
char * ems_getSolarModuleDescription(char * buffer);
char * ems_getHeatPumpDescription(char * buffer);
void ems_getThermostatValues(); void ems_getThermostatValues();
void ems_getBoilerValues(); void ems_getBoilerValues();
void ems_getOtherValues(); void ems_getSolarModuleValues();
bool ems_getPoll(); bool ems_getPoll();
bool ems_getTxEnabled(); bool ems_getTxEnabled();
bool ems_getThermostatEnabled(); bool ems_getThermostatEnabled();
bool ems_getBoilerEnabled(); bool ems_getBoilerEnabled();
bool ems_getSolarModuleEnabled();
bool ems_getHeatPumpEnabled();
bool ems_getBusConnected(); bool ems_getBusConnected();
_EMS_SYS_LOGGING ems_getLogging(); _EMS_SYS_LOGGING ems_getLogging();
bool ems_getEmsRefreshed(); bool ems_getEmsRefreshed();
uint8_t ems_getThermostatModel(); uint8_t ems_getThermostatModel();
uint8_t ems_getSolarModuleModel();
void ems_discoverModels(); void ems_discoverModels();
bool ems_getTxCapable(); bool ems_getTxCapable();
uint32_t ems_getPollFrequency(); uint32_t ems_getPollFrequency();
@@ -342,12 +448,12 @@ uint8_t _crcCalculator(uint8_t * data, uint8_t len);
void _processType(_EMS_RxTelegram * EMS_RxTelegram); void _processType(_EMS_RxTelegram * EMS_RxTelegram);
void _debugPrintPackage(const char * prefix, _EMS_RxTelegram * EMS_RxTelegram, const char * color); void _debugPrintPackage(const char * prefix, _EMS_RxTelegram * EMS_RxTelegram, const char * color);
void _ems_clearTxData(); void _ems_clearTxData();
int _ems_findBoilerModel(uint8_t model_id);
bool _ems_setModel(uint8_t model_id);
void _removeTxQueue(); void _removeTxQueue();
// global so can referenced in other classes // global so can referenced in other classes
extern _EMS_Sys_Status EMS_Sys_Status; extern _EMS_Sys_Status EMS_Sys_Status;
extern _EMS_Boiler EMS_Boiler; extern _EMS_Boiler EMS_Boiler;
extern _EMS_Thermostat EMS_Thermostat; extern _EMS_Thermostat EMS_Thermostat;
extern _EMS_SolarModule EMS_SolarModule;
extern _EMS_HeatPump EMS_HeatPump;
extern _EMS_Other EMS_Other; extern _EMS_Other EMS_Other;

View File

@@ -48,7 +48,10 @@
#define EMS_TYPE_SM100Energy 0x028E // SM100Energy #define EMS_TYPE_SM100Energy 0x028E // SM100Energy
#define EMS_TYPE_HPMonitor1 0xE3 // HeatPump Monitor 1 #define EMS_TYPE_HPMonitor1 0xE3 // HeatPump Monitor 1
#define EMS_TYPE_HPMonitor2 0xE5 // HeatPump Monitor 2 #define EMS_TYPE_HPMonitor2 0xE5 // HeatPump Monitor 2
#define EMS_TYPE_ISM1StatusMessage 0x0003 // Solar Module Junkers ISM1 Status #define EMS_TYPE_ISM1StatusMessage 0x0003 // Solar Module Junkers ISM1 Status
#define EMS_TYPE_ISM1Set 0x0001 // for setting values of the solar module like max boiler temp
#define EMS_OFFSET_ISM1Set_MaxBoilerTemp 6 // position of max boiler temp e.g. 50 in the following example: 90 30 FF 06 00 01 50 (CRC=2C)
/* /*
* Thermostats... * Thermostats...
@@ -63,7 +66,7 @@
#define EMS_TYPE_RC10Set 0xB0 // for setting values like temp and mode #define EMS_TYPE_RC10Set 0xB0 // for setting values like temp and mode
#define EMS_OFFSET_RC10Set_temp 4 // position of thermostat setpoint temperature #define EMS_OFFSET_RC10Set_temp 4 // position of thermostat setpoint temperature
#define EMS_OFFSET_RC10StatusMessage_setpoint 1 // setpoint temp #define EMS_OFFSET_RC10StatusMessage_setpoint 1 // setpoint temp
#define EMS_OFFSET_RC10StatusMessage_curr 3 // current temp #define EMS_OFFSET_RC10StatusMessage_curr 2 // current temp
// RC20 specific // RC20 specific
#define EMS_TYPE_RC20StatusMessage 0x91 // is an automatic thermostat broadcast giving us temps #define EMS_TYPE_RC20StatusMessage 0x91 // is an automatic thermostat broadcast giving us temps
@@ -109,6 +112,7 @@
#define EMS_OFFSET_RCPLUSStatusMessage_setpoint 3 // setpoint temp #define EMS_OFFSET_RCPLUSStatusMessage_setpoint 3 // setpoint temp
#define EMS_OFFSET_RCPLUSStatusMessage_curr 0 // current temp #define EMS_OFFSET_RCPLUSStatusMessage_curr 0 // current temp
#define EMS_OFFSET_RCPLUSGet_mode_day 8 // day/night mode #define EMS_OFFSET_RCPLUSGet_mode_day 8 // day/night mode
#define EMS_OFFSET_RCPLUSStatusMessage_mode 0x0A // thermostat mode (auto, manual)
// Junkers FR10, FW100 (EMS Plus) // Junkers FR10, FW100 (EMS Plus)
#define EMS_TYPE_JunkersStatusMessage 0x6F // is an automatic thermostat broadcast giving us temps #define EMS_TYPE_JunkersStatusMessage 0x6F // is an automatic thermostat broadcast giving us temps
@@ -118,16 +122,19 @@
// Known EMS types // Known EMS types
typedef enum { typedef enum {
EMS_MODEL_NONE, EMS_MODEL_NONE, // unset
EMS_MODEL_ALL, // common for all devices EMS_MODEL_ALL, // common for all devices
// generic ID for the boiler // heatpump
EMS_MODEL_HP,
// solar module
EMS_MODEL_SM,
// boiler
EMS_MODEL_UBA, EMS_MODEL_UBA,
// generic ID for all the other weird devices // and the thermostats
EMS_MODEL_OTHER,
// and finally the thermostats
EMS_MODEL_ES73, EMS_MODEL_ES73,
EMS_MODEL_RC10, EMS_MODEL_RC10,
EMS_MODEL_RC20, EMS_MODEL_RC20,
@@ -135,7 +142,7 @@ typedef enum {
EMS_MODEL_RC30, EMS_MODEL_RC30,
EMS_MODEL_RC35, EMS_MODEL_RC35,
EMS_MODEL_EASY, EMS_MODEL_EASY,
EMS_MODEL_RC310, EMS_MODEL_RC300,
EMS_MODEL_CW100, EMS_MODEL_CW100,
EMS_MODEL_1010, EMS_MODEL_1010,
EMS_MODEL_OT, EMS_MODEL_OT,
@@ -147,56 +154,71 @@ typedef enum {
} _EMS_MODEL_ID; } _EMS_MODEL_ID;
// EMS types for known devices. This list will be extended when new devices are recognized. // EMS types for known boilers. This list will be extended when new devices are recognized.
// The device_id is always 0x08 // The device_id is always 0x08
// format is MODEL_ID, PRODUCT ID, DESCRIPTION // format is PRODUCT ID, DESCRIPTION
const _Boiler_Type Boiler_Types[] = { const _Boiler_Type Boiler_Types[] = {
{EMS_MODEL_UBA, 72, "MC10 Module"}, {72, "MC10 Module"},
{EMS_MODEL_UBA, 123, "Buderus GB172/Nefit Trendline/Junkers Cerapur"}, {123, "Buderus GB172/Nefit Trendline/Junkers Cerapur"},
{EMS_MODEL_UBA, 115, "Nefit Topline Compact/Buderus GB162"}, {115, "Nefit Topline Compact/Buderus GB162"},
{EMS_MODEL_UBA, 203, "Buderus Logamax U122/Junkers Cerapur"}, {203, "Buderus Logamax U122/Junkers Cerapur"},
{EMS_MODEL_UBA, 208, "Buderus Logamax plus/GB192"}, {208, "Buderus Logamax plus/GB192"},
{EMS_MODEL_UBA, 64, "Sieger BK15/Nefit Smartline/Buderus GB152"}, {64, "Sieger BK15/Nefit Smartline/Buderus GB152"},
{EMS_MODEL_UBA, EMS_PRODUCTID_HEATRONICS, "Bosch Condens 2500/Junkers Heatronics3"}, // Junkers {EMS_PRODUCTID_HEATRONICS, "Bosch Condens 2500/Junkers Heatronics3"},
{EMS_MODEL_UBA, 122, "Nefit Proline"}, {122, "Nefit Proline"},
{EMS_MODEL_UBA, 172, "Nefit Enviline"} {172, "Nefit Enviline"}
};
// Other EMS devices which are not considered boilers or thermostats
const _Other_Type Other_Types[] = {
{EMS_MODEL_OTHER, 69, 0x21, "MM10 Mixer Module"},
{EMS_MODEL_OTHER, 71, 0x11, "WM10 Switch Module"},
{EMS_MODEL_OTHER, 160, 0x20, "MM100 Mixing Module"},
{EMS_MODEL_OTHER, 160, 0x21, "MM100 Mixing Module"},
{EMS_MODEL_OTHER, 159, 0x21, "MM50 Mixing Module"},
{EMS_MODEL_OTHER, 68, 0x09, "BC10/RFM20 Receiver"},
{EMS_MODEL_OTHER, 190, 0x09, "BC10 Base Controller"},
{EMS_MODEL_OTHER, 114, 0x09, "BC10 Base Controller"},
{EMS_MODEL_OTHER, 125, 0x09, "BC25 Base Controller"},
{EMS_MODEL_OTHER, 152, 0x09, "Junkers Controller"},
{EMS_MODEL_OTHER, 205, 0x02, "Nefit Moduline Easy Connect"},
{EMS_MODEL_OTHER, 73, EMS_ID_SM, "SM10 Solar Module"},
{EMS_MODEL_OTHER, 163, EMS_ID_SM, "SM100 Solar Module"},
{EMS_MODEL_OTHER, 171, 0x02, "EMS-OT OpenTherm converter"},
{EMS_MODEL_OTHER, 252, EMS_ID_HP, "HeatPump Module"}, // warning, fake product id!
{EMS_MODEL_OTHER, 101, 0x30, "Junkers ISM1 Solar Controller"},
{EMS_MODEL_OTHER, 189, EMS_ID_GATEWAY, "Web Gateway KM200"}
}; };
/*
* Known Solar Module types
* format is PRODUCT ID, DEVICE ID, DESCRIPTION
*/
const _SolarModule_Type SolarModule_Types[] = {
{EMS_PRODUCTID_SM10, EMS_ID_SM, "SM10 Solar Module"},
{EMS_PRODUCTID_SM100, EMS_ID_SM, "SM100 Solar Module"},
{EMS_PRODUCTID_ISM1, EMS_ID_SM, "Junkers ISM1 Solar Module"}
};
// Other EMS devices which are not considered boilers, thermostats or solar modules
// format is PRODUCT ID, DEVICE ID, DESCRIPTION
const _Other_Type Other_Types[] = {
{69, 0x21, "MM10 Mixer Module"},
{71, 0x11, "WM10 Switch Module"},
{160, 0x20, "MM100 Mixing Module"},
{160, 0x21, "MM100 Mixing Module"},
{159, 0x21, "MM50 Mixing Module"},
{68, 0x09, "BC10/RFM20 Receiver"},
{190, 0x09, "BC10 Base Controller"},
{114, 0x09, "BC10 Base Controller"},
{125, 0x09, "BC25 Base Controller"},
{152, 0x09, "Junkers Controller"},
{205, 0x02, "Nefit Moduline Easy Connect"},
{206, 0x02, "Bosch Easy Connect"},
{171, 0x02, "EMS-OT OpenTherm converter"},
{252, EMS_ID_HP, "HeatPump Module"},
{189, EMS_ID_GATEWAY, "Web Gateway KM200"}
};
// heatpump
// format is PRODUCT ID, DEVICE ID, DESCRIPTION
const _HeatPump_Type HeatPump_Types[] = {{252, EMS_ID_HP, "HeatPump Module"}};
/* /*
* Known thermostat types and their capabilities * Known thermostat types and their capabilities
* format is MODEL_ID, PRODUCT ID, DEVICE ID, DESCRIPTION
*/ */
const _Thermostat_Type Thermostat_Types[] = { const _Thermostat_Type Thermostat_Types[] = {
// Easy devices - not currently supporting write operations // Easy devices - not currently supporting write operations
{EMS_MODEL_EASY, 202, 0x18, "TC100/Nefit Easy", EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_EASY, 202, 0x18, "Logamatic TC100/Nefit Moduline Easy", EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_EASY, 203, 0x18, "Bosch EasyControl CT200", EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_EASY, 203, 0x18, "Bosch EasyControl CT200", EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_EASY, 206, 0x02, "Bosch Easy", EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_CW100, 157, 0x18, "Bosch CW100", EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_CW100, 157, 0x18, "CW100", EMS_THERMOSTAT_WRITE_NO},
// Buderus/Nefit // Buderus/Nefit
{EMS_MODEL_RC10, 79, 0x17, "RC10/Nefit Moduline 100", EMS_THERMOSTAT_WRITE_YES}, {EMS_MODEL_RC10, 79, 0x17, "RC10/Nefit Moduline 100", EMS_THERMOSTAT_WRITE_YES},
@@ -204,7 +226,7 @@ const _Thermostat_Type Thermostat_Types[] = {
{EMS_MODEL_RC20F, 93, 0x18, "RC20F", EMS_THERMOSTAT_WRITE_YES}, {EMS_MODEL_RC20F, 93, 0x18, "RC20F", EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_RC30, 78, 0x10, "RC30/Nefit Moduline 400", EMS_THERMOSTAT_WRITE_YES}, {EMS_MODEL_RC30, 78, 0x10, "RC30/Nefit Moduline 400", EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_RC35, 86, 0x10, "RC35", EMS_THERMOSTAT_WRITE_YES}, {EMS_MODEL_RC35, 86, 0x10, "RC35", EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_RC310, 158, 0x10, "RC3x0/Nefit Moduline 1010H", EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_RC300, 158, 0x10, "RC300/RC310/Nefit Moduline 3000", EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_1010, 165, 0x18, "Nefit Moduline 1010", EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_1010, 165, 0x18, "Nefit Moduline 1010", EMS_THERMOSTAT_WRITE_NO},
// Sieger // Sieger
@@ -217,5 +239,4 @@ const _Thermostat_Type Thermostat_Types[] = {
{EMS_MODEL_FR110, 108, 0x18, "Junkers FR110", EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_FR110, 108, 0x18, "Junkers FR110", EMS_THERMOSTAT_WRITE_NO},
{EMS_MODEL_FW120, 192, 0x10, "Junkers FW120", EMS_THERMOSTAT_WRITE_NO} {EMS_MODEL_FW120, 192, 0x10, "Junkers FW120", EMS_THERMOSTAT_WRITE_NO}
}; };

View File

@@ -29,7 +29,7 @@ static void emsuart_rx_intr_handler(void * para) {
EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_BUSY; // status set to busy EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_BUSY; // status set to busy
length = 0; length = 0;
} }
GPIO_H(RX_MARK_MASK);
// fill IRQ buffer, by emptying Rx FIFO // fill IRQ buffer, by emptying Rx FIFO
if (USIS(EMSUART_UART) & ((1 << UIFF) | (1 << UITO) | (1 << UIBD))) { if (USIS(EMSUART_UART) & ((1 << UIFF) | (1 << UITO) | (1 << UIBD))) {
while ((USS(EMSUART_UART) >> USRXC) & 0xFF) { while ((USS(EMSUART_UART) >> USRXC) & 0xFF) {
@@ -39,20 +39,20 @@ static void emsuart_rx_intr_handler(void * para) {
// clear Rx FIFO full and Rx FIFO timeout interrupts // clear Rx FIFO full and Rx FIFO timeout interrupts
USIC(EMSUART_UART) = (1 << UIFF) | (1 << UITO); USIC(EMSUART_UART) = (1 << UIFF) | (1 << UITO);
} }
GPIO_L(RX_MARK_MASK);
// BREAK detection = End of EMS data block // BREAK detection = End of EMS data block
if (USIS(EMSUART_UART) & ((1 << UIBD))) { if (USIS(EMSUART_UART) & ((1 << UIBD))) {
ETS_UART_INTR_DISABLE(); // disable all interrupts and clear them ETS_UART_INTR_DISABLE(); // disable all interrupts and clear them
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
pEMSRxBuf->length = length; pEMSRxBuf->length = length;
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, length); // copy data into transfer buffer, including the BRK 0x00 at the end os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, length); // copy data into transfer buffer, including the BRK 0x00 at the end
EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_IDLE; // set the status flag stating BRK has been received and we can start a new package EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_IDLE; // set the status flag stating BRK has been received and we can start a new package
ETS_UART_INTR_ENABLE(); // re-enable UART interrupts
system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
RX_PULSE(EMSUART_BIT_TIME / 2);
ETS_UART_INTR_ENABLE(); // re-enable UART interrupts
} }
} }
@@ -63,21 +63,22 @@ static void emsuart_rx_intr_handler(void * para) {
*/ */
static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events) { static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events) {
_EMSRxBuf * pCurrent = pEMSRxBuf; _EMSRxBuf * pCurrent = pEMSRxBuf;
pEMSRxBuf = paEMSRxBuf[++emsRxBufIdx % EMS_MAXBUFFERS]; // next free EMS Receive buffer
uint8_t length = pCurrent->length; // number of bytes including the BRK at the end uint8_t length = pCurrent->length; // number of bytes including the BRK at the end
pCurrent->length = 0;
// validate and transmit the EMS buffer, excluding the BRK // validate and transmit the EMS buffer, excluding the BRK
if (length == 2) { if (length == 2) {
// it's a poll or status code, single byte RX_PULSE(20);
// it's a poll or status code, single byte and ok to send on
ems_parseTelegram((uint8_t *)pCurrent->buffer, 1); ems_parseTelegram((uint8_t *)pCurrent->buffer, 1);
} else if ((length > 4) && (pCurrent->buffer[length - 2] != 0x00)) { } else if ((length > 4) && (length <= EMS_MAXBUFFERSIZE + 1) && (pCurrent->buffer[length - 2] != 0x00)) {
// ignore double BRK at the end, possibly from the Tx loopback // ignore double BRK at the end, possibly from the Tx loopback
// also telegrams with no data value // also telegrams with no data value
RX_PULSE(40);
ems_parseTelegram((uint8_t *)pCurrent->buffer, length - 1); // transmit EMS buffer, excluding the BRK ems_parseTelegram((uint8_t *)pCurrent->buffer, length - 1); // transmit EMS buffer, excluding the BRK
} }
// memset(pCurrent->buffer, 0x00, EMS_MAXBUFFERSIZE); // wipe memory just to be safe
memset(pCurrent->buffer, 0x00, EMS_MAXBUFFERSIZE); // wipe memory just to be safe
pEMSRxBuf = paEMSRxBuf[++emsRxBufIdx % EMS_MAXBUFFERS]; // next free EMS Receive buffer
} }
/* /*
@@ -94,7 +95,7 @@ static inline void ICACHE_FLASH_ATTR emsuart_flush_fifos() {
*/ */
void ICACHE_FLASH_ATTR emsuart_init() { void ICACHE_FLASH_ATTR emsuart_init() {
ETS_UART_INTR_DISABLE(); ETS_UART_INTR_DISABLE();
ETS_UART_INTR_ATTACH(NULL, NULL); ETS_UART_INTR_ATTACH(nullptr, nullptr);
// allocate and preset EMS Receive buffers // allocate and preset EMS Receive buffers
for (int i = 0; i < EMS_MAXBUFFERS; i++) { for (int i = 0; i < EMS_MAXBUFFERS; i++) {
@@ -117,11 +118,14 @@ void ICACHE_FLASH_ATTR emsuart_init() {
// conf1 params // conf1 params
// UCTOE = RX TimeOut enable (default is 1) // UCTOE = RX TimeOut enable (default is 1)
// UCTOT = RX TimeOut Threshold (7 bit) = want this when no more data after 2 characters (default is 2) // UCTOT = RX TimeOut Threshold (7 bit) = want this when no more data after 1 characters (default is 2)
// UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer (default was 127) // UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer (default was 127)
// see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf // see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf
//
// change: we set UCFFT to 1 to get an immediate indicator about incoming trafffic.
// Otherwise, we're only noticed by UCTOT or RxBRK!
USC1(EMSUART_UART) = 0; // reset config first USC1(EMSUART_UART) = 0; // reset config first
USC1(EMSUART_UART) = (EMS_MAX_TELEGRAM_LENGTH << UCFFT) | (0x02 << UCTOT) | (1 << UCTOE); // enable interupts USC1(EMSUART_UART) = (0x01 << UCFFT) | (0x01 << UCTOT) | (1 << UCTOE); // enable interupts
// set interrupts for triggers // set interrupts for triggers
USIC(EMSUART_UART) = 0xFFFF; // clear all interupts USIC(EMSUART_UART) = 0xFFFF; // clear all interupts
@@ -138,9 +142,9 @@ void ICACHE_FLASH_ATTR emsuart_init() {
system_set_os_print(0); system_set_os_print(0);
// swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively // swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively
system_uart_swap(); //system_uart_swap();
ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, NULL); ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, nullptr);
ETS_UART_INTR_ENABLE(); ETS_UART_INTR_ENABLE();
} }
@@ -160,30 +164,99 @@ void ICACHE_FLASH_ATTR emsuart_start() {
} }
/* /*
* set loopback mode and clear Tx/Rx FIFO * Send a BRK signal
* Which is a 11-bit set of zero's (11 cycles)
*/ */
static inline void ICACHE_FLASH_ATTR emsuart_loopback(bool enable) { void ICACHE_FLASH_ATTR emsuart_tx_brk() {
if (enable) uint32_t tmp;
USC0(EMSUART_UART) |= (1 << UCLBE); // enable loopback
else // must make sure Tx FIFO is empty
USC0(EMSUART_UART) &= ~(1 << UCLBE); // disable loopback while (((USS(EMSUART_UART) >> USTXC) & 0xFF) != 0)
;
tmp = ((1 << UCRXRST) | (1 << UCTXRST)); // bit mask
USC0(EMSUART_UART) |= (tmp); // set bits
USC0(EMSUART_UART) &= ~(tmp); // clear bits
// To create a 11-bit <BRK> we set TXD_BRK bit so the break signal will
// automatically be sent when the tx fifo is empty
tmp = (1 << UCBRK);
GPIO_H(TX_MARK_MASK);
USC0(EMSUART_UART) |= (tmp); // set bit
if (EMS_Sys_Status.emsTxMode <= 1) { // classic mode and ems+ (0, 1)
delayMicroseconds(EMSUART_TX_BRK_WAIT);
} else if (EMS_Sys_Status.emsTxMode == 3) { // junkers mode
delayMicroseconds(EMSUART_TX_WAIT_BRK - EMSUART_TX_LAG); // 1144 (11 Bits)
}
USC0(EMSUART_UART) &= ~(tmp); // clear bit
GPIO_L(TX_MARK_MASK);
} }
/* /*
* Send to Tx, ending with a <BRK> * Send to Tx, ending with a <BRK>
*/ */
void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) { _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
if (len == 0) _EMS_TX_STATUS result = EMS_TX_STATUS_OK;
return; if (len) {
LA_PULSE(50);
// temp code until we get mode 2 working without resets
if (EMS_Sys_Status.emsTxMode == 0) { // classic mode logic
for (uint8_t i = 0; i < len; i++) {
TX_PULSE(EMSUART_BIT_TIME / 4);
USF(EMSUART_UART) = buf[i];
}
emsuart_tx_brk(); // send <BRK>
} else if (EMS_Sys_Status.emsTxMode == 1) { // With extra tx delay for EMS+
for (uint8_t i = 0; i < len; i++) {
TX_PULSE(EMSUART_BIT_TIME / 4);
USF(EMSUART_UART) = buf[i];
delayMicroseconds(EMSUART_TX_BRK_WAIT); // https://github.com/proddy/EMS-ESP/issues/23#
}
emsuart_tx_brk(); // send <BRK>
} else if (EMS_Sys_Status.emsTxMode == 3) { // Junkers logic by @philrich
for (uint8_t i = 0; i < len; i++) {
TX_PULSE(EMSUART_BIT_TIME / 4);
USF(EMSUART_UART) = buf[i];
// just to be safe wait for tx fifo empty (needed?)
while (((USS(EMSUART_UART) >> USTXC) & 0xff) != 0)
;
// wait until bits are sent on wire
delayMicroseconds(EMSUART_TX_WAIT_BYTE - EMSUART_TX_LAG + EMSUART_TX_WAIT_GAP);
}
emsuart_tx_brk(); // send <BRK>
} else if (EMS_Sys_Status.emsTxMode == 2) {
/* /*
*
* based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch * based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch
* we emit the whole telegram, with Rx interrupt disabled, collecting busmaster response in FIFO. * we emit the whole telegram, with Rx interrupt disabled, collecting busmaster response in FIFO.
* after sending the last char we poll the Rx status until either * after sending the last char we poll the Rx status until either
* - size(Rx FIFO) == size(Tx-Telegram) * - size(Rx FIFO) == size(Tx-Telegram)
* - <BRK> is detected * - <BRK> is detected
* At end of receive we re-enable Rx-INT and send a Tx-BRK in loopback mode. * At end of receive we re-enable Rx-INT and send a Tx-BRK in loopback mode.
*
* EMS-Bus error handling
* 1. Busmaster stops echoing on Tx w/o permission
* 2. Busmaster cancel telegram by sending a BRK
*
* Case 1. is handled by a watchdog counter which is reset on each
* Tx attempt. The timeout should be 20x EMSUART_BIT_TIME plus
* some smart guess for processing time on targeted EMS device.
* We set EMS_Sys_Status.emsTxStatus to EMS_TX_WTD_TIMEOUT and return
*
* Case 2. is handled via a BRK chk during transmission.
* We set EMS_Sys_Status.emsTxStatus to EMS_TX_BRK_DETECT and return
*
*/ */
// shorter busy poll...
#define EMSUART_BUSY_WAIT (EMSUART_BIT_TIME / 8)
#define EMS_TX_TO_COUNT ((20 + 10000 / EMSUART_BIT_TIME) * 8)
uint16_t wdc = EMS_TX_TO_COUNT;
ETS_UART_INTR_DISABLE(); // disable rx interrupt ETS_UART_INTR_DISABLE(); // disable rx interrupt
// clear Rx status register // clear Rx status register
@@ -191,11 +264,25 @@ void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
emsuart_flush_fifos(); emsuart_flush_fifos();
// throw out the telegram... // throw out the telegram...
for (uint8_t i = 0; i < len;) { for (uint8_t i = 0; i < len && result == EMS_TX_STATUS_OK;) {
GPIO_H(TX_MARK_MASK);
wdc = EMS_TX_TO_COUNT;
volatile uint8_t _usrxc = (USS(EMSUART_UART) >> USRXC) & 0xFF;
USF(EMSUART_UART) = buf[i++]; // send each Tx byte USF(EMSUART_UART) = buf[i++]; // send each Tx byte
// wait for echo from busmaster // wait for echo from busmaster
while ((((USS(EMSUART_UART) >> USRXC) & 0xFF) < i || (USIS(EMSUART_UART) & (1 << UIBD)))) { GPIO_L(TX_MARK_MASK);
delayMicroseconds(EMSUART_BIT_TIME); // burn CPU cycles...
while (((USS(EMSUART_UART) >> USRXC) & 0xFF) == _usrxc) {
delayMicroseconds(EMSUART_BUSY_WAIT); // burn CPU cycles...
if (--wdc == 0) {
EMS_Sys_Status.emsTxStatus = result = EMS_TX_WTD_TIMEOUT;
break;
}
if (USIR(EMSUART_UART) & (1 << UIBD)) {
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
EMS_Sys_Status.emsTxStatus = result = EMS_TX_BRK_DETECT;
}
} }
} }
@@ -203,24 +290,30 @@ void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
// on Rx-BRK (bus collision), we simply enable Rx and leave it // on Rx-BRK (bus collision), we simply enable Rx and leave it
// otherwise we send the final Tx-BRK in the loopback and re=enable Rx-INT. // otherwise we send the final Tx-BRK in the loopback and re=enable Rx-INT.
// worst case, we'll see an additional Rx-BRK... // worst case, we'll see an additional Rx-BRK...
if (result != EMS_TX_STATUS_OK) {
LA_PULSE(200); // mark Tx error
} else {
// neither bus collision nor timeout - send terminating BRK signal
GPIO_H(TX_MARK_MASK);
if (!(USIS(EMSUART_UART) & (1 << UIBD))) { if (!(USIS(EMSUART_UART) & (1 << UIBD))) {
// no bus collision - send terminating BRK signal // no bus collision - send terminating BRK signal
emsuart_loopback(true); USC0(EMSUART_UART) |= (1 << UCLBE) | (1 << UCBRK); // enable loopback & set <BRK>
USC0(EMSUART_UART) |= (1 << UCBRK); // set <BRK>
// wait until BRK detected... // wait until BRK detected...
while (!(USIS(EMSUART_UART) & (1 << UIBD))) { while (!(USIR(EMSUART_UART) & (1 << UIBD))) {
delayMicroseconds(EMSUART_BIT_TIME); delayMicroseconds(EMSUART_BUSY_WAIT);
} }
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear <BRK> USC0(EMSUART_UART) &= ~((1 << UCBRK) | (1 << UCLBE)); // disable loopback & clear <BRK>
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
emsuart_loopback(false); // disable loopback mode
} }
GPIO_L(TX_MARK_MASK);
}
ETS_UART_INTR_ENABLE(); // receive anything from FIFO... ETS_UART_INTR_ENABLE(); // receive anything from FIFO...
} }
}
return result;
}
/* /*
* Send the Poll (our own ID) to Tx as a single byte and end with a <BRK> * Send the Poll (our own ID) to Tx as a single byte and end with a <BRK>

View File

@@ -8,16 +8,23 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <ems.h>
#define EMSUART_UART 0 // UART 0 #define EMSUART_UART 0 // UART 0
#define EMSUART_CONFIG 0x1C // 8N1 (8 bits, no stop bits, 1 parity) #define EMSUART_CONFIG 0x1C // 8N1 (8 bits, no stop bits, 1 parity)
#define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit #define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit
#define EMS_MAXBUFFERS 10 // buffers for circular filling to avoid collisions #define EMS_MAXBUFFERS 5 // buffers for circular filling to avoid collisions
#define EMS_MAXBUFFERSIZE 32 // max size of the buffer. packets are max 32 bytes to support EMS 1.0 #define EMS_MAXBUFFERSIZE (EMS_MAX_TELEGRAM_LENGTH + 2) // max size of the buffer. EMS packets are max 32 bytes, plus extra 2 for BRKs
#define EMSUART_BIT_TIME 104 // bit time @9600 baud #define EMSUART_BIT_TIME 104 // bit time @9600 baud
#define EMSUART_TX_BRK_WAIT 2070 // the BRK from Boiler master is roughly 1.039ms, so accounting for hardware lag using around 2078 (for half-duplex) - 8 (lag)
#define EMSUART_TX_WAIT_BYTE EMSUART_BIT_TIME * 10 // Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit)
#define EMSUART_TX_WAIT_BRK EMSUART_BIT_TIME * 11 // Time to send a BRK Signal (11 Bit)
#define EMSUART_TX_WAIT_GAP EMSUART_BIT_TIME * 7 // Gap between to Bytes
#define EMSUART_TX_LAG 8
#define EMSUART_recvTaskPrio 1 #define EMSUART_recvTaskPrio 1
#define EMSUART_recvTaskQueueLen 64 #define EMSUART_recvTaskQueueLen 64
@@ -29,5 +36,5 @@ typedef struct {
void ICACHE_FLASH_ATTR emsuart_init(); void ICACHE_FLASH_ATTR emsuart_init();
void ICACHE_FLASH_ATTR emsuart_stop(); void ICACHE_FLASH_ATTR emsuart_stop();
void ICACHE_FLASH_ATTR emsuart_start(); void ICACHE_FLASH_ATTR emsuart_start();
void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len); _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len);
void ICACHE_FLASH_ATTR emsuart_tx_poll(); void ICACHE_FLASH_ATTR emsuart_tx_poll();

View File

@@ -47,6 +47,7 @@
#define TOPIC_BOILER_WWACTIVATED "wwactivated" // for receiving MQTT message to change water on/off #define TOPIC_BOILER_WWACTIVATED "wwactivated" // for receiving MQTT message to change water on/off
#define TOPIC_BOILER_CMD_WWTEMP "boiler_cmd_wwtemp" // for received boiler wwtemp changes via MQTT #define TOPIC_BOILER_CMD_WWTEMP "boiler_cmd_wwtemp" // for received boiler wwtemp changes via MQTT
#define TOPIC_BOILER_CMD_COMFORT "boiler_cmd_comfort" // for received boiler ww comfort setting via MQTT #define TOPIC_BOILER_CMD_COMFORT "boiler_cmd_comfort" // for received boiler ww comfort setting via MQTT
#define TOPIC_BOILER_CMD_FLOWTEMP "boiler_cmd_flowtemp" // for received boiler flowtemp value via MQTT
// MQTT for SM10/SM100 Solar Module // MQTT for SM10/SM100 Solar Module
#define TOPIC_SM_DATA "sm_data" // topic name #define TOPIC_SM_DATA "sm_data" // topic name
@@ -57,6 +58,7 @@
#define SM_ENERGYLASTHOUR "energylasthour" // energy last hour #define SM_ENERGYLASTHOUR "energylasthour" // energy last hour
#define SM_ENERGYTODAY "energytoday" // energy today #define SM_ENERGYTODAY "energytoday" // energy today
#define SM_ENERGYTOTAL "energytotal" // energy total #define SM_ENERGYTOTAL "energytotal" // energy total
#define SM_PUMPWORKMIN "pumpWorkMin" // Total minutes
// MQTT for HP (HeatPump) // MQTT for HP (HeatPump)
#define TOPIC_HP_DATA "hp_data" // topic name #define TOPIC_HP_DATA "hp_data" // topic name
@@ -83,10 +85,15 @@
// can be enabled and disabled via the 'set led' command and pin set by 'set led_gpio' // can be enabled and disabled via the 'set led' command and pin set by 'set led_gpio'
#define EMSESP_LED_GPIO LED_BUILTIN #define EMSESP_LED_GPIO LED_BUILTIN
#ifdef LOGICANALYZER
#define EMSESP_DALLAS_GPIO D1
#define EMSESP_DALLAS_PARASITE false
#else
// set this if using an external temperature sensor like a DS18B20 // set this if using an external temperature sensor like a DS18B20
// D5 is the default on a bbqkees board // D5 is the default on a bbqkees board
#define EMSESP_DALLAS_GPIO D5 #define EMSESP_DALLAS_GPIO D5
#define EMSESP_DALLAS_PARASITE false #define EMSESP_DALLAS_PARASITE false
#endif
// By default the EMS bus will be scanned for known devices based on the product ids in ems_devices.h // By default the EMS bus will be scanned for known devices based on the product ids in ems_devices.h
// You can override the Thermostat and Boiler types here // You can override the Thermostat and Boiler types here

View File

@@ -49,7 +49,10 @@ static const char * TEST_DATA[] = {
"90 00 FF 00 00 6F 03 01 00 BE 00 BF", // test 44 - FR10 "90 00 FF 00 00 6F 03 01 00 BE 00 BF", // test 44 - FR10
"08 00 E3 00 01 00 01 00 00 00 00 00 00 00 00 00 DF 00 64 55", // test 45 - heatpump Enviline "08 00 E3 00 01 00 01 00 00 00 00 00 00 00 00 00 DF 00 64 55", // test 45 - heatpump Enviline
"08 00 E5 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0A", // test 46 - heatpump Enviline "08 00 E5 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0A", // test 46 - heatpump Enviline
"38 10 FF 00 03 2B 00 C7 07 C3 01" // test 47 - heatpump Enviline "38 10 FF 00 03 2B 00 C7 07 C3 01", // test 47 - heatpump Enviline
"08 0B 19 00 00 F7 80 00 80 00 00 00 00 00 03 58 97 0C 7B 1F 00 00 00 06 C4 DF 02 64 48 80 00", // test 48 - outdoor temp check
"88 00 19 00 00 DC 80 00 80 00 FF FF 00 00 00 21 9A 06 E1 7C 00 00 00 06 C2 13 00 1E 90 80 00", // test 49 - check max length
"30 00 FF 00 02 8E 00 00 41 82 00 00 28 36 00 00 82 21" // test 50 - SM100
}; };

View File

@@ -6,5 +6,5 @@
#pragma once #pragma once
#define APP_NAME "EMS-ESP" #define APP_NAME "EMS-ESP"
#define APP_VERSION "1.8.0" #define APP_VERSION "1.8.1"
#define APP_HOSTNAME "ems-esp" #define APP_HOSTNAME "ems-esp"