From cdf3781be61a78b694a4df3c571a988375b57b34 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 5 Oct 2019 14:56:29 +0200 Subject: [PATCH 01/76] new beta 1.9.2b1 --- src/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h b/src/version.h index 794851b85..ada419252 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define APP_VERSION "1.9.1" +#define APP_VERSION "1.9.2b1" From 8c3c8e0031a09c53305f88685418b30268b581c4 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 5 Oct 2019 14:58:09 +0200 Subject: [PATCH 02/76] prep for new changes in1.9.2 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cf4b01c6..041a1e0de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ 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] 2019-10- + ## [1.9.1] 2019-10-05 ### Added From 0d97f159132c771344724a6a2e8e032871215f65 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 5 Oct 2019 17:52:19 +0200 Subject: [PATCH 03/76] rollback version of ESPAsyncWebServer library to fix https://github.com/proddy/EMS-ESP/issues/194 --- platformio.ini | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/platformio.ini b/platformio.ini index dc49125a6..1fe64c787 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,9 +9,16 @@ default_envs = debug ;default_envs = tests [common] -; -DMYESP_TIMESTAMP -DTESTS -DCRASH -DFORCE_SERIAL -DLOGICANALYZER -;general_flags = -DFORCE_SERIAL -general_flags = +; build options are: +; -DMYESP_TIMESTAMP +; -DTESTS +; -DCRASH +; -DFORCE_SERIAL +; -DLOGICANALYZER + +;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 = [env] ;board = esp12e @@ -19,16 +26,17 @@ board = d1_mini framework = arduino platform = espressif8266 lib_deps = - ESPAsyncTCP - CRC32 - CircularBuffer - OneWire - JustWifi - AsyncMqttClient - ArduinoJson - EEPROM_Rotate - ESP Async WebServer - ESPAsyncUDP + https://github.com/bakercp/CRC32 + https://github.com/rlogiacco/CircularBuffer + https://github.com/PaulStoffregen/OneWire + https://github.com/xoseperez/justwifi + https://github.com/marvinroger/async-mqtt-client + https://github.com/xoseperez/eeprom_rotate + https://github.com/bblanchon/ArduinoJson + https://github.com/me-no-dev/ESPAsyncTCP +; https://github.com/me-no-dev/ESPAsyncWebServer + ESPAsyncWebServer#1.2.2 + https://github.com/me-no-dev/ESPAsyncUDP upload_speed = 921600 monitor_speed = 115200 From cce077175d468d4f9f5c6ac083a466d36fc3cacc Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 5 Oct 2019 18:00:16 +0200 Subject: [PATCH 04/76] update library reference --- platformio.ini | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index 1fe64c787..eb44063e0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,10 +33,9 @@ lib_deps = https://github.com/marvinroger/async-mqtt-client https://github.com/xoseperez/eeprom_rotate https://github.com/bblanchon/ArduinoJson - https://github.com/me-no-dev/ESPAsyncTCP -; https://github.com/me-no-dev/ESPAsyncWebServer - ESPAsyncWebServer#1.2.2 https://github.com/me-no-dev/ESPAsyncUDP + https://github.com/me-no-dev/ESPAsyncTCP + https://github.com/me-no-dev/ESPAsyncWebServer#b0c6144 upload_speed = 921600 monitor_speed = 115200 @@ -83,4 +82,3 @@ extra_scripts = pre:scripts/rename_fw.py build_type = debug build_flags = ${common.general_flags} -Wall extra_scripts = scripts/checkcode.py - From 2a8ed4d64f4d7728fe613696e0885022e679eb74 Mon Sep 17 00:00:00 2001 From: Klaudiusz Staniek Date: Sun, 6 Oct 2019 11:36:30 +0200 Subject: [PATCH 05/76] Add switch temperature --- src/ems-esp.cpp | 3 +++ src/ems.cpp | 1 + src/ems.h | 1 + 3 files changed, 5 insertions(+) diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 1a60851c1..62aba30a3 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -521,6 +521,7 @@ void showInfo() { _renderIntValue("Selected flow temperature", "C", EMS_Boiler.selFlowTemp); _renderUShortValue("Current flow temperature", "C", EMS_Boiler.curFlowTemp); _renderUShortValue("Return temperature", "C", EMS_Boiler.retTemp); + _renderUShortValue("Switch temperature", "C", EMS_Boiler.switchTemp); _renderBoolValue("Gas", EMS_Boiler.burnGas); _renderBoolValue("Boiler pump", EMS_Boiler.heatPmp); _renderBoolValue("Fan", EMS_Boiler.fanWork); @@ -775,6 +776,8 @@ void publishValues(bool force) { rootBoiler["curFlowTemp"] = (double)EMS_Boiler.curFlowTemp / 10; if (EMS_Boiler.retTemp != EMS_VALUE_USHORT_NOTSET) rootBoiler["retTemp"] = (double)EMS_Boiler.retTemp / 10; + if (EMS_Boiler.switchTemp != EMS_VALUE_USHORT_NOTSET) + rootBoiler["switchTemp"] = (double)EMS_Boiler.switchTemp / 10; if (EMS_Boiler.sysPress != EMS_VALUE_INT_NOTSET) rootBoiler["sysPress"] = (double)EMS_Boiler.sysPress / 10; if (EMS_Boiler.boilTemp != EMS_VALUE_USHORT_NOTSET) diff --git a/src/ems.cpp b/src/ems.cpp index d40d5c009..bb2451f35 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -1345,6 +1345,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); } /** diff --git a/src/ems.h b/src/ems.h index 659217c3e..057919ee8 100644 --- a/src/ems.h +++ b/src/ems.h @@ -322,6 +322,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 From 589caf33bcc024514ad210ebcc5024b91f83de37 Mon Sep 17 00:00:00 2001 From: WEBER Logan Date: Sun, 6 Oct 2019 17:29:54 +0200 Subject: [PATCH 06/76] feat(thermostats): display mode for FW100 and FW120 thermostats --- src/ems-esp.cpp | 11 ++++++++++- src/ems.cpp | 5 ++++- src/ems_devices.h | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 62aba30a3..a1d1c6e66 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -409,7 +409,16 @@ uint8_t _getThermostatMode(uint8_t hc_num) { } else if (mode == 1) { thermoMode = 2; // auto } - } else { // default for all other thermostats + } else if (model == EMS_MODEL_FW100 || model == EMS_MODEL_FW120) { + if (mode == 3) { + thermoMode = 4; + } else if (mode == 2) { + thermoMode = 3; + } else if (mode == 1) { + thermoMode = 0; + } + } + else { // default for all other thermostats if (mode == 0) { thermoMode = 3; // night } else if (mode == 1) { diff --git a/src/ems.cpp b/src/ems.cpp index bb2451f35..618e807ad 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -1521,14 +1521,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 } } diff --git a/src/ems_devices.h b/src/ems_devices.h index dc5ad4ae4..a28772162 100644 --- a/src/ems_devices.h +++ b/src/ems_devices.h @@ -140,6 +140,7 @@ // 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 From cbd205fce5cb04440c353ca6823e85442c3ee459 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 6 Oct 2019 17:34:02 +0200 Subject: [PATCH 07/76] updated web builder to gulp4 --- scripts/buildweb.py | 2 +- src/version.h | 2 +- .../woff/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes tools/webfilesbuilder/gulpfile.js | 265 +++++++----------- tools/webfilesbuilder/package.json | 26 +- 5 files changed, 113 insertions(+), 182 deletions(-) create mode 100644 src/websrc/3rdparty/woff/glyphicons-halflings-regular.woff diff --git a/scripts/buildweb.py b/scripts/buildweb.py index 93a75510c..96c41fa10 100644 --- a/scripts/buildweb.py +++ b/scripts/buildweb.py @@ -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") diff --git a/src/version.h b/src/version.h index ada419252..2deb849f5 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define APP_VERSION "1.9.2b1" +#define APP_VERSION "1.9.2b2" diff --git a/src/websrc/3rdparty/woff/glyphicons-halflings-regular.woff b/src/websrc/3rdparty/woff/glyphicons-halflings-regular.woff new file mode 100644 index 0000000000000000000000000000000000000000..9e612858f802245ddcbf59788a0db942224bab35 GIT binary patch literal 23424 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 G`u_lkUM?>H literal 0 HcmV?d00001 diff --git a/tools/webfilesbuilder/gulpfile.js b/tools/webfilesbuilder/gulpfile.js index 406354188..303fbb35a 100644 --- a/tools/webfilesbuilder/gulpfile.js +++ b/tools/webfilesbuilder/gulpfile.js @@ -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 . +*/ + +/*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"]); \ No newline at end of file +gulp.task('default', gulp.parallel('myespjs', 'requiredjs', 'requiredcss', 'fontwoff', 'myesphtml', 'indexhtml')); diff --git a/tools/webfilesbuilder/package.json b/tools/webfilesbuilder/package.json index 4615191fc..d9b335102 100644 --- a/tools/webfilesbuilder/package.json +++ b/tools/webfilesbuilder/package.json @@ -1,24 +1,18 @@ { - "name": "uglifier", - "version": "0.0.1", - "description": "Combine all js and css files into one and gzip them for the myESP 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": { + "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" } } From 3638a50b8e0497e4b3e4d6c5afd55c85f29df050 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 6 Oct 2019 17:34:30 +0200 Subject: [PATCH 08/76] external sensors minor changes in display settings --- src/MyESP.cpp | 10 +++++++--- src/MyESP.h | 2 +- src/ds18.cpp | 2 ++ src/ems-esp.cpp | 11 ++++++----- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/MyESP.cpp b/src/MyESP.cpp index 34144126c..2ce724166 100644 --- a/src/MyESP.cpp +++ b/src/MyESP.cpp @@ -389,10 +389,14 @@ 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); + // 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, _mqtt_retain, payload); - _addMQTTLog(topic, payload, 1); // add to the log, using type of 1 for Publish + if (packet_id) { + _addMQTTLog(topic, payload, 1); // add to the log, using type of 1 for Publish + } else { + myDebug_P(PSTR("[MQTT] Error publishing to %s with payload %s"), _mqttTopic(topic), payload); + } } // MQTT onConnect - when a connect is established diff --git a/src/MyESP.h b/src/MyESP.h index fbef5b9e3..5aec8c64f 100644 --- a/src/MyESP.h +++ b/src/MyESP.h @@ -9,7 +9,7 @@ #ifndef MyESP_h #define MyESP_h -#define MYESP_VERSION "1.2.6" +#define MYESP_VERSION "1.2.7" #include #include diff --git a/src/ds18.cpp b/src/ds18.cpp index 37aa6540b..0c1ba4985 100644 --- a/src/ds18.cpp +++ b/src/ds18.cpp @@ -112,6 +112,7 @@ char * DS18::getDeviceString(char * buffer, unsigned char index) { strlcpy(buffer, "Unknown", size); } +/* char a[30] = {0}; snprintf(a, sizeof(a), @@ -127,6 +128,7 @@ char * DS18::getDeviceString(char * buffer, unsigned char index) { _gpio); strlcat(buffer, a, size); + */ } else { strlcpy(buffer, "invalid", size); } diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 62aba30a3..277dd9c59 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -697,17 +697,18 @@ void publishSensorValues() { StaticJsonDocument<200> doc; JsonObject sensors = doc.to(); - bool hasdata = false; - char label[8] = {0}; - char valuestr[8] = {0}; // for formatting temp + bool hasdata = false; + char label[8] = {0}; + // char valuestr[8] = {0}; // for formatting temp // see if the sensor values have changed, if so send for (uint8_t i = 0; i < EMSESP_Settings.dallas_sensors; i++) { double sensorValue = ds18.getValue(i); if (sensorValue != DS18_DISCONNECTED && sensorValue != DS18_CRC_ERROR) { sprintf(label, PAYLOAD_EXTERNAL_SENSORS, (i + 1)); - sensors[label] = _float_to_char(valuestr, sensorValue); - hasdata = true; + // sensors[label] = _float_to_char(valuestr, sensorValue); + sensors[label] = sensorValue; // TODO check if works + hasdata = true; } } From 94844651b9402198be22e6ff01aa87d6e6e4dfeb Mon Sep 17 00:00:00 2001 From: Klaudiusz Staniek Date: Sun, 6 Oct 2019 19:06:01 +0200 Subject: [PATCH 09/76] MM100 EMS_TYPE_MMPLUSStatusMessage added --- src/ems-esp.cpp | 16 +++++++++++- src/ems.cpp | 62 +++++++++++++++++++++++++++++++++++++++++++++-- src/ems.h | 27 +++++++++++++++++++++ src/ems_devices.h | 19 ++++++++++++--- 4 files changed, 118 insertions(+), 6 deletions(-) diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 277dd9c59..dd1489bcf 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -449,10 +449,11 @@ void showInfo() { myDebug_P(PSTR(" %d external temperature sensor%s found"), EMSESP_Settings.dallas_sensors, (EMSESP_Settings.dallas_sensors == 1) ? "" : "s"); } - myDebug_P(PSTR(" Boiler is %s, Thermostat is %s, Solar Module is %s, Shower Timer is %s, Shower Alert is %s"), + myDebug_P(PSTR(" Boiler is %s, Thermostat is %s, Solar Module is %s, Mixing Module is %s, Shower Timer is %s, Shower Alert is %s"), (ems_getBoilerEnabled() ? "enabled" : "disabled"), (ems_getThermostatEnabled() ? "enabled" : "disabled"), (ems_getSolarModuleEnabled() ? "enabled" : "disabled"), + (ems_getMixingDeviceEnabled() ? "enabled" : "disabled"), ((EMSESP_Settings.shower_timer) ? "enabled" : "disabled"), ((EMSESP_Settings.shower_alert) ? "enabled" : "disabled")); @@ -666,6 +667,19 @@ void showInfo() { } } + // Mixing modules sensors + if (ems_getMixingDeviceEnabled()) { + myDebug_P(PSTR("")); // newline + myDebug_P(PSTR("%sMixing module stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF); + + for (uint8_t hc_num = 1; hc_num <= EMS_THERMOSTAT_MAXHC; hc_num++) { + if (EMS_Mixing.hc[hc_num - 1].active) { + myDebug_P(PSTR(" Mixing Circuit %d"), hc_num); + _renderShortValue(" Current flow temperature", "C", EMS_Mixing.hc[hc_num - 1].flowTemp); + } + } + } + // Dallas external temp sensors if (EMSESP_Settings.dallas_sensors != 0) { myDebug_P(PSTR("")); // newline diff --git a/src/ems.cpp b/src/ems.cpp index bb2451f35..51a1096dc 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -99,6 +99,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 +187,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,13 +202,15 @@ 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_Other EMS_Other; // for other known EMS devices +_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 const uint8_t ems_crc_table[] = {0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x24, 0x26, @@ -267,6 +275,16 @@ 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].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 +396,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); } @@ -1465,6 +1487,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 > 4) { + return; // invalid type + } + EMS_Mixing.hc[hc].active = true; + + if (EMS_RxTelegram->data_length == 1) { + + } else if (EMS_RxTelegram->data_length > 8) { + uint8_t hc_temp = _toShort(3); + EMS_Mixing.hc[hc].flowTemp = _toShort(EMS_OFFSET_MMPLUSStatusMessage_flow_temp); + } +} + /** * 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 @@ -1885,6 +1922,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)); @@ -2124,6 +2165,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) { diff --git a/src/ems.h b/src/ems.h index 057919ee8..ae9d6bdf9 100644 --- a/src/ems.h +++ b/src/ems.h @@ -132,6 +132,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 @@ -273,6 +274,12 @@ typedef struct { bool write_supported; } _Thermostat_Device; +typedef struct { + uint8_t product_id; + uint8_t device_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 +289,7 @@ typedef struct { char model_string[50]; } _Generic_Device; + /* * Telegram package defintions */ @@ -365,6 +373,23 @@ 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 + + uint8_t flowTemp; +} _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 @@ -464,6 +489,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(); @@ -492,5 +518,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; diff --git a/src/ems_devices.h b/src/ems_devices.h index dc5ad4ae4..84c8e1a68 100644 --- a/src/ems_devices.h +++ b/src/ems_devices.h @@ -143,6 +143,13 @@ #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 + // Known EMS devices typedef enum { @@ -174,7 +181,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; @@ -209,6 +219,11 @@ const _SolarModule_Device SolarModule_Devices[] = { }; +const _Mixing_Device Mixing_Devices[] = { + {160, 0x20, "MM100 Mixing Module"}, + {160, 0x21, "MM100 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[] = { @@ -216,8 +231,6 @@ 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"}, From a57b39a9183695fde92a9124c3fcf47d5a4aa465 Mon Sep 17 00:00:00 2001 From: Klaudiusz Staniek Date: Sun, 6 Oct 2019 19:21:03 +0200 Subject: [PATCH 10/76] Removed some leftovers --- src/ems.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ems.cpp b/src/ems.cpp index 51a1096dc..2c3f8d2b8 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -1497,7 +1497,6 @@ void _process_MMPLUSStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) { if (EMS_RxTelegram->data_length == 1) { } else if (EMS_RxTelegram->data_length > 8) { - uint8_t hc_temp = _toShort(3); EMS_Mixing.hc[hc].flowTemp = _toShort(EMS_OFFSET_MMPLUSStatusMessage_flow_temp); } } From bc0ff3b0014af32c322251b2dc60e97f0563d7e2 Mon Sep 17 00:00:00 2001 From: Klaudiusz Staniek Date: Sun, 6 Oct 2019 19:30:36 +0200 Subject: [PATCH 11/76] Info output updated --- src/ems-esp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index dd1489bcf..ea2493d7a 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -522,7 +522,6 @@ void showInfo() { _renderIntValue("Selected flow temperature", "C", EMS_Boiler.selFlowTemp); _renderUShortValue("Current flow temperature", "C", EMS_Boiler.curFlowTemp); _renderUShortValue("Return temperature", "C", EMS_Boiler.retTemp); - _renderUShortValue("Switch temperature", "C", EMS_Boiler.switchTemp); _renderBoolValue("Gas", EMS_Boiler.burnGas); _renderBoolValue("Boiler pump", EMS_Boiler.heatPmp); _renderBoolValue("Fan", EMS_Boiler.fanWork); @@ -671,11 +670,12 @@ void showInfo() { if (ems_getMixingDeviceEnabled()) { myDebug_P(PSTR("")); // newline myDebug_P(PSTR("%sMixing module stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF); + _renderUShortValue("Switch temperature", "C", EMS_Boiler.switchTemp); for (uint8_t hc_num = 1; hc_num <= EMS_THERMOSTAT_MAXHC; hc_num++) { if (EMS_Mixing.hc[hc_num - 1].active) { - myDebug_P(PSTR(" Mixing Circuit %d"), hc_num); - _renderShortValue(" Current flow temperature", "C", EMS_Mixing.hc[hc_num - 1].flowTemp); + myDebug_P(PSTR(" Mixing Circuit %d"), hc_num); + _renderShortValue(" Current flow temperature", "C", EMS_Mixing.hc[hc_num - 1].flowTemp); } } } From c941c37b060d8fd1a7fbcd4238136e30919ada55 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 6 Oct 2019 21:52:52 +0200 Subject: [PATCH 12/76] increase mqttlog size --- src/MyESP.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MyESP.h b/src/MyESP.h index 5aec8c64f..bed02c003 100644 --- a/src/MyESP.h +++ b/src/MyESP.h @@ -87,7 +87,7 @@ extern struct rst_info resetInfo; #define MQTT_MAX_PAYLOAD_SIZE 500 // 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_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 // Internal MQTT events From a45f122a11ffe3c5b3a53a279fdeafdb13c7e3b9 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 6 Oct 2019 21:53:02 +0200 Subject: [PATCH 13/76] b3 --- src/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h b/src/version.h index 2deb849f5..131a503ba 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define APP_VERSION "1.9.2b2" +#define APP_VERSION "1.9.2b3" From 06bf4a877413efec2ecbdad94cb2a80453540da2 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 6 Oct 2019 21:53:12 +0200 Subject: [PATCH 14/76] b3 --- CHANGELOG.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 041a1e0de..1abe18371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,24 @@ 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] 2019-10- +## [1.9.2 beta] + +### Added + +- Handling of MM100 Status Messages (thanks @kstaniek) +- retrieve/display mode for Junkers FW100/120 thermostats (thanks @Neonox31) + +### Fixed + +- `publish` command also sends Dallas external temperature sensor values + + +### Changed + +- External dallas sensor values sent in MQTT payload as float values and not strings +- Breaking change! all MQTT topics for the Thermostat have the Heating Circuit appended (e.g. `thermostat_data1`) +- Breaking change! Removed the heatingcircuit from the MQTT Thermostat topic payload + ## [1.9.1] 2019-10-05 From f24812d71f63a73cb2774a7243ba95e8f95f2b0d Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 6 Oct 2019 21:54:19 +0200 Subject: [PATCH 15/76] added MM50 to mixing device table --- src/ems_devices.h | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/ems_devices.h b/src/ems_devices.h index 2144d5835..dd8b265e7 100644 --- a/src/ems_devices.h +++ b/src/ems_devices.h @@ -140,16 +140,16 @@ // 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_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_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 // Known EMS devices @@ -208,7 +208,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[] = { @@ -220,9 +220,15 @@ 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, 0x20, "MM100 Mixing Module"}, - {160, 0x21, "MM100 Mixing Module"}, + {160, "MM100 Mixing Module"}, + {69, "MM10 Mixer Module"}, + {159, "MM50 Mixing Module"}, }; // Other EMS devices which are not considered boilers, thermostats or solar modules @@ -231,9 +237,6 @@ const _Other_Device Other_Devices[] = { {71, 0x11, "WM10 Switch Module"}, - {69, 0x21, "MM10 Mixer Module"}, - {159, 0x21, "MM50 Mixing Module"}, - {68, 0x09, "BC10/RFM20 Receiver"}, {190, 0x09, "BC10 Base Controller"}, {114, 0x09, "BC10 Base Controller"}, From a5719800a3a79016c6faeb1b4d99ecf4477442c8 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 6 Oct 2019 21:55:28 +0200 Subject: [PATCH 16/76] code cleanup, thermostat mqtt changes, dallas sensor to 2 precision doubles --- doc/home assistant/climate.yaml | 10 +++--- src/ems-esp.cpp | 63 +++++++++++++-------------------- src/ems.cpp | 55 +++++----------------------- src/ems.h | 7 ++-- 4 files changed, 40 insertions(+), 95 deletions(-) diff --git a/doc/home assistant/climate.yaml b/doc/home assistant/climate.yaml index 083b4246b..d2e70f472 100644 --- a/doc/home assistant/climate.yaml +++ b/doc/home assistant/climate.yaml @@ -5,12 +5,12 @@ - "heat" - "off" - mode_state_topic: "home/ems-esp/thermostat_data" - current_temperature_topic: "home/ems-esp/thermostat_data" - temperature_state_topic: "home/ems-esp/thermostat_data" + mode_state_topic: "home/ems-esp/thermostat_data1" + current_temperature_topic: "home/ems-esp/thermostat_data1" + temperature_state_topic: "home/ems-esp/thermostat_data1" - temperature_command_topic: "home/ems-esp/thermostat_cmd_temp" - mode_command_topic: "home/ems-esp/thermostat_cmd_mode" + temperature_command_topic: "home/ems-esp/thermostat_cmd_temp1" + mode_command_topic: "home/ems-esp/thermostat_cmd_mode1" mode_state_template: "{{ value_json.thermostat_mode }}" current_temperature_template: "{{ value_json.thermostat_currtemp }}" diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 5c1a07560..74ff85866 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -417,8 +417,7 @@ uint8_t _getThermostatMode(uint8_t hc_num) { } else if (mode == 1) { thermoMode = 0; } - } - else { // default for all other thermostats + } else { // default for all other thermostats if (mode == 0) { thermoMode = 3; // night } else if (mode == 1) { @@ -683,8 +682,8 @@ void showInfo() { for (uint8_t hc_num = 1; hc_num <= EMS_THERMOSTAT_MAXHC; hc_num++) { if (EMS_Mixing.hc[hc_num - 1].active) { - myDebug_P(PSTR(" Mixing Circuit %d"), hc_num); - _renderShortValue(" Current flow temperature", "C", EMS_Mixing.hc[hc_num - 1].flowTemp); + myDebug_P(PSTR(" Mixing Circuit %d"), hc_num); + _renderUShortValue(" Current flow temperature", "C", EMS_Mixing.hc[hc_num - 1].flowTemp); } } } @@ -722,22 +721,22 @@ void publishSensorValues() { bool hasdata = false; char label[8] = {0}; - // char valuestr[8] = {0}; // for formatting temp - // see if the sensor values have changed, if so send + // see if the sensor values have changed, if so send it on for (uint8_t i = 0; i < EMSESP_Settings.dallas_sensors; i++) { - double sensorValue = ds18.getValue(i); + // round to 2 decimal places. from https://arduinojson.org/v6/faq/how-to-configure-the-serialization-of-floats/ + double sensorValue = (int)(ds18.getValue(i) * 100 + 0.5) / 100.0; if (sensorValue != DS18_DISCONNECTED && sensorValue != DS18_CRC_ERROR) { sprintf(label, PAYLOAD_EXTERNAL_SENSORS, (i + 1)); - // sensors[label] = _float_to_char(valuestr, sensorValue); - sensors[label] = sensorValue; // TODO check if works - hasdata = true; + sensors[label] = sensorValue; + hasdata = true; } } if (hasdata) { char data[200] = {0}; serializeJson(doc, data, sizeof(data)); + myDebugLog("Publishing external sensor data via MQTT"); myESP.mqttPublish(TOPIC_EXTERNAL_SENSORS, data); } } @@ -872,20 +871,16 @@ void publishValues(bool force) { // handle the thermostat values if (ems_getThermostatEnabled()) { - uint8_t total_active_hc = 0; // number of HCs - bool hc_1_active = EMS_Thermostat.hc[EMS_THERMOSTAT_DEFAULTHC - 1].active; // do we have HC1 active? for (uint8_t hc_v = 1; hc_v <= EMS_THERMOSTAT_MAXHC; hc_v++) { _EMS_Thermostat_HC * thermostat = &EMS_Thermostat.hc[hc_v - 1]; // only send if we have an active Heating Circuit with real data if (thermostat->active) { - total_active_hc++; // increase count for #HCs we encounter - // build new json object doc.clear(); JsonObject rootThermostat = doc.to(); - rootThermostat[THERMOSTAT_HC] = _int_to_char(s, thermostat->hc); // heating circuit 1..4 + // rootThermostat[THERMOSTAT_HC] = _int_to_char(s, thermostat->hc); // heating circuit 1..4 // different logic depending on thermostat types if (ems_getThermostatModel() == EMS_MODEL_EASY) { @@ -949,14 +944,10 @@ void publishValues(bool force) { if ((previousThermostatPublishCRC != fchecksum) || force) { previousThermostatPublishCRC = fchecksum; char thermostat_topicname[20]; - strlcpy(thermostat_topicname, TOPIC_THERMOSTAT_DATA, sizeof(thermostat_topicname)); // "thermostat_data" - // if we have more than 1 active Heating Circuit - // or this is single HC and its not HC1 - // append topic name with the HC number - if ((total_active_hc > 1) || ((total_active_hc == 1) && (!hc_1_active))) { - char buffer[4]; - strlcat(thermostat_topicname, itoa(total_active_hc, buffer, 10), sizeof(thermostat_topicname)); - } + char buffer[4]; + // "thermostat_data" + Heating Cicruit # + strlcpy(thermostat_topicname, TOPIC_THERMOSTAT_DATA, sizeof(thermostat_topicname)); + strlcat(thermostat_topicname, itoa(hc_v, buffer, 10), sizeof(thermostat_topicname)); myDebugLog("Publishing thermostat data via MQTT"); myESP.mqttPublish(thermostat_topicname, data); } @@ -1456,7 +1447,8 @@ void TelnetCommandCallback(uint8_t wc, const char * commandLine) { } if (strcmp(first_cmd, "publish") == 0) { - publishValues(true); + do_publishValues(); + do_publishSensorValues(); ok = true; } @@ -1666,33 +1658,25 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { // subscribe to the 4 heating circuits char topic_s[50]; char buffer[4]; - - // subscribe to the normal batch to be backwards compatible - myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_TEMP); - myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_MODE); - myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_DAYTEMP); - myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_NIGHTTEMP); - myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP); - for (uint8_t hc = 1; hc <= EMS_THERMOSTAT_MAXHC; hc++) { strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_TEMP, sizeof(topic_s)); - strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); // add 1-4 at the end + strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); myESP.mqttSubscribe(topic_s); strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_MODE, sizeof(topic_s)); - strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); // add 1-4 at the end + strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); myESP.mqttSubscribe(topic_s); strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_DAYTEMP, sizeof(topic_s)); - strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); // add 1-4 at the end + strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); myESP.mqttSubscribe(topic_s); strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_NIGHTTEMP, sizeof(topic_s)); - strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); // add 1-4 at the end + strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); myESP.mqttSubscribe(topic_s); strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP, sizeof(topic_s)); - strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); // add 1-4 at the end + strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); myESP.mqttSubscribe(topic_s); } @@ -2187,16 +2171,17 @@ void loop() { // the main loop myESP.loop(); - // check Dallas sensors, every 2 seconds + // check Dallas sensors, using same schedule as publish_time (default 2 mins) // these values are published to MQTT separately via the timer publishSensorValuesTimer if (EMSESP_Settings.dallas_sensors != 0) { ds18.loop(); } - // publish the values to MQTT, only if the values have changed + // publish all the values to MQTT, only if the values have changed // although we don't want to publish when doing a deep scan of the thermostat if (ems_getEmsRefreshed() && (scanThermostat_count == 0)) { publishValues(false); + do_publishSensorValues(); ems_setEmsRefreshed(false); // reset } diff --git a/src/ems.cpp b/src/ems.cpp index 0f9b91517..5e363f35b 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -210,7 +210,7 @@ _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 +_EMS_Other EMS_Other; // for other known EMS devices // CRC lookup table with poly 12 for faster checking const uint8_t ems_crc_table[] = {0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x24, 0x26, @@ -655,7 +655,7 @@ void _ems_sendTelegram() { EMS_TxTelegram.data[1] = EMS_TxTelegram.dest; } else { // for a READ or VALIDATE - EMS_TxTelegram.data[1] = (EMS_TxTelegram.dest | 0x80); // read has 8th bit set, always + EMS_TxTelegram.data[1] = (EMS_TxTelegram.dest | 0x80); // read has 8th bit set, always } // complete the rest of the header depending on EMS or EMS+ @@ -1489,13 +1489,12 @@ void _process_EasyStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) { void _process_MMPLUSStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) { uint8_t hc = (EMS_RxTelegram->type - EMS_TYPE_MMPLUSStatusMessage_HC1); // 0 to 3 - if (hc > 4) { + if (hc > EMS_THERMOSTAT_MAXHC) { return; // invalid type } EMS_Mixing.hc[hc].active = true; - - if (EMS_RxTelegram->data_length == 1) { + if (EMS_RxTelegram->data_length == 1) { } else if (EMS_RxTelegram->data_length > 8) { EMS_Mixing.hc[hc].flowTemp = _toShort(EMS_OFFSET_MMPLUSStatusMessage_flow_temp); } @@ -1508,7 +1507,7 @@ void _process_MMPLUSStatusMessage(_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; @@ -1567,7 +1566,7 @@ void _process_JunkersStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) { 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 + EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT } } @@ -2203,47 +2202,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...")); 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); - } - - */ } /** @@ -2592,9 +2556,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(); } /** @@ -2845,7 +2806,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; } @@ -2955,7 +2916,7 @@ 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; } diff --git a/src/ems.h b/src/ems.h index ae9d6bdf9..4d5c25a98 100644 --- a/src/ems.h +++ b/src/ems.h @@ -276,7 +276,6 @@ typedef struct { typedef struct { uint8_t product_id; - uint8_t device_id; char model_string[50]; } _Mixing_Device; @@ -378,10 +377,10 @@ typedef struct { 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 + uint8_t hc; // heating circuit 1, 2, 3 or 4 + bool active; // true if there is data for this HC - uint8_t flowTemp; + uint16_t flowTemp; } _EMS_Mixing_HC; // Mixer data From 6003344cd61c5f2059ccf8b8f74442b0c20096d0 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 6 Oct 2019 22:04:10 +0200 Subject: [PATCH 17/76] fix out of bounds with hc num --- src/ems.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ems.cpp b/src/ems.cpp index 5e363f35b..2f3b74a7b 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -1489,7 +1489,7 @@ void _process_EasyStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) { 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) { + if (hc >= EMS_THERMOSTAT_MAXHC) { return; // invalid type } EMS_Mixing.hc[hc].active = true; @@ -1507,7 +1507,7 @@ void _process_MMPLUSStatusMessage(_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 > EMS_THERMOSTAT_MAXHC) { + if (hc >= EMS_THERMOSTAT_MAXHC) { return; // invalid type } EMS_Thermostat.hc[hc].active = true; From 0419e20592997506ab119aacd17362c463f01d9e Mon Sep 17 00:00:00 2001 From: Klaudiusz Staniek Date: Sun, 6 Oct 2019 21:51:00 +0200 Subject: [PATCH 18/76] Added pump modulation and valve status --- src/ems-esp.cpp | 4 +++- src/ems.cpp | 16 ++++++++++------ src/ems.h | 2 ++ src/ems_devices.h | 12 +++++++----- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 74ff85866..6a20ff555 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -682,8 +682,10 @@ void showInfo() { for (uint8_t hc_num = 1; hc_num <= EMS_THERMOSTAT_MAXHC; hc_num++) { if (EMS_Mixing.hc[hc_num - 1].active) { - myDebug_P(PSTR(" Mixing Circuit %d"), hc_num); + myDebug_P(PSTR(" Mixing Circuit %d"), hc_num); _renderUShortValue(" Current flow temperature", "C", EMS_Mixing.hc[hc_num - 1].flowTemp); + _renderIntValue(" Current pump modulation", "%", EMS_Mixing.hc[hc_num - 1].pumpMod); + _renderIntValue(" Current valve status", "%", EMS_Mixing.hc[hc_num - 1].valveStatus); } } } diff --git a/src/ems.cpp b/src/ems.cpp index 5e363f35b..ad4aebd3f 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -278,11 +278,13 @@ void ems_init() { 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].device_id = EMS_ID_NONE; - EMS_Mixing.hc[i].model_id = EMS_MODEL_NONE; - EMS_Mixing.hc[i].product_id = EMS_ID_NONE; + 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 @@ -1496,7 +1498,9 @@ void _process_MMPLUSStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) { 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].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); } } diff --git a/src/ems.h b/src/ems.h index 4d5c25a98..0d2d64f05 100644 --- a/src/ems.h +++ b/src/ems.h @@ -381,6 +381,8 @@ typedef struct { bool active; // true if there is data for this HC uint16_t flowTemp; + uint8_t pumpMod; + uint8_t valveStatus; } _EMS_Mixing_HC; // Mixer data diff --git a/src/ems_devices.h b/src/ems_devices.h index dd8b265e7..db74ac75a 100644 --- a/src/ems_devices.h +++ b/src/ems_devices.h @@ -145,11 +145,13 @@ #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_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 From 2cd0a76ef8b2425fd4bb91e40cb141a8f40229a4 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 6 Oct 2019 23:59:12 +0200 Subject: [PATCH 19/76] persist log_events to config file --- src/MyESP.cpp | 21 +++++++++++---------- src/MyESP.h | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/MyESP.cpp b/src/MyESP.cpp index 2ce724166..be8d3cdcb 100644 --- a/src/MyESP.cpp +++ b/src/MyESP.cpp @@ -395,7 +395,7 @@ void MyESP::mqttPublish(const char * topic, const char * payload) { if (packet_id) { _addMQTTLog(topic, payload, 1); // add to the log, using type of 1 for Publish } else { - myDebug_P(PSTR("[MQTT] Error publishing to %s with payload %s"), _mqttTopic(topic), payload); + myDebug_P(PSTR("[MQTT] Error publishing to %s with payload %s, error %d"), _mqttTopic(topic), payload, packet_id); } } @@ -1585,7 +1585,7 @@ bool MyESP::_fs_validateConfigFile(const char * filename, size_t maxsize, JsonDo // 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(); @@ -1640,11 +1640,11 @@ bool MyESP::_fs_validateLogFile(const char * filename) { // 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 + size_t maxsize = ESP.getFreeHeap() - 2000; // reserve some buffer + 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); + myDebug_P(PSTR("[FS] File %s size %d is too large"), filename, size); return false; } else if (size == 0) { eventlog.close(); @@ -1756,7 +1756,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 @@ -1903,10 +1903,11 @@ bool MyESP::_fs_writeConfig() { network["password"] = _network_password; network["wmode"] = _network_wmode; - JsonObject general = doc.createNestedObject("general"); - general["password"] = _general_password; - general["serial"] = _general_serial; - general["hostname"] = _general_hostname; + JsonObject general = doc.createNestedObject("general"); + 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; diff --git a/src/MyESP.h b/src/MyESP.h index bed02c003..d2cda6a9c 100644 --- a/src/MyESP.h +++ b/src/MyESP.h @@ -9,7 +9,7 @@ #ifndef MyESP_h #define MyESP_h -#define MYESP_VERSION "1.2.7" +#define MYESP_VERSION "1.2.8" #include #include From af394cdc7eea07907bed739c3417d38b8517b4d3 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 6 Oct 2019 23:59:31 +0200 Subject: [PATCH 20/76] set shower_* also sends MQTT topic --- src/ems-esp.cpp | 42 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 6a20ff555..5fcb0d989 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -135,7 +135,6 @@ static const command_t project_cmds[] PROGMEM = { {false, "devices [all]", "list all supported and detected EMS devices"}, {false, "queue", "show current Tx queue"}, {false, "autodetect [quick | deep]", "detect EMS devices and attempt to automatically set boiler and thermostat types"}, - {false, "shower ", "toggle either timer or alert on/off"}, {false, "send XX ...", "send raw telegram data to EMS bus (XX are hex values)"}, {false, "thermostat read ", "send read request to the thermostat for heating circuit hc 1-4"}, {false, "thermostat temp [hc] ", "set current thermostat temperature"}, @@ -682,7 +681,7 @@ void showInfo() { for (uint8_t hc_num = 1; hc_num <= EMS_THERMOSTAT_MAXHC; hc_num++) { if (EMS_Mixing.hc[hc_num - 1].active) { - myDebug_P(PSTR(" Mixing Circuit %d"), hc_num); + myDebug_P(PSTR(" Mixing Circuit %d"), hc_num); _renderUShortValue(" Current flow temperature", "C", EMS_Mixing.hc[hc_num - 1].flowTemp); _renderIntValue(" Current pump modulation", "%", EMS_Mixing.hc[hc_num - 1].pumpMod); _renderIntValue(" Current valve status", "%", EMS_Mixing.hc[hc_num - 1].valveStatus); @@ -1334,10 +1333,12 @@ bool SetListCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, co if ((strcmp(setting, "shower_timer") == 0) && (wc == 2)) { if (strcmp(value, "on") == 0) { EMSESP_Settings.shower_timer = true; - ok = true; + myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0"); + ok = true; } else if (strcmp(value, "off") == 0) { EMSESP_Settings.shower_timer = false; - ok = true; + myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0"); + ok = true; } else { myDebug_P(PSTR("Error. Usage: set shower_timer ")); } @@ -1347,10 +1348,12 @@ bool SetListCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, co if ((strcmp(setting, "shower_alert") == 0) && (wc == 2)) { if (strcmp(value, "on") == 0) { EMSESP_Settings.shower_alert = true; - ok = true; + myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0"); + ok = true; } else if (strcmp(value, "off") == 0) { EMSESP_Settings.shower_alert = false; - ok = true; + myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0"); + ok = true; } else { myDebug_P(PSTR("Error. Usage: set shower_alert ")); } @@ -1493,21 +1496,6 @@ void TelnetCommandCallback(uint8_t wc, const char * commandLine) { } } - - // shower settings - if ((strcmp(first_cmd, "shower") == 0) && (wc == 2)) { - char * second_cmd = _readWord(); - if (strcmp(second_cmd, "timer") == 0) { - EMSESP_Settings.shower_timer = !EMSESP_Settings.shower_timer; - myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0"); - ok = true; - } else if (strcmp(second_cmd, "alert") == 0) { - EMSESP_Settings.shower_alert = !EMSESP_Settings.shower_alert; - myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0"); - ok = true; - } - } - // logging if ((strcmp(first_cmd, "log") == 0) && (wc == 2)) { char * second_cmd = _readWord(); @@ -1657,6 +1645,10 @@ uint8_t _hasHCspecified(const char * topic, const char * input) { void MQTTCallback(unsigned int type, const char * topic, const char * message) { // we're connected. lets subscribe to some topics if (type == MQTT_CONNECT_EVENT) { + myESP.mqttSubscribe(TOPIC_SHOWER_TIMER); + myESP.mqttSubscribe(TOPIC_SHOWER_ALERT); + myESP.mqttSubscribe(TOPIC_SHOWER_COLDSHOT); + // subscribe to the 4 heating circuits char topic_s[50]; char buffer[4]; @@ -1687,13 +1679,9 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { myESP.mqttSubscribe(TOPIC_BOILER_CMD_COMFORT); myESP.mqttSubscribe(TOPIC_BOILER_CMD_FLOWTEMP); - myESP.mqttSubscribe(TOPIC_SHOWER_TIMER); - myESP.mqttSubscribe(TOPIC_SHOWER_ALERT); - myESP.mqttSubscribe(TOPIC_SHOWER_COLDSHOT); - // publish the status of the Shower parameters - myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0"); - myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0"); + // myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0"); + // myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0"); } // handle incoming MQTT publish events From 97e845bcee2a63039dbc747c83652032e91dee1b Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 7 Oct 2019 08:52:18 +0200 Subject: [PATCH 21/76] mention breaking changes in 1.9.2 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1abe18371..e6da6f9cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.9.2 beta] +Important! This build has breaking changes: + - the MQTT `thermostat_data` topic is always suffixed with the heat countroller number, e.g. `thermostat_data1` + - the web builder has been upgraded to use gulp 4. Delete the `tools/webfilesbuilder/node_modules` and re-install the libraries using `npm i` from within the `tools/webfilesbuilder` folder. + ### Added - Handling of MM100 Status Messages (thanks @kstaniek) From 935055d30574deda5380ae97efc00ab7c0c933fe Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 7 Oct 2019 09:52:17 +0200 Subject: [PATCH 22/76] mention breaking changes to building the web files --- CHANGELOG.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6da6f9cd..ae009d136 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,26 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.9.2 beta] -Important! This build has breaking changes: - - the MQTT `thermostat_data` topic is always suffixed with the heat countroller number, e.g. `thermostat_data1` - - the web builder has been upgraded to use gulp 4. Delete the `tools/webfilesbuilder/node_modules` and re-install the libraries using `npm i` from within the `tools/webfilesbuilder` folder. +#### Important! This build has breaking changes: + - the Thermostat MQTT topics are all always suffixed with the heat controller number, e.g. `thermostat_data1` + - the web builder has been upgraded to use Gulp 4. Remove `tools/webfilesbuilder/node_modules` and re-install the libraries using `npm i` from within the `tools/webfilesbuilder` folder. ### Added - Handling of MM100 Status Messages (thanks @kstaniek) -- retrieve/display mode for Junkers FW100/120 thermostats (thanks @Neonox31) +- Retrieve/display mode for Junkers FW100/120 thermostats (thanks @Neonox31) +- Added error fall-back for MQTT publishes that fail ### 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 -- Breaking change! all MQTT topics for the Thermostat have the Heating Circuit appended (e.g. `thermostat_data1`) -- Breaking change! Removed the heatingcircuit from the MQTT Thermostat topic payload +- 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 +### Removed + +- Removed telnet command `shower timer` and `shower alert` to toggle the switches +- Removed the heatingcircuit key/value from the MQTT Thermostat topic payload ## [1.9.1] 2019-10-05 From 4d422c5dd4fbe62a77258da86902b2c384becd36 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 7 Oct 2019 09:52:33 +0200 Subject: [PATCH 23/76] tidy up comments --- src/ems-esp.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 5fcb0d989..9977f2643 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -1616,9 +1616,10 @@ void OTACallback_post() { } -// see's if a topic is appended with an interger value +// see's if a topic string is appended with an interger value // used to identify a heating circuit -// returns HC number +// returns HC number 1 - 4 +// or the default (1) is no suffix can be found uint8_t _hasHCspecified(const char * topic, const char * input) { int orig_len = strlen(topic); // original length of the topic we're comparing too @@ -1631,7 +1632,7 @@ uint8_t _hasHCspecified(const char * topic, const char * input) { } if (diff == 0) { - return EMS_THERMOSTAT_DEFAULTHC; // identical, use default + return EMS_THERMOSTAT_DEFAULTHC; // identical, use default which is 1 } // return the value of the last char, 0-9 From cc7e59538a14b32dbe1537c81621ff641724eea7 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 7 Oct 2019 12:14:48 +0200 Subject: [PATCH 24/76] updated list of supported devices --- README.md | 58 ++++++++++++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 336792f04..fcc257795 100644 --- a/README.md +++ b/README.md @@ -40,54 +40,50 @@ The code is written for the Espressif **ESP8266** microcontroller and supports a ### Thermostats: +* Buderus RC10, RC20, RC20F, RC30, R35, RC300, RC310, RC3000 +* Buderus Logamatic TC100 (read-only) +* Nefit Moduline 100, 300, 400, 1010, 3000 +* Nefit Moduline Easy (read-only) +* Bosch Easy, CW100 (read-only) +* Junkers FR10, FR100, FR110, FW100, FW120 * Sieger ES73 -* RC10/Nefit Moduline 100 -* RC20/Nefit Moduline 300 -* RC20F -* RC30/Nefit Moduline 400 -* RC35 (only a single HC) -* RC300/RC310/RC3000 -* Nefit Moduline 1010 -* Junkers FR10 -* TC100/Nefit Easy (read-only) -* Bosch Easy (read-only) -* Bosch CW100 (read-only) ### Boilers: -* Buderus GB172/Nefit Trendline -* Nefit Topline Compact/Buderus GB162 -* Buderus Logamax U122 -* Buderus Logamax plus/GB192 -* Sieger BK15 Boiler/Nefit Smartline -* Bosch Condens 2500/Junkers Cerapur Comfort -* Nefit Proline +* Buderus GBx72, GB162, GB152, Logamax U122, Logamax plus/GB192, Logano +* Bosch Condens 2500 +* Junkers Cerapur, Heatronic 3 boilers +* Nefit Proline, Trendline, Topline, Enviline, Smartline +* Sieger BK15 Boiler ### Solar Modules: -* SM10 Solar Module -* SM100 Solar Module +* Buderus SM10, SM50, SM100 Solar Module * Junkers ISM1 Solar Module +### Mixing Modules: + +* Buderus MM10, MM50, MM100 Mixer Module + +### Heat Pump Modules: + +* Buderus HeatPump Module + ### Other devices: -* MM10 Mixer Module -* MC10 Module -* WM10 Switch Module -* MM100 Mixing Module -* MM100 Mixing Module -* BC10/RFM20 Receiver -* BC10 Base Controller -* BC25 Base Controller +* Generic Buderus MC10 Module +* Buderus WM10 Switch Module +* Buderus RFM20 Receiver +* Buderus BC10, BC25 Base Controller +* Buderus Web Gateway KM200 * Nefit Moduline Easy Connect * Bosch Easy Connect * EMS-OT OpenTherm converter -* Web Gateway KM200 -* HeatPump Module +* Junkers Controller ## Compatible with EMS Gateway -Using BBQKees' [EMS Gateway](https://shop.hotgoodies.nl/ems/) board with integrated Wemos D1: +Using BBQKees' [EMS Gateway](https://shop.hotgoodies.nl/ems/) board with integrated Wemos D1 ESP8266: | ![on boiler](https://github.com/proddy/EMS-ESP/raw/master/doc/ems%20gateway/on-boiler.jpg) | ![kit](https://github.com/proddy/EMS-ESP/raw/master/doc/ems%20gateway/ems-kit-2.jpg) | ![basic circuit](https://github.com/proddy/EMS-ESP/raw/master/doc/ems%20gateway/ems-board-white.jpg) | | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | From 439679d05530371d0c1d990eb6797cbf05bbd5bd Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 7 Oct 2019 21:07:58 +0200 Subject: [PATCH 25/76] added Worcester-Bosch --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fcc257795..0db9da98f 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ The code is written for the Espressif **ESP8266** microcontroller and supports a * Buderus GBx72, GB162, GB152, Logamax U122, Logamax plus/GB192, Logano * Bosch Condens 2500 +* Worcester-Bosch Greenstar 550CDi +* Worcester Bosch Greenstar 24i * Junkers Cerapur, Heatronic 3 boilers * Nefit Proline, Trendline, Topline, Enviline, Smartline * Sieger BK15 Boiler From 8920bea18198c029f0eec71de4c24a8cd0e3cfc3 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 8 Oct 2019 22:02:41 +0200 Subject: [PATCH 26/76] minor formatting bug - https://github.com/proddy/EMS-ESP/issues/138 --- src/ems.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ems.cpp b/src/ems.cpp index daba3eb72..910c03990 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -633,6 +633,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); } From 707ce8a4f65597f190ab34bda97a5828c2955b00 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 9 Oct 2019 12:33:56 +0200 Subject: [PATCH 27/76] tidy up, added custom_flags as optional build flags --- platformio.ini | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/platformio.ini b/platformio.ini index eb44063e0..aacc64ba6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,19 +9,21 @@ default_envs = debug ;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 = [env] -;board = esp12e +; board = esp12e +; board = nodemcu +; board = nodemcu2 board = d1_mini framework = arduino platform = espressif8266 @@ -47,35 +49,35 @@ monitor_speed = 115200 ;upload_protocol = espota ;upload_port = ems-esp.local +[env:clean] +extra_scripts = pre:scripts/clean_fw.py + [env:buildweb] extra_scripts = pre:scripts/buildweb.py [env:tests] build_type = debug -build_flags = ${common.general_flags} -DTESTS +build_flags = ${common.general_flags} ${common.custom_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 +build_flags = ${common.general_flags} ${common.custom_flags} -DCRASH extra_scripts = pre:scripts/rename_fw.py pre:scripts/buildweb.py [env:debug] build_type = debug -build_flags = ${common.general_flags} +build_flags = ${common.general_flags} ${common.custom_flags} 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 +build_flags = ${common.general_flags} ${common.custom_flags} extra_scripts = pre:scripts/rename_fw.py [env:checkcode] From fb1527eee135a705a64edc13c1911d371a477851 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 9 Oct 2019 12:34:49 +0200 Subject: [PATCH 28/76] fix issue when 'set setting' didn't reset to default value in telnet --- src/MyESP.cpp | 185 +++++++++++++++++++------------------------------- src/MyESP.h | 9 ++- 2 files changed, 76 insertions(+), 118 deletions(-) diff --git a/src/MyESP.cpp b/src/MyESP.cpp index be8d3cdcb..87935eac4 100644 --- a/src/MyESP.cpp +++ b/src/MyESP.cpp @@ -651,7 +651,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; } @@ -769,148 +769,53 @@ 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_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, "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) { @@ -918,13 +823,17 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) } } + if (restart) { + myDebug_P(PSTR("Please 'restart' to apply new settings.")); + } + bool ok = false; // if we were able to recognize the set command, continue 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); @@ -1787,6 +1696,52 @@ bool MyESP::_fs_loadConfig() { 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 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 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)) { + *setting = true; + } else if ((strcmp(value, "off") == 0) || (strcmp(value, "no") == 0) || (strcmp(value, "0") == 0)) { + *setting = false; + } else { + return false; // invalid setting value + } + } else { + *setting = value_default; // use the default value + } + + return true; +} + // load custom settings bool MyESP::_fs_loadCustomConfig() { StaticJsonDocument doc; diff --git a/src/MyESP.h b/src/MyESP.h index d2cda6a9c..762506799 100644 --- a/src/MyESP.h +++ b/src/MyESP.h @@ -9,7 +9,7 @@ #ifndef MyESP_h #define MyESP_h -#define MYESP_VERSION "1.2.8" +#define MYESP_VERSION "1.2.9" #include #include @@ -287,6 +287,9 @@ 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(bool * setting, const char * value, bool value_default); // Web void setWeb(web_callback_f callback_web); @@ -330,7 +333,7 @@ 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; @@ -406,7 +409,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 From d51e61fd1f7eacf3670ae94bfc8663ea76b08bad Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 9 Oct 2019 13:55:50 +0200 Subject: [PATCH 29/76] cleancode changes from cppcheck and cpplint --- src/MyESP.cpp | 10 ++++------ src/TelnetSpy.cpp | 5 ++--- src/TimeLib.cpp | 22 +++------------------- src/TimeLib.h | 16 ++++++---------- src/ds18.cpp | 5 ++--- src/emsuart.cpp | 2 +- 6 files changed, 18 insertions(+), 42 deletions(-) diff --git a/src/MyESP.cpp b/src/MyESP.cpp index 87935eac4..b2d031bd1 100644 --- a/src/MyESP.cpp +++ b/src/MyESP.cpp @@ -1228,7 +1228,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 @@ -1590,12 +1590,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 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') { @@ -1700,7 +1699,7 @@ bool MyESP::_fs_loadConfig() { // 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 + free(*setting); // first free any allocated memory } if (_hasValue(value)) { @@ -2244,12 +2243,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') { diff --git a/src/TelnetSpy.cpp b/src/TelnetSpy.cpp index 274749d9d..07b625c7d 100644 --- a/src/TelnetSpy.cpp +++ b/src/TelnetSpy.cpp @@ -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; @@ -573,7 +572,7 @@ void TelnetSpy::handle() { return; } if (!listening) { - if ((WiFi.status() == WL_DISCONNECTED) && (WiFi.getMode() & WIFI_AP)) { + if ((WiFi.status() == WL_DISCONNECTED) && (WiFi.getMode() & WIFI_AP)) { if (usedSer) { usedSer->println("[TELNET] in AP mode"); // added by Proddy } diff --git a/src/TimeLib.cpp b/src/TimeLib.cpp index 62da04d5b..e59b24dc2 100644 --- a/src/TimeLib.cpp +++ b/src/TimeLib.cpp @@ -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; diff --git a/src/TimeLib.h b/src/TimeLib.h index a827e38f0..0531e270a 100644 --- a/src/TimeLib.h +++ b/src/TimeLib.h @@ -1,5 +1,5 @@ -#ifndef _Time_h -#define _Time_h +#ifndef _TimeLib_h +#define _TimeLib_h #include @@ -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 diff --git a/src/ds18.cpp b/src/ds18.cpp index 0c1ba4985..02d9d7139 100644 --- a/src/ds18.cpp +++ b/src/ds18.cpp @@ -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,7 +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), diff --git a/src/emsuart.cpp b/src/emsuart.cpp index a22c190e3..f40268f71 100644 --- a/src/emsuart.cpp +++ b/src/emsuart.cpp @@ -220,7 +220,7 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) { USF(EMSUART_UART) = buf[i]; delayMicroseconds(EMSUART_TX_BRK_WAIT); // https://github.com/proddy/EMS-ESP/issues/23# } - emsuart_tx_brk(); // send + emsuart_tx_brk(); // send } 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); From c0ac34fc152c570c987398f5dc517f1c534d3d64 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2019 14:23:13 +0200 Subject: [PATCH 30/76] error handling during MQTT subscribe --- src/MyESP.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/MyESP.cpp b/src/MyESP.cpp index b2d031bd1..be8740703 100644 --- a/src/MyESP.cpp +++ b/src/MyESP.cpp @@ -369,13 +369,15 @@ void 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); - // add to mqtt log - _addMQTTLog(topic_s, "", 2); // type of 2 means Subscribe. Has an empty payload for now + if (packet_id) { + // add to mqtt log + _addMQTTLog(topic_s, "", 2); // type of 2 means Subscribe. Has an empty payload for now + } else { + myDebug_P(PSTR("[MQTT] Error subscribing to %s, error %d"), _mqttTopic(topic), packet_id); + } } } @@ -389,13 +391,15 @@ void MyESP::mqttUnsubscribe(const char * topic) { // MQTT Publish void MyESP::mqttPublish(const char * topic, const char * payload) { - // 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, _mqtt_retain, payload); + 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, _mqtt_retain, payload); - if (packet_id) { - _addMQTTLog(topic, payload, 1); // add to the log, using type of 1 for Publish - } else { - myDebug_P(PSTR("[MQTT] Error publishing to %s with payload %s, error %d"), _mqttTopic(topic), payload, packet_id); + if (packet_id) { + _addMQTTLog(topic, payload, 1); // add to the log, using type of 1 for Publish + } else { + myDebug_P(PSTR("[MQTT] Error publishing to %s with payload %s, error %d"), _mqttTopic(topic), payload, packet_id); + } } } From 1f1cd789cef4efa1c3a2cfeb5b2a469b58b6242d Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2019 14:23:47 +0200 Subject: [PATCH 31/76] https://github.com/proddy/EMS-ESP/issues/205 - product ID 95 is not unqiue to Junkers! --- src/ems.cpp | 6 ------ src/ems.h | 3 +-- src/ems_devices.h | 2 +- src/version.h | 2 +- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/ems.cpp b/src/ems.cpp index 910c03990..b0a1bc61a 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -2076,12 +2076,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; diff --git a/src/ems.h b/src/ems.h index 0d2d64f05..bb4659851 100644 --- a/src/ems.h +++ b/src/ems.h @@ -87,7 +87,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 @@ -247,7 +246,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 { diff --git a/src/ems_devices.h b/src/ems_devices.h index db74ac75a..221ff668e 100644 --- a/src/ems_devices.h +++ b/src/ems_devices.h @@ -202,7 +202,7 @@ const _Boiler_Device Boiler_Devices[] = { {203, "Buderus Logamax U122/Junkers Cerapur"}, {208, "Buderus Logamax plus/GB192"}, {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"} diff --git a/src/version.h b/src/version.h index 131a503ba..dc06860db 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define APP_VERSION "1.9.2b3" +#define APP_VERSION "1.9.2b4" From 893e90f89ab6d1818758d38b397e229b5a1e0535 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 10 Oct 2019 14:26:34 +0200 Subject: [PATCH 32/76] changing wifi timeout request from 10 to 20s - https://github.com/proddy/EMS-ESP/issues/187 --- src/MyESP.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MyESP.h b/src/MyESP.h index 762506799..7376d62e3 100644 --- a/src/MyESP.h +++ b/src/MyESP.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 From 0fdebbbccfc4e15b8893ef31c6cab7f6464bae57 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 10 Oct 2019 20:50:36 +0200 Subject: [PATCH 33/76] always rebuild web on release target --- platformio.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index aacc64ba6..7759ddfdc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -78,7 +78,9 @@ extra_scripts = [env:release] build_flags = ${common.general_flags} ${common.custom_flags} -extra_scripts = pre:scripts/rename_fw.py +extra_scripts = + pre:scripts/rename_fw.py + pre:scripts/buildweb.py [env:checkcode] build_type = debug From 3eb263736c072c655cd34295990a6035ba29b3ed Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 10 Oct 2019 21:44:03 +0200 Subject: [PATCH 34/76] added bosch devices - https://github.com/proddy/EMS-ESP/issues/206 --- src/ems_devices.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ems_devices.h b/src/ems_devices.h index 221ff668e..66a819e9d 100644 --- a/src/ems_devices.h +++ b/src/ems_devices.h @@ -198,9 +198,9 @@ const _Boiler_Device Boiler_Devices[] = { {72, "MC10 Module"}, {123, "Buderus GBx72/Nefit Trendline/Junkers Cerapur"}, - {115, "Nefit Topline Compact/Buderus GB162"}, + {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"}, {95, "Bosch Condens 2500/Buderus Logamax GB062/Junkers Heatronic 3"}, {122, "Nefit Proline"}, @@ -275,7 +275,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 From f6aafe840ed649654495382d6a38a5ae3c93048b Mon Sep 17 00:00:00 2001 From: Richard Pecl Date: Fri, 11 Oct 2019 02:38:22 +0200 Subject: [PATCH 35/76] more values published over MQTT --- src/MyESP.h | 2 +- src/ems-esp.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++++++-- src/my_config.h | 3 ++ 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/MyESP.h b/src/MyESP.h index 7376d62e3..86a1bc12c 100644 --- a/src/MyESP.h +++ b/src/MyESP.h @@ -84,7 +84,7 @@ extern struct rst_info resetInfo; #define MQTT_QOS 1 #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 60 // max number of log entries for MQTT publishes and subscribes diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 9977f2643..02eb627f0 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -766,7 +766,8 @@ void publishValues(bool force) { static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off static uint32_t previousBoilerPublishCRC = 0; // CRC check for boiler values - static uint32_t previousThermostatPublishCRC = 0; // CRC check for thermostat values + static uint32_t previousThermostatPublishCRC[EMS_THERMOSTAT_MAXHC]; // CRC check for thermostat values + static uint32_t previousMixingPublishCRC[EMS_THERMOSTAT_MAXHC]; // CRC check for mixing values static uint32_t previousSMPublishCRC = 0; // CRC check for Solar Module values (e.g. SM10) JsonObject rootBoiler = doc.to(); @@ -781,6 +782,8 @@ void publishValues(bool force) { if (EMS_Boiler.wWSelTemp != EMS_VALUE_INT_NOTSET) rootBoiler["wWSelTemp"] = EMS_Boiler.wWSelTemp; + if (EMS_Boiler.wWDesiredTemp != EMS_VALUE_INT_NOTSET) + rootBoiler["wWDesiredTemp"] = EMS_Boiler.wWDesiredTemp; if (EMS_Boiler.selFlowTemp != EMS_VALUE_INT_NOTSET) rootBoiler["selFlowTemp"] = EMS_Boiler.selFlowTemp; if (EMS_Boiler.selBurnPow != EMS_VALUE_INT_NOTSET) @@ -789,6 +792,8 @@ void publishValues(bool force) { rootBoiler["curBurnPow"] = EMS_Boiler.curBurnPow; if (EMS_Boiler.pumpMod != EMS_VALUE_INT_NOTSET) rootBoiler["pumpMod"] = EMS_Boiler.pumpMod; + if (EMS_Boiler.wWCircPump != EMS_VALUE_INT_NOTSET) + rootBoiler["wWCircPump"] = EMS_Boiler.wWCircPump; if (EMS_Boiler.extTemp != EMS_VALUE_SHORT_NOTSET) rootBoiler["outdoorTemp"] = (double)EMS_Boiler.extTemp / 10; @@ -813,6 +818,9 @@ void publishValues(bool force) { if (EMS_Boiler.burnGas != EMS_VALUE_INT_NOTSET) rootBoiler["burnGas"] = _bool_to_char(s, EMS_Boiler.burnGas); + if (EMS_Boiler.flameCurr != EMS_VALUE_USHORT_NOTSET) + rootBoiler["flameCurr"] = (double)(int16_t)EMS_Boiler.flameCurr / 10; + if (EMS_Boiler.heatPmp != EMS_VALUE_INT_NOTSET) rootBoiler["heatPmp"] = _bool_to_char(s, EMS_Boiler.heatPmp); @@ -825,9 +833,24 @@ void publishValues(bool force) { if (EMS_Boiler.wWCirc != EMS_VALUE_INT_NOTSET) rootBoiler["wWCirc"] = _bool_to_char(s, EMS_Boiler.wWCirc); + if (EMS_Boiler.heating_temp != EMS_VALUE_INT_NOTSET) + rootBoiler["heating_temp"] = EMS_Boiler.heating_temp; + if (EMS_Boiler.pump_mod_max != EMS_VALUE_INT_NOTSET) + rootBoiler["pump_mod_max"] = EMS_Boiler.pump_mod_max; + if (EMS_Boiler.pump_mod_min != EMS_VALUE_INT_NOTSET) + rootBoiler["pump_mod_min"] = EMS_Boiler.pump_mod_min; + if (EMS_Boiler.wWHeat != EMS_VALUE_INT_NOTSET) rootBoiler["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat); + // **** also add burnStarts, burnWorkMin, heatWorkMin + if (abs(EMS_Boiler.wWStarts) != EMS_VALUE_LONG_NOTSET) + rootBoiler["wWStarts"] = (double)EMS_Boiler.wWStarts; + if (abs(EMS_Boiler.wWWorkM) != EMS_VALUE_LONG_NOTSET) + rootBoiler["wWWorkM"] = (double)EMS_Boiler.wWWorkM; + if (abs(EMS_Boiler.UBAuptime) != EMS_VALUE_LONG_NOTSET) + rootBoiler["UBAuptime"] = (double)EMS_Boiler.UBAuptime; + // **** also add burnStarts, burnWorkMin, heatWorkMin if (abs(EMS_Boiler.burnStarts) != EMS_VALUE_LONG_NOTSET) rootBoiler["burnStarts"] = (double)EMS_Boiler.burnStarts; @@ -942,8 +965,8 @@ void publishValues(bool force) { crc.update(data[i]); } fchecksum = crc.finalize(); - if ((previousThermostatPublishCRC != fchecksum) || force) { - previousThermostatPublishCRC = fchecksum; + if ((previousThermostatPublishCRC[hc_v - 1] != fchecksum) || force) { + previousThermostatPublishCRC[hc_v - 1] = fchecksum; char thermostat_topicname[20]; char buffer[4]; // "thermostat_data" + Heating Cicruit # @@ -957,6 +980,53 @@ void publishValues(bool force) { } } + // handle the thermostat values + if (ems_getMixingDeviceEnabled()) { + for (uint8_t hc_v = 1; hc_v <= EMS_THERMOSTAT_MAXHC; hc_v++) { + _EMS_Mixing_HC * mixing = &EMS_Mixing.hc[hc_v - 1]; + + // only send if we have an active Heating Circuit with real data + if (mixing->active) { + // build new json object + doc.clear(); + JsonObject rootMixing = doc.to(); + + if (mixing->flowTemp != EMS_VALUE_SHORT_NOTSET) + rootMixing["flowTemp"] = (double)mixing->flowTemp / 10; + if (mixing->pumpMod != EMS_VALUE_INT_NOTSET) + rootMixing["pumpMod"] = mixing->pumpMod; + if (mixing->valveStatus != EMS_VALUE_INT_NOTSET) + rootMixing["valveStatus"] = mixing->valveStatus; + + data[0] = '\0'; // reset data for next package + serializeJson(doc, data, sizeof(data)); + + // check for empty json + jsonSize = measureJson(doc); + if (jsonSize > 2) { + // calculate new CRC + crc.reset(); + for (uint8_t i = 0; i < (jsonSize - 1); i++) { + crc.update(data[i]); + } + fchecksum = crc.finalize(); + if ((previousMixingPublishCRC[hc_v - 1] != fchecksum) || force) { + previousMixingPublishCRC[hc_v - 1] = fchecksum; + char mixing_topicname[20]; + char buffer[4]; + // "mixingt_data" + Heating Cicruit # + strlcpy(mixing_topicname, TOPIC_MIXING_DATA, sizeof(mixing_topicname)); + strlcat(mixing_topicname, itoa(hc_v, buffer, 10), sizeof(mixing_topicname)); + myDebugLog("Publishing mixing device data via MQTT"); + myESP.mqttPublish(mixing_topicname, data); + } + } + } + } + } + + + // For SM10 and SM100 Solar Modules if (ems_getSolarModuleEnabled()) { // build new json object diff --git a/src/my_config.h b/src/my_config.h index 7404377f9..0262c93ba 100644 --- a/src/my_config.h +++ b/src/my_config.h @@ -40,6 +40,9 @@ #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 +// 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 #define SM_COLLECTORTEMP "collectortemp" // collector temp From e2dec6654a643e8962cf872685dbd6350c41c426 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 12 Oct 2019 13:10:30 +0200 Subject: [PATCH 36/76] moved helper utils to seperate file --- src/ems_utils.cpp | 262 ++++++++++++++++++++++++++++++++++++++++++++++ src/ems_utils.h | 29 +++++ 2 files changed, 291 insertions(+) create mode 100644 src/ems_utils.cpp create mode 100644 src/ems_utils.h diff --git a/src/ems_utils.cpp b/src/ems_utils.cpp new file mode 100644 index 000000000..fedae1984 --- /dev/null +++ b/src/ems_utils.cpp @@ -0,0 +1,262 @@ +/* + * 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; +} diff --git a/src/ems_utils.h b/src/ems_utils.h new file mode 100644 index 000000000..e08856038 --- /dev/null +++ b/src/ems_utils.h @@ -0,0 +1,29 @@ +/* + * 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); From 165c40cec5376b77a97b08c588cf8885535bc41a Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 12 Oct 2019 13:11:14 +0200 Subject: [PATCH 37/76] MQTT error handling on Publish and Subscribes --- src/MyESP.cpp | 22 ++++++++++++++-------- src/MyESP.h | 16 ++++++++++------ 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/MyESP.cpp b/src/MyESP.cpp index be8740703..7a42e9771 100644 --- a/src/MyESP.cpp +++ b/src/MyESP.cpp @@ -365,7 +365,8 @@ 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); @@ -375,8 +376,10 @@ void MyESP::mqttSubscribe(const char * topic) { 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; } } } @@ -390,15 +393,18 @@ void MyESP::mqttUnsubscribe(const char * topic) { } // MQTT Publish -void MyESP::mqttPublish(const char * topic, const char * payload) { +// returns true if all good +bool MyESP::mqttPublish(const char * topic, const char * payload) { 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, _mqtt_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; } } } @@ -827,10 +833,6 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) } } - if (restart) { - myDebug_P(PSTR("Please 'restart' to apply new settings.")); - } - bool ok = false; // if we were able to recognize the set command, continue @@ -854,6 +856,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; } @@ -1800,7 +1806,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; } } @@ -1835,7 +1841,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; } diff --git a/src/MyESP.h b/src/MyESP.h index 86a1bc12c..4f90de789 100644 --- a/src/MyESP.h +++ b/src/MyESP.h @@ -9,7 +9,7 @@ #ifndef MyESP_h #define MyESP_h -#define MYESP_VERSION "1.2.9" +#define MYESP_VERSION "1.2.10" #include #include @@ -86,15 +86,19 @@ extern struct rst_info resetInfo; #define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT topic #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 60 // 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,9 @@ 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); void setMQTT(mqtt_callback_f callback); // OTA From be593d7654728ad2187bfad84a3433e938d7d575 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 12 Oct 2019 13:11:26 +0200 Subject: [PATCH 38/76] bump --- src/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h b/src/version.h index dc06860db..fed8d4cf9 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define APP_VERSION "1.9.2b4" +#define APP_VERSION "1.9.2b6" From d3009495c761a13508d8d3f93f35383fdf7f73b4 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 12 Oct 2019 13:12:00 +0200 Subject: [PATCH 39/76] changes to MQTT topics --- CHANGELOG.md | 11 +- doc/home assistant/climate.yaml | 70 +-- doc/home assistant/script.yaml | 7 +- doc/home assistant/switch.yaml | 30 +- doc/home assistant/ui-lovelace.yaml | 2 +- src/ems-esp.cpp | 652 +++++++++++----------------- src/ems.cpp | 76 ++-- src/ems.h | 11 +- src/my_config.h | 58 +-- 9 files changed, 377 insertions(+), 540 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae009d136..af4f3f461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.9.2 beta] #### Important! This build has breaking changes: - - the Thermostat MQTT topics are all always suffixed with the heat controller number, e.g. `thermostat_data1` - - the web builder has been upgraded to use Gulp 4. Remove `tools/webfilesbuilder/node_modules` and re-install the libraries using `npm i` from within the `tools/webfilesbuilder` folder. + - MQTT topics have changed. Use `mqttlog` to see the names of the subscriptions and the format of the payload data + - 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 MM100 Status Messages (thanks @kstaniek) - Retrieve/display mode for Junkers FW100/120 thermostats (thanks @Neonox31) -- Added error fall-back for MQTT publishes that fail +- Added sending of Mixer Module data via MQTT (thanks @peclik) +- Reporting of MQTT publish and subscribe errors ### Fixed @@ -27,11 +28,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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 -- Removed the heatingcircuit key/value from the MQTT Thermostat topic payload ## [1.9.1] 2019-10-05 @@ -40,7 +41,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 diff --git a/doc/home assistant/climate.yaml b/doc/home assistant/climate.yaml index d2e70f472..80e2c0783 100644 --- a/doc/home assistant/climate.yaml +++ b/doc/home assistant/climate.yaml @@ -1,37 +1,39 @@ - - platform: mqtt - name: Thermostat - modes: - - "auto" - - "heat" - - "off" +- 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" - mode_state_topic: "home/ems-esp/thermostat_data1" - current_temperature_topic: "home/ems-esp/thermostat_data1" - temperature_state_topic: "home/ems-esp/thermostat_data1" + 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 + name: boiler + modes: + - "auto" + - "off" + 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" + mode_state_topic: "home/ems-esp/boiler_data" - temperature_command_topic: "home/ems-esp/thermostat_cmd_temp1" - mode_command_topic: "home/ems-esp/thermostat_cmd_mode1" + 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_template: "{{ value_json.thermostat_mode }}" - current_temperature_template: "{{ value_json.thermostat_currtemp }}" - temperature_state_template: "{{ value_json.thermostat_seltemp }}" - - temp_step: 0.5 - - - platform: mqtt - name: boiler - modes: - - "auto" - - "off" - 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" - 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" - \ No newline at end of file + temperature_command_topic: "home/ems-esp/boiler_cmd_wwtemp" + mode_command_topic: "home/ems-esp/boiler_cmd_wwactivated" + \ No newline at end of file diff --git a/doc/home assistant/script.yaml b/doc/home assistant/script.yaml index 860d6a2c9..49b33a6d2 100644 --- a/doc/home assistant/script.yaml +++ b/doc/home assistant/script.yaml @@ -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"}' + \ No newline at end of file diff --git a/doc/home assistant/switch.yaml b/doc/home assistant/switch.yaml index 0649e3c97..0ebeba4f9 100644 --- a/doc/home assistant/switch.yaml +++ b/doc/home assistant/switch.yaml @@ -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" diff --git a/doc/home assistant/ui-lovelace.yaml b/doc/home assistant/ui-lovelace.yaml index 59f96d36e..d3a8e88d1 100644 --- a/doc/home assistant/ui-lovelace.yaml +++ b/doc/home assistant/ui-lovelace.yaml @@ -56,7 +56,7 @@ views: - sensor.current_room_temperature - sensor.dark_sky_temperature - type: thermostat - entity: climate.thermostat + entity: climate.thermostat_hc1 - type: thermostat name: WarmWater entity: climate.boiler diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 02eb627f0..c07c11581 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -11,6 +11,7 @@ #include "MyESP.h" #include "ems.h" #include "ems_devices.h" +#include "ems_utils.h" #include "emsuart.h" #include "my_config.h" #include "version.h" @@ -32,10 +33,6 @@ DS18 ds18; #define APP_URL "https://github.com/proddy/EMS-ESP" #define APP_UPDATEURL "https://api.github.com/repos/proddy/EMS-ESP/releases/latest" -// macros for easy debugging -#define myDebug(...) myESP.myDebug(__VA_ARGS__) -#define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__) - // set to value >0 if the ESP is overheating or there are timing issues. Recommend a value of 1. #define EMSESP_DELAY 0 // initially set to 0 for no delay. Change to 1 if getting WDT resets from wifi @@ -160,231 +157,6 @@ void myDebugLog(const char * s) { } } -// convert float to char -char * _float_to_char(char * a, float f, uint8_t precision = 2) { - 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 -// 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 = 1) { - // 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 -// 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 = 1) { - // 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 -// 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 = 1) { - 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 -// 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 = 1) { - 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 -char * _int_to_char(char * s, uint8_t value, uint8_t div = 1) { - 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 -void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, uint8_t div = 1) { - 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 -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 -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); -} - // figures out the thermostat mode // returns 0xFF=unknown, 0=low, 1=manual, 2=auto, 3=night, 4=day // hc_num is 1 to 4 @@ -657,9 +429,9 @@ void showInfo() { } // Render Termostat Mode, if we have a mode - uint8_t thermoMode = _getThermostatMode(hc_num); // 0xFF=unknown, 0=low, 1=manual, 2=auto, 3=night, 4=day + uint8_t thermoMode = _getThermostatMode(hc_num); // 0xFF=unknown, 0=off, 1=manual, 2=auto, 3=night, 4=day if (thermoMode == 0) { - myDebug_P(PSTR(" Mode is set to low")); + myDebug_P(PSTR(" Mode is set to off")); } else if (thermoMode == 1) { myDebug_P(PSTR(" Mode is set to manual")); } else if (thermoMode == 2) { @@ -764,11 +536,11 @@ void publishValues(bool force) { uint32_t fchecksum; uint8_t jsonSize; - static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off - static uint32_t previousBoilerPublishCRC = 0; // CRC check for boiler values + static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off + static uint32_t previousBoilerPublishCRC = 0; // CRC check for boiler values static uint32_t previousThermostatPublishCRC[EMS_THERMOSTAT_MAXHC]; // CRC check for thermostat values static uint32_t previousMixingPublishCRC[EMS_THERMOSTAT_MAXHC]; // CRC check for mixing values - static uint32_t previousSMPublishCRC = 0; // CRC check for Solar Module values (e.g. SM10) + static uint32_t previousSMPublishCRC = 0; // CRC check for Solar Module values (e.g. SM10) JsonObject rootBoiler = doc.to(); @@ -904,53 +676,57 @@ void publishValues(bool force) { doc.clear(); JsonObject rootThermostat = doc.to(); - // rootThermostat[THERMOSTAT_HC] = _int_to_char(s, thermostat->hc); // heating circuit 1..4 + // hc{1-4} + char hc[10]; + strncpy(hc, THERMOSTAT_HC, sizeof(hc)); + strncat(hc, _int_to_char(s, thermostat->hc), sizeof(hc)); + JsonObject dataThermostat = rootThermostat.createNestedObject(hc); // different logic depending on thermostat types if (ems_getThermostatModel() == EMS_MODEL_EASY) { if (thermostat->setpoint_roomTemp != EMS_VALUE_SHORT_NOTSET) - rootThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 100; + dataThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 100; if (thermostat->curr_roomTemp != EMS_VALUE_SHORT_NOTSET) - rootThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 100; + dataThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 100; } else if ((ems_getThermostatModel() == EMS_MODEL_FR10) || (ems_getThermostatModel() == EMS_MODEL_FW100) || (ems_getThermostatModel() == EMS_MODEL_FW120)) { if (thermostat->setpoint_roomTemp != EMS_VALUE_SHORT_NOTSET) - rootThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 10; + dataThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 10; if (thermostat->curr_roomTemp != EMS_VALUE_SHORT_NOTSET) - rootThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 10; + dataThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 10; } else { if (thermostat->setpoint_roomTemp != EMS_VALUE_SHORT_NOTSET) - rootThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 2; + dataThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 2; if (thermostat->curr_roomTemp != EMS_VALUE_SHORT_NOTSET) - rootThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 10; + dataThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 10; if (thermostat->daytemp != EMS_VALUE_INT_NOTSET) - rootThermostat[THERMOSTAT_DAYTEMP] = (double)thermostat->daytemp / 2; + dataThermostat[THERMOSTAT_DAYTEMP] = (double)thermostat->daytemp / 2; if (thermostat->nighttemp != EMS_VALUE_INT_NOTSET) - rootThermostat[THERMOSTAT_NIGHTTEMP] = (double)thermostat->nighttemp / 2; + dataThermostat[THERMOSTAT_NIGHTTEMP] = (double)thermostat->nighttemp / 2; if (thermostat->holidaytemp != EMS_VALUE_INT_NOTSET) - rootThermostat[THERMOSTAT_HOLIDAYTEMP] = (double)thermostat->holidaytemp / 2; + dataThermostat[THERMOSTAT_HOLIDAYTEMP] = (double)thermostat->holidaytemp / 2; if (thermostat->heatingtype != EMS_VALUE_INT_NOTSET) - rootThermostat[THERMOSTAT_HEATINGTYPE] = thermostat->heatingtype; + dataThermostat[THERMOSTAT_HEATINGTYPE] = thermostat->heatingtype; if (thermostat->circuitcalctemp != EMS_VALUE_INT_NOTSET) - rootThermostat[THERMOSTAT_CIRCUITCALCTEMP] = thermostat->circuitcalctemp; + dataThermostat[THERMOSTAT_CIRCUITCALCTEMP] = thermostat->circuitcalctemp; } uint8_t thermoMode = _getThermostatMode(hc_v); // 0xFF=unknown, 0=low, 1=manual, 2=auto, 3=night, 4=day // Termostat Mode if (thermoMode == 0) { - rootThermostat[THERMOSTAT_MODE] = "low"; + dataThermostat[THERMOSTAT_MODE] = "off"; } else if (thermoMode == 1) { - rootThermostat[THERMOSTAT_MODE] = "manual"; + dataThermostat[THERMOSTAT_MODE] = "heat"; } else if (thermoMode == 2) { - rootThermostat[THERMOSTAT_MODE] = "auto"; + dataThermostat[THERMOSTAT_MODE] = "auto"; } else if (thermoMode == 3) { - rootThermostat[THERMOSTAT_MODE] = "night"; + dataThermostat[THERMOSTAT_MODE] = "night"; } else if (thermoMode == 4) { - rootThermostat[THERMOSTAT_MODE] = "day"; + dataThermostat[THERMOSTAT_MODE] = "day"; } data[0] = '\0'; // reset data for next package @@ -967,13 +743,8 @@ void publishValues(bool force) { fchecksum = crc.finalize(); if ((previousThermostatPublishCRC[hc_v - 1] != fchecksum) || force) { previousThermostatPublishCRC[hc_v - 1] = fchecksum; - char thermostat_topicname[20]; - char buffer[4]; - // "thermostat_data" + Heating Cicruit # - strlcpy(thermostat_topicname, TOPIC_THERMOSTAT_DATA, sizeof(thermostat_topicname)); - strlcat(thermostat_topicname, itoa(hc_v, buffer, 10), sizeof(thermostat_topicname)); myDebugLog("Publishing thermostat data via MQTT"); - myESP.mqttPublish(thermostat_topicname, data); + myESP.mqttPublish(TOPIC_THERMOSTAT_DATA, data); } } } @@ -1103,20 +874,6 @@ void publishValues(bool force) { } } -// sets the shower timer on/off -void set_showerTimer() { - if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { - myDebug_P(PSTR("Shower timer has been set to %s"), EMSESP_Settings.shower_timer ? "enabled" : "disabled"); - } -} - -// sets the shower alert on/off -void set_showerAlert() { - if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { - myDebug_P(PSTR("Shower alert has been set to %s"), EMSESP_Settings.shower_alert ? "enabled" : "disabled"); - } -} - // 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"); @@ -1332,6 +1089,33 @@ bool LoadSaveCallback(MYESP_FSACTION action, JsonObject settings) { return false; } +// Publish shower data +bool do_publishShowerData() { + StaticJsonDocument<200> doc; + JsonObject rootShower = doc.to(); + rootShower[TOPIC_SHOWER_TIMER] = EMSESP_Settings.shower_timer ? "1" : "0"; + rootShower[TOPIC_SHOWER_ALERT] = EMSESP_Settings.shower_alert ? "1" : "0"; + + char s[50] = {0}; + if (EMSESP_Shower.duration > SHOWER_MIN_DURATION) { + char buffer[16] = {0}; + strlcpy(s, itoa((uint8_t)((EMSESP_Shower.duration / (1000 * 60)) % 60), buffer, 10), sizeof(s)); + strlcat(s, " minutes and ", sizeof(s)); + strlcat(s, itoa((uint8_t)((EMSESP_Shower.duration / 1000) % 60), buffer, 10), sizeof(s)); + strlcat(s, " seconds", sizeof(s)); + } else { + strlcpy(s, "n/a", sizeof(s)); + } + rootShower[TOPIC_SHOWER_DURATION] = s; + + char data[300] = {0}; + serializeJson(doc, data, sizeof(data)); + + myDebugLog("Publishing shower data via MQTT"); + + return (myESP.mqttPublish(TOPIC_SHOWER_DATA, data)); +} + // callback for custom settings when showing Stored Settings with the 'set' command // wc is number of arguments after the 'set' command // returns true if the setting was recognized and changed and should be saved back to SPIFFs @@ -1403,12 +1187,10 @@ bool SetListCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, co if ((strcmp(setting, "shower_timer") == 0) && (wc == 2)) { if (strcmp(value, "on") == 0) { EMSESP_Settings.shower_timer = true; - myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0"); - ok = true; + ok = do_publishShowerData(); } else if (strcmp(value, "off") == 0) { EMSESP_Settings.shower_timer = false; - myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0"); - ok = true; + ok = do_publishShowerData(); } else { myDebug_P(PSTR("Error. Usage: set shower_timer ")); } @@ -1418,12 +1200,10 @@ bool SetListCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, co if ((strcmp(setting, "shower_alert") == 0) && (wc == 2)) { if (strcmp(value, "on") == 0) { EMSESP_Settings.shower_alert = true; - myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0"); - ok = true; + ok = do_publishShowerData(); } else if (strcmp(value, "off") == 0) { EMSESP_Settings.shower_alert = false; - myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0"); - ok = true; + ok = do_publishShowerData(); } else { myDebug_P(PSTR("Error. Usage: set shower_alert ")); } @@ -1524,6 +1304,7 @@ void TelnetCommandCallback(uint8_t wc, const char * commandLine) { if (strcmp(first_cmd, "publish") == 0) { do_publishValues(); do_publishSensorValues(); + do_publishShowerData(); ok = true; } @@ -1690,11 +1471,11 @@ void OTACallback_post() { // used to identify a heating circuit // returns HC number 1 - 4 // or the default (1) is no suffix can be found -uint8_t _hasHCspecified(const char * topic, const char * input) { - int orig_len = strlen(topic); // original length of the topic we're comparing too +uint8_t _hasHCspecified(const char * key, const char * input) { + int orig_len = strlen(key); // original length of the topic we're comparing too // check if the strings match ignoring any suffix - if (strncmp(input, topic, orig_len) == 0) { + if (strncmp(input, key, orig_len) == 0) { // see if we have additional chars at the end, we want none or 1 uint8_t diff = (strlen(input) - orig_len); if (diff > 1) { @@ -1716,11 +1497,7 @@ uint8_t _hasHCspecified(const char * topic, const char * input) { void MQTTCallback(unsigned int type, const char * topic, const char * message) { // we're connected. lets subscribe to some topics if (type == MQTT_CONNECT_EVENT) { - myESP.mqttSubscribe(TOPIC_SHOWER_TIMER); - myESP.mqttSubscribe(TOPIC_SHOWER_ALERT); - myESP.mqttSubscribe(TOPIC_SHOWER_COLDSHOT); - - // subscribe to the 4 heating circuits + // subscribe to the 4 heating circuits for receiving setpoint temperature and modes char topic_s[50]; char buffer[4]; for (uint8_t hc = 1; hc <= EMS_THERMOSTAT_MAXHC; hc++) { @@ -1731,158 +1508,229 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_MODE, sizeof(topic_s)); strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); myESP.mqttSubscribe(topic_s); - - strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_DAYTEMP, sizeof(topic_s)); - strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); - myESP.mqttSubscribe(topic_s); - - strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_NIGHTTEMP, sizeof(topic_s)); - strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); - myESP.mqttSubscribe(topic_s); - - strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP, sizeof(topic_s)); - strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s)); - myESP.mqttSubscribe(topic_s); } + // generic incoming MQTT command for Thermostat + // this is used for example for setting daytemp, nighttemp, holidaytemp + myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD); + + // generic incoming MQTT command for Boiler + // this is used for example for comfort, flowtemp + myESP.mqttSubscribe(TOPIC_BOILER_CMD); + + // these two need to be unqiue topics myESP.mqttSubscribe(TOPIC_BOILER_CMD_WWACTIVATED); myESP.mqttSubscribe(TOPIC_BOILER_CMD_WWTEMP); - myESP.mqttSubscribe(TOPIC_BOILER_CMD_COMFORT); - myESP.mqttSubscribe(TOPIC_BOILER_CMD_FLOWTEMP); - // publish the status of the Shower parameters - // myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0"); - // myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0"); + // generic incoming MQTT command for EMS-ESP + // this is used for example for shower_coldshot + myESP.mqttSubscribe(TOPIC_GENERIC_CMD); + + // shower data + // for receiving shower_Timer and shower_alert switches + myESP.mqttSubscribe(TOPIC_SHOWER_DATA); + + return; } // handle incoming MQTT publish events - if (type == MQTT_MESSAGE_EVENT) { - uint8_t hc; - // thermostat temp changes - hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_TEMP, topic); - if (hc) { - float f = strtof((char *)message, 0); - char s[10] = {0}; - myDebug_P(PSTR("MQTT topic: thermostat HC%d temperature value %s"), hc, _float_to_char(s, f)); - ems_setThermostatTemp(f, hc); - publishValues(true); // publish back immediately + if (type != MQTT_MESSAGE_EVENT) { + return; + } + + // check first for generic commands + if (strcmp(topic, TOPIC_GENERIC_CMD) == 0) { + // convert JSON and get the command + StaticJsonDocument<100> doc; + JsonObject root = doc.to(); // create empty object + DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document + if (error) { + myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str()); + return; + } + const char * command = doc["cmd"]; + + // Check whatever the command is and act accordingly + if (strcmp(command, TOPIC_SHOWER_COLDSHOT) == 0) { + _showerColdShotStart(); return; } - // thermostat mode changes - hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_MODE, topic); - if (hc) { - myDebug_P(PSTR("MQTT topic: thermostat HC%d mode value %s"), hc, message); - if (strcmp((char *)message, "auto") == 0) { - ems_setThermostatMode(2, hc); - } else if (strcmp((char *)message, "day") == 0 || (strcmp((char *)message, "manual") == 0) || (strcmp((char *)message, "heat") == 0)) { - ems_setThermostatMode(1, hc); - } else if (strcmp((char *)message, "night") == 0 || strcmp((char *)message, "off") == 0) { - ems_setThermostatMode(0, hc); + return; // no match for generic commands + } + + // check for shower commands + if (strcmp(topic, TOPIC_SHOWER_DATA) == 0) { + StaticJsonDocument<100> doc; + JsonObject root = doc.to(); // create empty object + DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document + if (error) { + myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str()); + return; + } + + // assumes payload is "1" or "0" + const char * shower_alert = doc[TOPIC_SHOWER_ALERT]; + if (shower_alert) { + if ((shower_alert[0] - MYESP_MQTT_PAYLOAD_OFF) != EMSESP_Settings.shower_alert) { + EMSESP_Settings.shower_alert = shower_alert[0] - MYESP_MQTT_PAYLOAD_OFF; + myDebug_P(PSTR("Shower alert has been set to %s"), EMSESP_Settings.shower_alert ? "enabled" : "disabled"); } - return; } - // set night temp value - hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_NIGHTTEMP, topic); - if (hc) { - float f = strtof((char *)message, 0); - char s[10] = {0}; - myDebug_P(PSTR("MQTT topic: new thermostat HC%d night temperature value %s"), hc, _float_to_char(s, f)); - ems_setThermostatTemp(f, hc, 1); // night - return; - } - - // set daytemp value - hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_DAYTEMP, topic); - if (hc) { - float f = strtof((char *)message, 0); - char s[10] = {0}; - myDebug_P(PSTR("MQTT topic: new thermostat HC%d day temperature value %s"), hc, _float_to_char(s, f)); - ems_setThermostatTemp(f, hc, 2); // day - return; - } - - // set holiday value - hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP, topic); - if (hc) { - float f = strtof((char *)message, 0); - char s[10] = {0}; - myDebug_P(PSTR("MQTT topic: new thermostat HC%d holiday temperature value %s"), hc, _float_to_char(s, f)); - ems_setThermostatTemp(f, hc, 3); // holiday - return; - } - - // wwActivated - if (strcmp(topic, TOPIC_BOILER_CMD_WWACTIVATED) == 0) { - if ((message[0] == '1' || strcmp(message, "on") == 0) || (strcmp(message, "auto") == 0)) { - ems_setWarmWaterActivated(true); - } else if (message[0] == '0' || strcmp(message, "off") == 0) { - ems_setWarmWaterActivated(false); + // assumes payload is "1" or "0" + const char * shower_timer = doc[TOPIC_SHOWER_TIMER]; + if (shower_timer) { + if ((shower_timer[0] - MYESP_MQTT_PAYLOAD_OFF) != EMSESP_Settings.shower_timer) { + EMSESP_Settings.shower_timer = shower_timer[0] - MYESP_MQTT_PAYLOAD_OFF; + myDebug_P(PSTR("Shower timer has been set to %s"), EMSESP_Settings.shower_timer ? "enabled" : "disabled"); } - return; } - // boiler wwtemp changes - if (strcmp(topic, TOPIC_BOILER_CMD_WWTEMP) == 0) { - uint8_t t = atoi((char *)message); - myDebug_P(PSTR("MQTT topic: boiler warm water temperature value %d"), t); - ems_setWarmWaterTemp(t); - publishValues(true); // publish back immediately, can't remember why I do this?! + return; + } + + // check for boiler commands + if (strcmp(topic, TOPIC_BOILER_CMD) == 0) { + // convert JSON and get the command + StaticJsonDocument<100> doc; + JsonObject root = doc.to(); // create empty object + DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document + if (error) { + myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str()); return; } + const char * command = doc["cmd"]; // boiler ww comfort setting - if (strcmp(topic, TOPIC_BOILER_CMD_COMFORT) == 0) { - myDebug_P(PSTR("MQTT topic: boiler warm water comfort value is %s"), message); - if (strcmp((char *)message, "hot") == 0) { + if (strcmp(command, TOPIC_BOILER_CMD_COMFORT) == 0) { + const char * data = doc["data"]; + if (strcmp((char *)data, "hot") == 0) { ems_setWarmWaterModeComfort(1); - } else if (strcmp((char *)message, "comfort") == 0) { + } else if (strcmp((char *)data, "comfort") == 0) { ems_setWarmWaterModeComfort(2); - } else if (strcmp((char *)message, "intelligent") == 0) { + } else if (strcmp((char *)data, "intelligent") == 0) { ems_setWarmWaterModeComfort(3); } return; } // boiler flowtemp setting - if (strcmp(topic, TOPIC_BOILER_CMD_FLOWTEMP) == 0) { - uint8_t t = atoi((char *)message); - myDebug_P(PSTR("MQTT topic: boiler flowtemp value %d"), t); + if (strcmp(command, TOPIC_BOILER_CMD_FLOWTEMP) == 0) { + uint8_t t = doc["data"]; ems_setFlowTemp(t); return; } - // shower timer - if (strcmp(topic, TOPIC_SHOWER_TIMER) == 0) { - if (message[0] == '1') { - EMSESP_Settings.shower_timer = true; - } else if (message[0] == '0') { - EMSESP_Settings.shower_timer = false; - } - set_showerTimer(); + return; // unknown boiler command + } + + // check for unique boiler commands + + // wwActivated + if (strcmp(topic, TOPIC_BOILER_CMD_WWACTIVATED) == 0) { + if ((message[0] == MYESP_MQTT_PAYLOAD_ON || strcmp(message, "on") == 0) || (strcmp(message, "auto") == 0)) { + ems_setWarmWaterActivated(true); + } else if (message[0] == MYESP_MQTT_PAYLOAD_OFF || strcmp(message, "off") == 0) { + ems_setWarmWaterActivated(false); + } + return; + } + + // boiler wwtemp changes + if (strcmp(topic, TOPIC_BOILER_CMD_WWTEMP) == 0) { + uint8_t t = atoi((char *)message); + ems_setWarmWaterTemp(t); + publishValues(true); + return; + } + + uint8_t hc; + // thermostat temp changes + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_TEMP, topic); + if (hc) { + float f = strtof((char *)message, 0); + ems_setThermostatTemp(f, hc); + publishValues(true); // publish back immediately + return; + } + + // thermostat mode changes + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_MODE, topic); + if (hc) { + if (strncmp(message, "auto", 4) == 0) { + ems_setThermostatMode(2, hc); + } else if ((strncmp(message, "day", 4) == 0) || (strncmp(message, "manual", 6) == 0) || (strncmp(message, "heat", 4) == 0)) { + ems_setThermostatMode(1, hc); + } else if ((strncmp(message, "night", 5) == 0) || (strncmp(message, "off", 3) == 0)) { + ems_setThermostatMode(0, hc); + } + return; + } + + // check for generic thermostat commands + if (strcmp(topic, TOPIC_THERMOSTAT_CMD) == 0) { + // convert JSON and get the command + StaticJsonDocument<100> doc; + JsonObject root = doc.to(); // create empty object + DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document + if (error) { + myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str()); + return; + } + const char * command = doc["cmd"]; + + uint8_t hc; + // thermostat temp changes + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_TEMP, command); + if (hc) { + float f = doc["data"]; + ems_setThermostatTemp(f, hc); + publishValues(true); // publish back immediately return; } - // shower alert - if (strcmp(topic, TOPIC_SHOWER_ALERT) == 0) { - if (message[0] == '1') { - EMSESP_Settings.shower_alert = true; - } else if (message[0] == '0') { - EMSESP_Settings.shower_alert = false; + // thermostat mode changes + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_MODE, command); + if (hc) { + const char * data = doc["data"]; + if (strcmp(data, "auto") == 0) { + ems_setThermostatMode(2, hc); + } else if ((strcmp(data, "day") == 0) || (strcmp(data, "manual") == 0) || (strcmp(data, "heat") == 0)) { + ems_setThermostatMode(1, hc); + } else if ((strcmp(data, "night") == 0) || (strcmp(data, "off") == 0)) { + ems_setThermostatMode(0, hc); } - set_showerAlert(); return; } - // shower cold shot - if (strcmp(topic, TOPIC_SHOWER_COLDSHOT) == 0) { - _showerColdShotStart(); + // set night temp value + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_NIGHTTEMP, command); + if (hc) { + float f = doc["data"]; + ems_setThermostatTemp(f, hc, 1); // night + return; + } + + // set daytemp value + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_DAYTEMP, command); + if (hc) { + float f = doc["data"]; + ems_setThermostatTemp(f, hc, 2); // day + return; + } + + // set holiday value + hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP, command); + if (hc) { + float f = doc["data"]; + ems_setThermostatTemp(f, hc, 3); // holiday return; } } } + // Init callback, which is used to set functions and call methods after a wifi connection has been established void WIFICallback() { // This is where we enable the UART service to scan the incoming serial Tx/Rx bus signals @@ -1967,11 +1815,11 @@ void WebCallback(JsonObject root) { } // Render Termostat Mode, if we have a mode - uint8_t thermoMode = _getThermostatMode(hc_num); // 0xFF=unknown, 0=low, 1=manual, 2=auto, 3=night, 4=day + uint8_t thermoMode = _getThermostatMode(hc_num); // 0xFF=unknown, 0=off, 1=manual, 2=auto, 3=night, 4=day if (thermoMode == 0) { - thermostat["tmode"] = "low"; + thermostat["tmode"] = "off"; } else if (thermoMode == 1) { - thermostat["tmode"] = "manual"; + thermostat["tmode"] = "heat"; } else if (thermoMode == 2) { thermostat["tmode"] = "auto"; } else if (thermoMode == 3) { @@ -2131,16 +1979,10 @@ void showerCheck() { if ((EMSESP_Shower.timerPause - EMSESP_Shower.timerStart) > SHOWER_OFFSET_TIME) { EMSESP_Shower.duration = (EMSESP_Shower.timerPause - EMSESP_Shower.timerStart - SHOWER_OFFSET_TIME); if (EMSESP_Shower.duration > SHOWER_MIN_DURATION) { - char s[50] = {0}; - char buffer[16] = {0}; - strlcpy(s, itoa((uint8_t)((EMSESP_Shower.duration / (1000 * 60)) % 60), buffer, 10), sizeof(s)); - strlcat(s, " minutes and ", sizeof(s)); - strlcat(s, itoa((uint8_t)((EMSESP_Shower.duration / 1000) % 60), buffer, 10), sizeof(s)); - strlcat(s, " seconds", sizeof(s)); if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { - myDebug_P(PSTR("[Shower] finished with duration %s"), s); + myDebug_P(PSTR("[Shower] finished with duration %d"), EMSESP_Shower.duration); } - myESP.mqttPublish(TOPIC_SHOWERTIME, s); // publish to MQTT + do_publishShowerData(); // publish to MQTT } } diff --git a/src/ems.cpp b/src/ems.cpp index b0a1bc61a..b639fd965 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -7,6 +7,7 @@ */ #include "ems.h" +#include "ems_utils.h" #include "MyESP.h" #include "ems_devices.h" #include "emsuart.h" @@ -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 @@ -231,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 @@ -496,36 +492,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 @@ -630,11 +596,11 @@ void _ems_sendTelegram() { EMS_TxTelegram.data[EMS_TxTelegram.length - 1] = _crcCalculator(EMS_TxTelegram.data, EMS_TxTelegram.length); // add the CRC if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_NONE) { - _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 + _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); } @@ -1160,7 +1126,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) { @@ -2820,7 +2786,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; @@ -2922,17 +2892,31 @@ void ems_setThermostatMode(uint8_t mode, uint8_t hc_num) { 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 @@ -2941,7 +2925,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) { diff --git a/src/ems.h b/src/ems.h index bb4659851..17de5c35d 100644 --- a/src/ems.h +++ b/src/ems.h @@ -87,10 +87,10 @@ #define EMS_ID_GATEWAY 0x48 // KM200 Web Gateway // Product IDs -#define EMS_PRODUCTID_SM10 73 // SM10 solar module -#define EMS_PRODUCTID_SM50 162 // SM50 solar module -#define EMS_PRODUCTID_SM100 163 // SM100 solar module -#define EMS_PRODUCTID_ISM1 101 // Junkers ISM1 solar module +#define EMS_PRODUCTID_SM10 73 // SM10 solar module +#define EMS_PRODUCTID_SM50 162 // SM50 solar module +#define EMS_PRODUCTID_SM100 163 // SM100 solar module +#define EMS_PRODUCTID_ISM1 101 // Junkers ISM1 solar module #define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC #define EMS_MAX_TELEGRAM_LENGTH 32 // max length of a telegram, including CRC, for Rx and Tx. @@ -111,7 +111,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 @@ -503,7 +503,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); diff --git a/src/my_config.h b/src/my_config.h index 0262c93ba..c87396483 100644 --- a/src/my_config.h +++ b/src/my_config.h @@ -12,36 +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_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_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 "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_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 +#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 @@ -60,10 +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 #define TOPIC_EXTERNAL_SENSORS "sensors" // for sending sensor values to MQTT From ef611154f2d08d41cad94d33c282b48757a54413 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 13 Oct 2019 12:29:39 +0200 Subject: [PATCH 40/76] updated to show MQTT changes --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af4f3f461..5e87d73b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.9.2 beta] #### Important! This build has breaking changes: - - MQTT topics have changed. Use `mqttlog` to see the names of the subscriptions and the format of the payload data + - 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). - 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 From 633aec9fcd679bd197e87e4e584397e5046c0368 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 13 Oct 2019 12:32:46 +0200 Subject: [PATCH 41/76] adding package-lock.json files --- .gitignore | 3 - tools/webfilesbuilder/package-lock.json | 3810 +++++++++++++++++++++++ tools/wsemulator/package-lock.json | 27 + 3 files changed, 3837 insertions(+), 3 deletions(-) create mode 100644 tools/webfilesbuilder/package-lock.json create mode 100644 tools/wsemulator/package-lock.json diff --git a/.gitignore b/.gitignore index a99e551a2..81102fd7d 100644 --- a/.gitignore +++ b/.gitignore @@ -23,9 +23,6 @@ src/websrc/temp node_modules/ jspm_packages/ .npm -package-lock.json -tools/webfilesbuilder/package-lock.json -tools/wsemulator/package-lock.json # Output of 'npm pack' *.tgz diff --git a/tools/webfilesbuilder/package-lock.json b/tools/webfilesbuilder/package-lock.json new file mode 100644 index 000000000..6dd3f662a --- /dev/null +++ b/tools/webfilesbuilder/package-lock.json @@ -0,0 +1,3810 @@ +{ + "name": "webfilesbuilder", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "requires": { + "buffer-equal": "^1.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, + "requires": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dev": true, + "requires": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "requires": { + "async-done": "^1.2.2" + } + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, + "requires": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "bufferstreams": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.1.3.tgz", + "integrity": "sha512-HaJnVuslRF4g2kSDeyl++AaVizoitCpL9PglzCYwy0uHHyvWerfvEb8jWmYbF1z4kiVFolGomnxSGl+GUQp2jg==", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + } + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "requires": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-props": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", + "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "dev": true, + "requires": { + "each-props": "^1.3.0", + "is-plain-object": "^2.0.1" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, + "requires": { + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es5-ext": { + "version": "0.10.51", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz", + "integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "^1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.2.tgz", + "integrity": "sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==", + "dev": true, + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.51" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + } + }, + "flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "requires": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + } + }, + "glob-watcher": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", + "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "object.defaults": "^1.1.0" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "dev": true + }, + "gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "dev": true, + "requires": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "dependencies": { + "gulp-cli": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.2.0.tgz", + "integrity": "sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.1.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.0.1", + "yargs": "^7.1.0" + } + } + } + }, + "gulp-concat": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", + "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", + "dev": true, + "requires": { + "concat-with-sourcemaps": "^1.0.0", + "through2": "^2.0.0", + "vinyl": "^2.0.0" + } + }, + "gulp-flatmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/gulp-flatmap/-/gulp-flatmap-1.0.2.tgz", + "integrity": "sha512-xm+Ax2vPL/xiMBqLFI++wUyPtncm3b55ztGHewmRcoG/sYb0OUTatjSacOud3fee77rnk+jOgnDEHhwBtMHgFA==", + "dev": true, + "requires": { + "plugin-error": "0.1.2", + "through2": "2.0.3" + }, + "dependencies": { + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + } + } + }, + "gulp-gzip": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/gulp-gzip/-/gulp-gzip-1.4.2.tgz", + "integrity": "sha512-ZIxfkUwk2XmZPTT9pPHrHUQlZMyp9nPhg2sfoeN27mBGpi7OaHnOD+WCN41NXjfJQ69lV1nQ9LLm1hYxx4h3UQ==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "bytes": "^3.0.0", + "fancy-log": "^1.3.2", + "plugin-error": "^1.0.0", + "stream-to-array": "^2.3.0", + "through2": "^2.0.3" + }, + "dependencies": { + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + } + } + }, + "gulp-htmlmin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gulp-htmlmin/-/gulp-htmlmin-4.0.0.tgz", + "integrity": "sha512-9FX2d4QbSm+4WuXughjZ6GJn4jx0C4BmyK2e+AS6567NPAetNfB+hv2ZL/88AacdC+8OS+TzeIjfKRXPSAgOYw==", + "dev": true, + "requires": { + "bufferstreams": "^1.1.0", + "html-minifier": "^3.0.3", + "plugin-error": "^0.1.2", + "readable-stream": "^2.0.2", + "tryit": "^1.0.1" + } + }, + "gulp-uglify": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.2.tgz", + "integrity": "sha512-gk1dhB74AkV2kzqPMQBLA3jPoIAPd/nlNzP2XMDSG8XZrqnlCiDGAqC+rZOumzFvB5zOphlFh6yr3lgcAb/OOg==", + "dev": true, + "requires": { + "array-each": "^1.0.1", + "extend-shallow": "^3.0.2", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "isobject": "^3.0.1", + "make-error-cause": "^1.1.1", + "safe-buffer": "^5.1.2", + "through2": "^2.0.0", + "uglify-js": "^3.0.5", + "vinyl-sourcemaps-apply": "^0.2.0" + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "^1.0.0" + } + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", + "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", + "dev": true + }, + "html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "just-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", + "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, + "requires": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + } + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "requires": { + "readable-stream": "^2.0.5" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "requires": { + "flush-write-stream": "^1.0.2" + } + }, + "liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, + "requires": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "make-error-cause": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-1.2.2.tgz", + "integrity": "sha1-3wOI/NCzeBbf8KX7gQiTl3fcvJ0=", + "dev": true, + "requires": { + "make-error": "^1.2.0" + } + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "dev": true, + "requires": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "dependencies": { + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "dev": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, + "requires": { + "once": "^1.3.2" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "requires": { + "path-root-regex": "^0.1.0" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "dependencies": { + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "requires": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "requires": { + "value-or-function": "^3.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "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==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, + "requires": { + "sver-compat": "^1.5.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "stream-to-array": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", + "integrity": "sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M=", + "dev": true, + "requires": { + "any-promise": "^1.1.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, + "requires": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "requires": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "requires": { + "through2": "^2.0.3" + } + }, + "tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "dev": true + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", + "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "dev": true, + "requires": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "undertaker": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", + "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + } + }, + "undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, + "requires": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "v8flags": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", + "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "requires": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "requires": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "requires": { + "source-map": "^0.5.1" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "requires": { + "camelcase": "^3.0.0" + } + } + } +} diff --git a/tools/wsemulator/package-lock.json b/tools/wsemulator/package-lock.json new file mode 100644 index 000000000..bc4f0b396 --- /dev/null +++ b/tools/wsemulator/package-lock.json @@ -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" + } + } + } +} From a97ecb414a02b0714674472dd6b26bde17290290 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 13 Oct 2019 14:34:28 +0200 Subject: [PATCH 42/76] minor code optimizations --- src/MyESP.cpp | 2 +- src/ems-esp.cpp | 49 +++++++++++-------------------------------------- src/ems.cpp | 8 ++++---- src/emsuart.cpp | 4 ++-- 4 files changed, 18 insertions(+), 45 deletions(-) diff --git a/src/MyESP.cpp b/src/MyESP.cpp index 7a42e9771..638447c05 100644 --- a/src/MyESP.cpp +++ b/src/MyESP.cpp @@ -595,7 +595,7 @@ void MyESP::_telnetConnected() { // 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)) { + 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 diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index c07c11581..27770374d 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -462,7 +462,7 @@ void showInfo() { } // Dallas external temp sensors - if (EMSESP_Settings.dallas_sensors != 0) { + if (EMSESP_Settings.dallas_sensors) { myDebug_P(PSTR("")); // newline char buffer[128] = {0}; char valuestr[8] = {0}; // for formatting temp @@ -678,8 +678,8 @@ void publishValues(bool force) { // hc{1-4} char hc[10]; - strncpy(hc, THERMOSTAT_HC, sizeof(hc)); - strncat(hc, _int_to_char(s, thermostat->hc), sizeof(hc)); + strlcpy(hc, THERMOSTAT_HC, sizeof(hc)); + strlcat(hc, _int_to_char(s, thermostat->hc), sizeof(hc)); JsonObject dataThermostat = rootThermostat.createNestedObject(hc); // different logic depending on thermostat types @@ -909,7 +909,7 @@ char * _readWord() { // publish external dallas sensor temperature values to MQTT void do_publishSensorValues() { - if ((EMSESP_Settings.dallas_sensors != 0) && (EMSESP_Settings.publish_time != 0)) { + if ((EMSESP_Settings.dallas_sensors) && (EMSESP_Settings.publish_time)) { publishSensorValues(); } } @@ -917,7 +917,7 @@ void do_publishSensorValues() { // call PublishValues without forcing, so using CRC to see if we really need to publish void do_publishValues() { // don't publish if we're not connected to the EMS bus - if ((ems_getBusConnected()) && myESP.isMQTTConnected() && EMSESP_Settings.publish_time != 0) { + if ((ems_getBusConnected()) && myESP.isMQTTConnected() && EMSESP_Settings.publish_time) { publishValues(true); // force publish } } @@ -1542,7 +1542,6 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { if (strcmp(topic, TOPIC_GENERIC_CMD) == 0) { // convert JSON and get the command StaticJsonDocument<100> doc; - JsonObject root = doc.to(); // create empty object DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document if (error) { myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str()); @@ -1562,7 +1561,6 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { // check for shower commands if (strcmp(topic, TOPIC_SHOWER_DATA) == 0) { StaticJsonDocument<100> doc; - JsonObject root = doc.to(); // create empty object DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document if (error) { myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str()); @@ -1594,7 +1592,6 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { if (strcmp(topic, TOPIC_BOILER_CMD) == 0) { // convert JSON and get the command StaticJsonDocument<100> doc; - JsonObject root = doc.to(); // create empty object DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document if (error) { myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str()); @@ -1672,7 +1669,6 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { if (strcmp(topic, TOPIC_THERMOSTAT_CMD) == 0) { // convert JSON and get the command StaticJsonDocument<100> doc; - JsonObject root = doc.to(); // create empty object DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document if (error) { myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str()); @@ -1680,30 +1676,6 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { } const char * command = doc["cmd"]; - uint8_t hc; - // thermostat temp changes - hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_TEMP, command); - if (hc) { - float f = doc["data"]; - ems_setThermostatTemp(f, hc); - publishValues(true); // publish back immediately - return; - } - - // thermostat mode changes - hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_MODE, command); - if (hc) { - const char * data = doc["data"]; - if (strcmp(data, "auto") == 0) { - ems_setThermostatMode(2, hc); - } else if ((strcmp(data, "day") == 0) || (strcmp(data, "manual") == 0) || (strcmp(data, "heat") == 0)) { - ems_setThermostatMode(1, hc); - } else if ((strcmp(data, "night") == 0) || (strcmp(data, "off") == 0)) { - ems_setThermostatMode(0, hc); - } - return; - } - // set night temp value hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_NIGHTTEMP, command); if (hc) { @@ -1968,12 +1940,12 @@ void showerCheck() { } } else { // hot water is off // if it just turned off, record the time as it could be a short pause - if ((EMSESP_Shower.timerStart != 0) && (EMSESP_Shower.timerPause == 0)) { + if ((EMSESP_Shower.timerStart) && (EMSESP_Shower.timerPause == 0)) { EMSESP_Shower.timerPause = EMSESP_Settings.timestamp; } // if shower has been off for longer than the wait time - if ((EMSESP_Shower.timerPause != 0) && ((EMSESP_Settings.timestamp - EMSESP_Shower.timerPause) > SHOWER_PAUSE_TIME)) { + if ((EMSESP_Shower.timerPause) && ((EMSESP_Settings.timestamp - EMSESP_Shower.timerPause) > SHOWER_PAUSE_TIME)) { // it is over the wait period, so assume that the shower has finished and calculate the total time and publish // because its unsigned long, can't have negative so check if length is less than OFFSET_TIME if ((EMSESP_Shower.timerPause - EMSESP_Shower.timerStart) > SHOWER_OFFSET_TIME) { @@ -2047,7 +2019,7 @@ void setup() { } // set timers for MQTT publish - if (EMSESP_Settings.publish_time != 0) { + if (EMSESP_Settings.publish_time) { publishValuesTimer.attach(EMSESP_Settings.publish_time, do_publishValues); // post MQTT EMS values publishSensorValuesTimer.attach(EMSESP_Settings.publish_time, do_publishSensorValues); // post MQTT dallas sensor values } @@ -2076,7 +2048,7 @@ void loop() { // check Dallas sensors, using same schedule as publish_time (default 2 mins) // these values are published to MQTT separately via the timer publishSensorValuesTimer - if (EMSESP_Settings.dallas_sensors != 0) { + if (EMSESP_Settings.dallas_sensors) { ds18.loop(); } @@ -2085,6 +2057,7 @@ void loop() { if (ems_getEmsRefreshed() && (scanThermostat_count == 0)) { publishValues(false); do_publishSensorValues(); + do_publishShowerData(); ems_setEmsRefreshed(false); // reset } @@ -2093,7 +2066,7 @@ void loop() { showerCheck(); } - if (EMSESP_DELAY != 0) { + if (EMSESP_DELAY) { delay(EMSESP_DELAY); // some time to WiFi and everything else to catch up, and prevent overheating } } diff --git a/src/ems.cpp b/src/ems.cpp index b639fd965..a189226f9 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -7,9 +7,9 @@ */ #include "ems.h" -#include "ems_utils.h" #include "MyESP.h" #include "ems_devices.h" +#include "ems_utils.h" #include "emsuart.h" #include // https://github.com/rlogiacco/CircularBuffer @@ -1001,7 +1001,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)); @@ -1691,7 +1691,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; } @@ -1802,7 +1802,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 % diff --git a/src/emsuart.cpp b/src/emsuart.cpp index f40268f71..b9cb59906 100644 --- a/src/emsuart.cpp +++ b/src/emsuart.cpp @@ -178,7 +178,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 @@ -227,7 +227,7 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) { 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 From 7c6b41bc1397918a40013f194bd088e03a0a275e Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 13 Oct 2019 14:51:56 +0200 Subject: [PATCH 43/76] type in thermostat name - https://github.com/proddy/EMS-ESP/issues/208 --- doc/home assistant/ui-lovelace.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/home assistant/ui-lovelace.yaml b/doc/home assistant/ui-lovelace.yaml index d3a8e88d1..59f96d36e 100644 --- a/doc/home assistant/ui-lovelace.yaml +++ b/doc/home assistant/ui-lovelace.yaml @@ -56,7 +56,7 @@ views: - sensor.current_room_temperature - sensor.dark_sky_temperature - type: thermostat - entity: climate.thermostat_hc1 + entity: climate.thermostat - type: thermostat name: WarmWater entity: climate.boiler From 282891fd27b5e610e170421a2168b97dc2a277c4 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 13 Oct 2019 17:30:22 +0200 Subject: [PATCH 44/76] dont clear json doc on each HC - https://github.com/proddy/EMS-ESP/issues/162 --- src/ems-esp.cpp | 2 +- src/version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 27770374d..1f5e9a96f 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -665,6 +665,7 @@ void publishValues(bool force) { last_boilerActive = ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive); // remember last state } + doc.clear(); // handle the thermostat values if (ems_getThermostatEnabled()) { for (uint8_t hc_v = 1; hc_v <= EMS_THERMOSTAT_MAXHC; hc_v++) { @@ -673,7 +674,6 @@ void publishValues(bool force) { // only send if we have an active Heating Circuit with real data if (thermostat->active) { // build new json object - doc.clear(); JsonObject rootThermostat = doc.to(); // hc{1-4} diff --git a/src/version.h b/src/version.h index fed8d4cf9..6e4a789cb 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define APP_VERSION "1.9.2b6" +#define APP_VERSION "1.9.2b7" From c8845a17347bcf4bc076b4e8500f604351cd399c Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 13 Oct 2019 18:11:47 +0200 Subject: [PATCH 45/76] fix for HC MQTT --- src/ems-esp.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 1f5e9a96f..c77e44e8c 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -665,19 +665,18 @@ void publishValues(bool force) { last_boilerActive = ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive); // remember last state } - doc.clear(); // handle the thermostat values if (ems_getThermostatEnabled()) { + doc.clear(); + JsonObject rootThermostat = doc.to(); + for (uint8_t hc_v = 1; hc_v <= EMS_THERMOSTAT_MAXHC; hc_v++) { _EMS_Thermostat_HC * thermostat = &EMS_Thermostat.hc[hc_v - 1]; // only send if we have an active Heating Circuit with real data if (thermostat->active) { // build new json object - JsonObject rootThermostat = doc.to(); - - // hc{1-4} - char hc[10]; + char hc[10]; // hc{1-4} strlcpy(hc, THERMOSTAT_HC, sizeof(hc)); strlcat(hc, _int_to_char(s, thermostat->hc), sizeof(hc)); JsonObject dataThermostat = rootThermostat.createNestedObject(hc); From 1e85934b38610e9043142fdb09d66edab5676e63 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 13 Oct 2019 19:00:57 +0200 Subject: [PATCH 46/76] publish MQTT shower info when connected --- src/MyESP.cpp | 4 ++-- src/ems-esp.cpp | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/MyESP.cpp b/src/MyESP.cpp index 638447c05..9f9a969e3 100644 --- a/src/MyESP.cpp +++ b/src/MyESP.cpp @@ -396,14 +396,14 @@ void MyESP::mqttUnsubscribe(const char * topic) { // returns true if all good bool MyESP::mqttPublish(const char * topic, const char * payload) { if (mqttClient.connected() && (strlen(topic) > 0)) { - // myDebug_P(PSTR("[MQTT] Sending publish to %s with payload %s"), _mqttTopic(topic), payload); // for debugging + //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, _mqtt_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); + myDebug_P(PSTR("[MQTT] Error publishing to %s with payload %s [error %d]"), _mqttTopic(topic), payload, packet_id); return false; } } diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index c77e44e8c..de55ddbd0 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -1529,6 +1529,9 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { // for receiving shower_Timer and shower_alert switches myESP.mqttSubscribe(TOPIC_SHOWER_DATA); + // send Shower Alert and Timer switch settings + do_publishShowerData(); + return; } @@ -2056,7 +2059,6 @@ void loop() { if (ems_getEmsRefreshed() && (scanThermostat_count == 0)) { publishValues(false); do_publishSensorValues(); - do_publishShowerData(); ems_setEmsRefreshed(false); // reset } From e3d06e15a32e4ff9328434367424927c1e367f81 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 13 Oct 2019 21:03:15 +0200 Subject: [PATCH 47/76] added MQTT QOS and Keep Alive configurable --- CHANGELOG.md | 1 + src/MyESP.cpp | 35 ++++++++++++++++++++++++++++++----- src/MyESP.h | 7 ++++--- src/ems-esp.cpp | 33 --------------------------------- src/ems_utils.cpp | 33 +++++++++++++++++++++++++++++++++ src/ems_utils.h | 30 +++++++++++++++++------------- src/version.h | 2 +- src/websrc/myesp.htm | 26 +++++++++++++++++++++++--- src/websrc/myesp.js | 6 ++++++ tools/wsemulator/wserver.js | 2 ++ 10 files changed, 117 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e87d73b2..94bdf1900 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Retrieve/display mode for Junkers FW100/120 thermostats (thanks @Neonox31) - Added sending of Mixer Module data via MQTT (thanks @peclik) - Reporting of MQTT publish and subscribe errors +- Added MQTT QOS (default 1) and Keep Alive (default 60 seconds) as parameters to both telnet and WebUI ### Fixed diff --git a/src/MyESP.cpp b/src/MyESP.cpp index 9f9a969e3..8b712e306 100644 --- a/src/MyESP.cpp +++ b/src/MyESP.cpp @@ -677,8 +677,10 @@ void MyESP::_printSetCommands() { myDebug_P(PSTR(" set mqtt_enabled ")); myDebug_P(PSTR(" set [value]")); myDebug_P(PSTR(" set mqtt_heartbeat ")); - 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 ntp_enabled ")); myDebug_P(PSTR(" set serial ")); myDebug_P(PSTR(" set log_events ")); @@ -732,6 +734,8 @@ 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_qos=%d"), _mqtt_qos); myDebug_P(PSTR(" mqtt_heartbeat=%s"), (_mqtt_heartbeat) ? "on" : "off"); #ifdef FORCE_SERIAL @@ -815,6 +819,10 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) save_config = fs_setSettingValue(&_mqtt_base, value, MQTT_BASE_DEFAULT); } else if (strcmp(setting, "mqtt_port") == 0) { 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 = fs_setSettingValue(&_mqtt_enabled, value, false); } else if (strcmp(setting, "serial") == 0) { @@ -1690,6 +1698,8 @@ 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_qos = mqtt["qos"] | MQTT_QOS; _mqtt_password = strdup(mqtt["password"] | ""); _mqtt_base = strdup(mqtt["base"] | MQTT_BASE_DEFAULT); @@ -1721,7 +1731,7 @@ bool MyESP::fs_setSettingValue(char ** setting, const char * value, const char * return true; } -// saves a integer into a config setting, using default value if non set +// 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)) { @@ -1733,6 +1743,18 @@ bool MyESP::fs_setSettingValue(uint16_t * setting, const char * value, uint16_t 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) { @@ -1879,8 +1901,11 @@ bool MyESP::_fs_writeConfig() { mqtt["ip"] = _mqtt_ip; mqtt["user"] = _mqtt_user; mqtt["port"] = _mqtt_port; - mqtt["password"] = _mqtt_password; - mqtt["base"] = _mqtt_base; + mqtt["qos"] = _mqtt_qos; + mqtt["keepalive"] = _mqtt_keepalive; + + mqtt["password"] = _mqtt_password; + mqtt["base"] = _mqtt_base; JsonObject ntp = doc.createNestedObject("ntp"); ntp["server"] = _ntp_server; diff --git a/src/MyESP.h b/src/MyESP.h index 4f90de789..3d10e8a36 100644 --- a/src/MyESP.h +++ b/src/MyESP.h @@ -80,8 +80,8 @@ extern struct rst_info resetInfo; #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_KEEPALIVE 60 // default keepalive 1 minute +#define MQTT_QOS 1 // default qos #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 700 // max size of a JSON object. See https://arduinojson.org/v6/assistant/ @@ -293,6 +293,7 @@ class MyESP { 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 @@ -340,7 +341,7 @@ class MyESP { 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; diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index de55ddbd0..32ce146e0 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -873,39 +873,6 @@ void publishValues(bool force) { } } -// 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; -} - // publish external dallas sensor temperature values to MQTT void do_publishSensorValues() { if ((EMSESP_Settings.dallas_sensors) && (EMSESP_Settings.publish_time)) { diff --git a/src/ems_utils.cpp b/src/ems_utils.cpp index fedae1984..bc68a8436 100644 --- a/src/ems_utils.cpp +++ b/src/ems_utils.cpp @@ -260,3 +260,36 @@ char * _smallitoa3(uint16_t value, char * buffer) { 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; +} diff --git a/src/ems_utils.h b/src/ems_utils.h index e08856038..c3c624f63 100644 --- a/src/ems_utils.h +++ b/src/ems_utils.h @@ -14,16 +14,20 @@ #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); +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(); diff --git a/src/version.h b/src/version.h index 6e4a789cb..35ce183a7 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define APP_VERSION "1.9.2b7" +#define APP_VERSION "1.9.2b8" diff --git a/src/websrc/myesp.htm b/src/websrc/myesp.htm index c741d7a43..ddb56ead1 100644 --- a/src/websrc/myesp.htm +++ b/src/websrc/myesp.htm @@ -191,7 +191,7 @@ data-toggle="popover" data-trigger="hover" data-placement="right" data-content="MQTT Server IP Address"> -
@@ -201,11 +201,31 @@ aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right" data-content="MQTT Server port number"> -
+
+ + + + +
+
+
+ + + + +
+
-
diff --git a/src/websrc/myesp.js b/src/websrc/myesp.js index 0677b582e..ab5e940e6 100644 --- a/src/websrc/myesp.js +++ b/src/websrc/myesp.js @@ -25,6 +25,8 @@ var config = { "enabled": false, "ip": "", "port": 1883, + "qos": 1, + "keepalive": 60, "base": "", "user": "", "password": "", @@ -173,6 +175,8 @@ function savemqtt() { 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; @@ -323,6 +327,8 @@ function listmqtt() { 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; diff --git a/tools/wsemulator/wserver.js b/tools/wsemulator/wserver.js index 2d7f8c969..dbaa64e5d 100644 --- a/tools/wsemulator/wserver.js +++ b/tools/wsemulator/wserver.js @@ -76,6 +76,8 @@ var configfile = { "enabled": false, "ip": "ip", "port": "port", + "qos": "qos", + "keepalive": "keepalive", "base": "base", "user": "user", "password": "password", From 42265b450186b950976dd7c0efef79492b5e99e2 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 13 Oct 2019 22:05:39 +0200 Subject: [PATCH 48/76] https://github.com/proddy/EMS-ESP/issues/162 --- src/ems-esp.cpp | 94 ++++++++++++++++++++++++------------------------- src/my_config.h | 3 +- 2 files changed, 47 insertions(+), 50 deletions(-) diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 32ce146e0..427501050 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -536,11 +536,11 @@ void publishValues(bool force) { uint32_t fchecksum; uint8_t jsonSize; - static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off - static uint32_t previousBoilerPublishCRC = 0; // CRC check for boiler values - static uint32_t previousThermostatPublishCRC[EMS_THERMOSTAT_MAXHC]; // CRC check for thermostat values - static uint32_t previousMixingPublishCRC[EMS_THERMOSTAT_MAXHC]; // CRC check for mixing values - static uint32_t previousSMPublishCRC = 0; // CRC check for Solar Module values (e.g. SM10) + static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off + static uint32_t previousBoilerPublishCRC = 0; // CRC check for boiler values + static uint32_t previousThermostatPublishCRC; // CRC check for thermostat values + static uint32_t previousMixingPublishCRC; // CRC check for mixing values + static uint32_t previousSMPublishCRC = 0; // CRC check for Solar Module values (e.g. SM10) JsonObject rootBoiler = doc.to(); @@ -727,39 +727,44 @@ void publishValues(bool force) { } else if (thermoMode == 4) { dataThermostat[THERMOSTAT_MODE] = "day"; } + } + } - data[0] = '\0'; // reset data for next package - serializeJson(doc, data, sizeof(data)); + data[0] = '\0'; // reset data for next package + serializeJson(doc, data, sizeof(data)); - // check for empty json - jsonSize = measureJson(doc); - if (jsonSize > 2) { - // calculate new CRC - crc.reset(); - for (uint8_t i = 0; i < (jsonSize - 1); i++) { - crc.update(data[i]); - } - fchecksum = crc.finalize(); - if ((previousThermostatPublishCRC[hc_v - 1] != fchecksum) || force) { - previousThermostatPublishCRC[hc_v - 1] = fchecksum; - myDebugLog("Publishing thermostat data via MQTT"); - myESP.mqttPublish(TOPIC_THERMOSTAT_DATA, data); - } - } + // check for empty json + jsonSize = measureJson(doc); + if (jsonSize > 2) { + // calculate new CRC + crc.reset(); + for (uint8_t i = 0; i < (jsonSize - 1); i++) { + crc.update(data[i]); + } + fchecksum = crc.finalize(); + if ((previousThermostatPublishCRC != fchecksum) || force) { + previousThermostatPublishCRC = fchecksum; + myDebugLog("Publishing thermostat data via MQTT"); + myESP.mqttPublish(TOPIC_THERMOSTAT_DATA, data); } } } // handle the thermostat values if (ems_getMixingDeviceEnabled()) { + doc.clear(); + JsonObject rootMixing = doc.to(); + for (uint8_t hc_v = 1; hc_v <= EMS_THERMOSTAT_MAXHC; hc_v++) { _EMS_Mixing_HC * mixing = &EMS_Mixing.hc[hc_v - 1]; // only send if we have an active Heating Circuit with real data if (mixing->active) { // build new json object - doc.clear(); - JsonObject rootMixing = doc.to(); + char hc[10]; // hc{1-4} + strlcpy(hc, THERMOSTAT_HC, sizeof(hc)); + strlcat(hc, _int_to_char(s, mixing->hc), sizeof(hc)); + JsonObject dataThermostat = rootMixing.createNestedObject(hc); if (mixing->flowTemp != EMS_VALUE_SHORT_NOTSET) rootMixing["flowTemp"] = (double)mixing->flowTemp / 10; @@ -767,36 +772,29 @@ void publishValues(bool force) { rootMixing["pumpMod"] = mixing->pumpMod; if (mixing->valveStatus != EMS_VALUE_INT_NOTSET) rootMixing["valveStatus"] = mixing->valveStatus; + } + } - data[0] = '\0'; // reset data for next package - serializeJson(doc, data, sizeof(data)); + data[0] = '\0'; // reset data for next package + serializeJson(doc, data, sizeof(data)); - // check for empty json - jsonSize = measureJson(doc); - if (jsonSize > 2) { - // calculate new CRC - crc.reset(); - for (uint8_t i = 0; i < (jsonSize - 1); i++) { - crc.update(data[i]); - } - fchecksum = crc.finalize(); - if ((previousMixingPublishCRC[hc_v - 1] != fchecksum) || force) { - previousMixingPublishCRC[hc_v - 1] = fchecksum; - char mixing_topicname[20]; - char buffer[4]; - // "mixingt_data" + Heating Cicruit # - strlcpy(mixing_topicname, TOPIC_MIXING_DATA, sizeof(mixing_topicname)); - strlcat(mixing_topicname, itoa(hc_v, buffer, 10), sizeof(mixing_topicname)); - myDebugLog("Publishing mixing device data via MQTT"); - myESP.mqttPublish(mixing_topicname, data); - } - } + // check for empty json + jsonSize = measureJson(doc); + if (jsonSize > 2) { + // calculate new CRC + crc.reset(); + for (uint8_t i = 0; i < (jsonSize - 1); i++) { + crc.update(data[i]); + } + fchecksum = crc.finalize(); + if ((previousMixingPublishCRC != fchecksum) || force) { + previousMixingPublishCRC = fchecksum; + myDebugLog("Publishing mixing device data via MQTT"); + myESP.mqttPublish(TOPIC_MIXING_DATA, data); } } } - - // For SM10 and SM100 Solar Modules if (ems_getSolarModuleEnabled()) { // build new json object diff --git a/src/my_config.h b/src/my_config.h index c87396483..8e63d914c 100644 --- a/src/my_config.h +++ b/src/my_config.h @@ -72,7 +72,6 @@ #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 From cf4145caa972a5468523f0b4987a0e2d9886ba81 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 13 Oct 2019 22:17:14 +0200 Subject: [PATCH 49/76] update HA sensors - https://github.com/proddy/EMS-ESP/issues/208 --- doc/home assistant/sensor.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/home assistant/sensor.yaml b/doc/home assistant/sensor.yaml index 9bc626fba..e9f7efcfd 100644 --- a/doc/home assistant/sensor.yaml +++ b/doc/home assistant/sensor.yaml @@ -2,18 +2,18 @@ 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 From 955d848bc200557791f88e02e79f17b0092f9bad Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 14 Oct 2019 09:58:23 +0200 Subject: [PATCH 50/76] mentioned HA changes --- CHANGELOG.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94bdf1900..0cb23f00f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,15 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### 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). - - 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. + - 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 MM100 Status Messages (thanks @kstaniek) -- Retrieve/display mode for Junkers FW100/120 thermostats (thanks @Neonox31) -- Added sending of Mixer Module data via MQTT (thanks @peclik) -- Reporting of MQTT publish and subscribe errors -- Added MQTT QOS (default 1) and Keep Alive (default 60 seconds) as parameters to both telnet and WebUI +- 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 1) and Keep Alive (`mqtt_keepalive`, default 60 seconds) as parameters to both telnet and WebUI ### Fixed From e86b24d6be04f6667dde25f6881c7a4893fcb728 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 14 Oct 2019 10:06:34 +0200 Subject: [PATCH 51/76] add --- doc/ems gateway/logo-proddy-fw.jpg | Bin 0 -> 23115 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/ems gateway/logo-proddy-fw.jpg diff --git a/doc/ems gateway/logo-proddy-fw.jpg b/doc/ems gateway/logo-proddy-fw.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3f0d7c36fa4db310a889b899fe105afb64e6d61f GIT binary patch literal 23115 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�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 literal 0 HcmV?d00001 From 2228efdf58af7e4dd4767c8c560f2be93b0c952d Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 14 Oct 2019 10:10:42 +0200 Subject: [PATCH 52/76] added logo --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0db9da98f..64f425f31 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +| ![logo](https://github.com/proddy/EMS-ESP/raw/master/doc/ems gateway/logo-proddy-fw.jpg) # EMS-ESP [![version](https://img.shields.io/github/release/proddy/EMS-ESP.svg?label=Latest%20Release)](https://github.com/proddy/EMS-ESP/blob/master/CHANGELOG.md) From 59f1aade56819c3b1f274ad2959a2c090f02b3fa Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 14 Oct 2019 10:14:52 +0200 Subject: [PATCH 53/76] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 64f425f31..97fceb10a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -| ![logo](https://github.com/proddy/EMS-ESP/raw/master/doc/ems gateway/logo-proddy-fw.jpg) +![logo](https://github.com/proddy/EMS-ESP/raw/master/doc/ems%20gateway/logo-proddy-fw.jpg) # EMS-ESP [![version](https://img.shields.io/github/release/proddy/EMS-ESP.svg?label=Latest%20Release)](https://github.com/proddy/EMS-ESP/blob/master/CHANGELOG.md) From cda27cb12eb7b473d05fc7a527f4f9f388cde4ad Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 14 Oct 2019 10:16:40 +0200 Subject: [PATCH 54/76] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97fceb10a..32a2b0a42 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The code is written for the Espressif **ESP8266** microcontroller and supports a #### MQTT support for Home Assistant and Domoticz -![ha](https://github.com/proddy/EMS-ESP/raw/master/doc/home_assistant/ha.png) +![ha](https://github.com/proddy/EMS-ESP/raw/master/doc/home%20assistant/ha.png) #### Telnet for advanced configuration and verbose traffic logging From e3e3349cef407a276e932b42fd15baa99dc34d38 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 14 Oct 2019 13:09:12 +0200 Subject: [PATCH 55/76] remove -g -w flags as new pio does this automagically --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 7759ddfdc..9299ec756 100644 --- a/platformio.ini +++ b/platformio.ini @@ -17,8 +17,8 @@ default_envs = debug ; -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 = -DNO_GLOBAL_EEPROM -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH -DBEARSSL_SSL_BASIC +general_flags = -DNO_GLOBAL_EEPROM [env] ; board = esp12e From cb7260538cfb68384e194c7384cff0368863ae60 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 14 Oct 2019 13:09:22 +0200 Subject: [PATCH 56/76] add Bosch CT200 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32a2b0a42..ee49d7894 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ The code is written for the Espressif **ESP8266** microcontroller and supports a * Buderus Logamatic TC100 (read-only) * Nefit Moduline 100, 300, 400, 1010, 3000 * Nefit Moduline Easy (read-only) -* Bosch Easy, CW100 (read-only) +* Bosch Easy, CW100, CT200 (read-only) * Junkers FR10, FR100, FR110, FW100, FW120 * Sieger ES73 From 6a72deffd283d0cee0cd0ee5948fdc6ec56d8649 Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 14 Oct 2019 13:16:00 +0200 Subject: [PATCH 57/76] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ee49d7894..8cea64560 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ The code is written for the Espressif **ESP8266** microcontroller and supports a * EMS-OT OpenTherm converter * Junkers Controller +See [the complete list here](https://github.com/proddy/EMS-ESP/wiki/Supported-EMS-Devices) from the Wiki. + ## Compatible with EMS Gateway Using BBQKees' [EMS Gateway](https://shop.hotgoodies.nl/ems/) board with integrated Wemos D1 ESP8266: From da46558675641bd42b9d1a4fb4dc0a89be59c906 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 14 Oct 2019 13:18:18 +0200 Subject: [PATCH 58/76] fix for mixing module MQTT --- src/ems-esp.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 427501050..d8fb1eab3 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -764,14 +764,14 @@ void publishValues(bool force) { char hc[10]; // hc{1-4} strlcpy(hc, THERMOSTAT_HC, sizeof(hc)); strlcat(hc, _int_to_char(s, mixing->hc), sizeof(hc)); - JsonObject dataThermostat = rootMixing.createNestedObject(hc); + JsonObject dataMixing = rootMixing.createNestedObject(hc); if (mixing->flowTemp != EMS_VALUE_SHORT_NOTSET) - rootMixing["flowTemp"] = (double)mixing->flowTemp / 10; + dataMixing["flowTemp"] = (double)mixing->flowTemp / 10; if (mixing->pumpMod != EMS_VALUE_INT_NOTSET) - rootMixing["pumpMod"] = mixing->pumpMod; + dataMixing["pumpMod"] = mixing->pumpMod; if (mixing->valveStatus != EMS_VALUE_INT_NOTSET) - rootMixing["valveStatus"] = mixing->valveStatus; + dataMixing["valveStatus"] = mixing->valveStatus; } } From 85aef858f2ec7e431920b9d92e5c7a932a2eca99 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 14 Oct 2019 13:31:39 +0200 Subject: [PATCH 59/76] removed supported devices and referenced wiki --- README.md | 59 ++++++------------------------------------------------- 1 file changed, 6 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 8cea64560..3edb27d89 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The code is written for the Espressif **ESP8266** microcontroller and supports a #### A web interface for easy configuration and real-time monitoring of the EMS bus | ![web menu](https://github.com/proddy/EMS-ESP/raw/master/doc/web/system_status.PNG) | ![web menu](https://github.com/proddy/EMS-ESP/raw/master/doc/web/ems_dashboard.PNG) | -| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| -------------------------------------------------------- | ---------------------------------------------------------- | #### MQTT support for Home Assistant and Domoticz @@ -33,62 +33,15 @@ The code is written for the Espressif **ESP8266** microcontroller and supports a #### Telnet for advanced configuration and verbose traffic logging | ![telnet menu](https://github.com/proddy/EMS-ESP/raw/master/doc/telnet/telnet_menu.jpg) | ![telnet menu](https://github.com/proddy/EMS-ESP/raw/master/doc/telnet/telnet_stats.PNG) | -| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| ------------------------------------------------------------ | -------------------------------------------------------- | ---- +## Supported EMS devices -## Current list of supported EMS devices +More than **50** EMS devices are currently support. See [the complete list here](https://github.com/proddy/EMS-ESP/wiki/Supported-EMS-Devices) from the Wiki. -### Thermostats: +## Compatible with bbqkees' EMS Gateway -* Buderus RC10, RC20, RC20F, RC30, R35, RC300, RC310, RC3000 -* Buderus Logamatic TC100 (read-only) -* Nefit Moduline 100, 300, 400, 1010, 3000 -* Nefit Moduline Easy (read-only) -* Bosch Easy, CW100, CT200 (read-only) -* Junkers FR10, FR100, FR110, FW100, FW120 -* Sieger ES73 - -### Boilers: - -* Buderus GBx72, GB162, GB152, Logamax U122, Logamax plus/GB192, Logano -* Bosch Condens 2500 -* Worcester-Bosch Greenstar 550CDi -* Worcester Bosch Greenstar 24i -* Junkers Cerapur, Heatronic 3 boilers -* Nefit Proline, Trendline, Topline, Enviline, Smartline -* Sieger BK15 Boiler - -### Solar Modules: - -* Buderus SM10, SM50, SM100 Solar Module -* Junkers ISM1 Solar Module - -### Mixing Modules: - -* Buderus MM10, MM50, MM100 Mixer Module - -### Heat Pump Modules: - -* Buderus HeatPump Module - -### Other devices: - -* Generic Buderus MC10 Module -* Buderus WM10 Switch Module -* Buderus RFM20 Receiver -* Buderus BC10, BC25 Base Controller -* Buderus Web Gateway KM200 -* Nefit Moduline Easy Connect -* Bosch Easy Connect -* EMS-OT OpenTherm converter -* Junkers Controller - -See [the complete list here](https://github.com/proddy/EMS-ESP/wiki/Supported-EMS-Devices) from the Wiki. - -## Compatible with EMS Gateway - -Using BBQKees' [EMS Gateway](https://shop.hotgoodies.nl/ems/) board with integrated Wemos D1 ESP8266: +The firmware fully supports BBQKees' [EMS Gateway](https://shop.hotgoodies.nl/ems/) board with integrated Wemos D1 ESP8266. | ![on boiler](https://github.com/proddy/EMS-ESP/raw/master/doc/ems%20gateway/on-boiler.jpg) | ![kit](https://github.com/proddy/EMS-ESP/raw/master/doc/ems%20gateway/ems-kit-2.jpg) | ![basic circuit](https://github.com/proddy/EMS-ESP/raw/master/doc/ems%20gateway/ems-board-white.jpg) | | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | From 1378dcf20d8a582e24e0b3043466a26cddec883e Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 14 Oct 2019 13:33:30 +0200 Subject: [PATCH 60/76] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3edb27d89..ce575c42c 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ The code is written for the Espressif **ESP8266** microcontroller and supports a ## Supported EMS devices -More than **50** EMS devices are currently support. See [the complete list here](https://github.com/proddy/EMS-ESP/wiki/Supported-EMS-Devices) from the Wiki. +More than **50** EMS devices are currently supported. See [the complete list here](https://github.com/proddy/EMS-ESP/wiki/Supported-EMS-Devices) from the Wiki. ## Compatible with bbqkees' EMS Gateway From 270ed5a1ea70fc00f3e56f4f0a3378d648840d27 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 14 Oct 2019 18:16:31 +0200 Subject: [PATCH 61/76] update shower MQTT for HA --- doc/home assistant/automation.yaml | 14 +------------- doc/home assistant/sensor.yaml | 2 +- src/ems-esp.cpp | 15 +++++---------- 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/doc/home assistant/automation.yaml b/doc/home assistant/automation.yaml index f277824f8..f896a69ce 100644 --- a/doc/home assistant/automation.yaml +++ b/doc/home assistant/automation.yaml @@ -7,19 +7,7 @@ - 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' - action: - - service: notify.admin_notify - data_template: - title: "Shower Alert!" - message: "Shower time exceeded limit" + message: "{{ trigger.payload_json['duration'] }}" # when ems-esp starts send boottime - id: boiler_restart diff --git a/doc/home assistant/sensor.yaml b/doc/home assistant/sensor.yaml index e9f7efcfd..c88224f1c 100644 --- a/doc/home assistant/sensor.yaml +++ b/doc/home assistant/sensor.yaml @@ -140,7 +140,7 @@ - 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") }}' diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index d8fb1eab3..c08f284ba 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -1060,6 +1060,7 @@ bool do_publishShowerData() { rootShower[TOPIC_SHOWER_TIMER] = EMSESP_Settings.shower_timer ? "1" : "0"; rootShower[TOPIC_SHOWER_ALERT] = EMSESP_Settings.shower_alert ? "1" : "0"; + // only publish shower duration if there is a value char s[50] = {0}; if (EMSESP_Shower.duration > SHOWER_MIN_DURATION) { char buffer[16] = {0}; @@ -1067,10 +1068,8 @@ bool do_publishShowerData() { strlcat(s, " minutes and ", sizeof(s)); strlcat(s, itoa((uint8_t)((EMSESP_Shower.duration / 1000) % 60), buffer, 10), sizeof(s)); strlcat(s, " seconds", sizeof(s)); - } else { - strlcpy(s, "n/a", sizeof(s)); + rootShower[TOPIC_SHOWER_DURATION] = s; } - rootShower[TOPIC_SHOWER_DURATION] = s; char data[300] = {0}; serializeJson(doc, data, sizeof(data)); @@ -1939,13 +1938,9 @@ void showerCheck() { // SETUP // void setup() { - // LA trigger create a small puls to show setup is starting... - INIT_MARKERS(0); - LA_PULSE(50); - - // GPIO15 has a pull down, so we must set it to HIGH - pinMode(15, OUTPUT); - digitalWrite(15, 1); + // GPIO15/D8 has a pull down, so we must set it to HIGH so it doesn't bring the whole EMS bus down + pinMode(D8, OUTPUT); + digitalWrite(D8, 1); // init our own parameters initEMSESP(); From 865f94cda298cfa564fd34c895e919afb8cb475d Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 14 Oct 2019 18:24:52 +0200 Subject: [PATCH 62/76] added another HeatPump --- src/ems_devices.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ems_devices.h b/src/ems_devices.h index 66a819e9d..f151c57a0 100644 --- a/src/ems_devices.h +++ b/src/ems_devices.h @@ -4,7 +4,6 @@ * Paul Derbyshire - https://github.com/proddy/EMS-ESP * * See ChangeLog.md for History - * See README.md for Acknowledgments * */ @@ -256,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 From f39ecb860a4f48d6afde8ae58610c99fa4632122 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 14 Oct 2019 18:25:12 +0200 Subject: [PATCH 63/76] remove emsuart debug messages --- src/ems.cpp | 1 - src/ems.h | 62 +------------------------------------------------ src/emsuart.cpp | 26 +-------------------- src/emsuart.h | 10 +++++--- src/version.h | 2 +- 5 files changed, 10 insertions(+), 91 deletions(-) diff --git a/src/ems.cpp b/src/ems.cpp index a189226f9..af5fa9b47 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -924,7 +924,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); diff --git a/src/ems.h b/src/ems.h index 17de5c35d..c223b0577 100644 --- a/src/ems.h +++ b/src/ems.h @@ -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 #include // 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 diff --git a/src/emsuart.cpp b/src/emsuart.cpp index b9cb59906..923583298 100644 --- a/src/emsuart.cpp +++ b/src/emsuart.cpp @@ -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 } } @@ -188,7 +183,6 @@ void ICACHE_FLASH_ATTR emsuart_tx_brk() { // To create a 11-bit 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,18 +205,14 @@ _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 } 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?) @@ -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 // 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... } diff --git a/src/emsuart.h b/src/emsuart.h index 0859aaf2f..3e3e5550d 100644 --- a/src/emsuart.h +++ b/src/emsuart.h @@ -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 diff --git a/src/version.h b/src/version.h index 35ce183a7..6bee081e5 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define APP_VERSION "1.9.2b8" +#define APP_VERSION "1.9.2b9" From 0a6c902872afce4b5289ad2ed5632371a58f2c46 Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 15 Oct 2019 10:02:50 +0200 Subject: [PATCH 64/76] default MQTT qos to 0 and added Retain flag option --- CHANGELOG.md | 2 +- src/MyESP.cpp | 13 ++++++++++--- src/MyESP.h | 4 ++-- src/websrc/myesp.htm | 13 +++++++++++++ src/websrc/myesp.js | 10 ++++++++++ tools/wsemulator/wserver.js | 9 +++++---- 6 files changed, 41 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cb23f00f..171ff69dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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 1) and Keep Alive (`mqtt_keepalive`, default 60 seconds) as parameters to both telnet and WebUI +- Added MQTT QOS (`mqtt_qos`, default 1), Keep Alive (`mqtt_keepalive`, default 60 seconds) and Retain (`mqtt_retain`, default on) as parameters to both telnet and WebUI ### Fixed diff --git a/src/MyESP.cpp b/src/MyESP.cpp index 8b712e306..b6702f71e 100644 --- a/src/MyESP.cpp +++ b/src/MyESP.cpp @@ -379,9 +379,10 @@ bool MyESP::mqttSubscribe(const char * topic) { return true; } else { myDebug_P(PSTR("[MQTT] Error subscribing to %s, error %d"), _mqttTopic(topic), packet_id); - return false; } } + + return false; // didn't work } // MQTT unsubscribe @@ -681,6 +682,7 @@ void MyESP::_printSetCommands() { 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 ")); myDebug_P(PSTR(" set serial ")); myDebug_P(PSTR(" set log_events ")); @@ -735,6 +737,7 @@ void MyESP::_printSetCommands() { } 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"); @@ -825,6 +828,8 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) save_config = fs_setSettingValue(&_mqtt_qos, value, MQTT_QOS); } else if (strcmp(setting, "mqtt_enabled") == 0) { 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 = fs_setSettingValue(&_general_serial, value, false); restart = save_config; @@ -1699,6 +1704,7 @@ bool MyESP::_fs_loadConfig() { _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); @@ -1759,9 +1765,9 @@ bool MyESP::fs_setSettingValue(uint8_t * setting, const char * value, uint8_t va // 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)) { + 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)) { + } 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 @@ -1903,6 +1909,7 @@ bool MyESP::_fs_writeConfig() { mqtt["port"] = _mqtt_port; mqtt["qos"] = _mqtt_qos; mqtt["keepalive"] = _mqtt_keepalive; + mqtt["retain"] = _mqtt_retain; mqtt["password"] = _mqtt_password; mqtt["base"] = _mqtt_base; diff --git a/src/MyESP.h b/src/MyESP.h index 3d10e8a36..4c50ddf7e 100644 --- a/src/MyESP.h +++ b/src/MyESP.h @@ -79,9 +79,9 @@ 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_RETAIN true #define MQTT_KEEPALIVE 60 // default keepalive 1 minute -#define MQTT_QOS 1 // default qos +#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 700 // max size of a JSON object. See https://arduinojson.org/v6/assistant/ diff --git a/src/websrc/myesp.htm b/src/websrc/myesp.htm index ddb56ead1..c534f4167 100644 --- a/src/websrc/myesp.htm +++ b/src/websrc/myesp.htm @@ -226,6 +226,19 @@
+
+ +
+
+ + +
+
+