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
@@ -376,4 +406,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.1.0] 2018-05-14 ## [0.1.0] 2018-05-14
- Initial development version - Initial development version

View File

@@ -21,8 +21,8 @@
- 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
temp_step: 1 temp_step: 1
@@ -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" {
void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
#include "user_interface.h" #include "user_interface.h"
void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
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();
@@ -231,18 +287,17 @@ class MyESP {
void crashInfo(); void crashInfo();
// general // general
void end(); void end();
void loop(); void loop();
void begin(const char * app_hostname, const char * app_name, const char * app_version); void begin(const char * app_hostname, const char * app_name, const char * app_version);
void setBoottime(const char * boottime); void setBoottime(const char * boottime);
void resetESP(); void resetESP();
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

File diff suppressed because it is too large Load Diff

200
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
@@ -74,11 +140,12 @@ typedef enum {
/* EMS logging */ /* EMS logging */
typedef enum { typedef enum {
EMS_SYS_LOGGING_NONE, // no messages EMS_SYS_LOGGING_NONE, // no messages
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_VERBOSE // everything EMS_SYS_LOGGING_SOLARMODULE, // only telegrams sent from thermostat
EMS_SYS_LOGGING_VERBOSE // everything
} _EMS_SYS_LOGGING; } _EMS_SYS_LOGGING;
// status/counters since last power on // status/counters since last power on
@@ -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,39 +397,48 @@ 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);
void ems_setThermostatHC(uint8_t hc); void ems_setThermostatHC(uint8_t hc);
void ems_setWarmWaterTemp(uint8_t temperature); void ems_setWarmWaterTemp(uint8_t temperature);
void ems_setFlowTemp(uint8_t temperature); void ems_setFlowTemp(uint8_t temperature);
void ems_setWarmWaterActivated(bool activated); void ems_setWarmWaterActivated(bool activated);
void ems_setWarmTapWaterActivated(bool activated); void ems_setWarmTapWaterActivated(bool activated);
void ems_setPoll(bool b); void ems_setPoll(bool b);
void ems_setLogging(_EMS_SYS_LOGGING loglevel); void ems_setLogging(_EMS_SYS_LOGGING loglevel);
void ems_setEmsRefreshed(bool b); void ems_setEmsRefreshed(bool b);
void ems_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_Other EMS_Other; extern _EMS_SolarModule EMS_SolarModule;
extern _EMS_HeatPump EMS_HeatPump;
extern _EMS_Other EMS_Other;

View File

@@ -41,14 +41,17 @@
#define EMS_OFFSET_UBASetPoints_flowtemp 0 // flow temp #define EMS_OFFSET_UBASetPoints_flowtemp 0 // flow temp
// Other // Other
#define EMS_TYPE_SM10Monitor 0x97 // SM10Monitor #define EMS_TYPE_SM10Monitor 0x97 // SM10Monitor
#define EMS_TYPE_SM100Monitor 0x0262 // SM100Monitor #define EMS_TYPE_SM100Monitor 0x0262 // SM100Monitor
#define EMS_TYPE_SM100Status 0x0264 // SM100Status #define EMS_TYPE_SM100Status 0x0264 // SM100Status
#define EMS_TYPE_SM100Status2 0x026A // SM100Status2 #define EMS_TYPE_SM100Status2 0x026A // SM100Status2
#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;
uint8_t length = pCurrent->length; // number of bytes including the BRK at the end pEMSRxBuf = paEMSRxBuf[++emsRxBufIdx % EMS_MAXBUFFERS]; // next free EMS Receive buffer
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
USC1(EMSUART_UART) = 0; // reset config first //
USC1(EMSUART_UART) = (EMS_MAX_TELEGRAM_LENGTH << UCFFT) | (0x02 << UCTOT) | (1 << UCTOE); // enable interupts // 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) = (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,66 +164,155 @@ 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?)
* based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch while (((USS(EMSUART_UART) >> USTXC) & 0xff) != 0)
* 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
* - size(Rx FIFO) == size(Tx-Telegram)
* - <BRK> is detected
* At end of receive we re-enable Rx-INT and send a Tx-BRK in loopback mode.
*/
ETS_UART_INTR_DISABLE(); // disable rx interrupt
// clear Rx status register // wait until bits are sent on wire
USC0(EMSUART_UART) |= (1 << UCRXRST); // reset uart rx fifo delayMicroseconds(EMSUART_TX_WAIT_BYTE - EMSUART_TX_LAG + EMSUART_TX_WAIT_GAP);
emsuart_flush_fifos(); }
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
* 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
* - size(Rx FIFO) == size(Tx-Telegram)
* - <BRK> is detected
* 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
*
*/
// throw out the telegram... // shorter busy poll...
for (uint8_t i = 0; i < len;) { #define EMSUART_BUSY_WAIT (EMSUART_BIT_TIME / 8)
USF(EMSUART_UART) = buf[i++]; // send each Tx byte #define EMS_TX_TO_COUNT ((20 + 10000 / EMSUART_BIT_TIME) * 8)
// wait for echo from busmaster uint16_t wdc = EMS_TX_TO_COUNT;
while ((((USS(EMSUART_UART) >> USRXC) & 0xFF) < i || (USIS(EMSUART_UART) & (1 << UIBD)))) {
delayMicroseconds(EMSUART_BIT_TIME); // burn CPU cycles... ETS_UART_INTR_DISABLE(); // disable rx interrupt
// clear Rx status register
USC0(EMSUART_UART) |= (1 << UCRXRST); // reset uart rx fifo
emsuart_flush_fifos();
// throw out the telegram...
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
// wait for echo from busmaster
GPIO_L(TX_MARK_MASK);
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;
}
}
}
// we got the whole telegram in the Rx buffer
// 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.
// 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))) {
// no bus collision - send terminating BRK signal
USC0(EMSUART_UART) |= (1 << UCLBE) | (1 << UCBRK); // enable loopback & set <BRK>
// wait until BRK detected...
while (!(USIR(EMSUART_UART) & (1 << UIBD))) {
delayMicroseconds(EMSUART_BUSY_WAIT);
}
USC0(EMSUART_UART) &= ~((1 << UCBRK) | (1 << UCLBE)); // disable loopback & clear <BRK>
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
}
GPIO_L(TX_MARK_MASK);
}
ETS_UART_INTR_ENABLE(); // receive anything from FIFO...
} }
} }
return result;
// we got the whole telegram in the Rx buffer
// 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.
// worst case, we'll see an additional Rx-BRK...
if (!(USIS(EMSUART_UART) & (1 << UIBD))) {
// no bus collision - send terminating BRK signal
emsuart_loopback(true);
USC0(EMSUART_UART) |= (1 << UCBRK); // set <BRK>
// wait until BRK detected...
while (!(USIS(EMSUART_UART) & (1 << UIBD))) {
delayMicroseconds(EMSUART_BIT_TIME);
}
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear <BRK>
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
emsuart_loopback(false); // disable loopback mode
}
ETS_UART_INTR_ENABLE(); // receive anything from FIFO...
} }
/* /*

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

@@ -41,12 +41,13 @@
#define THERMOSTAT_CIRCUITCALCTEMP "thermostat_circuitcalctemp" // RC35 specific #define THERMOSTAT_CIRCUITCALCTEMP "thermostat_circuitcalctemp" // RC35 specific
// MQTT for boiler // MQTT for boiler
#define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values to MQTT #define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values to MQTT
#define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running #define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running
#define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on #define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on
#define TOPIC_BOILER_WWACTIVATED "wwactivated" // for receiving MQTT message to change water on/off #define TOPIC_BOILER_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"