diff --git a/CHANGELOG.md b/CHANGELOG.md index 97a9d203..f6ac4e68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,25 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [1.5.3] 2017-02-02 +### Fixed +- Issue #50 and #54. Fixed domoticz MQTT message format + +### Added +- Energy calculation and aggregation. API entry points and MQTT messages. + +## [1.5.2] 2017-01-29 +### Fixed +- Fix bug in emon topic payload + +## [1.5.1] 2017-01-28 +### Added +- OpenEnergyMonitor WiFi MQTT Relay / Thermostat support (thanks to Denis French) + +### Fixed +- NTP connection refresh upon wifi connection +- Filesystem image build using local gulp installation + ## [1.5.0] 2017-01-21 ### Added - Pulse mode. Allows to define a pulse time after which the relay will switch back @@ -47,7 +66,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Support for inverse logic relays ### Fixed -- Fixed error in relay identification from MQTT messages (issue #31) +- Issue #31. Fixed error in relay identification from MQTT messages ## [1.4.1] 2017-01-05 ### Added @@ -63,7 +82,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Support for static IP connections ### Fixed -- Enforce minimum password strength in web interface (#16) +- Issue #16. Enforce minimum password strength in web interface ### Changed - Using default client_id provided by AsyncMqttClient diff --git a/README.md b/README.md index 4b8b0bb6..44293698 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ESPurna ("spark" in Catalan) is a custom firmware for ESP8266 based smart switch It was originally developed with the **[IteadStudio Sonoff](https://www.itead.cc/sonoff-wifi-wireless-switch.html)** in mind but now it supports a growing number of ESP8266-based boards. It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries. -**Current Release Version is 1.5.0**, read the [changelog](CHANGELOG.md). +**Current Release Version is 1.5.3**, read the [changelog](CHANGELOG.md). ## Features @@ -46,17 +46,25 @@ For more information please refer to the [ESPurna Wiki](https://bitbucket.org/xo ## Supported hardware -|![IteadStudio S20](images/devices/s20.jpg) **IteadStudio S20**|![IteadStudio Slampher](images/devices/slampher.jpg) **IteadStudio Slampher**|![IteadStudio Sonoff 4CH](images/devices/sonoff-4ch.jpg) **IteadStudio Sonoff 4CH**| -|![IteadStudio Sonoff Basic](images/devices/sonoff-basic.jpg) **IteadStudio Sonoff Basic**|![IteadStudio Motor Switch](images/devices/motor-switch.jpg) **IteadStudio Motor Switch**|![IteadStudio 1CH Inching](images/devices/1ch-inching.jpg) **IteadStudio 1CH Inching**| -|![IteadStudio Sonoff Dual](images/devices/sonoff-dual.jpg) **IteadStudio Sonoff Dual**|![IteadStudio Sonoff POW](images/devices/sonoff-pow.jpg) **IteadStudio Sonoff POW**|![IteadStudio Sonoff TH10/TH16](images/devices/sonoff-th10-th16.jpg) **IteadStudio Sonoff TH10/TH16**| -|![IteadStudio Sonoff RF](images/devices/sonoff-rf.jpg) **IteadStudio Sonoff RF**|![IteadStudio Sonoff SV](images/devices/sonoff-sv.jpg) **IteadStudio Sonoff SV**|![IteadStudio Sonoff Touch](images/devices/sonoff-touch.jpg) **IteadStudio Sonoff Touch**| -|![Wemos D1 Mini Relay Shield](images/devices/d1mini.jpg) **Wemos D1 Mini Relay Shield**|![Electrodragon Relay Board](images/devices/electrodragon-relay-board.jpg) **Electrodragon Relay Board**|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg) **WorkChoice EcoPlug**| -|![JanGoe Wifi Relay (NO/NC)](images/devices/jangoe-wifi-relay.png) **JanGoe Wifi Relay (NO/NC)**||| +|||| +|-|-|-| +|![IteadStudio S20](images/devices/s20.jpg)|![IteadStudio Slampher](images/devices/slampher.jpg)|![IteadStudio Sonoff 4CH](images/devices/sonoff-4ch.jpg)| +|**IteadStudio S20**|**IteadStudio Slampher**|**IteadStudio Sonoff 4CH**| +|![IteadStudio Sonoff Basic](images/devices/sonoff-basic.jpg)|![IteadStudio Motor Switch](images/devices/motor-switch.jpg)|![IteadStudio 1CH Inching](images/devices/1ch-inching.jpg)| +|**IteadStudio Sonoff Basic**|**IteadStudio Motor Switch**|**IteadStudio 1CH Inching**| +|![IteadStudio Sonoff Dual](images/devices/sonoff-dual.jpg)|![IteadStudio Sonoff POW](images/devices/sonoff-pow.jpg)|![IteadStudio Sonoff TH10/TH16](images/devices/sonoff-th10-th16.jpg)| +|**IteadStudio Sonoff Dual**|**IteadStudio Sonoff POW**|**IteadStudio Sonoff TH10/TH16**| +|![IteadStudio Sonoff RF](images/devices/sonoff-rf.jpg)|![IteadStudio Sonoff SV](images/devices/sonoff-sv.jpg)|![IteadStudio Sonoff Touch](images/devices/sonoff-touch.jpg)| +|**IteadStudio Sonoff RF**|**IteadStudio Sonoff SV**|**IteadStudio Sonoff Touch**| +|![Wemos D1 Mini Relay Shield](images/devices/d1mini.jpg)|![Electrodragon Relay Board](images/devices/electrodragon-relay-board.jpg)|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg)| +|**Wemos D1 Mini Relay Shield**|**Electrodragon Relay Board**|**WorkChoice EcoPlug**| +|![JanGoe Wifi Relay (NO/NC)](images/devices/jangoe-wifi-relay.png)|![OpenEnergyMonitor WiFi MQTT Relay / Thermostat](images/devices/mqtt-relay.jpg)|| +|**JanGoe Wifi Relay (NO/NC)**|**OpenEnergyMonitor WiFi MQTT Relay / Thermostat**|| ## License -Copyright (C) 2016-2017 by Xose P�rez (@xoseperez) +Copyright (C) 2016-2017 by Xose Pérez (@xoseperez) 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 diff --git a/code/build-fs b/code/build-fs new file mode 100755 index 00000000..d2ca4286 --- /dev/null +++ b/code/build-fs @@ -0,0 +1,2 @@ +#!/bin/bash +node node_modules/gulp/bin/gulp.js diff --git a/code/espurna/config/arduino.h b/code/espurna/config/arduino.h index 489c42b8..437148c3 100644 --- a/code/espurna/config/arduino.h +++ b/code/espurna/config/arduino.h @@ -28,6 +28,7 @@ //#define WIFI_RELAY_NC //#define WIFI_RELAY_NO //#define ESPURNA +//#define MQTT_RELAY //-------------------------------------------------------------------------------- // Features (values below are non-default values) diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 4d0e3b5a..5e0acb74 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -7,6 +7,13 @@ #define BUFFER_SIZE 1024 #define HEARTBEAT_INTERVAL 300000 +//-------------------------------------------------------------------------------- +// EEPROM +//-------------------------------------------------------------------------------- + +#define EEPROM_RELAY_STATUS 0 +#define EEPROM_POWER_COUNT 1 + //-------------------------------------------------------------------------------- // RELAY //-------------------------------------------------------------------------------- diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 88fa41b2..49330d1a 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -256,6 +256,21 @@ #define LED1_PIN 13 #define LED1_PIN_INVERSE 0 +// ----------------------------------------------------------------------------- +// WiFi MQTT Relay / Thermostat +// ----------------------------------------------------------------------------- + +#elif defined(MQTT_RELAY) + + #define MANUFACTURER "OPENENERGYMONITOR" + #define DEVICE "MQTT_RELAY" + #define BUTTON1_PIN 0 + #define BUTTON1_RELAY 1 + #define RELAY1_PIN 12 + #define RELAY1_PIN_INVERSE 0 + #define LED1_PIN 16 + #define LED1_PIN_INVERSE 0 + // ----------------------------------------------------------------------------- // Unknown hardware // ----------------------------------------------------------------------------- diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index 2c4fe604..c3a56929 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -12,3 +12,4 @@ void mqttRegister(void (*callback)(unsigned int, const char *, const char *)); template bool setSetting(const String& key, T value); template String getSetting(const String& key, T defaultValue); template void domoticzSend(const char * key, T value); +template void domoticzSend(const char * key, T nvalue, const char * svalue); diff --git a/code/espurna/config/sensors.h b/code/espurna/config/sensors.h index fa69ee5a..a39793e5 100644 --- a/code/espurna/config/sensors.h +++ b/code/espurna/config/sensors.h @@ -20,6 +20,11 @@ #define DHT_TEMPERATURE_TOPIC "/temperature" #define DHT_HUMIDITY_TOPIC "/humidity" +#define HUMIDITY_NORMAL 0 +#define HUMIDITY_COMFORTABLE 1 +#define HUMIDITY_DRY 2 +#define HUMIDITY_WET 3 + //-------------------------------------------------------------------------------- // DS18B20 temperature sensor // Enable support by passing ENABLE_DS18B20=1 build flag @@ -46,6 +51,7 @@ #define EMON_MAINS_VOLTAGE 230 #define EMON_CURRENT_RATIO 180 #define EMON_POWER_TOPIC "/power" +#define EMON_ENERGY_TOPIC "/energy" //-------------------------------------------------------------------------------- // HLW8012 power sensor (Sonoff POW) diff --git a/code/espurna/config/version.h b/code/espurna/config/version.h index fd2a8b39..e7c0c185 100644 --- a/code/espurna/config/version.h +++ b/code/espurna/config/version.h @@ -1,4 +1,4 @@ #define APP_NAME "ESPurna" -#define APP_VERSION "1.5.0" +#define APP_VERSION "1.5.3" #define APP_AUTHOR "xose.perez@gmail.com" #define APP_WEBSITE "http://tinkerman.cat" diff --git a/code/espurna/dht.ino b/code/espurna/dht.ino index 61ba98f5..c1aeb45e 100644 --- a/code/espurna/dht.ino +++ b/code/espurna/dht.ino @@ -73,8 +73,22 @@ void dhtLoop() { // Send to Domoticz #if ENABLE_DOMOTICZ - domoticzSend("dczTmpIdx", temperature); - domoticzSend("dczHumIdx", humidity); + { + domoticzSend("dczTmpIdx", 0, temperature); + int status; + if (h > 70) { + status = HUMIDITY_WET; + } else if (h > 45) { + status = HUMIDITY_COMFORTABLE; + } else if (h > 30) { + status = HUMIDITY_NORMAL; + } else { + status = HUMIDITY_DRY; + } + char buffer[2]; + sprintf(buffer, "%d", status); + domoticzSend("dczHumIdx", humidity, buffer); + } #endif // Update websocket clients diff --git a/code/espurna/domoticz.ino b/code/espurna/domoticz.ino index 607acb8c..6534b91f 100644 --- a/code/espurna/domoticz.ino +++ b/code/espurna/domoticz.ino @@ -8,13 +8,17 @@ Copyright (C) 2016-2017 by Xose Pérez #if ENABLE_DOMOTICZ -template void domoticzSend(const char * key, T value) { +template void domoticzSend(const char * key, T nvalue, const char * svalue) { unsigned int idx = getSetting(key).toInt(); if (idx > 0) { - char payload[45]; - sprintf(payload, "{\"idx\": %d, \"nvalue\": %s, \"svalue\": \"\"}", idx, String(value).c_str()); + char payload[128]; + snprintf(payload, 128, "{\"idx\": %d, \"nvalue\": %s, \"svalue\": \"%s\"}", idx, String(nvalue).c_str(), svalue); mqttSendRaw(getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC).c_str(), payload); } } +template void domoticzSend(const char * key, T nvalue) { + domoticzSend(key, nvalue, ""); +} + #endif diff --git a/code/espurna/ds18b20.ino b/code/espurna/ds18b20.ino index 2011530f..7424241b 100644 --- a/code/espurna/ds18b20.ino +++ b/code/espurna/ds18b20.ino @@ -62,7 +62,7 @@ void dsLoop() { // Send to Domoticz #if ENABLE_DOMOTICZ - domoticzSend("dczTmpIdx", temperature); + domoticzSend("dczTmpIdx", 0, temperature); #endif // Update websocket clients diff --git a/code/espurna/emon.ino b/code/espurna/emon.ino index c9abb550..892fc2bf 100644 --- a/code/espurna/emon.ino +++ b/code/espurna/emon.ino @@ -9,10 +9,12 @@ Copyright (C) 2016-2017 by Xose Pérez #if ENABLE_EMON #include +#include EmonLiteESP emon; double _current = 0; unsigned int _power = 0; +double _energy = 0; // ----------------------------------------------------------------------------- // EMON @@ -26,6 +28,10 @@ unsigned int getPower() { return _power; } +double getEnergy() { + return _energy; +} + double getCurrent() { return _current; } @@ -34,6 +40,20 @@ unsigned int currentCallback() { return analogRead(EMON_CURRENT_PIN); } +void retrieveEnergy() { + unsigned long energy = EEPROM.read(EEPROM_POWER_COUNT + 1); + energy = (energy << 8) + EEPROM.read(EEPROM_POWER_COUNT); + if (energy == 0xFFFF) energy = 0; + _energy = energy; +} + +void saveEnergy() { + unsigned int energy = (int) _energy; + EEPROM.write(EEPROM_POWER_COUNT, energy & 0xFF); + EEPROM.write(EEPROM_POWER_COUNT + 1, (energy >> 8) & 0xFF); + EEPROM.commit(); +} + void powerMonitorSetup() { // backwards compatibility @@ -56,6 +76,11 @@ void powerMonitorSetup() { apiRegister("/api/power", "power", [](char * buffer, size_t len) { snprintf(buffer, len, "%d", _power); }); + apiRegister("/api/energy", "energy", [](char * buffer, size_t len) { + snprintf(buffer, len, "%ld", (unsigned long) _energy); + }); + + retrieveEnergy(); } @@ -108,14 +133,24 @@ void powerMonitorLoop() { if (measurements == EMON_MEASUREMENTS) { _power = (int) ((sum - max - min) * mainsVoltage / (measurements - 2)); + double window = (double) EMON_INTERVAL * EMON_MEASUREMENTS / 1000.0 / 3600.0; + _energy += _power * window; + saveEnergy(); sum = 0; measurements = 0; char power[6]; - snprintf(power, "%d", 6, _power); + snprintf(power, 6, "%d", _power); + char energy[8]; + snprintf(energy, 6, "%ld", (unsigned long) _energy); mqttSend(getSetting("emonPowerTopic", EMON_POWER_TOPIC).c_str(), power); + mqttSend(getSetting("emonEnergyTopic", EMON_ENERGY_TOPIC).c_str(), energy); #if ENABLE_DOMOTICZ - domoticzSend("dczPowIdx", power); + { + char buffer[20]; + snprintf(buffer, 20, "%s;%s", power, energy); + domoticzSend("dczPowIdx", 0, buffer); + } #endif diff --git a/code/espurna/ntp.ino b/code/espurna/ntp.ino index 0772ef20..99cbdbaa 100644 --- a/code/espurna/ntp.ino +++ b/code/espurna/ntp.ino @@ -14,7 +14,7 @@ Copyright (C) 2016-2017 by Xose Pérez // NTP // ----------------------------------------------------------------------------- -void ntpConnect(WiFiEventStationModeGotIP ipInfo) { +void ntpConnect() { NTP.begin(NTP_SERVER, NTP_TIME_OFFSET, NTP_DAY_LIGHT); NTP.setInterval(NTP_UPDATE_INTERVAL); } @@ -33,9 +33,6 @@ void ntpSetup() { } }); - static WiFiEventHandler e; - e = WiFi.onStationModeGotIP(ntpConnect); - } void ntpLoop() { diff --git a/code/espurna/pow.ino b/code/espurna/pow.ino index ac2e43b9..a575baf7 100644 --- a/code/espurna/pow.ino +++ b/code/espurna/pow.ino @@ -12,9 +12,11 @@ Copyright (C) 2016-2017 by Xose Pérez #include #include #include +#include HLW8012 hlw8012; bool _powEnabled = false; +double _energy = 0; // ----------------------------------------------------------------------------- // POW @@ -117,8 +119,26 @@ unsigned int getPowerFactor() { return (int) (100 * hlw8012.getPowerFactor()); } +double getEnergy() { + return _energy; +} + // ----------------------------------------------------------------------------- +void retrieveEnergy() { + unsigned long energy = EEPROM.read(EEPROM_POWER_COUNT + 1); + energy = (energy << 8) + EEPROM.read(EEPROM_POWER_COUNT); + if (energy == 0xFFFF) energy = 0; + _energy = energy; +} + +void saveEnergy() { + unsigned int energy = (int) _energy; + EEPROM.write(EEPROM_POWER_COUNT, energy & 0xFF); + EEPROM.write(EEPROM_POWER_COUNT + 1, (energy >> 8) & 0xFF); + EEPROM.commit(); +} + void powSetup() { // Initialize HLW8012 @@ -143,10 +163,16 @@ void powSetup() { // Retrieve calibration values powRetrieveCalibration(); + // Recover energy reading + retrieveEnergy(); + // API definitions apiRegister("/api/power", "power", [](char * buffer, size_t len) { snprintf(buffer, len, "%d", getActivePower()); }); + apiRegister("/api/energy", "energy", [](char * buffer, size_t len) { + snprintf(buffer, len, "%ld", (unsigned long) _energy); + }); apiRegister("/api/current", "current", [](char * buffer, size_t len) { dtostrf(getCurrent(), len-1, 2, buffer); }); @@ -216,8 +242,12 @@ void powLoop() { reactive = (apparent > power) ? sqrt(apparent * apparent - power * power) : 0; factor = (apparent > 0) ? 100 * power / apparent : 100; if (factor > 100) factor = 100; + unsigned long window = (double) POW_REPORT_EVERY * POW_UPDATE_INTERVAL / 1000.0 / 3600.0; + _energy += power * window; + saveEnergy(); mqttSend(getSetting("powPowerTopic", POW_POWER_TOPIC).c_str(), String(power).c_str()); + mqttSend(getSetting("powEnergyTopic", POW_ENERGY_TOPIC).c_str(), String(_energy).c_str()); mqttSend(getSetting("powCurrentTopic", POW_CURRENT_TOPIC).c_str(), String(current).c_str()); mqttSend(getSetting("powVoltageTopic", POW_VOLTAGE_TOPIC).c_str(), String(voltage).c_str()); mqttSend(getSetting("powAPowerTopic", POW_APOWER_TOPIC).c_str(), String(apparent).c_str()); @@ -225,7 +255,11 @@ void powLoop() { mqttSend(getSetting("powPFactorTopic", POW_PFACTOR_TOPIC).c_str(), String(factor).c_str()); #if ENABLE_DOMOTICZ - domoticzSend("dczPowIdx", power); + { + char buffer[20]; + snprintf(buffer, 20, "%d;%ld", power, (unsigned long) _energy); + domoticzSend("dczPowIdx", 0, buffer); + } #endif power_sum = current_sum = voltage_sum = 0; diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index 433e3610..ed4c99cf 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -208,20 +208,21 @@ void relaySave() { if (relayStatus(i)) mask += bit; bit += bit; } - EEPROM.write(0, mask); + EEPROM.write(EEPROM_RELAY_STATUS, mask); EEPROM.commit(); } void relayRetrieve(bool invert) { recursive = true; unsigned char bit = 1; - unsigned char mask = invert ? ~EEPROM.read(0) : EEPROM.read(0); + unsigned char mask = invert ? ~EEPROM.read(EEPROM_RELAY_STATUS) : EEPROM.read(EEPROM_RELAY_STATUS); for (unsigned int i=0; i < _relays.size(); i++) { relayStatus(i, ((mask & bit) == bit)); bit += bit; } if (invert) { - relaySave(); + EEPROM.write(EEPROM_RELAY_STATUS, mask); + EEPROM.commit(); } recursive = false; } @@ -385,6 +386,7 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo if (type == MQTT_MESSAGE_EVENT) { // Match topic + if (strncmp(topic, "domoticz", 8) == 0) return; String t = String(topic + mqttTopicRootLength()); if (!t.startsWith(MQTT_RELAY_TOPIC)) return; if (!t.endsWith(mqttSetter)) return; diff --git a/code/espurna/wifi.ino b/code/espurna/wifi.ino index 70335e32..8826c256 100644 --- a/code/espurna/wifi.ino +++ b/code/espurna/wifi.ino @@ -168,6 +168,11 @@ void wifiSetup() { dnsServer.stop(); } + // NTP connection reset + if (code == MESSAGE_CONNECTED) { + ntpConnect(); + } + // Manage POW #if ENABLE_POW if (code == MESSAGE_CONNECTED) { diff --git a/code/html/index.html b/code/html/index.html index 9e2448f7..3764f358 100644 --- a/code/html/index.html +++ b/code/html/index.html @@ -391,19 +391,19 @@ -
+
Set to 0 to disable notifications.
-
+
Set to 0 to disable notifications.
-
+
Set to 0 to disable notifications.
diff --git a/code/pio_hooks.py b/code/pio_hooks.py index 5b818421..e6037ad8 100644 --- a/code/pio_hooks.py +++ b/code/pio_hooks.py @@ -6,6 +6,6 @@ from SCons.Script import DefaultEnvironment env = DefaultEnvironment() def before_build_spiffs(source, target, env): - env.Execute("gulp") + env.Execute("node node_modules/gulp/bin/gulp.js") env.AddPreAction(".pioenvs/%s/spiffs.bin" % env['PIOENV'], before_build_spiffs) diff --git a/code/platformio.ini b/code/platformio.ini index cb9ca7eb..843b95d4 100644 --- a/code/platformio.ini +++ b/code/platformio.ini @@ -284,3 +284,24 @@ build_flags = -g -DDEBUG_PORT=Serial -DWIFI_RELAY_NC upload_speed = 115200 upload_port = "192.168.4.1" upload_flags = --auth=fibonacci --port 8266 + +[env:mqtt-relay-debug] +platform = espressif8266 +framework = arduino +board = esp_wroom_02 +lib_deps = ${common.lib_deps} +lib_ignore = ${common.lib_ignore} +extra_script = pio_hooks.py +build_flags = -g -DDEBUG_PORT=Serial -DMQTT_RELAY -DENABLE_DS18B20=1 + +[env:mqtt-relay-debug-ota] +platform = espressif8266 +framework = arduino +board = esp_wroom_02 +lib_deps = ${common.lib_deps} +lib_ignore = ${common.lib_ignore} +extra_script = pio_hooks.py +build_flags = -g -DDEBUG_PORT=Serial -DMQTT_RELAY -DENABLE_DS18B20=1 +upload_speed = 115200 +upload_port = "192.168.4.1" +upload_flags = --auth=fibonacci --port 8266 diff --git a/images/devices/mqtt-relay.jpg b/images/devices/mqtt-relay.jpg new file mode 100644 index 00000000..e12231c9 Binary files /dev/null and b/images/devices/mqtt-relay.jpg differ