added ESPurna version

This commit is contained in:
proddy
2018-06-06 17:55:05 +02:00
parent db519d2238
commit ee2bec847b
8 changed files with 2251 additions and 16 deletions

View File

@@ -382,28 +382,46 @@ Roughly these are the steps needed when running Windows:
### Using ESPurna
*Note: This is still work in progress. The ESPurna code for the HTML config is still to be added.*
[ESPurna](https://github.com/xoseperez/espurna/wiki) is framework that handles most of the tedious tasks of building IoT devices so you can focus on the functionality you need. This replaces my ESPHelper code in the standalone version above. ESPurna is built on PlatformIO and Visual Studio Code too. Follow these steps:
[ESPurna](https://github.com/xoseperez/espurna/wiki) is framework that handles most of the tedious tasks of building IoT devices so you can focus on the functionality you need. This replaces my ESPHelper code in the standalone version above. ESPurna is natively built on PlatformIO and Visual Studio Code too which is nice. So if you're brave, follow these steps:
- Download and install [NodeJS and npm](https://nodejs.org/en/download). Choose the LTS version.
- Download espurna by cloning the ESPurna git repository from `https://github.com/xoseperez/espurna.git`.
- Restart VSC. PlatformIO should detect and set some things up for you automagically.
- From VSC open the folder ``espurna\code``
- open a terminal window (ctrl-`)
- Install the node modules: ``npm install --only=dev``
- Build the web interface: ``node node_modules/gulp/bin/gulp.js``
- Finally copy the files `custom.h, index.html, boiler.ino and the esp*.cpp/h` files from the `espurna` directory in this repo to the code directory and build.
1. Download and install [NodeJS](https://nodejs.org/en/download). This gives you npm. Choose the LTS version
2. Download ESPurna by cloning the ESPurna git repository from `https://github.com/xoseperez/espurna.git`
3. Restart VSC. PlatformIO should detect and set some things up for you automagically
4. From VSC open the folder `espurna\code`
5. open a terminal window (*ctrl-`*)
6. Install the node modules: `npm install --only=dev`
7. Build the web interface: `node node_modules/gulp/bin/gulp.js`. This will create a compressed `code/espurna/static/index.html.gz.h`
8. First time users build the filesystem by *ctrl-alt-t* and run the task 'uploadfs'
9. Copy the files from this repo's *espurna* directory to where you installed ESPurna
If you run into issues refer to official ESPurnas setup instructions [here](https://github.com/xoseperez/espurna/wiki/Build-and-update-from-Visual-Studio-Code-using-PlatformIO).
```
code/html/index.html
code/config/custom.h
code/espurna/boiler-espurna.ino
code/espurna/ems*.*
```
10. Now build and upload as you usually would. Look at my version of platformio.ini as an example.
If you run into issues refer to ESPurna's official setup instructions [here](https://github.com/xoseperez/espurna/wiki/Build-and-update-from-Visual-Studio-Code-using-PlatformIO).
This is what ESPurna looks like with the custom boiler code:
![Example running in ESPurna](doc/espurna/example.PNG)
### Using the pre-built firmware's
I will eventually put pre-built version based on ESPurna in the directory `/firmware` which you can upload using [esptool](https://github.com/espressif/esptool) bootloader. On Windows, follow these instructions:
pre-baked firmwares for some ESP8266 devices based on ESPurna are available in the directory `/firmware` which you can upload yourself using [esptool](https://github.com/espressif/esptool) bootloader. On Windows, follow these instructions:
1. Check if you have python 2.7 installed. If not [download it](https://www.python.org/downloads/) and make sure you select the option to add Python to the windows PATH.
2. Install the ESPTool by running `pip install esptool` from a command prompt.
3. Connect the ESP via USB, figure out the COM port.
1. Check if you have **python 2.7** installed. If not [download it](https://www.python.org/downloads/) and make sure you select the option to add Python to the windows PATH
2. Install the ESPTool by running `pip install esptool` from a command prompt
3. Connect the ESP via USB, figure out the COM port
4. run `esptool.py -p <com> write_flash 0x00000 <firmware>` where firmware is the `.bin` file and \<com\> is the COM port, e.g. `COM3`
5. Connect using WiFi from a phone or PC to the "**ESPURNA_XXXXXX**" network. Pasword is '**fibonacci**'
6. Once connected browse to "http://192.168.4.1" and setup your wifi, mqtt etc
Again, if you run into problems read [this](https://github.com/xoseperez/espurna/wiki/Configuration) from ESPurna's configuation help page.
### Using the Arduino IDE
@@ -435,7 +453,6 @@ Some annoying issues that need fixing:
Here's my top things I'm still working on:
- Make an ESPurna version. ESPurna is a lovely framework that takes care of the WiFi, MQTT, web server, telnet & debugging.
- Complete the ESP32 version. It's surprisingly a lot easier doing the UART handling on an ESP32 with the ESP-IDF SDK. I have a first version beta that is working.
- Find a better way to control the 3-way valve to switch the warm water off quickly rather than deactivating the warm water heater each time. There is an unsupported call to do this, but I think its too risky and experimental. I'd love to get my hands on an Nefit Easy, sniff the packets being sent and reverse engineer the logic. Anyone help?

BIN
doc/espurna/example.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

359
espurna/boiler-espurna.ino Normal file
View File

@@ -0,0 +1,359 @@
// Boiler
// Espurna version
// Paul Derbyshire - https://github.com/proddy/EMS-ESP-Boiler
#include "emsuart.h"
#include <ArduinoJson.h>
#include <ems.h>
#define myDebug(...) debugSend(__VA_ARGS__)
#define BOILER_THERMOSTAT_ENABLED 1
#define BOILER_SHOWER_ENABLED 1
#define BOILER_POLLING_ENABLED 0
#define BOILER_LOGGING_NONE 1
typedef struct {
bool wifi_connected;
bool boiler_online;
bool thermostat_enabled;
bool shower_enabled; // true if we want to report back on shower times
bool shower_timer; // true if we want the cold water reminder
} _Boiler_Status;
typedef struct {
bool showerOn;
unsigned long timerStart; // ms
unsigned long timerPause; // ms
unsigned long duration; // ms
bool isColdShot; // true if we've just sent a jolt of cold water
} _Boiler_Shower;
// store for overall system status
_Boiler_Status Boiler_Status;
_Boiler_Shower Boiler_Shower;
// Config
void _boilerConfigure() {
Boiler_Status.thermostat_enabled = getSetting("boilerThermostat", BOILER_THERMOSTAT_ENABLED).toInt() == 1;
ems_setThermostatEnabled(Boiler_Status.thermostat_enabled);
bool _boilerPolling = getSetting("boilerPolling", BOILER_POLLING_ENABLED).toInt() == 1;
ems_setPoll(_boilerPolling);
uint8_t _boilerLogging = getSetting("boilerLogging", BOILER_LOGGING_NONE).toInt();
ems_setLogging((_EMS_SYS_LOGGING)_boilerLogging);
Boiler_Status.shower_enabled = getSetting("boilerShower", BOILER_SHOWER_ENABLED).toInt() == 1;
}
// WEB callbacks
bool _boilerWebSocketOnReceive(const char * key, JsonVariant & value) {
return (strncmp(key, "boiler", 6) == 0);
}
void _boilerWebSocketOnSend(JsonObject & root) {
root["boilerThermostat"] = getSetting("boilerThermostat", BOILER_THERMOSTAT_ENABLED).toInt() == 1;
root["boilerShower"] = getSetting("boilerShower", BOILER_SHOWER_ENABLED).toInt() == 1;
root["boilerPolling"] = getSetting("boilerPolling", BOILER_POLLING_ENABLED).toInt() == 1;
root["boilerLogging"] = getSetting("boilerLogging", BOILER_LOGGING_NONE).toInt();
}
// used if we have a button
void _boilerWebSocketOnAction(uint32_t client_id, const char * action, JsonObject & data) {
}
// send to HA
void sendHA() {
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX);
String output;
// assume ha is enabled
DynamicJsonBuffer jsonBuffer;
JsonObject & config = jsonBuffer.createObject();
String name = getSetting("hostname");
config.set("name", name);
config.set("platform", "mqtt");
config.printTo(output);
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
// Show command - display stats on an 's' command
void showInfo() {
char s[10]; // for formatting floats using the _float_to_char() function
// General stats from EMS bus
myDebug("EMS Bus stats:\n");
myDebug(" Thermostat is %s, Poll is %s, Shower is %s, Shower timer is %s, RxPgks=%d, TxPkgs=%d, #CrcErrors=%d",
((Boiler_Status.thermostat_enabled) ? "enabled" : "disabled"),
((EMS_Sys_Status.emsPollEnabled) ? "enabled" : "disabled"),
((Boiler_Status.shower_enabled) ? "enabled" : "disabled"),
((Boiler_Status.shower_timer) ? "enabled" : "disabled"),
EMS_Sys_Status.emsRxPgks,
EMS_Sys_Status.emsTxPkgs,
EMS_Sys_Status.emxCrcErr);
myDebug(", RxStatus=");
switch (EMS_Sys_Status.emsRxStatus) {
case EMS_RX_IDLE:
myDebug("idle");
break;
case EMS_RX_ACTIVE:
myDebug("active");
break;
}
myDebug(", TxStatus=");
switch (EMS_Sys_Status.emsTxStatus) {
case EMS_TX_IDLE:
myDebug("idle");
break;
case EMS_TX_PENDING:
myDebug("pending");
break;
case EMS_TX_ACTIVE:
myDebug("active");
break;
}
myDebug(", TxAction=");
switch (EMS_TxTelegram.action) {
case EMS_TX_READ:
myDebug("read");
break;
case EMS_TX_WRITE:
myDebug("write");
break;
case EMS_TX_VALIDATE:
myDebug("validate");
break;
case EMS_TX_NONE:
myDebug("none");
break;
}
myDebug("\nBoiler stats:\n");
// UBAMonitorWWMessage & UBAParameterWW
myDebug(" Warm Water activated: %s\n", (EMS_Boiler.wWActivated ? "yes" : "no"));
myDebug(" Warm Water selected temperature: %d C\n", EMS_Boiler.wWSelTemp);
myDebug(" Warm Water circulation pump available: %s\n", (EMS_Boiler.wWCircPump ? "yes" : "no"));
myDebug(" Warm Water desired temperature: %d C\n", EMS_Boiler.wWDesiredTemp);
myDebug(" Warm Water current temperature: %s C\n", _float_to_char(s, EMS_Boiler.wWCurTmp));
myDebug(" Warm Water # starts: %d times\n", EMS_Boiler.wWStarts);
myDebug(" Warm Water active time: %d days %d hours %d minutes\n",
EMS_Boiler.wWWorkM / 1440,
(EMS_Boiler.wWWorkM % 1440) / 60,
EMS_Boiler.wWWorkM % 60);
myDebug(" Warm Water 3-way valve: %s\n", EMS_Boiler.wWHeat ? "on" : "off");
// UBAMonitorFast
myDebug(" Selected flow temperature: %d C\n", EMS_Boiler.selFlowTemp);
myDebug(" Current flow temperature: %s C\n", _float_to_char(s, EMS_Boiler.curFlowTemp));
myDebug(" Return temperature: %s C\n", _float_to_char(s, EMS_Boiler.retTemp));
myDebug(" Gas: %s\n", EMS_Boiler.burnGas ? "on" : "off");
myDebug(" Boiler pump: %s\n", EMS_Boiler.heatPmp ? "on" : "off");
myDebug(" Fan: %s\n", EMS_Boiler.fanWork ? "on" : "off");
myDebug(" Ignition: %s\n", EMS_Boiler.ignWork ? "on" : "off");
myDebug(" Circulation pump: %s\n", EMS_Boiler.wWCirc ? "on" : "off");
myDebug(" Burner max power: %d %%\n", EMS_Boiler.selBurnPow);
myDebug(" Burner current power: %d %%\n", EMS_Boiler.curBurnPow);
myDebug(" Flame current: %s uA\n", _float_to_char(s, EMS_Boiler.flameCurr));
myDebug(" System pressure: %s bar\n", _float_to_char(s, EMS_Boiler.sysPress));
// UBAMonitorSlow
myDebug(" Outside temperature: %s C\n", _float_to_char(s, EMS_Boiler.extTemp));
myDebug(" Boiler temperature: %s C\n", _float_to_char(s, EMS_Boiler.boilTemp));
myDebug(" Pump modulation: %d %%\n", EMS_Boiler.pumpMod);
myDebug(" # burner restarts: %d\n", EMS_Boiler.burnStarts);
myDebug(" Total burner operating time: %d days %d hours %d minutes\n",
EMS_Boiler.burnWorkMin / 1440,
(EMS_Boiler.burnWorkMin % 1440) / 60,
EMS_Boiler.burnWorkMin % 60);
myDebug(" Total heat operating time: %d days %d hours %d minutes\n",
EMS_Boiler.heatWorkMin / 1440,
(EMS_Boiler.heatWorkMin % 1440) / 60,
EMS_Boiler.heatWorkMin % 60);
// Thermostat stats
if (Boiler_Status.thermostat_enabled) {
myDebug("Thermostat stats:\n Thermostat time is %02d:%02d:%02d %d/%d/%d\n",
EMS_Thermostat.hour,
EMS_Thermostat.minute,
EMS_Thermostat.second,
EMS_Thermostat.day,
EMS_Thermostat.month,
EMS_Thermostat.year + 2000);
myDebug(" Setpoint room temperature is %s C\n", _float_to_char(s, EMS_Thermostat.setpoint_roomTemp));
myDebug(" Current room temperature is %s C\n", _float_to_char(s, EMS_Thermostat.curr_roomTemp));
myDebug(" Mode is set to ");
if (EMS_Thermostat.mode == 0) {
myDebug("low\n");
} else if (EMS_Thermostat.mode == 1) {
myDebug("manual\n");
} else if (EMS_Thermostat.mode == 2) {
myDebug("clock/auto\n");
} else {
myDebug("<unknown>\n");
}
}
// show the Shower Info
if (Boiler_Status.shower_enabled) {
myDebug("Shower stats:\n Shower is %s\n", (Boiler_Shower.showerOn ? "on" : "off"));
char s[70];
uint8_t sec = (uint8_t)((Boiler_Shower.duration / 1000) % 60);
uint8_t min = (uint8_t)((Boiler_Shower.duration / (1000 * 60)) % 60);
sprintf(s, " Last shower duration was %d minutes and %d %s\n", min, sec, (sec == 1) ? "second" : "seconds");
myDebug(s);
}
myDebug("\n");
}
// Init telnet commands
void _boilerInitCommands() {
settingsRegisterCommand(F("BOILER.INFO"), [](Embedis * e) {
showInfo();
DEBUG_MSG(_boilerGetConfig().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("BOILER.POLLING"), [](Embedis * e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: arg is 0 or 1\n"));
return;
}
int param = String(e->argv[1]).toInt();
bool b = setSetting("boilerPolling", param);
if (b) {
_boilerConfigure();
}
wsSend(_boilerWebSocketOnSend); // update web
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("BOILER.LOGGING"), [](Embedis * e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: arg is 0 or 1\n"));
return;
}
int param = String(e->argv[1]).toInt();
bool b = setSetting("boilerLogging", param);
if (b) {
_boilerConfigure();
}
wsSend(_boilerWebSocketOnSend); // update web
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("BOILER.SEND"), [](Embedis * e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
int cmd = String(e->argv[1]).toInt();
DEBUG_MSG_P(PSTR("Sending %d\n"), cmd);
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
void _boilerMQTTCallback(unsigned int type, const char * topic, const char * payload) {
/*
if (type == MQTT_CONNECT_EVENT) {
char buffer[strlen(MQTT_TOPIC_LED) + 3];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_LED);
mqttSubscribe(buffer);
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttMagnitude((char *) topic);
if (!t.startsWith(MQTT_TOPIC_LED)) return;
// Get led ID
unsigned int ledID = t.substring(strlen(MQTT_TOPIC_LED)+1).toInt();
if (ledID >= _ledCount()) {
DEBUG_MSG_P(PSTR("[LED] Wrong ledID (%d)\n"), ledID);
return;
}
// Check if LED is managed
if (_ledMode(ledID) != LED_MODE_MQTT) return;
// get value
unsigned char value = relayParsePayload(payload);
// Action to perform
if (value == 2) {
_ledToggle(ledID);
} else {
_ledStatus(ledID, value == 1);
}
}
*/
}
// TELNET commands
String _boilerGetConfig() {
String output;
// get values and print them
output = String("Thermostat is ") + getSetting("boilerThermostat") + String(", Polling is ")
+ getSetting("boilerPolling") + String(", Logging is ") + getSetting("boilerLogging")
+ String(", Shower is ") + getSetting("boilerShower") + String("\n");
return output;
}
// SETUP
void extraSetup() {
boilerSetup();
}
void boilerSetup() {
// configure
_boilerConfigure();
wsOnSendRegister(_boilerWebSocketOnSend);
wsOnAfterParseRegister(_boilerConfigure);
wsOnActionRegister(_boilerWebSocketOnAction);
wsOnReceiveRegister(_boilerWebSocketOnReceive);
mqttRegister(_boilerMQTTCallback);
_boilerInitCommands();
// init shower
Boiler_Shower.timerStart = 0;
Boiler_Shower.timerPause = 0;
Boiler_Shower.duration = 0;
Boiler_Shower.isColdShot = false;
// ems init values
ems_init();
// start uart
emsuart_init();
// Register loop
espurnaRegisterLoop(_boilerLoop);
}
// LOOP
void _boilerLoop() {
}

28
espurna/custom.h Normal file
View File

@@ -0,0 +1,28 @@
// export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'"
// e.g.
// build_flags = -g -DMQTT_MAX_PACKET_SIZE=400 ${env.ESPURNA_FLAGS} -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH -DUSE_CUSTOM_H -DUSE_EXTRA
#undef EMBEDDED_WEB
#define EMBEDDED_WEB 1
#undef NTP_SERVER
#define NTP_SERVER "nl.pool.ntp.org"
//#undef MQTT_TOPIC
//#define MQTT_TOPIC "/{identifier}"
// default is "{hostname}"
#undef ENABLE_DOMOTICZ
#define ENABLE_DOMOTICZ 0
#undef THINGSPEAK_SUPPORT
#define THINGSPEAK_SUPPORT 0
#undef SCHEDULER_SUPPORT
#define SCHEDULER_SUPPORT 0
#undef ENABLE_FAUXMO
#define ENABLE_FAUXMO 0
#undef DEBUG_SERIAL_SUPPORT
#define DEBUG_SERIAL_SUPPORT 0

1831
espurna/index.html Normal file

File diff suppressed because it is too large Load Diff

BIN
firmware/d1_mini.bin Normal file

Binary file not shown.

BIN
firmware/nodemcuv2.bin Normal file

Binary file not shown.