release 1.9.2

This commit is contained in:
Paul
2019-10-19 11:27:43 +02:00
32 changed files with 5319 additions and 1321 deletions

View File

@@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.9.2 beta]
#### Important! This build has breaking changes:
- MQTT topics have changed. Use the `mqttlog` command to see the names of the subscriptions and the format of the payload data. Also reference the [Wiki page](https://github.com/proddy/EMS-ESP/wiki/MQTT).
- Home Assistant `.yaml` files need updating to reflect the recent MQTT changes
- The web builder has been upgraded to use Gulp 4. Remove `tools/webfilesbuilder/node_modules` and re-install the libraries using `npm ci` from within the `tools/webfilesbuilder` folder
### Added
- Handling of Mixing Module MM100 Status Messages (thanks @kstaniek)
- Retrieve/Set thermostat mode for Junkers FW100/120 thermostats (thanks @Neonox31)
- Added sending of all Mixer Module data via MQTT (thanks @peclik)
- Improved handling of MQTT publish and subscribe errors
- Added MQTT QOS (`mqtt_qos`, default 0), Keep Alive (`mqtt_keepalive`, default 60 seconds) and Retain (`mqtt_retain`, default off) as parameters to both telnet and WebUI
### Fixed
- `publish` command also sends Dallas external temperature sensor values
- `log_events` setting wasn't persisted in config file
### Changed
- External dallas sensor values sent in MQTT payload as float values and not strings
- All MQTT topics for the Thermostat have the Heating Circuit appended (e.g. `thermostat_data1`). This includes the commands.
- Shower timer and shower alert and not MQTT published at boot up
- Heating Active logic change to use Selected Flow Temp of min 30 instead of 70 (https://github.com/proddy/EMS-ESP/issues/193)
### Removed
- Removed telnet command `shower timer` and `shower alert` to toggle the switches
## [1.9.1] 2019-10-05
### Added
@@ -12,7 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for multiple Heating Circuits - https://github.com/proddy/EMS-ESP/issues/162
- new `mqttlog` command also shows which MQTT topics it is subscribed too
- Optimized event log loading in web and added integrity checks on all config and log files during boot
- `autodetect quick`
- `autodetect quick` for detecting known devices from our database list
- `log_events` option, now optional to save the log events to SPIFFS
### Fixed

View File

@@ -1,25 +1,14 @@
- id: boiler_shower
alias: Alert shower time
initial_state: true
trigger:
platform: mqtt
topic: home/ems-esp/showertime
action:
- service: notify.general_notify
data_template:
title: "Shower finished at {{states.sensor.time.state}}"
message: '{{trigger.payload}}'
- id: boiler_shower_alarm
alias: Alert shower too long
trigger:
platform: mqtt
topic: home/ems-esp/command
payload: 'shower_alarm'
topic: home/ems-esp/shower_data
action:
- service: notify.admin_notify
data_template:
title: "Shower Alert!"
message: "Shower time exceeded limit"
title: Shower finished at {{states.sensor.time.state}}
message: "{{ trigger.payload_json['duration'] }}"
# when ems-esp starts send boottime
- id: boiler_restart

View File

@@ -1,24 +1,23 @@
- platform: mqtt
- platform: mqtt
name: Thermostat
modes:
- "auto"
- "heat"
- "off"
mode_command_topic: "home/ems-esp/thermostat_cmd_mode1"
temperature_command_topic: "home/ems-esp/thermostat_cmd_temp1"
mode_state_topic: "home/ems-esp/thermostat_data"
current_temperature_topic: "home/ems-esp/thermostat_data"
temperature_state_topic: "home/ems-esp/thermostat_data"
temperature_command_topic: "home/ems-esp/thermostat_cmd_temp"
mode_command_topic: "home/ems-esp/thermostat_cmd_mode"
mode_state_template: "{{ value_json.thermostat_mode }}"
current_temperature_template: "{{ value_json.thermostat_currtemp }}"
temperature_state_template: "{{ value_json.thermostat_seltemp }}"
mode_state_template: "{{ value_json.hc1.mode }}"
current_temperature_template: "{{ value_json.hc1.currtemp }}"
temperature_state_template: "{{ value_json.hc1.seltemp }}"
temp_step: 0.5
- platform: mqtt
- platform: mqtt
name: boiler
modes:
- "auto"
@@ -26,12 +25,15 @@
min_temp: 40
max_temp: 60
temp_step: 1
current_temperature_topic: "home/ems-esp/boiler_data"
temperature_state_topic: "home/ems-esp/boiler_data"
temperature_command_topic: "home/ems-esp/boiler_cmd_wwtemp"
mode_state_topic: "home/ems-esp/boiler_data"
current_temperature_template: "{{ value_json.wWCurTmp }}"
temperature_state_template: "{{ value_json.wWSelTemp }}"
mode_state_template: "{% if value_json.wWActivated == 'off' %} off {% else %} auto {% endif %}"
mode_state_topic: "home/ems-esp/boiler_data"
mode_command_topic: "home/ems-esp/wwactivated"
temperature_command_topic: "home/ems-esp/boiler_cmd_wwtemp"
mode_command_topic: "home/ems-esp/boiler_cmd_wwactivated"

View File

@@ -1,7 +1,8 @@
# ems-esp
shower_coldshot:
sequence:
- service: mqtt.publish
data_template:
topic: 'home/ems-esp/shower_coldshot'
payload: '1'
topic: 'home/ems-esp/generic_cmd'
payload: '{cmd:"coldshot"}'

View File

@@ -1,30 +1,24 @@
# thermostat HC1
- platform: mqtt
state_topic: 'home/ems-esp/thermostat_data'
name: 'Current Room Temperature'
unit_of_measurement: '°C'
value_template: "{{ value_json.thermostat_currtemp }}"
value_template: "{{ value_json.hc1.currtemp }}"
- platform: mqtt
state_topic: 'home/ems-esp/thermostat_data'
name: 'Current Set Temperature'
unit_of_measurement: '°C'
value_template: "{{ value_json.thermostat_seltemp }}"
value_template: "{{ value_json.hc1.seltemp }}"
- platform: mqtt
state_topic: 'home/ems-esp/thermostat_data'
name: 'Current Mode'
value_template: "{{ value_json.thermostat_mode }}"
value_template: "{{ value_json.hc1.mode }}"
# last time esp-esp was started
- platform: template
sensors:
boiler_boottime:
value_template: '{{ as_timestamp(states.automation.see_if_boiler_restarts.attributes.last_triggered) | timestamp_custom("%H:%M:%S %d/%m/%y") }}'
- platform: mqtt
state_topic: 'home/ems-esp/showertime'
name: 'Last shower duration'
force_update: true
# boiler
- platform: mqtt
state_topic: 'home/ems-esp/boiler_data'
@@ -137,13 +131,20 @@
unit_of_measurement: '%'
value_template: '{{ value_json.pumpMod }}'
# shower time duration
- platform: mqtt
name: 'Last shower duration'
state_topic: "home/ems-esp/shower_data"
value_template: "{{ value_json.duration }}"
force_update: true
- platform: template
sensors:
showertime_time:
value_template: '{{ as_timestamp(states.sensor.last_shower_duration.last_updated) | int | timestamp_custom("%-I:%M %P on %a %-d %b") }}'
value_template: '{{ as_timestamp(states.sensor.last_shower_duration.last_updated) | int | timestamp_custom("%-I:%M on %a %-d %b") }}'
boiler_updated:
value_template: '{{ as_timestamp(states.sensor.boiler_temperature.last_updated) | timestamp_custom("%H:%M on %d/%b") }}'
boiler_boottime:
value_template: '{{ as_timestamp(states.automation.see_if_boiler_restarts.attributes.last_triggered) | timestamp_custom("%H:%M:%S %d/%m/%y") }}'

View File

@@ -1,20 +1,20 @@
# EMS-ESP
- platform: mqtt
name: "Shower Timer"
state_topic: "home/ems-esp/shower_timer"
command_topic: "home/ems-esp/shower_timer"
payload_on: "1"
payload_off: "0"
optimistic: false
qos: 1
retain: false
state_topic: "home/ems-esp/shower_data"
value_template: "{{ value_json.timer }}"
command_topic: "home/ems-esp/shower_data"
payload_on: '{"timer":"1"}'
payload_off: '{"timer":"0"}'
state_on: "1"
state_off: "0"
- platform: mqtt
name: "Long Shower Alert"
state_topic: "home/ems-esp/shower_alert"
command_topic: "home/ems-esp/shower_alert"
payload_on: "1"
payload_off: "0"
optimistic: false
qos: 1
retain: false
state_topic: "home/ems-esp/shower_data"
value_template: "{{ value_json.alert }}"
command_topic: "home/ems-esp/shower_data"
payload_on: '{"alert":"1"}'
payload_off: '{"alert":"0"}'
state_on: "1"
state_off: "0"

View File

@@ -5,23 +5,23 @@
[platformio]
default_envs = release
;default_envs = debug
;default_envs = crash
;default_envs = tests
[common]
; build options are:
; custom build options are:
; -DMYESP_TIMESTAMP
; -DTESTS
; -DCRASH
; -DFORCE_SERIAL
; -DLOGICANALYZER
custom_flags =
;general_flags = -g -w -DNO_GLOBAL_EEPROM -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH -DBEARSSL_SSL_BASIC
general_flags = -g -w -DNO_GLOBAL_EEPROM
;general_flags =
;general_flags = -DNO_GLOBAL_EEPROM -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH -DBEARSSL_SSL_BASIC
general_flags = -DNO_GLOBAL_EEPROM
[env]
;board = esp12e
; board = esp12e
; board = nodemcu
; board = nodemcu2
board = d1_mini
framework = arduino
platform = espressif8266
@@ -43,40 +43,22 @@ monitor_speed = 115200
;upload_port = /dev/cu.wchusbserial14403
;upload_port = /dev/cu.usbserial-1440
; uncomment next 2 lines for OTA
;upload_protocol = espota
;upload_port = ems-esp.local
[env:buildweb]
extra_scripts = pre:scripts/buildweb.py
[env:tests]
build_type = debug
build_flags = ${common.general_flags} -DTESTS
extra_scripts =
pre:scripts/rename_fw.py
pre:scripts/buildweb.py
[env:crash]
build_type = debug
build_flags = ${common.general_flags} -DNO_GLOBAL_EEPROM -DCRASH
extra_scripts =
pre:scripts/rename_fw.py
pre:scripts/buildweb.py
; comment next 2 lines is not using OTA
upload_protocol = espota
upload_port = ems-esp.local
[env:debug]
build_type = debug
build_flags = ${common.general_flags}
build_flags = ${common.general_flags} ${common.custom_flags} -DTESTS
extra_scripts =
pre:scripts/rename_fw.py
pre:scripts/buildweb.py
[env:clean]
extra_scripts = pre:scripts/clean_fw.py
[env:release]
build_flags = ${common.general_flags} -DNO_GLOBAL_EEPROM
extra_scripts = pre:scripts/rename_fw.py
build_flags = ${common.general_flags} ${common.custom_flags}
extra_scripts =
pre:scripts/rename_fw.py
pre:scripts/buildweb.py
[env:checkcode]
build_type = debug

View File

@@ -1,3 +1,3 @@
Import("env")
env.Execute("node ./tools/webfilesbuilder/node_modules/gulp/bin/gulp.js --cwd ./tools/webfilesbuilder")
env.Execute("node ./tools/webfilesbuilder/node_modules/gulp/bin/gulp.js --silent --cwd ./tools/webfilesbuilder")

View File

@@ -199,7 +199,10 @@ uint32_t MyESP::_getInitialFreeHeap() {
// called when WiFi is connected, and used to start OTA, MQTT
void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
if ((code == MESSAGE_CONNECTED)) {
if (code == MESSAGE_CONNECTED) {
myDebug_P(PSTR("[WIFI] Connected to SSID %s (hostname: %s, IP: %s)"), WiFi.SSID().c_str(), _getESPhostname().c_str(), WiFi.localIP().toString().c_str());
/*
myDebug_P(PSTR("[WIFI] SSID %s"), WiFi.SSID().c_str());
myDebug_P(PSTR("[WIFI] CH %d"), WiFi.channel());
myDebug_P(PSTR("[WIFI] RSSI %d"), WiFi.RSSI());
@@ -209,10 +212,11 @@ void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
myDebug_P(PSTR("[WIFI] MASK %s"), WiFi.subnetMask().toString().c_str());
myDebug_P(PSTR("[WIFI] DNS %s"), WiFi.dnsIP().toString().c_str());
myDebug_P(PSTR("[WIFI] HOST %s"), _getESPhostname().c_str());
*/
// start OTA
ArduinoOTA.begin(); // moved to support esp32
myDebug_P(PSTR("[OTA] listening to %s.local:%u"), ArduinoOTA.getHostname().c_str(), OTA_PORT);
myDebug_P(PSTR("[OTA] Listening to firmware updates on %s.local:%u"), ArduinoOTA.getHostname().c_str(), OTA_PORT);
/*
// show reason for the restart if any
@@ -232,11 +236,11 @@ void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
// if we don't want Serial anymore, turn it off
if (!_general_serial) {
myDebug_P(PSTR("[SYSTEM] Disabling serial port communication."));
myDebug_P(PSTR("[SYSTEM] Disabling serial port communication"));
SerialAndTelnet.flush(); // flush so all buffer is printed to serial
setUseSerial(false);
} else {
myDebug_P(PSTR("[SYSTEM] Serial port communication is enabled."));
myDebug_P(PSTR("[SYSTEM] Serial port communication is enabled"));
}
_wifi_connected = true;
@@ -263,11 +267,11 @@ void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
// if we don't want Serial anymore, turn it off
if (!_general_serial) {
myDebug_P(PSTR("[SYSTEM] Disabling serial port communication."));
myDebug_P(PSTR("[SYSTEM] Disabling serial port communication"));
SerialAndTelnet.flush(); // flush so all buffer is printed to serial
setUseSerial(false);
} else {
myDebug_P(PSTR("[SYSTEM] Serial port communication is enabled."));
myDebug_P(PSTR("[SYSTEM] Serial port communication is enabled"));
}
_wifi_connected = true;
@@ -279,7 +283,7 @@ void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
}
if (code == MESSAGE_CONNECTING) {
myDebug_P(PSTR("[WIFI] Connecting to %s"), parameter);
myDebug_P(PSTR("[WIFI] Connecting to %s..."), parameter);
_wifi_connected = false;
}
@@ -365,18 +369,24 @@ void MyESP::_mqttOnMessage(char * topic, char * payload, size_t len) {
}
// MQTT subscribe
void MyESP::mqttSubscribe(const char * topic) {
// returns false if failed
bool MyESP::mqttSubscribe(const char * topic) {
if (mqttClient.connected() && (strlen(topic) > 0)) {
char * topic_s = _mqttTopic(topic);
//char topic_s[MQTT_MAX_TOPIC_SIZE];
//strlcpy(topic_s, _mqttTopic(topic), sizeof(topic_s));
(void)mqttClient.subscribe(topic_s, _mqtt_qos);
uint16_t packet_id = mqttClient.subscribe(topic_s, _mqtt_qos);
// myDebug_P(PSTR("[MQTT] Subscribing to %s"), topic_s);
if (packet_id) {
// add to mqtt log
_addMQTTLog(topic_s, "", 2); // type of 2 means Subscribe. Has an empty payload for now
return true;
} else {
myDebug_P(PSTR("[MQTT] Error subscribing to %s, error %d"), _mqttTopic(topic), packet_id);
}
}
return false; // didn't work
}
// MQTT unsubscribe
@@ -387,30 +397,47 @@ void MyESP::mqttUnsubscribe(const char * topic) {
}
}
// MQTT Publish
void MyESP::mqttPublish(const char * topic, const char * payload) {
// myDebug_P(PSTR("[MQTT] Sending pubish to %s with payload %s"), _mqttTopic(topic), payload); // for debugging
mqttClient.publish(_mqttTopic(topic), _mqtt_qos, _mqtt_retain, payload);
// Publish using the user's custom retain flag
bool MyESP::mqttPublish(const char * topic, const char * payload) {
// use the custom MQTT retain flag
return mqttPublish(topic, payload, _mqtt_retain);
}
// MQTT Publish
// returns true if all good
bool MyESP::mqttPublish(const char * topic, const char * payload, bool retain) {
if (mqttClient.connected() && (strlen(topic) > 0)) {
//myDebug_P(PSTR("[MQTT] Sending publish to %s with payload %s"), _mqttTopic(topic), payload); // for debugging
uint16_t packet_id = mqttClient.publish(_mqttTopic(topic), _mqtt_qos, retain, payload);
if (packet_id) {
_addMQTTLog(topic, payload, 1); // add to the log, using type of 1 for Publish
return true;
} else {
myDebug_P(PSTR("[MQTT] Error publishing to %s with payload %s [error %d]"), _mqttTopic(topic), payload, packet_id);
}
}
return false; // failed
}
// MQTT onConnect - when a connect is established
void MyESP::_mqttOnConnect() {
myDebug_P(PSTR("[MQTT] is connected"));
myDebug_P(PSTR("[MQTT] MQTT connected established"));
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
_mqtt_last_connection = millis();
// say we're alive to the Last Will topic
mqttClient.publish(_mqttTopic(_mqtt_will_topic), 1, true, _mqtt_will_online_payload); // qos=1, retain=true
mqttPublish(_mqtt_will_topic, _mqtt_will_online_payload, true); // force retain on
// subscribe to general subs
mqttSubscribe(MQTT_TOPIC_RESTART);
// subscribe to a start message and send the first publish
// forcing retain to off since we only want to send this once
mqttSubscribe(MQTT_TOPIC_START);
mqttPublish(MQTT_TOPIC_START, MQTT_TOPIC_START_PAYLOAD);
mqttPublish(MQTT_TOPIC_START, MQTT_TOPIC_START_PAYLOAD, false);
// send heartbeat if enabled
_heartbeatCheck(true);
@@ -574,17 +601,8 @@ void MyESP::setTelnet(telnetcommand_callback_f callback_cmd, telnet_callback_f c
}
void MyESP::_telnetConnected() {
myDebug_P(PSTR("[TELNET] Telnet connection established"));
_consoleShowHelp(); // Show the initial message
#ifdef CRASH
// show crash dump if just restarted after a fatal crash
uint32_t crash_time;
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
if ((crash_time != 0) && (crash_time != 0xFFFFFFFF)) {
myDebug_P(PSTR("[SYSTEM] There is stack data available from the last system crash. Use 'crash dump' to view and 'crash clear' to reset"));
}
#endif
myDebug_P(PSTR("[TELNET] Connected to %s version %s. Type ? for commands."), _app_name, _app_version);
//_consoleShowHelp(); // Show the initial message
// call callback
if (_telnet_callback_f) {
@@ -593,7 +611,7 @@ void MyESP::_telnetConnected() {
}
void MyESP::_telnetDisconnected() {
myDebug_P(PSTR("[TELNET] Telnet connection closed"));
// myDebug_P(PSTR("[TELNET] Telnet connection closed"));
if (_telnet_callback_f) {
(_telnet_callback_f)(TELNET_EVENT_DISCONNECT); // call callback
}
@@ -615,7 +633,7 @@ void MyESP::_telnet_setup() {
// Show help of commands
void MyESP::_consoleShowHelp() {
myDebug_P(PSTR(""));
myDebug_P(PSTR("* Connected to: %s version %s"), _app_name, _app_version);
myDebug_P(PSTR("* %s version %s"), _app_name, _app_version);
if (isAPmode()) {
myDebug_P(PSTR("* Device is in AP mode with SSID %s"), jw.getAPSSID().c_str());
@@ -633,8 +651,15 @@ void MyESP::_consoleShowHelp() {
myDebug_P(PSTR("* Commands:"));
myDebug_P(PSTR("* ?/help=show commands, CTRL-D/quit=close telnet session"));
myDebug_P(PSTR("* set, system, restart, mqttlog"));
#ifdef CRASH
myDebug_P(PSTR("* crash <dump | clear | test [n]>"));
// show crash dump if just restarted after a fatal crash
uint32_t crash_time;
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
if ((crash_time) && (crash_time != 0xFFFFFFFF)) {
myDebug_P(PSTR("[SYSTEM] There is stack data available from the last system crash. Use 'crash dump' to view and 'crash clear' to reset"));
}
#endif
// call callback function
@@ -647,7 +672,7 @@ void MyESP::_consoleShowHelp() {
// see if a char * string is empty. It could not be initialized yet.
// return true if there is a value
bool MyESP::_hasValue(char * s) {
bool MyESP::_hasValue(const char * s) {
if ((s == nullptr) || (strlen(s) == 0)) {
return false;
}
@@ -663,8 +688,11 @@ void MyESP::_printSetCommands() {
myDebug_P(PSTR(" set mqtt_enabled <on | off>"));
myDebug_P(PSTR(" set <mqtt_ip | mqtt_username | mqtt_password> [value]"));
myDebug_P(PSTR(" set mqtt_heartbeat <on | off>"));
myDebug_P(PSTR(" set mqtt_base [value]"));
myDebug_P(PSTR(" set mqtt_port [value]"));
myDebug_P(PSTR(" set mqtt_base [string]"));
myDebug_P(PSTR(" set mqtt_port [number]"));
myDebug_P(PSTR(" set mqtt_qos [0,1,2,3]"));
myDebug_P(PSTR(" set mqtt_keepalive [seconds]"));
myDebug_P(PSTR(" set mqtt_retain [on | off]"));
myDebug_P(PSTR(" set ntp_enabled <on | off>"));
myDebug_P(PSTR(" set serial <on | off>"));
myDebug_P(PSTR(" set log_events <on | off>"));
@@ -718,6 +746,9 @@ void MyESP::_printSetCommands() {
myDebug_P(PSTR(" mqtt_base="));
}
myDebug_P(PSTR(" mqtt_port=%d"), _mqtt_port);
myDebug_P(PSTR(" mqtt_keepalive=%d"), _mqtt_keepalive);
myDebug_P(PSTR(" mqtt_retain=%d"), (_mqtt_retain) ? "on" : "off");
myDebug_P(PSTR(" mqtt_qos=%d"), _mqtt_qos);
myDebug_P(PSTR(" mqtt_heartbeat=%s"), (_mqtt_heartbeat) ? "on" : "off");
#ifdef FORCE_SERIAL
@@ -765,148 +796,59 @@ char * MyESP::_telnet_readWord(bool allow_all_chars) {
bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) {
bool save_config = false;
bool save_custom_config = false;
bool restart = false;
// check for our internal commands first
if (strcmp(setting, "erase") == 0) {
_fs_eraseConfig();
return true;
} else if (strcmp(setting, "wifi_ssid") == 0) {
if (value) {
free(_network_ssid);
_network_ssid = strdup(value);
}
save_config = true;
save_config = fs_setSettingValue(&_network_ssid, value, "");
restart = save_config;
//jw.enableSTA(false);
myDebug_P(PSTR("Note: please 'restart' to apply new WiFi settings"));
} else if (strcmp(setting, "wifi_password") == 0) {
if (value) {
free(_network_password);
_network_password = strdup(value);
}
save_config = true;
save_config = fs_setSettingValue(&_network_password, value, "");
restart = save_config;
//jw.enableSTA(false);
myDebug_P(PSTR("Note: please 'restart' to apply new WiFi settings"));
} else if (strcmp(setting, "wifi_mode") == 0) {
if (value) {
if (strcmp(value, "ap") == 0) {
_network_wmode = 1;
save_config = true;
myDebug_P(PSTR("Note: please 'restart' to apply new WiFi settings"));
save_config = restart = true;
} else if (strcmp(value, "client") == 0) {
_network_wmode = 0;
save_config = true;
myDebug_P(PSTR("Note: please 'restart' to apply new WiFi settings"));
save_config = restart = true;
} else {
save_config = false;
}
}
} else if (strcmp(setting, "mqtt_ip") == 0) {
if (value) {
free(_mqtt_ip);
_mqtt_ip = strdup(value);
}
save_config = true;
save_config = fs_setSettingValue(&_mqtt_ip, value, "");
} else if (strcmp(setting, "mqtt_username") == 0) {
if (value) {
free(_mqtt_user);
_mqtt_user = strdup(value);
}
save_config = true;
save_config = fs_setSettingValue(&_mqtt_user, value, "");
} else if (strcmp(setting, "mqtt_password") == 0) {
if (value) {
free(_mqtt_password);
_mqtt_password = strdup(value);
}
save_config = true;
save_config = fs_setSettingValue(&_mqtt_password, value, "");
} else if (strcmp(setting, "mqtt_base") == 0) {
if (value) {
free(_mqtt_base);
_mqtt_base = strdup(value);
}
save_config = true;
save_config = fs_setSettingValue(&_mqtt_base, value, MQTT_BASE_DEFAULT);
} else if (strcmp(setting, "mqtt_port") == 0) {
if (value) {
_mqtt_port = atoi(value);
}
save_config = true;
save_config = fs_setSettingValue(&_mqtt_port, value, MQTT_PORT);
} else if (strcmp(setting, "mqtt_keepalive") == 0) {
save_config = fs_setSettingValue(&_mqtt_keepalive, value, MQTT_KEEPALIVE);
} else if (strcmp(setting, "mqtt_qos") == 0) {
save_config = fs_setSettingValue(&_mqtt_qos, value, MQTT_QOS);
} else if (strcmp(setting, "mqtt_enabled") == 0) {
save_config = true;
if (value) {
if (strcmp(value, "on") == 0) {
_mqtt_enabled = true;
save_config = true;
} else if (strcmp(value, "off") == 0) {
_mqtt_enabled = false;
save_config = true;
} else {
save_config = false;
}
}
save_config = fs_setSettingValue(&_mqtt_enabled, value, false);
} else if (strcmp(setting, "mqtt_retain") == 0) {
save_config = fs_setSettingValue(&_mqtt_retain, value, MQTT_RETAIN);
} else if (strcmp(setting, "serial") == 0) {
save_config = true;
if (value) {
if (strcmp(value, "on") == 0) {
_general_serial = true;
save_config = true;
myDebug_P(PSTR("Type 'restart' to activate Serial mode."));
} else if (strcmp(value, "off") == 0) {
_general_serial = false;
save_config = true;
myDebug_P(PSTR("Type 'restart' to deactivate Serial mode."));
} else {
save_config = false;
}
}
save_config = fs_setSettingValue(&_general_serial, value, false);
restart = save_config;
} else if (strcmp(setting, "mqtt_heartbeat") == 0) {
save_config = true;
if (value) {
if (strcmp(value, "on") == 0) {
_mqtt_heartbeat = true;
save_config = true;
myDebug_P(PSTR("Heartbeat on"));
} else if (strcmp(value, "off") == 0) {
_mqtt_heartbeat = false;
save_config = true;
myDebug_P(PSTR("Heartbeat off"));
} else {
save_config = false;
}
}
save_config = fs_setSettingValue(&_mqtt_heartbeat, value, false);
} else if (strcmp(setting, "ntp_enabled") == 0) {
save_config = true;
if (value) {
if (strcmp(value, "on") == 0) {
_ntp_enabled = true;
save_config = true;
myDebug_P(PSTR("NTP on"));
} else if (strcmp(value, "off") == 0) {
_ntp_enabled = false;
save_config = true;
myDebug_P(PSTR("NTP off"));
} else {
save_config = false;
}
}
save_config = fs_setSettingValue(&_ntp_enabled, value, false);
} else if (strcmp(setting, "log_events") == 0) {
save_config = true;
if (value) {
if (strcmp(value, "on") == 0) {
_general_log_events = true;
save_config = true;
myDebug_P(PSTR("Event logging on"));
} else if (strcmp(value, "off") == 0) {
_general_log_events = false;
save_config = true;
myDebug_P(PSTR("Event logging off"));
} else {
save_config = false;
}
}
save_config = fs_setSettingValue(&_general_log_events, value, false);
} else {
// finally check for any custom commands
if (_fs_setlist_callback_f) {
@@ -920,7 +862,7 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value)
if ((save_config || save_custom_config)) {
// check for 2 params
if (value == nullptr) {
myDebug_P(PSTR("%s setting reset to its default value."), setting);
myDebug_P(PSTR("%s has been reset to its default value."), setting);
} else {
// must be 3 params
myDebug_P(PSTR("%s changed."), setting);
@@ -937,6 +879,10 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value)
ok = _fs_createCustomConfig();
}
if (restart) {
myDebug_P(PSTR("Please 'restart' to apply new settings."));
}
return ok;
}
@@ -999,7 +945,7 @@ void MyESP::_telnetCommand(char * commandLine) {
}
// restart command
if ((strcmp(ptrToCommandName, "restart") == 0) && (wc == 1)) {
if (((strcmp(ptrToCommandName, "restart") == 0) || (strcmp(ptrToCommandName, "reboot") == 0)) && (wc == 1)) {
resetESP();
return;
}
@@ -1315,7 +1261,7 @@ void MyESP::showSystemStats() {
}
if (_ntp_enabled) {
myDebug_P(PSTR(" [NTP] Time in UTC is %02d:%02d:%02d"), hour(now()), minute(now()), second(now()));
myDebug_P(PSTR(" [NTP] Time in UTC is %02d:%02d:%02d"), to_hour(now()), to_minute(now()), to_second(now()));
}
#ifdef CRASH
@@ -1426,7 +1372,7 @@ void MyESP::_heartbeatCheck(bool force = false) {
strlcat(payload, itoa(mem_available, s, 10), sizeof(payload)); // free mem as a %
strlcat(payload, "%", sizeof(payload));
mqttPublish(MQTT_TOPIC_HEARTBEAT, payload); // send to MQTT
mqttPublish(MQTT_TOPIC_HEARTBEAT, payload, false); // send to MQTT with retain off
}
}
@@ -1570,27 +1516,27 @@ char * MyESP::_mqttTopic(const char * topic) {
}
// validates a file in SPIFFS, loads it into the json buffer and returns true if ok
bool MyESP::_fs_validateConfigFile(const char * filename, size_t maxsize, JsonDocument & doc) {
size_t MyESP::_fs_validateConfigFile(const char * filename, size_t maxsize, JsonDocument & doc) {
// see if we can open it
File file = SPIFFS.open(filename, "r");
if (!file) {
myDebug_P(PSTR("[FS] File %s not found"), filename);
return false;
return 0;
}
// check size
size_t size = file.size();
myDebug_P(PSTR("[FS] Checking file %s (size %d bytes)"), filename, size); // remove for debugging
// myDebug_P(PSTR("[FS] Checking file %s (%d bytes)"), filename, size); // remove for debugging
if (size > maxsize) {
file.close();
myDebug_P(PSTR("[FS] File %s size %d is too large (max %d)"), filename, size, maxsize);
return false;
myDebug_P(PSTR("[FS] Error. File %s size %d is too large (max %d)"), filename, size, maxsize);
return 0;
} else if (size == 0) {
file.close();
myDebug_P(PSTR("[FS] Corrupted file %s"), filename);
return false;
myDebug_P(PSTR("[FS] Error. Corrupted file %s"), filename);
return 0;
}
// check integrity by reading file from SPIFFS into the char array
@@ -1599,53 +1545,53 @@ bool MyESP::_fs_validateConfigFile(const char * filename, size_t maxsize, JsonDo
size_t real_size = file.readBytes(buffer, size);
if (real_size != size) {
file.close();
myDebug_P(PSTR("[FS] Error, file %s sizes don't match (%d/%d), looks corrupted"), filename, real_size, size);
myDebug_P(PSTR("[FS] Error. File %s sizes don't match (%d/%d), looks corrupted"), filename, real_size, size);
delete[] buffer;
return false;
return 0;
}
// now read into the given json
DeserializationError error = deserializeJson(doc, buffer);
if (error) {
myDebug_P(PSTR("[FS] Failed to deserialize json, error %s"), error.c_str());
myDebug_P(PSTR("[FS] Error. Failed to deserialize json, error %s"), error.c_str());
delete[] buffer;
return false;
return 0;
}
// serializeJsonPretty(doc, Serial); // enable for debugging
file.close();
delete[] buffer;
return true;
return size;
}
// validates the event log file in SPIFFS
// returns true if all OK
bool MyESP::_fs_validateLogFile(const char * filename) {
size_t MyESP::_fs_validateLogFile(const char * filename) {
// exit if we have disabled logging
if (!_general_log_events) {
return true;
return 0;
}
// see if we can open it
File eventlog = SPIFFS.open(filename, "r");
if (!eventlog) {
myDebug_P(PSTR("[FS] File %s not found"), filename);
return false;
return 0;
}
// check sizes
size_t size = eventlog.size();
size_t maxsize = ESP.getFreeHeap() - 2000; // reserve some buffer
myDebug_P(PSTR("[FS] Checking file %s (size %d bytes, max is %d)"), filename, size, maxsize); // remove for debugging
// myDebug_P(PSTR("[FS] Checking file %s (%d/%d bytes)"), filename, size, maxsize); // remove for debugging
if (size > maxsize) {
eventlog.close();
myDebug_P(PSTR("[FS] File %s size %d is too large (max %d)"), filename, size, maxsize);
return false;
myDebug_P(PSTR("[FS] File %s size %d is too large"), filename, size);
return 0;
} else if (size == 0) {
eventlog.close();
myDebug_P(PSTR("[FS] Corrupted file %s"), filename);
return false;
return 0;
}
/*
@@ -1677,12 +1623,11 @@ bool MyESP::_fs_validateLogFile(const char * filename) {
uint16_t char_count = 0;
bool abort = false;
char char_buffer[MYESP_JSON_LOG_MAXSIZE];
char c;
StaticJsonDocument<MYESP_JSON_LOG_MAXSIZE> doc;
// eventlog.seek(0);
while (eventlog.available() && !abort) {
c = eventlog.read(); // read a char
char c = eventlog.read(); // read a char
// see if we have reached the end of the string
if (c == '\0' || c == '\n') {
@@ -1706,7 +1651,12 @@ bool MyESP::_fs_validateLogFile(const char * filename) {
}
eventlog.close();
return !abort;
if (abort) {
return 0;
}
return size;
}
// format File System
@@ -1738,8 +1688,9 @@ bool MyESP::_fs_loadConfig() {
StaticJsonDocument<MYESP_SPIFFS_MAXSIZE_CONFIG> doc;
// set to true to print out contents of file
if (!_fs_validateConfigFile(MYESP_CONFIG_FILE, MYESP_SPIFFS_MAXSIZE_CONFIG, doc)) {
myDebug_P(PSTR("[FS] Failed to open system config"));
size_t size = _fs_validateConfigFile(MYESP_CONFIG_FILE, MYESP_SPIFFS_MAXSIZE_CONFIG, doc);
if (!size) {
myDebug_P(PSTR("[FS] Error. Failed to open system config"));
return false;
}
@@ -1752,7 +1703,7 @@ bool MyESP::_fs_loadConfig() {
_general_password = strdup(general["password"] | MYESP_HTTP_PASSWORD);
_ws->setAuthentication("admin", _general_password);
_general_hostname = strdup(general["hostname"]);
_general_log_events = general["log_events"] | false; // default is off
_general_log_events = general["log_events"];
// serial is only on when booting
#ifdef FORCE_SERIAL
@@ -1768,6 +1719,9 @@ bool MyESP::_fs_loadConfig() {
_mqtt_ip = strdup(mqtt["ip"] | "");
_mqtt_user = strdup(mqtt["user"] | "");
_mqtt_port = mqtt["port"] | MQTT_PORT;
_mqtt_keepalive = mqtt["keepalive"] | MQTT_KEEPALIVE;
_mqtt_retain = mqtt["retain"];
_mqtt_qos = mqtt["qos"] | MQTT_QOS;
_mqtt_password = strdup(mqtt["password"] | "");
_mqtt_base = strdup(mqtt["base"] | MQTT_BASE_DEFAULT);
@@ -1778,7 +1732,65 @@ bool MyESP::_fs_loadConfig() {
_ntp_interval = 60;
_ntp_enabled = ntp["enabled"];
myDebug_P(PSTR("[FS] System config loaded"));
myDebug_P(PSTR("[FS] System config loaded (%d bytes)"), size);
return true;
}
// saves a string into a config setting, using default value if non set
// returns true if successful
bool MyESP::fs_setSettingValue(char ** setting, const char * value, const char * value_default) {
if (*setting == nullptr) {
free(*setting); // first free any allocated memory
}
if (_hasValue(value)) {
*setting = strdup(value);
} else {
*setting = strdup(value_default); // use the default value
}
return true;
}
// saves a 2-byte short integer into a config setting, using default value if non set
// returns true if successful
bool MyESP::fs_setSettingValue(uint16_t * setting, const char * value, uint16_t value_default) {
if (_hasValue(value)) {
*setting = (uint16_t)atoi(value);
} else {
*setting = value_default; // use the default value
}
return true;
}
// saves an 8-bit integer into a config setting, using default value if non set
// returns true if successful
bool MyESP::fs_setSettingValue(uint8_t * setting, const char * value, uint8_t value_default) {
if (_hasValue(value)) {
*setting = (uint8_t)atoi(value);
} else {
*setting = value_default; // use the default value
}
return true;
}
// saves a bool into a config setting, using default value if non set
// returns true if successful
bool MyESP::fs_setSettingValue(bool * setting, const char * value, bool value_default) {
if (_hasValue(value)) {
if ((strcmp(value, "on") == 0) || (strcmp(value, "yes") == 0) || (strcmp(value, "1") == 0) || (strcmp(value, "true") == 0)) {
*setting = true;
} else if ((strcmp(value, "off") == 0) || (strcmp(value, "no") == 0) || (strcmp(value, "0") == 0) || (strcmp(value, "false") == 0)) {
*setting = false;
} else {
return false; // invalid setting value
}
} else {
*setting = value_default; // use the default value
}
return true;
}
@@ -1787,8 +1799,9 @@ bool MyESP::_fs_loadConfig() {
bool MyESP::_fs_loadCustomConfig() {
StaticJsonDocument<MYESP_SPIFFS_MAXSIZE_CONFIG> doc;
if (!_fs_validateConfigFile(MYESP_CUSTOMCONFIG_FILE, MYESP_SPIFFS_MAXSIZE_CONFIG, doc)) {
myDebug_P(PSTR("[FS] Failed to open custom config"));
size_t size = _fs_validateConfigFile(MYESP_CUSTOMCONFIG_FILE, MYESP_SPIFFS_MAXSIZE_CONFIG, doc);
if (!size) {
myDebug_P(PSTR("[FS] Error. Failed to open custom config"));
return false;
}
@@ -1798,7 +1811,7 @@ bool MyESP::_fs_loadCustomConfig() {
myDebug_P(PSTR("[FS] Error reading custom config"));
return false;
} else {
myDebug_P(PSTR("[FS] Custom config loaded"));
myDebug_P(PSTR("[FS] Custom config loaded (%d bytes)"), size);
}
}
@@ -1838,7 +1851,7 @@ bool MyESP::fs_saveCustomConfig(JsonObject root) {
_writeEvent("INFO", "system", "Custom config stored in the SPIFFS", "");
}
myDebug_P(PSTR("[FS] custom config saved"));
// myDebug_P(PSTR("[FS] custom config saved"));
ok = true;
}
}
@@ -1873,7 +1886,7 @@ bool MyESP::fs_saveConfig(JsonObject root) {
if (_general_log_events) {
_writeEvent("INFO", "system", "System config stored in the SPIFFS", "");
}
myDebug_P(PSTR("[FS] system config saved"));
// myDebug_P(PSTR("[FS] system config saved"));
ok = true;
}
@@ -1903,6 +1916,7 @@ bool MyESP::_fs_writeConfig() {
general["password"] = _general_password;
general["serial"] = _general_serial;
general["hostname"] = _general_hostname;
general["log_events"] = _general_log_events;
JsonObject mqtt = doc.createNestedObject("mqtt");
mqtt["enabled"] = _mqtt_enabled;
@@ -1910,6 +1924,10 @@ bool MyESP::_fs_writeConfig() {
mqtt["ip"] = _mqtt_ip;
mqtt["user"] = _mqtt_user;
mqtt["port"] = _mqtt_port;
mqtt["qos"] = _mqtt_qos;
mqtt["keepalive"] = _mqtt_keepalive;
mqtt["retain"] = _mqtt_retain;
mqtt["password"] = _mqtt_password;
mqtt["base"] = _mqtt_base;
@@ -1987,8 +2005,9 @@ void MyESP::_fs_setup() {
*/
// validate the event log. Sometimes it can can corrupted.
if (_fs_validateLogFile(MYESP_EVENTLOG_FILE)) {
myDebug_P(PSTR("[FS] Event log is healthy"));
size_t size = _fs_validateLogFile(MYESP_EVENTLOG_FILE);
if (size) {
myDebug_P(PSTR("[FS] Event log loaded (%d bytes)"), size);
} else {
myDebug_P(PSTR("[FS] Resetting event log"));
SPIFFS.remove(MYESP_EVENTLOG_FILE);
@@ -2284,12 +2303,11 @@ void MyESP::_sendEventLog(uint8_t page) {
uint8_t line_count = 0;
bool abort = false;
char char_buffer[MYESP_JSON_LOG_MAXSIZE];
char c;
float pages;
// start at top and read until we find the page we want (sets of 10)
while (eventlog.available() && !abort) {
c = eventlog.read();
char c = eventlog.read();
// see if we have reached the end of the string
if (c == '\0' || c == '\n') {

View File

@@ -9,7 +9,7 @@
#ifndef MyESP_h
#define MyESP_h
#define MYESP_VERSION "1.2.6"
#define MYESP_VERSION "1.2.12"
#include <ArduinoJson.h>
#include <ArduinoOTA.h>
@@ -64,7 +64,7 @@ extern struct rst_info resetInfo;
#define MYESP_LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms) = 30 seconds
// WIFI
#define MYESP_WIFI_CONNECT_TIMEOUT 10000 // Connecting timeout for WIFI in ms (10 seconds)
#define MYESP_WIFI_CONNECT_TIMEOUT 20000 // Connecting timeout for WIFI in ms (20 seconds)
#define MYESP_WIFI_RECONNECT_INTERVAL 600000 // If could not connect to WIFI, retry after this time in ms. 10 minutes
// MQTT
@@ -79,22 +79,26 @@ extern struct rst_info resetInfo;
#define MQTT_WILL_ONLINE_PAYLOAD "online" // for last will & testament payload
#define MQTT_WILL_OFFLINE_PAYLOAD "offline" // for last will & testament payload
#define MQTT_BASE_DEFAULT "home" // default MQTT prefix to topics
#define MQTT_RETAIN false
#define MQTT_KEEPALIVE 60 // 1 minute
#define MQTT_QOS 1
#define MQTT_RETAIN false // default false
#define MQTT_KEEPALIVE 60 // default keepalive 1 minute
#define MQTT_QOS 0 // default qos 0
#define MQTT_WILL_TOPIC "status" // for last will & testament topic name
#define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT topic
#define MQTT_MAX_PAYLOAD_SIZE 500 // max size of a JSON object. See https://arduinojson.org/v6/assistant/
#define MQTT_MAX_PAYLOAD_SIZE 700 // max size of a JSON object. See https://arduinojson.org/v6/assistant/
#define MQTT_MAX_PAYLOAD_SIZE_LARGE 2000 // max size of a large JSON object, like for sending MQTT log
#define MYESP_JSON_MAXSIZE 2000 // for large Dynamic json files
#define MYESP_MQTTLOG_MAX 40 // max number of log entries for MQTT publishes and subscribes
#define MYESP_JSON_LOG_MAXSIZE 300 // max size of an JSON log entry
// Internal MQTT events
#define MQTT_CONNECT_EVENT 0
#define MQTT_DISCONNECT_EVENT 1
#define MQTT_MESSAGE_EVENT 2
#define MYESP_JSON_MAXSIZE 2000 // for large Dynamic json files
#define MYESP_MQTTLOG_MAX 60 // max number of log entries for MQTT publishes and subscribes
#define MYESP_JSON_LOG_MAXSIZE 300 // max size of an JSON log entry
#define MYESP_MQTT_PAYLOAD_ON '1' // for MQTT switch on
#define MYESP_MQTT_PAYLOAD_OFF '0' // for MQTT switch off
// Telnet
#define TELNET_SERIAL_BAUD 115200
#define TELNET_MAX_COMMAND_LENGTH 80 // length of a command
@@ -268,9 +272,10 @@ class MyESP {
// mqtt
bool isMQTTConnected();
void mqttSubscribe(const char * topic);
bool mqttSubscribe(const char * topic);
void mqttUnsubscribe(const char * topic);
void mqttPublish(const char * topic, const char * payload);
bool mqttPublish(const char * topic, const char * payload);
bool mqttPublish(const char * topic, const char * payload, bool retain);
void setMQTT(mqtt_callback_f callback);
// OTA
@@ -287,6 +292,10 @@ class MyESP {
void setSettings(fs_loadsave_callback_f loadsave, fs_setlist_callback_f setlist, bool useSerial = true);
bool fs_saveConfig(JsonObject root);
bool fs_saveCustomConfig(JsonObject root);
bool fs_setSettingValue(char ** setting, const char * value, const char * value_default);
bool fs_setSettingValue(uint16_t * setting, const char * value, uint16_t value_default);
bool fs_setSettingValue(uint8_t * setting, const char * value, uint8_t value_default);
bool fs_setSettingValue(bool * setting, const char * value, bool value_default);
// Web
void setWeb(web_callback_f callback_web);
@@ -330,10 +339,10 @@ class MyESP {
char * _mqtt_ip;
char * _mqtt_user;
char * _mqtt_password;
int _mqtt_port;
uint16_t _mqtt_port;
char * _mqtt_base;
bool _mqtt_enabled;
uint32_t _mqtt_keepalive;
uint16_t _mqtt_keepalive;
uint8_t _mqtt_qos;
bool _mqtt_retain;
char * _mqtt_will_topic;
@@ -384,8 +393,8 @@ class MyESP {
bool _fs_writeConfig();
bool _fs_createCustomConfig();
bool _fs_sendConfig();
bool _fs_validateConfigFile(const char * filename, size_t maxsize, JsonDocument & doc);
bool _fs_validateLogFile(const char * filename);
size_t _fs_validateConfigFile(const char * filename, size_t maxsize, JsonDocument & doc);
size_t _fs_validateLogFile(const char * filename);
fs_loadsave_callback_f _fs_loadsave_callback_f;
fs_setlist_callback_f _fs_setlist_callback_f;
@@ -406,7 +415,7 @@ class MyESP {
bool _formatreq;
unsigned long _getUptime();
char * _getBuildTime();
bool _hasValue(char * s);
bool _hasValue(const char * s);
void _printHeap(const char * s);
// reset reason and rtcmem

View File

@@ -242,9 +242,8 @@ size_t TelnetSpy::write(uint8_t data) {
sendBlock();
}
if (bufUsed == bufLen) {
char c;
while (bufUsed > 0) {
c = pullTelnetBuf();
char c = pullTelnetBuf();
if (c == '\n') {
addTelnetBuf('\r');
break;

View File

@@ -73,7 +73,6 @@ void breakTime(time_t timeInput, tmElements_t & tm) {
days -= LEAP_YEAR(year) ? 366 : 365;
time -= days; // now it is days in this year, starting at 0
days = 0;
month = 0;
monthLength = 0;
for (month = 0; month < 12; month++) {
@@ -104,36 +103,21 @@ void refreshCache(time_t t) {
}
}
int day(time_t t) { // the day for the given time (0-6)
refreshCache(t);
return tm.Day;
}
int month(time_t t) { // the month for the given time
refreshCache(t);
return tm.Month;
}
int second(time_t t) { // the second for the given time
uint8_t to_second(time_t t) { // the second for the given time
refreshCache(t);
return tm.Second;
}
int minute(time_t t) { // the minute for the given time
uint8_t to_minute(time_t t) { // the minute for the given time
refreshCache(t);
return tm.Minute;
}
int hour(time_t t) { // the hour for the given time
uint8_t to_hour(time_t t) { // the hour for the given time
refreshCache(t);
return tm.Hour;
}
int year(time_t t) { // the year for the given time
refreshCache(t);
return tmYearToCalendar(tm.Year);
}
void setTime(time_t t) {
sysTime = (uint32_t)t;
nextSyncTime = (uint32_t)t + syncInterval;

View File

@@ -1,5 +1,5 @@
#ifndef _Time_h
#define _Time_h
#ifndef _TimeLib_h
#define _TimeLib_h
#include <Arduino.h>
@@ -27,7 +27,7 @@ typedef struct {
uint8_t Day;
uint8_t Month;
uint8_t Year; // offset from 1970;
} tmElements_t, TimeElements, *tmElementsPtr_t;
} tmElements_t;
typedef time_t (*getExternalTime)();
@@ -38,12 +38,8 @@ void setSyncProvider(getExternalTime getTimeFunction); // identify the e
void setSyncInterval(time_t interval); // set the number of seconds between re-sync
time_t makeTime(const tmElements_t & tm); // convert time elements into time_t
int hour(time_t t); // the hour for the given time
int minute(time_t t); // the minute for the given time
int second(time_t t); // the second for the given time
int day(time_t t); // the day for the given time
int month(time_t t); // the month for the given time
int weekday(time_t t); // the weekday for the given time
int year(time_t t); // the year for the given time
uint8_t to_hour(time_t t); // the hour for the given time
uint8_t to_minute(time_t t); // the minute for the given time
uint8_t to_second(time_t t); // the second for the given time
}
#endif

View File

@@ -97,8 +97,6 @@ void DS18::loop() {
char * DS18::getDeviceString(char * buffer, unsigned char index) {
uint8_t size = 128;
if (index < _count) {
uint8_t * address = _devices[index].address;
unsigned char chip_id = chip(index);
if (chip_id == DS18_CHIP_DS18S20) {
strlcpy(buffer, "DS18S20", size);
@@ -112,6 +110,8 @@ char * DS18::getDeviceString(char * buffer, unsigned char index) {
strlcpy(buffer, "Unknown", size);
}
/*
uint8_t * address = _devices[index].address;
char a[30] = {0};
snprintf(a,
sizeof(a),
@@ -127,6 +127,7 @@ char * DS18::getDeviceString(char * buffer, unsigned char index) {
_gpio);
strlcat(buffer, a, size);
*/
} else {
strlcpy(buffer, "invalid", size);
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@
#include "ems.h"
#include "MyESP.h"
#include "ems_devices.h"
#include "ems_utils.h"
#include "emsuart.h"
#include <CircularBuffer.h> // https://github.com/rlogiacco/CircularBuffer
@@ -17,10 +18,6 @@
uint8_t _TEST_DATA_max = ArraySize(TEST_DATA);
#endif
// MyESP class for logging to telnet and serial
#define myDebug(...) myESP.myDebug(__VA_ARGS__)
#define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__)
_EMS_Sys_Status EMS_Sys_Status; // EMS Status
CircularBuffer<_EMS_TxTelegram, EMS_TX_TELEGRAM_QUEUE_MAX> EMS_TxQueue; // FIFO queue for Tx send buffer
@@ -99,6 +96,9 @@ void _process_RCPLUSStatusMode(_EMS_RxTelegram * EMS_RxTelegram);
// Junkers FR10 & FW100
void _process_JunkersStatusMessage(_EMS_RxTelegram * EMS_RxTelegram);
// Mixers MM100
void _process_MMPLUSStatusMessage(_EMS_RxTelegram * EMS_RxTelegram);
/**
* Recognized EMS types and the functions they call to process the telegrams
* Format: MODEL ID, TYPE ID, Description, function, emsplus
@@ -184,8 +184,11 @@ const _EMS_Type EMS_Types[] = {
{EMS_MODEL_ALL, EMS_TYPE_RCPLUSStatusMode, "RCPLUSStatusMode", _process_RCPLUSStatusMode},
// Junkers FR10
{EMS_MODEL_ALL, EMS_TYPE_JunkersStatusMessage, "JunkersStatusMessage", _process_JunkersStatusMessage}
{EMS_MODEL_ALL, EMS_TYPE_JunkersStatusMessage, "JunkersStatusMessage", _process_JunkersStatusMessage},
// Mixing devices
{EMS_MODEL_MM100, EMS_TYPE_MMPLUSStatusMessage_HC1, "MMPLUSStatusMessage_HC1", _process_MMPLUSStatusMessage},
{EMS_MODEL_MM100, EMS_TYPE_MMPLUSStatusMessage_HC2, "MMPLUSStatusMessage_HC2", _process_MMPLUSStatusMessage},
};
@@ -196,12 +199,14 @@ uint8_t _SolarModule_Devices_max = ArraySize(SolarModule_Devices); // number of
uint8_t _Other_Devices_max = ArraySize(Other_Devices); // number of other ems devices
uint8_t _Thermostat_Devices_max = ArraySize(Thermostat_Devices); // number of defined thermostat types
uint8_t _HeatPump_Devices_max = ArraySize(HeatPump_Devices); // number of defined heatpump types
uint8_t _Mixing_Devices_max = ArraySize(Mixing_Devices); // number of mixing device types
// these structs contain the data we store from the specific EMS devices
_EMS_Boiler EMS_Boiler; // for boiler
_EMS_Thermostat EMS_Thermostat; // for thermostat
_EMS_SolarModule EMS_SolarModule; // for solar modules
_EMS_HeatPump EMS_HeatPump; // for heatpumps
_EMS_Mixing EMS_Mixing; // for mixing devices
_EMS_Other EMS_Other; // for other known EMS devices
// CRC lookup table with poly 12 for faster checking
@@ -223,7 +228,6 @@ const uint8_t TX_WRITE_TIMEOUT_COUNT = 2; // 3 retries before timeout
const uint32_t EMS_BUS_TIMEOUT = 15000; // timeout in ms before recognizing the ems bus is offline (15 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
void ems_init() {
ems_clearDeviceList(); // init the device map
@@ -267,6 +271,18 @@ void ems_init() {
EMS_Thermostat.hc[i].curr_roomTemp = EMS_VALUE_SHORT_NOTSET;
}
EMS_Mixing.detected = false;
// init all mixing modules
for (uint8_t i = 0; i < EMS_THERMOSTAT_MAXHC; i++) {
EMS_Mixing.hc[i].hc = i + 1;
EMS_Mixing.hc[i].flowTemp = EMS_VALUE_SHORT_NOTSET;
EMS_Mixing.hc[i].pumpMod = EMS_VALUE_INT_NOTSET;
EMS_Mixing.hc[i].valveStatus = EMS_VALUE_INT_NOTSET;
EMS_Mixing.hc[i].device_id = EMS_ID_NONE;
EMS_Mixing.hc[i].model_id = EMS_MODEL_NONE;
EMS_Mixing.hc[i].product_id = EMS_ID_NONE;
}
// UBAParameterWW
EMS_Boiler.wWActivated = EMS_VALUE_INT_NOTSET; // Warm Water activated
EMS_Boiler.wWSelTemp = EMS_VALUE_INT_NOTSET; // Warm Water selected temperature
@@ -378,6 +394,10 @@ bool ems_getThermostatEnabled() {
return (EMS_Thermostat.device_id != EMS_ID_NONE);
}
bool ems_getMixingDeviceEnabled() {
return EMS_Mixing.detected;
}
bool ems_getSolarModuleEnabled() {
return (EMS_SolarModule.device_id != EMS_ID_NONE);
}
@@ -424,9 +444,13 @@ _EMS_SYS_LOGGING ems_getLogging() {
return EMS_Sys_Status.emsLogging;
}
void ems_setLogging(_EMS_SYS_LOGGING loglevel) {
void ems_setLogging(_EMS_SYS_LOGGING loglevel, bool silent) {
if (loglevel <= EMS_SYS_LOGGING_JABBER) {
EMS_Sys_Status.emsLogging = loglevel;
if (silent) {
return; // don't print to telnet/serial
}
if (loglevel == EMS_SYS_LOGGING_NONE) {
myDebug_P(PSTR("System Logging set to None"));
} else if (loglevel == EMS_SYS_LOGGING_BASIC) {
@@ -472,36 +496,6 @@ uint8_t _crcCalculator(uint8_t * data, uint8_t len) {
return crc;
}
// like itoa but for hex, and quicker
char * _hextoa(uint8_t value, char * buffer) {
char * p = buffer;
byte nib1 = (value >> 4) & 0x0F;
byte nib2 = (value >> 0) & 0x0F;
*p++ = nib1 < 0xA ? '0' + nib1 : 'A' + nib1 - 0xA;
*p++ = nib2 < 0xA ? '0' + nib2 : 'A' + nib2 - 0xA;
*p = '\0'; // null terminate just in case
return buffer;
}
// for decimals 0 to 99, printed as a 2 char string
char * _smallitoa(uint8_t value, char * buffer) {
buffer[0] = ((value / 10) == 0) ? '0' : (value / 10) + '0';
buffer[1] = (value % 10) + '0';
buffer[2] = '\0';
return buffer;
}
/* for decimals 0 to 999, printed as a string
* From @nomis
*/
char * _smallitoa3(uint16_t value, char * buffer) {
buffer[0] = ((value / 100) == 0) ? '0' : (value / 100) + '0';
buffer[1] = (((value % 100) / 10) == 0) ? '0' : ((value % 100) / 10) + '0';
buffer[2] = (value % 10) + '0';
buffer[3] = '\0';
return buffer;
}
/**
* Find the pointer to the EMS_Types array for a given type ID
* or -1 if not found
@@ -609,6 +603,7 @@ void _ems_sendTelegram() {
_EMS_RxTelegram EMS_RxTelegram; // create new Rx object
EMS_RxTelegram.length = EMS_TxTelegram.length; // full length of telegram
EMS_RxTelegram.telegram = EMS_TxTelegram.data;
EMS_RxTelegram.data_length = 0; // ignore #data=
EMS_RxTelegram.timestamp = millis(); // now
_debugPrintTelegram("Sending raw: ", &EMS_RxTelegram, COLOR_CYAN, true);
}
@@ -933,7 +928,6 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
// 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
if (telegram[length - 1] != _crcCalculator(telegram, length)) {
LA_PULSE(200);
EMS_Sys_Status.emxCrcErr++;
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) {
_debugPrintTelegram("Corrupt telegram: ", &EMS_RxTelegram, COLOR_RED, true);
@@ -1010,7 +1004,7 @@ void _printMessage(_EMS_RxTelegram * EMS_RxTelegram) {
strlcpy(color_s, COLOR_MAGENTA, sizeof(color_s));
}
if (length != 0) {
if (length) {
// type
strlcat(output_str, ", type 0x", sizeof(output_str));
@@ -1135,7 +1129,7 @@ void _processType(_EMS_RxTelegram * EMS_RxTelegram) {
// release the lock on the TxQueue
EMS_Sys_Status.emsTxStatus = EMS_TX_STATUS_IDLE;
// at this point we can assume TxStatus is EMS_TX_STATUS_WAIT as we just sent a read or validate
// at this point we can assume TxStatus was EMS_TX_STATUS_WAIT as we just sent a read or validate telegram
// for READ or VALIDATE the dest (telegram[1]) is always us, so check for this
// and if not we probably didn't get any response so remove the last Tx from the queue and process the telegram anyway
if ((telegram[1] & 0x7F) != EMS_ID_ME) {
@@ -1345,6 +1339,7 @@ void _process_UBAMonitorSlow(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Boiler.burnStarts = _toLong(10);
EMS_Boiler.burnWorkMin = _toLong(13);
EMS_Boiler.heatWorkMin = _toLong(19);
EMS_Boiler.switchTemp = _toShort(25);
}
/**
@@ -1464,6 +1459,21 @@ void _process_EasyStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT
}
void _process_MMPLUSStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
uint8_t hc = (EMS_RxTelegram->type - EMS_TYPE_MMPLUSStatusMessage_HC1); // 0 to 3
if (hc >= EMS_THERMOSTAT_MAXHC) {
return; // invalid type
}
EMS_Mixing.hc[hc].active = true;
if (EMS_RxTelegram->data_length == 1) {
} else if (EMS_RxTelegram->data_length > 8) {
EMS_Mixing.hc[hc].flowTemp = _toShort(EMS_OFFSET_MMPLUSStatusMessage_flow_temp);
EMS_Mixing.hc[hc].pumpMod = _toByte(EMS_OFFSET_MMPLUSStatusMessage_pump_mod);
EMS_Mixing.hc[hc].valveStatus = _toByte(EMS_OFFSET_MMPLUSStatusMessage_valve_status);
}
}
/**
* type 0x01A5 - data from the Nefit RC1010/3000 thermostat (0x18) and RC300/310s on 0x10
* EMS+ messages may come in with different offsets so handle them here
@@ -1471,7 +1481,7 @@ void _process_EasyStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
void _process_RCPLUSStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
// figure out which heating circuit
uint8_t hc = (EMS_RxTelegram->type - EMS_TYPE_RCPLUSStatusMessage_HC1); // 0 to 3
if (hc > 4) {
if (hc >= EMS_THERMOSTAT_MAXHC) {
return; // invalid type
}
EMS_Thermostat.hc[hc].active = true;
@@ -1520,14 +1530,17 @@ void _process_RCPLUSStatusMode(_EMS_RxTelegram * EMS_RxTelegram) {
* FR10 Junkers - type x006F
*/
void _process_JunkersStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
if (EMS_RxTelegram->offset == 0) {
if (EMS_RxTelegram->offset == 0 && EMS_RxTelegram->data_length > 1) {
uint8_t hc = EMS_THERMOSTAT_DEFAULTHC - 1; // use HC1
EMS_Thermostat.hc[hc].active = true;
// 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
EMS_Thermostat.hc[hc].curr_roomTemp = _toShort(EMS_OFFSET_JunkersStatusMessage_curr); // value is * 10
EMS_Thermostat.hc[hc].setpoint_roomTemp = _toShort(EMS_OFFSET_JunkersStatusMessage_setpoint); // value is * 10
EMS_Thermostat.hc[hc].mode = _toByte(EMS_OFFSET_JunkersStatusMessage_mode);
EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT
}
}
@@ -1681,7 +1694,7 @@ void _process_SM10Monitor(_EMS_RxTelegram * EMS_RxTelegram) {
*/
void _process_SM100Monitor(_EMS_RxTelegram * EMS_RxTelegram) {
// only process the complete telegram, not partial
if (EMS_RxTelegram->offset != 0) {
if (EMS_RxTelegram->offset) {
return;
}
@@ -1792,7 +1805,7 @@ void _process_ISM1Set(_EMS_RxTelegram * EMS_RxTelegram) {
*/
void _process_SetPoints(_EMS_RxTelegram * EMS_RxTelegram) {
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) {
if (EMS_RxTelegram->data_length != 0) {
if (EMS_RxTelegram->data_length) {
uint8_t setpoint = EMS_RxTelegram->data[0]; // flow temp
//uint8_t ww_power = data[2]; // power in %
@@ -1884,6 +1897,10 @@ void _addDevice(uint8_t model_type, uint8_t src, uint8_t product_id, char * vers
strlcat(device_type, "Heat Pump", sizeof(device_type));
strlcpy(device.model_string, HeatPump_Devices[i].model_string, sizeof(device.model_string));
break;
case EMS_MODELTYPE_MIXING:
strlcat(device_type, "Mixing Device", sizeof(device_type));
strlcpy(device.model_string, Mixing_Devices[i].model_string, sizeof(device.model_string));
break;
case EMS_MODELTYPE_OTHER:
strlcat(device_type, "Other", sizeof(device_type));
strlcpy(device.model_string, Other_Devices[i].model_string, sizeof(device.model_string));
@@ -2028,12 +2045,6 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Boiler.product_id = Boiler_Devices[i].product_id;
strlcpy(EMS_Boiler.version, version, sizeof(EMS_Boiler.version));
// check to see if its a Junkers Heatronic 3, which has a different poll'ing logic
if (EMS_Boiler.product_id == EMS_PRODUCTID_HEATRONIC) {
EMS_Sys_Status.emsIDMask = 0x80;
EMS_Sys_Status.emsPollAck[0] = EMS_ID_ME ^ EMS_Sys_Status.emsIDMask;
}
ems_getBoilerValues(); // get Boiler values that we would usually have to wait for
}
return;
@@ -2123,6 +2134,23 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
return;
}
// look for mixing devices
i = 0;
while (i < _Mixing_Devices_max) {
if (Mixing_Devices[i].product_id == product_id) {
typeFound = true;
break;
}
i++;
}
if (typeFound) {
_addDevice(EMS_MODELTYPE_MIXING, EMS_RxTelegram->src, product_id, version, i);
ems_doReadCommand(EMS_TYPE_MMPLUSStatusMessage_HC1, EMS_RxTelegram->src);
EMS_Mixing.detected = true;
return;
}
// finally look for the other EMS devices
i = 0;
while (i < _Other_Devices_max) {
@@ -2142,47 +2170,12 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
}
}
/*
* See if we have a Junkers Heatronic 3 compatible device
* Do a read command for the version with the src having the MSB set
*/
void _ems_detectJunkers() {
#ifdef JUNKERS_DETECT
char s[30] = {0};
snprintf(s, sizeof(s), "%02X %02X %02X 00 %02X", (EMS_ID_ME | 0x80), (EMS_ID_BOILER | 0x080), EMS_TYPE_Version, EMS_MAX_TELEGRAM_LENGTH);
ems_sendRawTelegram(s);
#endif
}
/*
* Figure out the boiler and thermostat types
*/
void ems_discoverModels() {
myDebug_P(PSTR("Starting auto discover of EMS devices..."));
//myDebug_P(PSTR("Starting auto discover of EMS devices..."));
ems_doReadCommand(EMS_TYPE_UBADevices, EMS_ID_BOILER);
// ems_startupTelegrams();
// TODO remove this part eventually, ems_discoverModels()
/*
// boiler...
// ems_doReadCommand(EMS_TYPE_Version, EMS_ID_BOILER);
// _ems_detectJunkers(); // special hack for Junkers detection
ems_doReadCommand(EMS_TYPE_Version, EMS_ID_SM); // check if there is Solar Module available
ems_doReadCommand(EMS_TYPE_Version, EMS_ID_HP); // check if there is HeatPump Module available
// thermostat...
// if it hasn't been set, auto discover it
if (EMS_Thermostat.device_id == EMS_ID_NONE) {
ems_scanDevices(); // auto-discover it
} else {
// set the model as hardcoded (see my_devices.h) and fetch the version and product id
ems_doReadCommand(EMS_TYPE_Version, EMS_Thermostat.device_id);
}
*/
}
/**
@@ -2531,9 +2524,6 @@ void ems_scanDevices() {
for (uint8_t device_id : Device_Ids) {
ems_doReadCommand(EMS_TYPE_Version, device_id);
}
// add a check for Junkers onto the queue
_ems_detectJunkers();
}
/**
@@ -2784,7 +2774,7 @@ void ems_setThermostatTemp(float temperature, uint8_t hc_num, uint8_t temptype)
return;
}
if (hc_num < 1 || hc_num > 4) {
if (hc_num < 1 || hc_num > EMS_THERMOSTAT_MAXHC) {
myDebug_P(PSTR("Invalid HC number"));
return;
}
@@ -2799,7 +2789,11 @@ void ems_setThermostatTemp(float temperature, uint8_t hc_num, uint8_t temptype)
EMS_TxTelegram.action = EMS_TX_TELEGRAM_WRITE;
EMS_TxTelegram.dest = device_id;
myDebug_P(PSTR("Setting new thermostat temperature for heating circuit %d type %d (0=auto,1=night,2=day,3=holiday)"), hc_num, temptype);
char s[10] = {0};
myDebug_P(PSTR("Setting new thermostat temperature to %s for heating circuit %d type %d (0=auto,1=night,2=day,3=holiday)"),
_float_to_char(s, temperature),
hc_num,
temptype);
if (model_id == EMS_MODEL_RC20) {
EMS_TxTelegram.type = EMS_TYPE_RC20Set;
@@ -2894,24 +2888,38 @@ void ems_setThermostatMode(uint8_t mode, uint8_t hc_num) {
return;
}
if (hc_num < 1 || hc_num > 4) {
if (hc_num < 1 || hc_num > EMS_THERMOSTAT_MAXHC) {
myDebug_P(PSTR("Invalid HC number"));
return;
}
uint8_t model_id = EMS_Thermostat.model_id;
uint8_t device_id = EMS_Thermostat.device_id;
uint8_t set_mode;
// RC300/1000/3000 have different settings
if (model_id == EMS_MODEL_RC300) {
if (mode == 1) {
mode = 0; // manual
set_mode = 0; // manual/heat
} else {
mode = 0xFF; // auto
set_mode = 0xFF; // auto
}
} else {
set_mode = mode;
}
myDebug_P(PSTR("Setting thermostat mode to %d for heating circuit %d"), mode, hc_num);
// 0=off, 1=manual, 2=auto, 3=night, 4=day
if (mode == 0) {
myDebug_P(PSTR("Setting thermostat mode to off for heating circuit %d"), hc_num);
} else if (set_mode == 1) {
myDebug_P(PSTR("Setting thermostat mode to manual for heating circuit %d"), hc_num);
} else if (set_mode == 2) {
myDebug_P(PSTR("Setting thermostat mode to auto for heating circuit %d"), hc_num);
} else if (set_mode == 3) {
myDebug_P(PSTR("Setting thermostat mode to night for heating circuit %d"), hc_num);
} else if (set_mode == 4) {
myDebug_P(PSTR("Setting thermostat mode to day for heating circuit %d"), hc_num);
}
_EMS_TxTelegram EMS_TxTelegram = EMS_TX_TELEGRAM_NEW; // create new Tx
EMS_TxTelegram.timestamp = millis(); // set timestamp
@@ -2920,7 +2928,7 @@ void ems_setThermostatMode(uint8_t mode, uint8_t hc_num) {
EMS_TxTelegram.action = EMS_TX_TELEGRAM_WRITE;
EMS_TxTelegram.dest = device_id;
EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH;
EMS_TxTelegram.dataValue = mode;
EMS_TxTelegram.dataValue = set_mode;
// handle different thermostat types
if (model_id == EMS_MODEL_RC20) {

View File

@@ -4,7 +4,6 @@
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
*
* See ChangeLog.md for history
* See README.md for Acknowledgments
*
*/
@@ -13,67 +12,8 @@
#include <Arduino.h>
#include <list> // std::list
/* 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
// EMS tx_mode types
#define EMS_TXMODE_DEFAULT 1 // Default (was previously known as tx_mode 2)
#define EMS_TXMODE_DEFAULT 1 // Default (was previously known as tx_mode 2 in v1.8.x)
#define EMS_TXMODE_EMSPLUS 2 // EMS+
#define EMS_TXMODE_HT3 3 // Junkers HT3
@@ -87,7 +27,6 @@
#define EMS_ID_GATEWAY 0x48 // KM200 Web Gateway
// Product IDs
#define EMS_PRODUCTID_HEATRONIC 95 // Junkers Heatronic 3 device
#define EMS_PRODUCTID_SM10 73 // SM10 solar module
#define EMS_PRODUCTID_SM50 162 // SM50 solar module
#define EMS_PRODUCTID_SM100 163 // SM100 solar module
@@ -112,7 +51,7 @@
// trigger settings to determine if hot tap water or the heating is active
#define EMS_BOILER_BURNPOWER_TAPWATER 100
#define EMS_BOILER_SELFLOWTEMP_HEATING 70
#define EMS_BOILER_SELFLOWTEMP_HEATING 30 // was 70, changed to 30 for https://github.com/proddy/EMS-ESP/issues/193
// define maximum setable tapwater temperature
#define EMS_BOILER_TAPWATER_TEMPERATURE_MAX 60
@@ -132,6 +71,7 @@
#define EMS_MODELTYPE_HP 4 // success color
#define EMS_MODELTYPE_OTHER 5 // no color
#define EMS_MODELTYPE_UNKNOWN 6 // no color
#define EMS_MODELTYPE_MIXING 7
#define EMS_MODELTYPE_UNKNOWN_STRING "unknown?" // model type text to use when discovering an unknown device
@@ -246,7 +186,7 @@ const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
// where defintions are stored
typedef struct {
uint8_t product_id;
char model_string[50];
char model_string[70];
} _Boiler_Device;
typedef struct {
@@ -273,6 +213,11 @@ typedef struct {
bool write_supported;
} _Thermostat_Device;
typedef struct {
uint8_t product_id;
char model_string[50];
} _Mixing_Device;
// for consolidating all types
typedef struct {
uint8_t model_type; // 1=boiler, 2=thermostat, 3=sm, 4=other, 5=unknown
@@ -282,6 +227,7 @@ typedef struct {
char model_string[50];
} _Generic_Device;
/*
* Telegram package defintions
*/
@@ -322,6 +268,7 @@ typedef struct {
uint32_t burnStarts; // # burner starts
uint32_t burnWorkMin; // Total burner operating time
uint32_t heatWorkMin; // Total heat operating time
uint16_t switchTemp; // Switch temperature
// UBAMonitorWWMessage
uint16_t wWCurTmp; // Warm Water current temperature
@@ -364,6 +311,25 @@ typedef struct {
char version[10];
} _EMS_Other;
typedef struct {
uint8_t device_id;
uint8_t model_id;
uint8_t product_id;
char version[10];
uint8_t hc; // heating circuit 1, 2, 3 or 4
bool active; // true if there is data for this HC
uint16_t flowTemp;
uint8_t pumpMod;
uint8_t valveStatus;
} _EMS_Mixing_HC;
// Mixer data
typedef struct {
bool detected;
_EMS_Mixing_HC hc[EMS_THERMOSTAT_MAXHC]; // array for the 4 heating circuits
} _EMS_Mixing;
// SM Solar Module - SM10/SM100/ISM1
typedef struct {
uint8_t device_id; // the device ID of the Solar Module
@@ -444,7 +410,7 @@ void ems_setFlowTemp(uint8_t temperature);
void ems_setWarmWaterActivated(bool activated);
void ems_setWarmTapWaterActivated(bool activated);
void ems_setPoll(bool b);
void ems_setLogging(_EMS_SYS_LOGGING loglevel);
void ems_setLogging(_EMS_SYS_LOGGING loglevel, bool silent = false);
void ems_setEmsRefreshed(bool b);
void ems_setWarmWaterModeComfort(uint8_t comfort);
void ems_setModels();
@@ -463,6 +429,7 @@ void ems_getSolarModuleValues();
bool ems_getPoll();
bool ems_getTxEnabled();
bool ems_getThermostatEnabled();
bool ems_getMixingDeviceEnabled();
bool ems_getBoilerEnabled();
bool ems_getSolarModuleEnabled();
bool ems_getHeatPumpEnabled();
@@ -476,7 +443,6 @@ bool ems_getTxCapable();
uint32_t ems_getPollFrequency();
bool ems_getTxDisabled();
// private functions
uint8_t _crcCalculator(uint8_t * data, uint8_t len);
void _processType(_EMS_RxTelegram * EMS_RxTelegram);
@@ -491,5 +457,6 @@ extern _EMS_Thermostat EMS_Thermostat;
extern _EMS_SolarModule EMS_SolarModule;
extern _EMS_HeatPump EMS_HeatPump;
extern _EMS_Other EMS_Other;
extern _EMS_Mixing EMS_Mixing;
extern std::list<_Generic_Device> Devices;

View File

@@ -4,7 +4,6 @@
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
*
* See ChangeLog.md for History
* See README.md for Acknowledgments
*
*/
@@ -140,9 +139,19 @@
// Junkers FR10, FW100 (EMS Plus)
#define EMS_TYPE_JunkersStatusMessage 0x6F // is an automatic thermostat broadcast giving us temps
#define EMS_OFFSET_JunkersStatusMessage_mode 0 // current mode
#define EMS_OFFSET_JunkersStatusMessage_setpoint 2 // setpoint temp
#define EMS_OFFSET_JunkersStatusMessage_curr 4 // current temp
// MM100 (EMS Plus)
#define EMS_TYPE_MMPLUSStatusMessage_HC1 0x01D7 // mixer status HC1
#define EMS_TYPE_MMPLUSStatusMessage_HC2 0x01D8 // mixer status HC2
#define EMS_TYPE_MMPLUSStatusMessage_HC3 0x01D9 // mixer status HC3
#define EMS_TYPE_MMPLUSStatusMessage_HC4 0x01DA // mixer status HC4
#define EMS_OFFSET_MMPLUSStatusMessage_flow_temp 3 // flow temperature
#define EMS_OFFSET_MMPLUSStatusMessage_pump_mod 5 // pump modulation
#define EMS_OFFSET_MMPLUSStatusMessage_valve_status 2 // valve in percent
// Known EMS devices
typedef enum {
@@ -174,7 +183,10 @@ typedef enum {
EMS_MODEL_FR10,
EMS_MODEL_FR100,
EMS_MODEL_FR110,
EMS_MODEL_FW120
EMS_MODEL_FW120,
// mixing devices
EMS_MODEL_MM100
} _EMS_MODEL_ID;
@@ -184,12 +196,12 @@ typedef enum {
const _Boiler_Device Boiler_Devices[] = {
{72, "MC10 Module"},
{123, "Buderus GBx72/Nefit Trendline/Junkers Cerapur"},
{115, "Nefit Topline Compact/Buderus GB162"},
{123, "Buderus GBx72/Nefit Trendline/Junkers Cerapur/Worcester Greenstar Si"},
{115, "Nefit Topline/Buderus GB162"},
{203, "Buderus Logamax U122/Junkers Cerapur"},
{208, "Buderus Logamax plus/GB192"},
{208, "Buderus Logamax plus/GB192/Bosch Condens GC9000"},
{64, "Sieger BK15/Nefit Smartline/Buderus GB152"},
{EMS_PRODUCTID_HEATRONIC, "Bosch Condens 2500/Junkers Heatronic 3"},
{95, "Bosch Condens 2500/Buderus Logamax GB062/Junkers Heatronic 3"},
{122, "Nefit Proline"},
{170, "Buderus Logano GB212"},
{172, "Nefit Enviline"}
@@ -197,7 +209,7 @@ const _Boiler_Device Boiler_Devices[] = {
};
/*
* Known Solar Module types, device type 0x30
* Known Solar Module types, device id is 0x30
* format is PRODUCT ID, DESCRIPTION
*/
const _SolarModule_Device SolarModule_Devices[] = {
@@ -209,17 +221,23 @@ const _SolarModule_Device SolarModule_Devices[] = {
};
/*
* Mixing Units
* Typically device id is 0x20 or 0x21
* format is PRODUCT ID, DESCRIPTION
*/
const _Mixing_Device Mixing_Devices[] = {
{160, "MM100 Mixing Module"},
{69, "MM10 Mixer Module"},
{159, "MM50 Mixing Module"},
};
// Other EMS devices which are not considered boilers, thermostats or solar modules
// format is PRODUCT ID, DEVICE ID, DESCRIPTION
const _Other_Device Other_Devices[] = {
{71, 0x11, "WM10 Switch Module"},
{69, 0x21, "MM10 Mixer 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"},
@@ -237,7 +255,12 @@ const _Other_Device Other_Devices[] = {
// heatpump, device ID 0x38
// format is PRODUCT ID, DEVICE ID, DESCRIPTION
const _HeatPump_Device HeatPump_Devices[] = {{252, "HeatPump Module"}};
const _HeatPump_Device HeatPump_Devices[] = {
{252, "HeatPump Module"},
{200, "HeatPump Module"}
};
/*
* Known thermostat types and their capabilities
@@ -256,7 +279,7 @@ const _Thermostat_Device Thermostat_Devices[] = {
{EMS_MODEL_RC20F, 93, 0x18, "RC20F", EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_RC30, 78, 0x10, "RC30/Moduline 400", EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_RC35, 86, 0x10, "RC35", EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_RC300, 158, 0x10, "RC300/RC310/Moduline 3000", EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_RC300, 158, 0x10, "RC300/RC310/Moduline 3000/Bosch CW400", EMS_THERMOSTAT_WRITE_YES},
{EMS_MODEL_1010, 165, 0x18, "RC100/Moduline 1010", EMS_THERMOSTAT_WRITE_NO},
// Sieger

295
src/ems_utils.cpp Normal file
View File

@@ -0,0 +1,295 @@
/*
* Generic utils
*
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
*
*/
#include "ems_utils.h"
// convert float to char
char * _float_to_char(char * a, float f, uint8_t precision) {
long p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
char * ret = a;
long whole = (long)f;
itoa(whole, a, 10);
while (*a != '\0')
a++;
*a++ = '.';
long decimal = abs((long)((f - whole) * p[precision]));
itoa(decimal, a, 10);
return ret;
}
// convert bool to text. bools are stored as bytes
char * _bool_to_char(char * s, uint8_t value) {
if (value == EMS_VALUE_INT_ON) {
strlcpy(s, "on", sizeof(s));
} else if (value == EMS_VALUE_INT_OFF) {
strlcpy(s, "off", sizeof(s));
} else {
strlcpy(s, "?", sizeof(s));
}
return s;
}
// convert short (two bytes) to text string and returns string
// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100
// negative values are assumed stored as 1-compliment (https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c)
char * _short_to_char(char * s, int16_t value, uint8_t decimals) {
// remove errors or invalid values
if (value == EMS_VALUE_SHORT_NOTSET) {
strlcpy(s, "?", 10);
return (s);
}
// just print
if (decimals == 0) {
ltoa(value, s, 10);
return (s);
}
// do floating point
char s2[10] = {0};
// check for negative values
if (value < 0) {
strlcpy(s, "-", 10);
value *= -1; // convert to positive
}
if (decimals == 2) {
// divide by 2
strlcpy(s, ltoa(value / 2, s2, 10), 10);
strlcat(s, ".", 10);
strlcat(s, ((value & 0x01) ? "5" : "0"), 10);
} else {
strlcpy(s, ltoa(value / (decimals * 10), s2, 10), 10);
strlcat(s, ".", 10);
strlcat(s, ltoa(value % (decimals * 10), s2, 10), 10);
}
return s;
}
// convert short (two bytes) to text string and prints it
// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100
char * _ushort_to_char(char * s, uint16_t value, uint8_t decimals) {
// remove errors or invalid values
if (value == EMS_VALUE_USHORT_NOTSET) {
strlcpy(s, "?", 10);
return (s);
}
// just print
if (decimals == 0) {
ltoa(value, s, 10);
return (s);
}
// do floating point
char s2[10] = {0};
if (decimals == 2) {
// divide by 2
strlcpy(s, ltoa(value / 2, s2, 10), 10);
strlcat(s, ".", 10);
strlcat(s, ((value & 0x01) ? "5" : "0"), 10);
} else {
strlcpy(s, ltoa(value / (decimals * 10), s2, 10), 10);
strlcat(s, ".", 10);
strlcat(s, ltoa(value % (decimals * 10), s2, 10), 10);
}
return s;
}
// takes a signed short value (2 bytes), converts to a fraction and prints it
// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100
void _renderShortValue(const char * prefix, const char * postfix, int16_t value, uint8_t decimals) {
static char buffer[200] = {0};
static char s[20] = {0};
strlcpy(buffer, " ", sizeof(buffer));
strlcat(buffer, prefix, sizeof(buffer));
strlcat(buffer, ": ", sizeof(buffer));
strlcat(buffer, _short_to_char(s, value, decimals), sizeof(buffer));
if (postfix != nullptr) {
strlcat(buffer, " ", sizeof(buffer));
strlcat(buffer, postfix, sizeof(buffer));
}
myDebug(buffer);
}
// takes a unsigned short value (2 bytes), converts to a fraction and prints it
// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100
void _renderUShortValue(const char * prefix, const char * postfix, uint16_t value, uint8_t decimals) {
static char buffer[200] = {0};
static char s[20] = {0};
strlcpy(buffer, " ", sizeof(buffer));
strlcat(buffer, prefix, sizeof(buffer));
strlcat(buffer, ": ", sizeof(buffer));
strlcat(buffer, _ushort_to_char(s, value, decimals), sizeof(buffer));
if (postfix != nullptr) {
strlcat(buffer, " ", sizeof(buffer));
strlcat(buffer, postfix, sizeof(buffer));
}
myDebug(buffer);
}
// convert int (single byte) to text value and returns it
char * _int_to_char(char * s, uint8_t value, uint8_t div) {
if (value == EMS_VALUE_INT_NOTSET) {
strlcpy(s, "?", sizeof(s));
return (s);
}
static char s2[5] = {0};
switch (div) {
case 1:
itoa(value, s, 10);
break;
case 2:
strlcpy(s, itoa(value >> 1, s2, 10), 5);
strlcat(s, ".", sizeof(s));
strlcat(s, ((value & 0x01) ? "5" : "0"), 5);
break;
case 10:
strlcpy(s, itoa(value / 10, s2, 10), 5);
strlcat(s, ".", sizeof(s));
strlcat(s, itoa(value % 10, s2, 10), 5);
break;
default:
itoa(value, s, 10);
break;
}
return s;
}
// takes an int value (1 byte), converts to a fraction and prints
void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, uint8_t div) {
static char buffer[200] = {0};
static char s[20] = {0};
strlcpy(buffer, " ", sizeof(buffer));
strlcat(buffer, prefix, sizeof(buffer));
strlcat(buffer, ": ", sizeof(buffer));
strlcat(buffer, _int_to_char(s, value, div), sizeof(buffer));
if (postfix != nullptr) {
strlcat(buffer, " ", sizeof(buffer));
strlcat(buffer, postfix, sizeof(buffer));
}
myDebug(buffer);
}
// takes a long value at prints it to debug log and prints
void _renderLongValue(const char * prefix, const char * postfix, uint32_t value) {
static char buffer[200] = {0};
strlcpy(buffer, " ", sizeof(buffer));
strlcat(buffer, prefix, sizeof(buffer));
strlcat(buffer, ": ", sizeof(buffer));
if (value == EMS_VALUE_LONG_NOTSET) {
strlcat(buffer, "?", sizeof(buffer));
} else {
char s[20] = {0};
strlcat(buffer, ltoa(value, s, 10), sizeof(buffer));
}
if (postfix != nullptr) {
strlcat(buffer, " ", sizeof(buffer));
strlcat(buffer, postfix, sizeof(buffer));
}
myDebug(buffer);
}
// takes a bool value at prints it to debug log and prints
void _renderBoolValue(const char * prefix, uint8_t value) {
static char buffer[200] = {0};
static char s[20] = {0};
strlcpy(buffer, " ", sizeof(buffer));
strlcat(buffer, prefix, sizeof(buffer));
strlcat(buffer, ": ", sizeof(buffer));
strlcat(buffer, _bool_to_char(s, value), sizeof(buffer));
myDebug(buffer);
}
// like itoa but for hex, and quicker
char * _hextoa(uint8_t value, char * buffer) {
char * p = buffer;
byte nib1 = (value >> 4) & 0x0F;
byte nib2 = (value >> 0) & 0x0F;
*p++ = nib1 < 0xA ? '0' + nib1 : 'A' + nib1 - 0xA;
*p++ = nib2 < 0xA ? '0' + nib2 : 'A' + nib2 - 0xA;
*p = '\0'; // null terminate just in case
return buffer;
}
// for decimals 0 to 99, printed as a 2 char string
char * _smallitoa(uint8_t value, char * buffer) {
buffer[0] = ((value / 10) == 0) ? '0' : (value / 10) + '0';
buffer[1] = (value % 10) + '0';
buffer[2] = '\0';
return buffer;
}
/* for decimals 0 to 999, printed as a string
*/
char * _smallitoa3(uint16_t value, char * buffer) {
buffer[0] = ((value / 100) == 0) ? '0' : (value / 100) + '0';
buffer[1] = (((value % 100) / 10) == 0) ? '0' : ((value % 100) / 10) + '0';
buffer[2] = (value % 10) + '0';
buffer[3] = '\0';
return buffer;
}
// used to read the next string from an input buffer and convert to an 8 bit int
uint8_t _readIntNumber() {
char * numTextPtr = strtok(nullptr, ", \n");
if (numTextPtr == nullptr) {
return 0;
}
return atoi(numTextPtr);
}
// used to read the next string from an input buffer and convert to a double
float _readFloatNumber() {
char * numTextPtr = strtok(nullptr, ", \n");
if (numTextPtr == nullptr) {
return 0;
}
return atof(numTextPtr);
}
// used to read the next string from an input buffer as a hex value and convert to a 16 bit int
uint16_t _readHexNumber() {
char * numTextPtr = strtok(nullptr, ", \n");
if (numTextPtr == nullptr) {
return 0;
}
return (uint16_t)strtol(numTextPtr, 0, 16);
}
// used to read the next string from an input buffer
char * _readWord() {
char * word = strtok(nullptr, ", \n");
return word;
}

33
src/ems_utils.h Normal file
View File

@@ -0,0 +1,33 @@
/*
* Generic utils
*
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
*
*/
#pragma once
#include "MyESP.h"
#include "ems.h"
#define myDebug(...) myESP.myDebug(__VA_ARGS__)
#define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__)
char * _float_to_char(char * a, float f, uint8_t precision = 2);
char * _bool_to_char(char * s, uint8_t value);
char * _short_to_char(char * s, int16_t value, uint8_t decimals = 1);
char * _ushort_to_char(char * s, uint16_t value, uint8_t decimals = 1);
void _renderShortValue(const char * prefix, const char * postfix, int16_t value, uint8_t decimals = 1);
void _renderUShortValue(const char * prefix, const char * postfix, uint16_t value, uint8_t decimals = 1);
char * _int_to_char(char * s, uint8_t value, uint8_t div = 1);
void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, uint8_t div = 1);
void _renderLongValue(const char * prefix, const char * postfix, uint32_t value);
void _renderBoolValue(const char * prefix, uint8_t value);
char * _hextoa(uint8_t value, char * buffer);
char * _smallitoa(uint8_t value, char * buffer);
char * _smallitoa3(uint16_t value, char * buffer);
uint8_t _readIntNumber();
float _readFloatNumber();
uint16_t _readHexNumber();
char * _readWord();

View File

@@ -29,7 +29,6 @@ static void emsuart_rx_intr_handler(void * para) {
EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_BUSY; // status set to busy
length = 0;
}
GPIO_H(RX_MARK_MASK);
// fill IRQ buffer, by emptying Rx FIFO
if (USIS(EMSUART_UART) & ((1 << UIFF) | (1 << UITO) | (1 << UIBD))) {
while ((USS(EMSUART_UART) >> USRXC) & 0xFF) {
@@ -41,7 +40,6 @@ static void emsuart_rx_intr_handler(void * para) {
// clear Rx FIFO full and Rx FIFO timeout interrupts
USIC(EMSUART_UART) = (1 << UIFF) | (1 << UITO);
}
GPIO_L(RX_MARK_MASK);
// BREAK detection = End of EMS data block
if (USIS(EMSUART_UART) & ((1 << UIBD))) {
@@ -55,7 +53,6 @@ static void emsuart_rx_intr_handler(void * para) {
ETS_UART_INTR_ENABLE(); // re-enable UART interrupts
system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
RX_PULSE(EMSUART_BIT_TIME / 2);
}
}
@@ -76,13 +73,11 @@ static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events) {
}
if (length == 2) {
RX_PULSE(20);
// it's a poll or status code, single byte and ok to send on
ems_parseTelegram((uint8_t *)pCurrent->buffer, 1);
} else if ((length > 4) && (length <= EMS_MAXBUFFERSIZE + 1)) {
// ignore double BRK at the end, possibly from the Tx loopback
// also telegrams with no data value
RX_PULSE(40);
ems_parseTelegram((uint8_t *)pCurrent->buffer, length - 1); // transmit EMS buffer, excluding the BRK
}
}
@@ -178,7 +173,7 @@ void ICACHE_FLASH_ATTR emsuart_tx_brk() {
uint32_t tmp;
// must make sure Tx FIFO is empty
while (((USS(EMSUART_UART) >> USTXC) & 0xFF) != 0)
while (((USS(EMSUART_UART) >> USTXC) & 0xFF))
;
tmp = ((1 << UCRXRST) | (1 << UCTXRST)); // bit mask
@@ -188,7 +183,6 @@ void ICACHE_FLASH_ATTR emsuart_tx_brk() {
// 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 == EMS_TXMODE_EMSPLUS) { // EMS+ mode
@@ -198,7 +192,6 @@ void ICACHE_FLASH_ATTR emsuart_tx_brk() {
}
USC0(EMSUART_UART) &= ~(tmp); // clear bit
GPIO_L(TX_MARK_MASK);
}
/*
@@ -212,22 +205,18 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
}
if (len) {
LA_PULSE(50);
if (EMS_Sys_Status.emsTxMode == EMS_TXMODE_EMSPLUS) { // 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 == EMS_TXMODE_HT3) { // 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)
while (((USS(EMSUART_UART) >> USTXC) & 0xff))
;
// wait until bits are sent on wire
@@ -256,11 +245,6 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
* 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_CHARS (2 + 20)
#define EMS_TX_TO_COUNT ((EMS_TX_TO_CHARS)*10 * 8)
uint16_t wdc = EMS_TX_TO_COUNT;
ETS_UART_INTR_DISABLE(); // disable rx interrupt
@@ -270,13 +254,10 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
// 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) {
@@ -294,18 +275,14 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
// 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 {
if (result == EMS_TX_STATUS_OK) {
// 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);
delayMicroseconds(EMSUART_BIT_TIME);
}
@@ -313,7 +290,6 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
phantomBreak = 1;
}
GPIO_L(TX_MARK_MASK);
}
ETS_UART_INTR_ENABLE(); // receive anything from FIFO...
}

View File

@@ -19,11 +19,15 @@
#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_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_BUSY_WAIT (EMSUART_BIT_TIME / 8)
#define EMS_TX_TO_CHARS (2 + 20)
#define EMS_TX_TO_COUNT ((EMS_TX_TO_CHARS)*10 * 8)
#define EMSUART_recvTaskPrio 1
#define EMSUART_recvTaskQueueLen 64

View File

@@ -12,33 +12,42 @@
// TOPICS with _CMD_ are used for receiving commands from an MQTT Broker
// EMS-ESP will subscribe to these topics
#define TOPIC_GENERIC_CMD "generic_cmd" // for receiving generic system commands via MQTT
// MQTT for thermostat
// these topics can be suffixed with a Heating Circuit number, e.g. thermostat_cmd_temp1 and thermostat_data1
#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values to MQTT
#define TOPIC_THERMOSTAT_CMD "thermostat_cmd" // for receiving thermostat commands via MQTT
#define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // temp changes via MQTT
#define TOPIC_THERMOSTAT_CMD_MODE "thermostat_cmd_mode" // mode changes via MQTT
#define TOPIC_THERMOSTAT_CMD_DAYTEMP "thermostat_daytemp" // day temp (RC35 specific)
#define TOPIC_THERMOSTAT_CMD_NIGHTTEMP "thermostat_nighttemp" // night temp (RC35 specific)
#define TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP "thermostat_holidayttemp" // holiday temp (RC35 specific)
#define THERMOSTAT_CURRTEMP "thermostat_currtemp" // current temperature
#define THERMOSTAT_SELTEMP "thermostat_seltemp" // selected temperature
#define THERMOSTAT_HC "thermostat_hc" // which heating circuit number
#define THERMOSTAT_MODE "thermostat_mode" // mode
#define THERMOSTAT_DAYTEMP "thermostat_daytemp" // RC35 specific
#define THERMOSTAT_NIGHTTEMP "thermostat_nighttemp" // RC35 specific
#define THERMOSTAT_HOLIDAYTEMP "thermostat_holidayttemp" // RC35 specific
#define THERMOSTAT_HEATINGTYPE "thermostat_heatingtype" // RC35 specific (3=floorheating)
#define THERMOSTAT_CIRCUITCALCTEMP "thermostat_circuitcalctemp" // RC35 specific
#define TOPIC_THERMOSTAT_CMD_DAYTEMP "daytemp" // day temp (RC35 specific)
#define TOPIC_THERMOSTAT_CMD_NIGHTTEMP "nighttemp" // night temp (RC35 specific)
#define TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP "holidayttemp" // holiday temp (RC35 specific)
#define THERMOSTAT_CURRTEMP "currtemp" // current temperature
#define THERMOSTAT_SELTEMP "seltemp" // selected temperature
#define THERMOSTAT_HC "hc" // which heating circuit number
#define THERMOSTAT_MODE "mode" // mode
#define THERMOSTAT_DAYTEMP "daytemp" // RC35 specific
#define THERMOSTAT_NIGHTTEMP "nighttemp" // RC35 specific
#define THERMOSTAT_HOLIDAYTEMP "holidayttemp" // RC35 specific
#define THERMOSTAT_HEATINGTYPE "heatingtype" // RC35 specific (3=floorheating)
#define THERMOSTAT_CIRCUITCALCTEMP "circuitcalctemp" // RC35 specific
// MQTT for boiler
#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_HEATING_ACTIVE "heating_active" // if heating is on
#define TOPIC_BOILER_CMD "boiler_cmd" // for receiving boiler commands via MQTT
#define TOPIC_BOILER_CMD_WWACTIVATED "boiler_cmd_wwactivated" // change water on/off
#define TOPIC_BOILER_CMD_WWTEMP "boiler_cmd_wwtemp" // wwtemp changes via MQTT
#define TOPIC_BOILER_CMD_COMFORT "boiler_cmd_comfort" // ww comfort setting via MQTT
#define TOPIC_BOILER_CMD_FLOWTEMP "boiler_cmd_flowtemp" // flowtemp value via MQTT
#define TOPIC_BOILER_CMD_COMFORT "comfort" // ww comfort setting via MQTT
#define TOPIC_BOILER_CMD_FLOWTEMP "flowtemp" // flowtemp value via MQTT
// MQTT for mixing device
#define TOPIC_MIXING_DATA "mixing_data" // for sending mixing device values to MQTT
// MQTT for SM10/SM100 Solar Module
#define TOPIC_SM_DATA "sm_data" // topic name
@@ -57,11 +66,12 @@
#define HP_PUMPSPEED "pumpspeed" // pump speed
// shower time
#define TOPIC_SHOWERTIME "showertime" // for sending shower time results
#define TOPIC_SHOWER_TIMER "shower_timer" // toggle switch for enabling the shower logic
#define TOPIC_SHOWER_ALERT "shower_alert" // toggle switch for enabling the shower alarm logic
#define TOPIC_SHOWER_COLDSHOT "shower_coldshot" // used to trigger a coldshot from an MQTT command
#define TOPIC_SHOWER_DATA "shower_data" // for sending shower time results
#define TOPIC_SHOWER_TIMER "timer" // toggle switch for enabling the shower logic
#define TOPIC_SHOWER_ALERT "alert" // toggle switch for enabling the shower alarm logic
#define TOPIC_SHOWER_COLDSHOT "coldshot" // used to trigger a coldshot from an MQTT command
#define TOPIC_SHOWER_DURATION "duration" // duration of the last shower
// MQTT for EXTERNAL SENSORS
// MQTT for External Sensors
#define TOPIC_EXTERNAL_SENSORS "sensors" // for sending sensor values to MQTT
#define PAYLOAD_EXTERNAL_SENSORS "temp_%d" // for formatting the payload for each external dallas sensor

View File

@@ -1 +1 @@
#define APP_VERSION "1.9.1"
#define APP_VERSION "1.9.2b11"

Binary file not shown.

View File

@@ -191,7 +191,7 @@
data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="MQTT Server IP Address"></i></label>
<span class="col-xs-9">
<input class="form-control input-sm" placeholder="MQTT IP" style="display:inline;max-width:185px"
<input class="form-control input-sm" placeholder="IP address" style="display:inline;max-width:185px"
id="mqttip" type="text">
</span>
<br>
@@ -201,11 +201,44 @@
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="MQTT Server port number"></i></label>
<span class="col-xs-9">
<input class="form-control input-sm" placeholder="MQTT Port" value="" style="display:inline;max-width:185px"
<input class="form-control input-sm" placeholder="1883" value="" style="display:inline;max-width:185px"
id="mqttport" type="text">
</span>
<br>
</div>
<div class="row form-group">
<label class="col-xs-3">QOS<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="MQTT QOS 0,1 or 2"></i></label>
<span class="col-xs-9">
<input class="form-control input-sm" placeholder="1" value="" style="display:inline;max-width:185px"
id="mqttqos" type="text">
</span>
<br>
</div>
<div class="row form-group">
<label class="col-xs-3">Keep Alive<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="MQTT Keep Alive time in seconds"></i></label>
<span class="col-xs-9">
<input class="form-control input-sm" placeholder="60" value="" style="display:inline;max-width:185px"
id="mqttkeepalive" type="text">
</span>
<br>
</div>
<div class="row form-group">
<label class="col-xs-3">Retain<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="MQTT Retain Flag"></i></label>
<div class="col-xs-9">
<form>
<label class="radio-inline">
<input type="radio" value="1" name="mqttretain">Enabled</label>
<label class="radio-inline">
<input type="radio" value="0" name="mqttretain" checked>Disabled</label>
</form>
</div>
</div>
<div class="row form-group">
<label class="col-xs-3">Username<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
@@ -231,7 +264,7 @@
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="MQTT topic prefix (&lt;mqtt base&gt;/&lt;host name&gt;/)"></i></label>
<span class="col-xs-9">
<input class="form-control input-sm" placeholder="MQTT base" value="" style="display:inline;max-width:185px"
<input class="form-control input-sm" placeholder="home" value="" style="display:inline;max-width:185px"
id="mqttbase" type="text">
</span>
</div>

View File

@@ -25,6 +25,9 @@ var config = {
"enabled": false,
"ip": "",
"port": 1883,
"qos": 1,
"keepalive": 60,
"retain": true,
"base": "",
"user": "",
"password": "",
@@ -171,8 +174,15 @@ function savemqtt() {
config.mqtt.heartbeat = true;
}
config.mqtt.retain = false;
if (parseInt($("input[name=\"mqttretain\"]:checked").val()) === 1) {
config.mqtt.retain = true;
}
config.mqtt.ip = document.getElementById("mqttip").value;
config.mqtt.port = parseInt(document.getElementById("mqttport").value);
config.mqtt.qos = parseInt(document.getElementById("mqttqos").value);
config.mqtt.keepalive = parseInt(document.getElementById("mqttkeepalive").value);
config.mqtt.base = document.getElementById("mqttbase").value;
config.mqtt.user = document.getElementById("mqttuser").value;
config.mqtt.password = document.getElementById("mqttpwd").value;
@@ -321,8 +331,14 @@ function listmqtt() {
$("input[name=\"mqttheartbeat\"][value=\"1\"]").prop("checked", true);
}
if (config.mqtt.retain) {
$("input[name=\"mqttretain\"][value=\"1\"]").prop("checked", true);
}
document.getElementById("mqttip").value = config.mqtt.ip;
document.getElementById("mqttport").value = config.mqtt.port;
document.getElementById("mqttqos").value = config.mqtt.qos;
document.getElementById("mqttkeepalive").value = config.mqtt.keepalive;
document.getElementById("mqttbase").value = config.mqtt.base;
document.getElementById("mqttuser").value = config.mqtt.user;
document.getElementById("mqttpwd").value = config.mqtt.password;

View File

@@ -1,15 +1,82 @@
var gulp = require('gulp');
var fs = require('fs');
var concat = require('gulp-concat');
var gzip = require('gulp-gzip');
var flatmap = require('gulp-flatmap');
var path = require('path');
var htmlmin = require('gulp-htmlmin');
var uglify = require('gulp-uglify');
var pump = require('pump');
/*
EMS-ESP web server file system builder
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gulp.task('myespjs-concat', function () {
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*eslint quotes: ['error', 'single']*/
/*eslint-env es6*/
const gulp = require('gulp');
const fs = require('fs');
const concat = require('gulp-concat');
const gzip = require('gulp-gzip');
const flatmap = require('gulp-flatmap');
const path = require('path');
const htmlmin = require('gulp-htmlmin');
const uglify = require('gulp-uglify');
const pump = require('pump');
const through = require('through2');
// file name includes extension
var buildHeader = function (name) {
return through.obj(function (source, encoding, callback) {
var parts = source.path.split(path.sep);
var filename = parts[parts.length - 1];
var extension = filename.split('.')[1];
// var safename = name.split('.').join('_');
var safename = name.replace(/\.|-/g, "_");
var destination = "../../src/webh/" + filename + ".h";
// html files go into root
if (extension === "html") {
var source = "../../src/websrc/temp/gzipped/" + name + ".gz";
} else {
var source = "../../src/websrc/temp/gzipped/" + extension + "/" + name + ".gz";
}
console.info('Creating file: ' + filename + ' Extension: ' + extension);
var wstream = fs.createWriteStream(destination);
wstream.on('error', function (err) {
console.log(err);
});
var data = fs.readFileSync(source);
wstream.write('#define ' + safename + '_gz_len ' + data.length + '\n');
wstream.write('const uint8_t ' + safename + '_gz[] PROGMEM = {');
for (i = 0; i < data.length; i++) {
if (i % 1000 == 0) wstream.write("\n");
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < data.length - 1) wstream.write(',');
}
wstream.write('\n};')
wstream.end();
callback(null, destination);
});
};
gulp.task('myespjs', function () {
return gulp.src(['../../src/websrc/myesp.js', '../../src/custom.js'])
.pipe(concat({
path: 'myesp.js',
@@ -18,77 +85,16 @@ gulp.task('myespjs-concat', function () {
}
}))
.pipe(gulp.dest('../../src/websrc/temp/js'))
});
gulp.task('myespjsminify', ["myespjs-concat"], function (cb) {
pump([
gulp.src('../../src/websrc/temp/js/myesp.js'),
uglify(),
gulp.dest('../../src/websrc/temp/js/ugly'),
],
cb
);
});
gulp.task("myespjsgz", ["myespjsminify"], function () {
return gulp.src("../../src/websrc/temp/js/ugly/myesp.js")
.pipe(uglify())
.pipe(gulp.dest('../../src/websrc/temp/js/ugly'))
.pipe(gzip({
append: true
}))
.pipe(gulp.dest('../../src/websrc/temp/gzipped/js'));
.pipe(gulp.dest('../../src/websrc/temp/gzipped/js'))
.pipe(buildHeader('myesp.js'));
});
gulp.task('myespjsgzh', ["myespjsgz"], function () {
var source = "../../src/websrc/temp/gzipped/js/" + "myesp.js.gz";
var destination = "../../src/webh/" + "myesp.js.gz.h";
var wstream = fs.createWriteStream(destination);
wstream.on('error', function (err) {
console.log(err);
});
var data = fs.readFileSync(source);
wstream.write('#define myesp_js_gz_len ' + data.length + '\n');
wstream.write('const uint8_t myesp_js_gz[] PROGMEM = {')
for (i = 0; i < data.length; i++) {
if (i % 1000 == 0) wstream.write("\n");
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < data.length - 1) wstream.write(',');
}
wstream.write('\n};')
wstream.end();
});
gulp.task("scripts", ["scripts-concat"], function () {
var source = "../../src/websrc/temp/gzipped/js/" + "required.js.gz";
var destination = "../../src/webh/" + "required.js.gz.h";
var wstream = fs.createWriteStream(destination);
wstream.on('error', function (err) {
console.log(err);
});
var data = fs.readFileSync(source);
wstream.write('#define required_js_gz_len ' + data.length + '\n');
wstream.write('const uint8_t required_js_gz[] PROGMEM = {')
for (i = 0; i < data.length; i++) {
if (i % 1000 == 0) wstream.write("\n");
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < data.length - 1) wstream.write(',');
}
wstream.write('\n};')
wstream.end();
});
gulp.task('scripts-concat', ["myespjsgzh"], function () {
gulp.task('requiredjs', function () {
return gulp.src(['../../src/websrc/3rdparty/js/jquery-1.12.4.min.js', '../../src/websrc/3rdparty/js/bootstrap-3.3.7.min.js', '../../src/websrc/3rdparty/js/footable-3.1.6.min.js'])
.pipe(concat({
path: 'required.js',
@@ -100,10 +106,12 @@ gulp.task('scripts-concat', ["myespjsgzh"], function () {
.pipe(gzip({
append: true
}))
.pipe(gulp.dest('../../src/websrc/temp/gzipped/js/'));
.pipe(gulp.dest('../../src/websrc/temp/gzipped/js/'))
.pipe(buildHeader('required.js'));
});
gulp.task('styles-concat', function () {
gulp.task('requiredcss', function () {
return gulp.src(['../../src/websrc/3rdparty/css/bootstrap-3.3.7.min.css', '../../src/websrc/3rdparty/css/footable.bootstrap-3.1.6.min.css', '../../src/websrc/3rdparty/css/sidebar.css'])
.pipe(concat({
path: 'required.css',
@@ -115,70 +123,21 @@ gulp.task('styles-concat', function () {
.pipe(gzip({
append: true
}))
.pipe(gulp.dest('../../src/websrc/temp/gzipped/css/'));
.pipe(gulp.dest('../../src/websrc/temp/gzipped/css/'))
.pipe(buildHeader('required.css'));
});
gulp.task("styles", ["styles-concat"], function () {
var source = "../../src/websrc/temp/gzipped/css/" + "required.css.gz";
var destination = "../../src/webh/" + "required.css.gz.h";
var wstream = fs.createWriteStream(destination);
wstream.on('error', function (err) {
console.log(err);
});
var data = fs.readFileSync(source);
wstream.write('#define required_css_gz_len ' + data.length + '\n');
wstream.write('const uint8_t required_css_gz[] PROGMEM = {')
for (i = 0; i < data.length; i++) {
if (i % 1000 == 0) wstream.write("\n");
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < data.length - 1) wstream.write(',');
}
wstream.write('\n};')
wstream.end();
});
gulp.task("fontgz", function () {
return gulp.src("../../src/websrc/3rdparty/fonts/*.*")
.pipe(gulp.dest("../../src/websrc/temp/fonts/"))
gulp.task("fontwoff", function () {
return gulp.src("../../src/websrc/3rdparty/woff/*.*")
.pipe(gulp.dest("../../src/websrc/temp/woff/"))
.pipe(gzip({
append: true
}))
.pipe(gulp.dest('../../src/websrc/temp/gzipped/fonts/'));
.pipe(gulp.dest('../../src/websrc/temp/gzipped/woff/'))
.pipe(buildHeader('glyphicons-halflings-regular.woff'));
});
gulp.task("fonts", ["fontgz"], function () {
return gulp.src("../../src/websrc/temp/gzipped/fonts/*.*")
.pipe(flatmap(function (stream, file) {
var filename = path.basename(file.path);
var wstream = fs.createWriteStream("../../src/webh/" + filename + ".h");
wstream.on("error", function (err) {
gutil.log(err);
});
var data = file.contents;
wstream.write("#define " + filename.replace(/\.|-/g, "_") + "_len " + data.length + "\n");
wstream.write("const uint8_t " + filename.replace(/\.|-/g, "_") + "[] PROGMEM = {")
for (i = 0; i < data.length; i++) {
if (i % 1000 == 0) wstream.write("\n");
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < data.length - 1) wstream.write(',');
}
wstream.write("\n};")
wstream.end();
return stream;
}));
});
gulp.task('html-concat', function () {
gulp.task('myesphtml', function () {
return gulp.src(['../../src/websrc/myesp.htm', '../../src/custom.htm'])
.pipe(concat({
path: 'myesp.html',
@@ -191,42 +150,20 @@ gulp.task('html-concat', function () {
.pipe(gzip({
append: true
}))
.pipe(gulp.dest('../../src/websrc/temp/gzipped/'));
.pipe(gulp.dest('../../src/websrc/temp/gzipped/'))
.pipe(buildHeader('myesp.html'));
});
gulp.task('htmlsprep', ["html-concat"], function () {
gulp.task('indexhtml', function () {
return gulp.src('../../src/websrc/index.html')
.pipe(htmlmin({ collapseWhitespace: true, minifyJS: true }))
.pipe(gulp.dest('../../src/websrc/temp/'))
.pipe(gzip({
append: true
}))
.pipe(gulp.dest('../../src/websrc/temp/gzipped/'));
.pipe(gulp.dest('../../src/websrc/temp/gzipped/'))
.pipe(buildHeader('index.html'));
});
gulp.task("htmls", ["htmlsprep"], function () {
return gulp.src("../../src/websrc/temp/gzipped/*.gz")
.pipe(flatmap(function (stream, file) {
var filename = path.basename(file.path);
var wstream = fs.createWriteStream("../../src/webh/" + filename + ".h");
wstream.on("error", function (err) {
gutil.log(err);
});
var data = file.contents;
wstream.write("#define " + filename.replace(/\.|-/g, "_") + "_len " + data.length + "\n");
wstream.write("const uint8_t " + filename.replace(/\.|-/g, "_") + "[] PROGMEM = {")
for (i = 0; i < data.length; i++) {
if (i % 1000 == 0) wstream.write("\n");
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < data.length - 1) wstream.write(',');
}
wstream.write("\n};")
wstream.end();
return stream;
}));
});
gulp.task('default', ['scripts', 'styles', "fonts", "htmls"]);
gulp.task('default', gulp.parallel('myespjs', 'requiredjs', 'requiredcss', 'fontwoff', 'myesphtml', 'indexhtml'));

3810
tools/webfilesbuilder/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,18 @@
{
"name": "uglifier",
"version": "0.0.1",
"description": "Combine all js and css files into one and gzip them for the EMS-ESP project",
"main": "unglify.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "gulp"
},
"name": "webfilesbuilder",
"version": "1.0.0",
"description": "Combine all js and css files into one and gzip them",
"main": "gulpfile.js",
"author": "proddy",
"license": "UNLICENSED",
"dependencies": {
"gulp-cli": "^2.2.0",
"private": true,
"license": "GPL-3.0",
"devDependencies": {
"gulp": "^4.0.0",
"gulp-htmlmin": "^4.0.0",
"gulp-gzip": "^1.4.2",
"gulp-concat": "^2.6.1",
"gulp-flatmap": "^1.0.2",
"gulp-gzip": "^1.4.2",
"gulp-htmlmin": "^4.0.0",
"gulp-uglify": "^3.0.2",
"pump": "^3.0.0"
},
"bin": "node_modules\\gulp\\bin\\gulp.js",
"devDependencies": {
"gulp": "^3.9.1"
}
}

27
tools/wsemulator/package-lock.json generated Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "wsemulator",
"version": "0.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"ws": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz",
"integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==",
"requires": {
"async-limiter": "~1.0.0",
"safe-buffer": "~5.1.0"
}
}
}
}

View File

@@ -74,8 +74,11 @@ var configfile = {
},
"mqtt": {
"enabled": false,
"ip": "ip",
"port": "port",
"ip": "10.10.10.10",
"port": 1883,
"qos": 1,
"keepalive": 60,
"retain": true,
"base": "base",
"user": "user",
"password": "password",