mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-08 16:59:50 +03:00
1.5.7b
This commit is contained in:
@@ -1,895 +0,0 @@
|
||||
/*
|
||||
Based off :
|
||||
1) ESPHelper.cpp - Copyright (c) 2017 ItKindaWorks Inc All right reserved. github.com/ItKindaWorks
|
||||
2) https://github.com/JoaoLopesF/ESP8266-RemoteDebug-Telnet
|
||||
|
||||
*/
|
||||
|
||||
#include "ESPHelper.h"
|
||||
|
||||
WiFiServer telnetServer(TELNET_PORT);
|
||||
|
||||
//initializer with single netInfo network
|
||||
ESPHelper::ESPHelper(netInfo * startingNet) {
|
||||
//disconnect from and previous wifi networks
|
||||
WiFi.softAPdisconnect();
|
||||
WiFi.disconnect();
|
||||
|
||||
//setup current network information
|
||||
_currentNet = *startingNet;
|
||||
|
||||
//validate various bits of network/MQTT info
|
||||
|
||||
//network pass
|
||||
if (_currentNet.pass[0] == '\0') {
|
||||
_passSet = false;
|
||||
} else {
|
||||
_passSet = true;
|
||||
}
|
||||
|
||||
//ssid
|
||||
if (_currentNet.ssid[0] == '\0') {
|
||||
_ssidSet = false;
|
||||
} else {
|
||||
_ssidSet = true;
|
||||
}
|
||||
|
||||
//mqtt host
|
||||
if (_currentNet.mqttHost[0] == '\0') {
|
||||
_mqttSet = false;
|
||||
} else {
|
||||
_mqttSet = true;
|
||||
}
|
||||
|
||||
//mqtt port
|
||||
if (_currentNet.mqttPort == 0) {
|
||||
_currentNet.mqttPort = 1883;
|
||||
}
|
||||
|
||||
//mqtt username
|
||||
if (_currentNet.mqttUser[0] == '\0') {
|
||||
_mqttUserSet = false;
|
||||
} else {
|
||||
_mqttUserSet = true;
|
||||
}
|
||||
|
||||
//mqtt password
|
||||
if (_currentNet.mqttPass[0] == '\0') {
|
||||
_mqttPassSet = false;
|
||||
} else {
|
||||
_mqttPassSet = true;
|
||||
}
|
||||
|
||||
//disable hopping on single network
|
||||
_hoppingAllowed = false;
|
||||
|
||||
//disable ota by default
|
||||
_useOTA = false;
|
||||
}
|
||||
|
||||
//start the wifi & mqtt systems and attempt connection (currently blocking)
|
||||
//true on: parameter check validated
|
||||
//false on: parameter check failed
|
||||
bool ESPHelper::begin(const char * hostname, const char * app_name, const char * app_version) {
|
||||
#ifdef USE_SERIAL1
|
||||
Serial1.begin(115200);
|
||||
Serial1.setDebugOutput(true);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL
|
||||
Serial.begin(115200);
|
||||
Serial.setDebugOutput(true);
|
||||
#endif
|
||||
|
||||
// set hostname first
|
||||
strcpy(_hostname, hostname);
|
||||
OTA_enable();
|
||||
|
||||
strcpy(_app_name, app_name); // app name
|
||||
strcpy(_app_version, app_version); // app version
|
||||
|
||||
|
||||
setBoottime("<unknown>");
|
||||
|
||||
if (_ssidSet) {
|
||||
strcpy(_clientName, hostname);
|
||||
|
||||
/*
|
||||
// Generate client name based on MAC address and last 8 bits of microsecond counter
|
||||
|
||||
_clientName += "esp-";
|
||||
uint8_t mac[6];
|
||||
WiFi.macAddress(mac);
|
||||
_clientName += macToStr(mac);
|
||||
*/
|
||||
|
||||
// set hostname
|
||||
// can ping by <hostname> or on Windows10 it's <hostname>.
|
||||
#if defined(ESP8266)
|
||||
WiFi.hostname(_hostname);
|
||||
#elif defined(ESP32)
|
||||
WiFi.setHostname(_hostname);
|
||||
#endif
|
||||
|
||||
//set the wifi mode to station and begin the wifi (connect using either ssid or ssid/pass)
|
||||
WiFi.mode(WIFI_STA);
|
||||
if (_passSet) {
|
||||
WiFi.begin(_currentNet.ssid, _currentNet.pass);
|
||||
} else {
|
||||
WiFi.begin(_currentNet.ssid);
|
||||
}
|
||||
|
||||
//as long as an mqtt ip has been set create an instance of PubSub for client
|
||||
if (_mqttSet) {
|
||||
//make mqtt client use either the secure or non-secure wifi client depending on the setting
|
||||
if (_useSecureClient) {
|
||||
client = PubSubClient(_currentNet.mqttHost, _currentNet.mqttPort, wifiClientSecure);
|
||||
} else {
|
||||
client = PubSubClient(_currentNet.mqttHost, _currentNet.mqttPort, wifiClient);
|
||||
}
|
||||
|
||||
//set the mqtt message callback if needed
|
||||
if (_mqttCallbackSet) {
|
||||
client.setCallback(_mqttCallback);
|
||||
}
|
||||
}
|
||||
|
||||
//define a dummy instance of mqtt so that it is instantiated if no mqtt ip is set
|
||||
else {
|
||||
//make mqtt client use either the secure or non-secure wifi client depending on the setting
|
||||
//(this shouldnt be needed if making a dummy connection since the idea would be that there wont be mqtt in this case)
|
||||
if (_useSecureClient) {
|
||||
client = PubSubClient("192.168.1.255", _currentNet.mqttPort, wifiClientSecure);
|
||||
} else {
|
||||
client = PubSubClient("192.168.1.255", _currentNet.mqttPort, wifiClient);
|
||||
}
|
||||
}
|
||||
|
||||
//ota event handlers
|
||||
ArduinoOTA.onStart([]() { /* ota start code */ });
|
||||
ArduinoOTA.onEnd([]() {
|
||||
//on ota end we disconnect from wifi cleanly before restarting.
|
||||
WiFi.softAPdisconnect();
|
||||
WiFi.disconnect();
|
||||
uint8_t timeout = 0;
|
||||
//max timeout of 2seconds before just dropping out and restarting
|
||||
while (WiFi.status() != WL_DISCONNECTED && timeout < 200) {
|
||||
timeout++;
|
||||
}
|
||||
});
|
||||
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { /* ota progress code */ });
|
||||
ArduinoOTA.onError([](ota_error_t error) { /* ota error code */ });
|
||||
|
||||
//initially attempt to connect to wifi when we begin (but only block for 2 seconds before timing out)
|
||||
uint8_t timeout = 0; //counter for begin connection attempts
|
||||
while (((!client.connected() && _mqttSet) || WiFi.status() != WL_CONNECTED)
|
||||
&& timeout < 200) { //max 2 sec before timeout
|
||||
reconnect();
|
||||
timeout++;
|
||||
}
|
||||
|
||||
//attempt to start ota if needed
|
||||
OTA_begin();
|
||||
|
||||
// Initialize the telnet server
|
||||
telnetServer.begin();
|
||||
telnetServer.setNoDelay(true);
|
||||
|
||||
// init command buffer for console commands
|
||||
memset(_command, 0, sizeof(_command));
|
||||
|
||||
consoleShowHelp(); // show this at bootup
|
||||
|
||||
// mark the system as started and return
|
||||
_hasBegun = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//if no ssid was set even then dont try to begin and return false
|
||||
return false;
|
||||
}
|
||||
|
||||
//end the instance of ESPHelper (shutdown wifi, ota, mqtt)
|
||||
void ESPHelper::end() {
|
||||
// Stop telnet Client & Server
|
||||
if (telnetClient && telnetClient.connected()) {
|
||||
telnetClient.stop();
|
||||
}
|
||||
|
||||
// Stop server
|
||||
telnetServer.stop();
|
||||
|
||||
OTA_disable();
|
||||
WiFi.softAPdisconnect();
|
||||
WiFi.disconnect();
|
||||
|
||||
uint8_t timeout = 0;
|
||||
while (WiFi.status() != WL_DISCONNECTED && timeout < 200) {
|
||||
timeout++;
|
||||
}
|
||||
|
||||
#ifdef USE_SERIAL
|
||||
Serial.flush();
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL1
|
||||
Serial1.flush();
|
||||
#endif
|
||||
}
|
||||
|
||||
//main loop - should be called as often as possible - handles wifi/mqtt connection and mqtt handler
|
||||
//true on: network/server connected
|
||||
//false on: network or server disconnected
|
||||
int ESPHelper::loop() {
|
||||
if (_ssidSet) {
|
||||
//check for good connections and attempt a reconnect if needed
|
||||
if (((_mqttSet && !client.connected()) || setConnectionStatus() < WIFI_ONLY) && _connectionStatus != BROADCAST) {
|
||||
reconnect();
|
||||
}
|
||||
|
||||
//run the wifi loop as long as the connection status is at a minimum of BROADCAST
|
||||
if (_connectionStatus >= BROADCAST) {
|
||||
//run the MQTT loop if we have a full connection
|
||||
if (_connectionStatus == FULL_CONNECTION) {
|
||||
client.loop();
|
||||
}
|
||||
|
||||
//check for whether we want to use OTA and whether the system is running
|
||||
if (_useOTA && _OTArunning) {
|
||||
ArduinoOTA.handle();
|
||||
}
|
||||
|
||||
//if we want to use OTA but its not running yet, start it up.
|
||||
else if (_useOTA && !_OTArunning) {
|
||||
OTA_begin();
|
||||
ArduinoOTA.handle();
|
||||
}
|
||||
|
||||
// do the telnet stuff
|
||||
consoleHandle();
|
||||
|
||||
return _connectionStatus;
|
||||
}
|
||||
}
|
||||
|
||||
//return -1 for no connection because of bad network info
|
||||
return -1;
|
||||
}
|
||||
|
||||
//subscribe to a specific topic (does not add to topic list)
|
||||
//true on: subscription success
|
||||
//false on: subscription failed (either from PubSub lib or network is disconnected)
|
||||
bool ESPHelper::subscribe(const char * topic, uint8_t qos) {
|
||||
if (_connectionStatus == FULL_CONNECTION) {
|
||||
//set the return value to the output of subscribe
|
||||
bool returnVal = client.subscribe(topic, qos);
|
||||
|
||||
//loop mqtt client
|
||||
client.loop();
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
//if not fully connected return false
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//add a topic to the list of subscriptions and attempt to subscribe to the topic on the spot
|
||||
//true on: subscription added to list (does not guarantee that the topic was subscribed to, only that it was added to the list)
|
||||
//false on: subscription not added to list
|
||||
bool ESPHelper::addSubscription(const char * topic) {
|
||||
//default return value is false
|
||||
bool subscribed = false;
|
||||
|
||||
//loop through finding the next available slot for a subscription and add it
|
||||
for (uint8_t i = 0; i < MAX_SUBSCRIPTIONS; i++) {
|
||||
if (_subscriptions[i].isUsed == false) {
|
||||
_subscriptions[i].topic = topic;
|
||||
_subscriptions[i].isUsed = true;
|
||||
subscribed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//if added to the list, subscribe to the topic
|
||||
if (subscribed) {
|
||||
subscribe(topic, _qos);
|
||||
}
|
||||
|
||||
return subscribed;
|
||||
}
|
||||
|
||||
//loops through list of subscriptions and attempts to subscribe to all topics
|
||||
void ESPHelper::resubscribe() {
|
||||
for (uint8_t i = 0; i < MAX_SUBSCRIPTIONS; i++) {
|
||||
if (_subscriptions[i].isUsed) {
|
||||
subscribe(_subscriptions[i].topic, _qos);
|
||||
yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//manually unsubscribes from a topic (This is basically just a wrapper for the pubsubclient function)
|
||||
bool ESPHelper::unsubscribe(const char * topic) {
|
||||
return client.unsubscribe(topic);
|
||||
}
|
||||
|
||||
//publish to a specified topic
|
||||
void ESPHelper::publish(const char * topic, const char * payload) {
|
||||
if (_mqttSet) {
|
||||
publish(topic, payload, false);
|
||||
}
|
||||
}
|
||||
|
||||
//publish to a specified topic with a given retain level
|
||||
void ESPHelper::publish(const char * topic, const char * payload, bool retain) {
|
||||
client.publish(topic, payload, retain);
|
||||
}
|
||||
|
||||
//set the callback function for MQTT
|
||||
void ESPHelper::setMQTTCallback(MQTT_CALLBACK_SIGNATURE) {
|
||||
_mqttCallback = callback;
|
||||
|
||||
//only set the callback if using mqtt AND the system has already been started. Otherwise just save it for later
|
||||
if (_hasBegun && _mqttSet) {
|
||||
client.setCallback(_mqttCallback);
|
||||
}
|
||||
_mqttCallbackSet = true;
|
||||
}
|
||||
|
||||
//sets a custom function to run when connection to wifi is established
|
||||
void ESPHelper::setWifiCallback(void (*callback)()) {
|
||||
_wifiCallback = callback;
|
||||
_wifiCallbackSet = true;
|
||||
}
|
||||
|
||||
//sets a custom function to run when telnet is started
|
||||
void ESPHelper::setInitCallback(void (*callback)()) {
|
||||
_initCallback = callback;
|
||||
_initCallbackSet = true;
|
||||
}
|
||||
|
||||
//attempts to connect to wifi & mqtt server if not connected
|
||||
void ESPHelper::reconnect() {
|
||||
static uint8_t tryCount = 0;
|
||||
|
||||
if (_connectionStatus != BROADCAST && setConnectionStatus() != FULL_CONNECTION) {
|
||||
logger(LOG_CONSOLE, "Attempting WiFi Connection...");
|
||||
//attempt to connect to the wifi if connection is lost
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
_connectionStatus = NO_CONNECTION;
|
||||
|
||||
//increment try count each time it cannot connect (this is used to determine when to hop to a new network)
|
||||
tryCount++;
|
||||
if (tryCount == 20) {
|
||||
//change networks (if possible) when we have tried to connect 20 times and failed
|
||||
changeNetwork();
|
||||
tryCount = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure we are connected to WIFI before attempting to reconnect to MQTT
|
||||
//----note---- maybe want to reset tryCount whenever we succeed at getting wifi connection?
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
//if the wifi previously wasnt connected but now is, run the callback
|
||||
if (_connectionStatus < WIFI_ONLY && _wifiCallbackSet) {
|
||||
_wifiCallback();
|
||||
}
|
||||
|
||||
logger(LOG_CONSOLE, "---WiFi Connected!---");
|
||||
_connectionStatus = WIFI_ONLY;
|
||||
|
||||
//attempt to connect to mqtt when we finally get connected to WiFi
|
||||
if (_mqttSet) {
|
||||
static uint8_t timeout = 0; //allow a max of 5 mqtt connection attempts before timing out
|
||||
if (!client.connected() && timeout < 5) {
|
||||
logger(LOG_CONSOLE, "Attempting MQTT connection...");
|
||||
|
||||
uint8_t connected = 0;
|
||||
|
||||
//connect to mqtt with user/pass
|
||||
if (_mqttUserSet) {
|
||||
connected = client.connect(_clientName, _currentNet.mqttUser, _currentNet.mqttPass);
|
||||
}
|
||||
|
||||
//connect to mqtt without credentials
|
||||
else {
|
||||
connected = client.connect(_clientName);
|
||||
}
|
||||
|
||||
//if connected, subscribe to the topic(s) we want to be notified about
|
||||
if (connected) {
|
||||
logger(LOG_CONSOLE, " -- Connected");
|
||||
|
||||
//if using https, verify the fingerprint of the server before setting full connection (return on fail)
|
||||
// removing this as not supported with ESP32, see https://github.com/espressif/arduino-esp32/issues/278
|
||||
/*
|
||||
if (wifiClientSecure.verify(_fingerprint,
|
||||
_currentNet.mqttHost)) {
|
||||
logger(LOG_CONSOLE,
|
||||
"Certificate Matches - SUCCESS\n");
|
||||
} else {
|
||||
logger(LOG_CONSOLE,
|
||||
"Certificate Doesn't Match - FAIL\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
_connectionStatus = FULL_CONNECTION;
|
||||
resubscribe();
|
||||
timeout = 0;
|
||||
} else {
|
||||
logger(LOG_CONSOLE, " -- Failed\n");
|
||||
}
|
||||
timeout++;
|
||||
}
|
||||
|
||||
//if we still cant connect to mqtt after 10 attempts increment the try count
|
||||
if (timeout >= 5 && !client.connected()) {
|
||||
timeout = 0;
|
||||
tryCount++;
|
||||
if (tryCount == 20) {
|
||||
changeNetwork();
|
||||
tryCount = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//reset the reconnect metro
|
||||
//reconnectMetro.reset();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ESPHelper::setConnectionStatus() {
|
||||
//assume no connection
|
||||
uint8_t returnVal = NO_CONNECTION;
|
||||
|
||||
//make sure were not in broadcast mode
|
||||
if (_connectionStatus != BROADCAST) {
|
||||
//if connected to wifi set the mode to wifi only and run the callback if needed
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
if (_connectionStatus < WIFI_ONLY
|
||||
&& _wifiCallbackSet) { //if the wifi previously wasn't connected but now is, run the callback
|
||||
_wifiCallback();
|
||||
}
|
||||
returnVal = WIFI_ONLY;
|
||||
|
||||
//if mqtt is connected as well then set the status to full connection
|
||||
if (client.connected()) {
|
||||
returnVal = FULL_CONNECTION;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
returnVal = BROADCAST;
|
||||
}
|
||||
|
||||
//set the connection status and return
|
||||
_connectionStatus = returnVal;
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
//changes the current network settings to the next listed network if network hopping is allowed
|
||||
void ESPHelper::changeNetwork() {
|
||||
//only attempt to change networks if hopping is allowed
|
||||
if (_hoppingAllowed) {
|
||||
//change the index/reset to 0 if we've hit the last network setting
|
||||
_currentIndex++;
|
||||
if (_currentIndex >= _netCount) {
|
||||
_currentIndex = 0;
|
||||
}
|
||||
|
||||
//set the current netlist to the new network
|
||||
_currentNet = *_netList[_currentIndex];
|
||||
|
||||
//verify various bits of network info
|
||||
|
||||
//network password
|
||||
if (_currentNet.pass[0] == '\0') {
|
||||
_passSet = false;
|
||||
} else {
|
||||
_passSet = true;
|
||||
}
|
||||
|
||||
//ssid
|
||||
if (_currentNet.ssid[0] == '\0') {
|
||||
_ssidSet = false;
|
||||
} else {
|
||||
_ssidSet = true;
|
||||
}
|
||||
|
||||
//mqtt host
|
||||
if (_currentNet.mqttHost[0] == '\0') {
|
||||
_mqttSet = false;
|
||||
} else {
|
||||
_mqttSet = true;
|
||||
}
|
||||
|
||||
//mqtt username
|
||||
if (_currentNet.mqttUser[0] == '\0') {
|
||||
_mqttUserSet = false;
|
||||
} else {
|
||||
_mqttUserSet = true;
|
||||
}
|
||||
|
||||
//mqtt password
|
||||
if (_currentNet.mqttPass[0] == '\0') {
|
||||
_mqttPassSet = false;
|
||||
} else {
|
||||
_mqttPassSet = true;
|
||||
}
|
||||
|
||||
printf("Trying next network: %s\n", _currentNet.ssid);
|
||||
|
||||
//update the network connection
|
||||
updateNetwork();
|
||||
}
|
||||
}
|
||||
|
||||
void ESPHelper::updateNetwork() {
|
||||
logger(LOG_CONSOLE, "\tDisconnecting from WiFi");
|
||||
WiFi.disconnect();
|
||||
logger(LOG_CONSOLE, "\tAttempting to begin on new network...");
|
||||
|
||||
//set the wifi mode
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
//connect to the network
|
||||
if (_passSet && _ssidSet) {
|
||||
WiFi.begin(_currentNet.ssid, _currentNet.pass);
|
||||
} else if (_ssidSet) {
|
||||
WiFi.begin(_currentNet.ssid);
|
||||
} else {
|
||||
WiFi.begin("NO_SSID_SET");
|
||||
}
|
||||
|
||||
logger(LOG_CONSOLE, "\tSetting new MQTT server");
|
||||
//setup the mqtt broker info
|
||||
if (_mqttSet) {
|
||||
client.setServer(_currentNet.mqttHost, _currentNet.mqttPort);
|
||||
} else {
|
||||
client.setServer("192.168.1.3", 1883);
|
||||
}
|
||||
|
||||
logger(LOG_CONSOLE, "\tDone - Ready for next reconnect attempt");
|
||||
}
|
||||
|
||||
//enable use of OTA updates
|
||||
void ESPHelper::OTA_enable() {
|
||||
_useOTA = true;
|
||||
ArduinoOTA.setHostname(_hostname);
|
||||
OTA_begin();
|
||||
}
|
||||
|
||||
//begin the OTA subsystem but with a check for connectivity and enabled use of OTA
|
||||
void ESPHelper::OTA_begin() {
|
||||
if (_connectionStatus >= BROADCAST && _useOTA) {
|
||||
ArduinoOTA.begin();
|
||||
_OTArunning = true;
|
||||
}
|
||||
}
|
||||
|
||||
//disable use of OTA updates
|
||||
void ESPHelper::OTA_disable() {
|
||||
_useOTA = false;
|
||||
_OTArunning = false;
|
||||
}
|
||||
|
||||
// Is CR or LF ?
|
||||
bool ESPHelper::isCRLF(char character) {
|
||||
return (character == '\r' || character == '\n');
|
||||
}
|
||||
|
||||
// handler for Telnet
|
||||
void ESPHelper::consoleHandle() {
|
||||
// look for Client
|
||||
if (telnetServer.hasClient()) {
|
||||
if (telnetClient && telnetClient.connected()) {
|
||||
// Verify if the IP is same than actual connection
|
||||
WiFiClient newClient;
|
||||
newClient = telnetServer.available();
|
||||
String ip = newClient.remoteIP().toString();
|
||||
|
||||
if (ip == telnetClient.remoteIP().toString()) {
|
||||
// Reconnect
|
||||
telnetClient.stop();
|
||||
telnetClient = newClient;
|
||||
} else {
|
||||
// Desconnect (not allow more than one connection)
|
||||
newClient.stop();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// New TCP client
|
||||
telnetClient = telnetServer.available();
|
||||
}
|
||||
|
||||
if (!telnetClient) { // No client yet ???
|
||||
return;
|
||||
}
|
||||
|
||||
// Set client
|
||||
telnetClient.setNoDelay(true); // faster
|
||||
telnetClient.flush(); // clear input buffer, to prevent strange characters
|
||||
|
||||
_lastTimeCommand = millis(); // To mark time for inactivity
|
||||
|
||||
// Show the initial message
|
||||
consoleShowHelp();
|
||||
|
||||
_initCallback(); // call callback to set any custom things
|
||||
|
||||
// Empty buffer
|
||||
while (telnetClient.available()) {
|
||||
telnetClient.read();
|
||||
}
|
||||
}
|
||||
|
||||
// Is client connected ? (to reduce overhead in active)
|
||||
_telnetConnected = (telnetClient && telnetClient.connected());
|
||||
|
||||
// Get command over telnet
|
||||
if (_telnetConnected) {
|
||||
char last = ' '; // To avoid processing double "\r\n"
|
||||
|
||||
while (telnetClient.available()) { // get data from Client
|
||||
|
||||
// Get character
|
||||
char character = telnetClient.read();
|
||||
|
||||
// Newline (CR or LF) - once one time if (\r\n)
|
||||
if (isCRLF(character) == true) {
|
||||
if (isCRLF(last) == false) {
|
||||
// Process the command
|
||||
if (strlen(_command) > 0) {
|
||||
consoleProcessCommand();
|
||||
}
|
||||
}
|
||||
// reset for next command
|
||||
memset(_command, 0, sizeof(_command));
|
||||
} else if (isPrintable(character)) {
|
||||
// Concat char to end of buffer
|
||||
uint16_t len = strlen(_command);
|
||||
_command[len] = character;
|
||||
_command[len + 1] = '\0';
|
||||
}
|
||||
|
||||
// Last char
|
||||
last = character;
|
||||
}
|
||||
|
||||
// Inactivity - close connection if not received commands from user in telnet to reduce overheads
|
||||
if ((millis() - _lastTimeCommand) > MAX_TIME_INACTIVE) {
|
||||
telnetClient.println("* Closing telnet session due to inactivity");
|
||||
telnetClient.flush();
|
||||
telnetClient.stop();
|
||||
_telnetConnected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set callback of sketch function to process project messages
|
||||
void ESPHelper::consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)()) {
|
||||
_helpProjectCmds = cmds; // command list
|
||||
_helpProjectCmds_count = count; // number of commands
|
||||
_consoleCallbackProjectCmds = callback; // external function to handle commands
|
||||
}
|
||||
|
||||
// Set bootime received as a string from HA
|
||||
void ESPHelper::setBoottime(const char * boottime) {
|
||||
strcpy(_boottime, boottime);
|
||||
}
|
||||
|
||||
// overrides the write call to print to the telnet connection
|
||||
size_t ESPHelper::write(uint8_t character) {
|
||||
if (!_verboseMessages)
|
||||
return 0;
|
||||
|
||||
//static uint32_t elapsed = 0;
|
||||
|
||||
// If start of a new line, initiate a new string buffer with time counter as a prefix
|
||||
if (_newLine) {
|
||||
unsigned long upt = millis();
|
||||
sprintf(bufferPrint,
|
||||
"(%s%02d:%02d:%02d%s) ",
|
||||
COLOR_CYAN,
|
||||
(uint8_t)((upt / (1000 * 60 * 60)) % 24),
|
||||
(uint8_t)((upt / (1000 * 60)) % 60),
|
||||
(uint8_t)((upt / 1000) % 60),
|
||||
COLOR_RESET);
|
||||
_newLine = false;
|
||||
}
|
||||
|
||||
// Print ?
|
||||
bool doPrint = false;
|
||||
|
||||
// New line ?
|
||||
if (character == '\n') {
|
||||
_newLine = true;
|
||||
doPrint = true;
|
||||
} else if (strlen(bufferPrint) == BUFFER_PRINT - 1) { // Limit of buffer
|
||||
doPrint = true;
|
||||
}
|
||||
|
||||
// Add character to telnet buffer
|
||||
uint16_t len = strlen(bufferPrint);
|
||||
bufferPrint[len] = character;
|
||||
|
||||
if (_newLine) {
|
||||
// add additional \r for windows
|
||||
bufferPrint[++len] = '\r';
|
||||
}
|
||||
|
||||
// terminate string
|
||||
bufferPrint[++len] = '\0';
|
||||
|
||||
// Send the characters buffered by print.h
|
||||
if (doPrint) {
|
||||
if (_telnetConnected) {
|
||||
telnetClient.print(bufferPrint);
|
||||
}
|
||||
|
||||
// echo to Serial if enabled
|
||||
#ifdef USE_SERIAL
|
||||
Serial.print(bufferPrint);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL1
|
||||
Serial1.print(bufferPrint);
|
||||
#endif
|
||||
|
||||
// Empty the buffer
|
||||
bufferPrint[0] = '\0';
|
||||
}
|
||||
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
// Show help of commands
|
||||
void ESPHelper::consoleShowHelp() {
|
||||
String help = "**********************************************\n\r* Remote Telnet Command Center & Log Monitor "
|
||||
"*\n\r**********************************************\n\r";
|
||||
help += "* Device hostname: " + WiFi.hostname() + "\tIP: " + WiFi.localIP().toString()
|
||||
+ "\tMAC address: " + WiFi.macAddress() + "\n\r";
|
||||
help += "* Connected to WiFi AP: " + WiFi.SSID() + "\n\r";
|
||||
help += "* Boot time: ";
|
||||
help.concat(_boottime);
|
||||
help += "\n\r* ";
|
||||
help.concat(_app_name);
|
||||
help += " Version ";
|
||||
help.concat(_app_version);
|
||||
help += "\n\r* Free RAM: ";
|
||||
help.concat(ESP.getFreeHeap());
|
||||
help += " bytes\n\r";
|
||||
help += "*\n\r* Commands:\n\r* ?=this help, q=quit telnet, $=show free memory, !=reboot, &=suspend all "
|
||||
"notifications\n\r";
|
||||
|
||||
char s[100];
|
||||
|
||||
// print custom commands if available
|
||||
if (_consoleCallbackProjectCmds) {
|
||||
for (uint8_t i = 0; i < _helpProjectCmds_count; i++) {
|
||||
help += FPSTR("* ");
|
||||
help += FPSTR(_helpProjectCmds[i].key);
|
||||
for (int j = 0; j < (8 - strlen(_helpProjectCmds[i].key)); j++) { // padding
|
||||
help += FPSTR(" ");
|
||||
}
|
||||
help += FPSTR(_helpProjectCmds[i].description);
|
||||
help += FPSTR("\n\r");
|
||||
}
|
||||
}
|
||||
|
||||
telnetClient.print(help);
|
||||
|
||||
#ifdef USE_SERIAL
|
||||
Serial.print(help);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL1
|
||||
Serial1.print(help);
|
||||
#endif
|
||||
}
|
||||
|
||||
// reset / restart
|
||||
void ESPHelper::resetESP() {
|
||||
telnetClient.println("* Reboot ESP...");
|
||||
telnetClient.flush();
|
||||
telnetClient.stop();
|
||||
// end();
|
||||
|
||||
// Reset
|
||||
ESP.restart();
|
||||
// ESP.reset(); // for ESP8266 only
|
||||
}
|
||||
|
||||
// Get last command received
|
||||
char * ESPHelper::consoleGetLastCommand() {
|
||||
return _command;
|
||||
}
|
||||
|
||||
// Process user command over telnet
|
||||
void ESPHelper::consoleProcessCommand() {
|
||||
// Set time of last command received
|
||||
_lastTimeCommand = millis();
|
||||
uint8_t cmd = _command[0];
|
||||
|
||||
if (!_verboseMessages) {
|
||||
telnetClient.println("Warning, all messages are supsended. Use & to enable.");
|
||||
}
|
||||
|
||||
// Process the command
|
||||
if (cmd == '?') {
|
||||
consoleShowHelp(); // Show help
|
||||
} else if (cmd == 'q') { // quit
|
||||
telnetClient.println("* Closing telnet connection...");
|
||||
telnetClient.stop();
|
||||
} else if (cmd == '$') {
|
||||
telnetClient.print("* Free RAM (bytes): ");
|
||||
telnetClient.println(ESP.getFreeHeap());
|
||||
} else if (cmd == '!') {
|
||||
resetESP();
|
||||
} else if (cmd == '&') {
|
||||
_verboseMessages = !_verboseMessages; // toggle
|
||||
telnetClient.printf("Suspend all messages is %s\n\r", _verboseMessages ? "disabled" : "enabled");
|
||||
} else {
|
||||
// custom Project commands
|
||||
if (_consoleCallbackProjectCmds) {
|
||||
_consoleCallbackProjectCmds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logger
|
||||
// LOG_CONSOLE sends to the Telnet session
|
||||
// LOG_HA sends to Telnet session plus a MQTT for Home Assistant
|
||||
// LOG_NONE turns off all logging
|
||||
void ESPHelper::logger(log_level_t level, const char * message) {
|
||||
// do we log to the telnet window?
|
||||
if ((level == LOG_CONSOLE) && (telnetClient && telnetClient.connected())) {
|
||||
telnetClient.println(message);
|
||||
telnetClient.flush();
|
||||
} else if (level == LOG_HA) {
|
||||
char s[100];
|
||||
sprintf(s, "%s: %s\n", _hostname, message); // add new line, for the debug telnet printer
|
||||
publish(MQTT_NOTIFICATION, s, false);
|
||||
}
|
||||
|
||||
// print to Serial if set in platform.io (requires recompile)
|
||||
#ifdef USE_SERIAL
|
||||
Serial.println(message);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL1
|
||||
Serial.println(message);
|
||||
#endif
|
||||
}
|
||||
|
||||
// send specific command to HA via MQTT
|
||||
// format is: home/<hostname>/command/<cmd>
|
||||
void ESPHelper::sendHACommand(const char * cmd) {
|
||||
//logger(LOG_CONSOLE, "Sending command to HA...");
|
||||
|
||||
char s[100];
|
||||
sprintf(s, "%s%s/%s", MQTT_BASE, _hostname, MQTT_TOPIC_COMMAND);
|
||||
|
||||
publish(s, cmd, false);
|
||||
}
|
||||
|
||||
// send specific start command to HA via MQTT, which returns the boottime
|
||||
// format is: home/<hostname>/start
|
||||
void ESPHelper::sendStart() {
|
||||
//logger(LOG_CONSOLE, "Sending Start command to HA...");
|
||||
|
||||
char s[100];
|
||||
sprintf(s, "%s%s/%s", MQTT_BASE, _hostname, MQTT_TOPIC_START);
|
||||
|
||||
// send initial payload of "start" to kick things off
|
||||
publish(s, MQTT_TOPIC_START, false);
|
||||
}
|
||||
219
src/ESPHelper.h
219
src/ESPHelper.h
@@ -1,219 +0,0 @@
|
||||
/*
|
||||
ESPHelper.h
|
||||
Copyright (c) 2017 ItKindaWorks Inc All right reserved.
|
||||
github.com/ItKindaWorks
|
||||
|
||||
This file is part of ESPHelper
|
||||
|
||||
ESPHelper 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.
|
||||
|
||||
ESPHelper 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 ESPHelper. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <ArduinoOTA.h>
|
||||
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <Print.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <pgmspace.h>
|
||||
|
||||
// MQTT stuff
|
||||
#define DEFAULT_QOS 1 //at least once - devices are guarantee to get a message.
|
||||
#define MQTT_BASE "home/"
|
||||
#define MQTT_NOTIFICATION MQTT_BASE "notification"
|
||||
#define MQTT_TOPIC_COMMAND "command"
|
||||
#define MQTT_TOPIC_START "start"
|
||||
#define MQTT_HA MQTT_BASE "ha"
|
||||
|
||||
#define MAX_SUBSCRIPTIONS 25 // max # of subscriptions
|
||||
#define MAX_TIME_INACTIVE 600000 // Max time for inactivity (ms) - 10 mins
|
||||
#define TELNET_PORT 23 // telnet port
|
||||
#define BUFFER_PRINT 500 // length of telnet buffer (default was 150)
|
||||
#define COMMAND_LENGTH 20 // length of a command
|
||||
|
||||
// ANSI Colors
|
||||
#define COLOR_RESET "\x1B[0m"
|
||||
#define COLOR_BLACK "\x1B[0;30m"
|
||||
#define COLOR_RED "\x1B[0;31m"
|
||||
#define COLOR_GREEN "\x1B[0;32m"
|
||||
#define COLOR_YELLOW "\x1B[0;33m"
|
||||
#define COLOR_BLUE "\x1B[0;34m"
|
||||
#define COLOR_MAGENTA "\x1B[0;35m"
|
||||
#define COLOR_CYAN "\x1B[0;36m"
|
||||
#define COLOR_WHITE "\x1B[0;37m"
|
||||
|
||||
// Logger
|
||||
typedef enum { LOG_NONE, LOG_CONSOLE, LOG_HA } log_level_t;
|
||||
|
||||
enum connStatus { NO_CONNECTION, BROADCAST, WIFI_ONLY, FULL_CONNECTION };
|
||||
|
||||
typedef struct {
|
||||
const char * mqttHost;
|
||||
const char * mqttUser;
|
||||
const char * mqttPass;
|
||||
uint16_t mqttPort;
|
||||
const char * ssid;
|
||||
const char * pass;
|
||||
} netInfo;
|
||||
|
||||
typedef struct {
|
||||
bool isUsed = false;
|
||||
const char * topic;
|
||||
} subscription;
|
||||
|
||||
typedef struct {
|
||||
char key[10];
|
||||
char description[400];
|
||||
} command_t;
|
||||
|
||||
// class ESPHelper {
|
||||
class ESPHelper : public Print {
|
||||
public:
|
||||
void consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)());
|
||||
char * consoleGetLastCommand();
|
||||
void resetESP();
|
||||
void logger(log_level_t level, const char * message);
|
||||
|
||||
virtual size_t write(uint8_t);
|
||||
|
||||
ESPHelper(netInfo * startingNet);
|
||||
|
||||
bool begin(const char * hostname, const char * app_name, const char * app_version);
|
||||
|
||||
void end();
|
||||
|
||||
void useSecureClient(const char * fingerprint);
|
||||
|
||||
int loop();
|
||||
|
||||
bool subscribe(const char * topic, uint8_t qos);
|
||||
bool addSubscription(const char * topic);
|
||||
bool removeSubscription(const char * topic);
|
||||
bool unsubscribe(const char * topic);
|
||||
bool addHASubscription(const char * topic);
|
||||
|
||||
void publish(const char * topic, const char * payload);
|
||||
void publish(const char * topic, const char * payload, bool retain);
|
||||
|
||||
bool setCallback(MQTT_CALLBACK_SIGNATURE);
|
||||
void setMQTTCallback(MQTT_CALLBACK_SIGNATURE);
|
||||
|
||||
void setWifiCallback(void (*callback)());
|
||||
void setInitCallback(void (*callback)());
|
||||
|
||||
void sendHACommand(const char * s);
|
||||
void sendStart();
|
||||
|
||||
void reconnect();
|
||||
|
||||
void updateNetwork();
|
||||
|
||||
const char * getSSID();
|
||||
void setSSID(const char * ssid);
|
||||
|
||||
const char * getPASS();
|
||||
void setPASS(const char * pass);
|
||||
|
||||
const char * getMQTTIP();
|
||||
void setMQTTIP(const char * mqttIP);
|
||||
void setMQTTIP(const char * mqttIP, const char * mqttUser, const char * mqttPass);
|
||||
|
||||
uint8_t getMQTTQOS();
|
||||
void setMQTTQOS(uint8_t qos);
|
||||
|
||||
String getIP();
|
||||
IPAddress getIPAddress();
|
||||
|
||||
uint8_t getStatus();
|
||||
|
||||
void setNetInfo(netInfo newNetwork);
|
||||
void setNetInfo(netInfo * newNetwork);
|
||||
netInfo * getNetInfo();
|
||||
|
||||
void setHopping(bool canHop);
|
||||
|
||||
void listSubscriptions();
|
||||
|
||||
void OTA_enable();
|
||||
void OTA_disable();
|
||||
void OTA_begin();
|
||||
|
||||
void setBoottime(const char * boottime);
|
||||
|
||||
void consoleHandle();
|
||||
|
||||
private:
|
||||
netInfo _currentNet;
|
||||
PubSubClient client;
|
||||
WiFiClient wifiClient;
|
||||
WiFiClientSecure wifiClientSecure;
|
||||
const char * _fingerprint;
|
||||
bool _useSecureClient = false;
|
||||
char _clientName[40];
|
||||
void (*_wifiCallback)();
|
||||
bool _wifiCallbackSet = false;
|
||||
void (*_initCallback)();
|
||||
bool _initCallbackSet = false;
|
||||
|
||||
std::function<void(char *, uint8_t *, uint8_t)> _mqttCallback;
|
||||
|
||||
bool _mqttCallbackSet = false;
|
||||
uint8_t _connectionStatus = NO_CONNECTION;
|
||||
uint8_t _netCount = 0;
|
||||
uint8_t _currentIndex = 0;
|
||||
bool _ssidSet = false;
|
||||
bool _passSet = false;
|
||||
bool _mqttSet = false;
|
||||
bool _mqttUserSet = false;
|
||||
bool _mqttPassSet = false;
|
||||
bool _useOTA = false;
|
||||
bool _OTArunning = false;
|
||||
bool _hoppingAllowed = false;
|
||||
bool _hasBegun = false;
|
||||
netInfo ** _netList;
|
||||
bool _verboseMessages = true;
|
||||
subscription _subscriptions[MAX_SUBSCRIPTIONS];
|
||||
char _hostname[24];
|
||||
uint8_t _qos = DEFAULT_QOS;
|
||||
IPAddress _apIP = IPAddress(192, 168, 1, 254);
|
||||
void changeNetwork();
|
||||
String macToStr(const uint8_t * mac);
|
||||
bool checkParams();
|
||||
void resubscribe();
|
||||
uint8_t setConnectionStatus();
|
||||
|
||||
char _boottime[24];
|
||||
char _app_name[24];
|
||||
char _app_version[10];
|
||||
|
||||
// console/telnet specific
|
||||
WiFiClient telnetClient;
|
||||
|
||||
bool _telnetConnected = false; // Client is connected ?
|
||||
bool _newLine = true; // New line write ?
|
||||
|
||||
char _command[COMMAND_LENGTH]; // Command received, includes options seperated by a space
|
||||
uint32_t _lastTimeCommand = millis(); // Last time command received
|
||||
|
||||
command_t * _helpProjectCmds; // Help of commands setted by project
|
||||
uint8_t _helpProjectCmds_count; // # available commands
|
||||
|
||||
void (*_consoleCallbackProjectCmds)(); // Callable for projects commands
|
||||
void consoleShowHelp();
|
||||
void consoleProcessCommand();
|
||||
bool isCRLF(char character);
|
||||
|
||||
char bufferPrint[BUFFER_PRINT];
|
||||
};
|
||||
956
src/boiler.ino
956
src/boiler.ino
@@ -1,956 +0,0 @@
|
||||
/*
|
||||
* EMS-ESP-Boiler
|
||||
* Paul Derbyshire - May 2018 - https://github.com/proddy/EMS-ESP-Boiler
|
||||
* https://community.home-assistant.io/t/thermostat-and-boiler-controller-for-ems-based-boilers-nefit-buderus-bosch-using-esp/53382
|
||||
*
|
||||
* See README for Acknowledgments
|
||||
*/
|
||||
|
||||
// local libraries
|
||||
#include "ESPHelper.h"
|
||||
#include "ems.h"
|
||||
#include "emsuart.h"
|
||||
#include "my_config.h"
|
||||
#include "version.h"
|
||||
|
||||
// public libraries
|
||||
#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson
|
||||
#include <CRC32.h> // https://github.com/bakercp/CRC32
|
||||
|
||||
// standard arduino libs
|
||||
#include <Ticker.h> // https://github.com/esp8266/Arduino/tree/master/libraries/Ticker
|
||||
|
||||
// timers, all values are in seconds
|
||||
#define PUBLISHVALUES_TIME 120 // every 2 minutes post HA values
|
||||
Ticker publishValuesTimer;
|
||||
|
||||
#define SYSTEMCHECK_TIME 10 // every 10 seconds check if Boiler is online and execute other requests
|
||||
Ticker systemCheckTimer;
|
||||
|
||||
#define REGULARUPDATES_TIME 60 // every minute a call is made
|
||||
Ticker regularUpdatesTimer;
|
||||
|
||||
#define HEARTBEAT_TIME 1 // every second blink heartbeat LED
|
||||
Ticker heartbeatTimer;
|
||||
|
||||
// thermostat scan - for debugging
|
||||
Ticker scanThermostat;
|
||||
#define SCANTHERMOSTAT_TIME 4
|
||||
uint8_t scanThermostat_count;
|
||||
|
||||
Ticker showerColdShotStopTimer;
|
||||
|
||||
// GPIOs
|
||||
#define LED_HEARTBEAT LED_BUILTIN // onboard LED
|
||||
|
||||
// hostname is also used as the MQTT topic identifier (home/<hostname>)
|
||||
#define HOSTNAME "boiler"
|
||||
|
||||
// app specific - do not change
|
||||
#define MQTT_BOILER MQTT_BASE HOSTNAME "/"
|
||||
#define TOPIC_START MQTT_BOILER MQTT_TOPIC_START
|
||||
|
||||
// thermostat
|
||||
#define TOPIC_THERMOSTAT_DATA MQTT_BOILER "thermostat_data" // for sending thermostat values
|
||||
#define TOPIC_THERMOSTAT_CMD_TEMP MQTT_BOILER "thermostat_cmd_temp" // for received thermostat temp changes
|
||||
#define TOPIC_THERMOSTAT_CMD_MODE MQTT_BOILER "thermostat_cmd_mode" // for received thermostat mode changes
|
||||
#define TOPIC_THERMOSTAT_CURRTEMP "thermostat_currtemp" // current temperature
|
||||
#define TOPIC_THERMOSTAT_SELTEMP "thermostat_seltemp" // selected temperature
|
||||
#define TOPIC_THERMOSTAT_MODE "thermostat_mode" // mode
|
||||
|
||||
// boiler
|
||||
#define TOPIC_BOILER_DATA MQTT_BOILER "boiler_data" // for sending boiler values
|
||||
#define TOPIC_BOILER_TAPWATER_ACTIVE MQTT_BOILER "tapwater_active" // if hot tap water is running
|
||||
#define TOPIC_BOILER_HEATING_ACTIVE MQTT_BOILER "heating_active" // if heating is on
|
||||
|
||||
// shower time
|
||||
#define TOPIC_SHOWERTIME MQTT_BOILER "showertime" // for sending shower time results
|
||||
#define TOPIC_SHOWER_ALARM "shower_alarm" // for notifying HA that shower time has reached its limit
|
||||
#define TOPIC_SHOWER_TIMER MQTT_BOILER "shower_timer" // toggle switch for enabling the shower logic
|
||||
#define TOPIC_SHOWER_ALERT MQTT_BOILER "shower_alert" // toggle switch for enabling the shower alarm logic
|
||||
#define TOPIC_SHOWER_COLDSHOT MQTT_BOILER "shower_coldshot" // used to trigger a coldshot from HA publish
|
||||
|
||||
// logging - EMS_SYS_LOGGING_VERBOSE, EMS_SYS_LOGGING_NONE, EMS_SYS_LOGGING_BASIC (see ems.h)
|
||||
#define BOILER_DEFAULT_LOGGING EMS_SYS_LOGGING_NONE
|
||||
|
||||
// shower settings for DEBUGGING only
|
||||
#ifdef SHOWER_TEST
|
||||
#undef SHOWER_PAUSE_TIME
|
||||
#undef SHOWER_MIN_DURATION
|
||||
#undef SHOWER_MAX_DURATION
|
||||
#undef SHOWER_COLDSHOT_DURATION
|
||||
#undef SHOWER_OFFSET_TIME
|
||||
const unsigned long SHOWER_PAUSE_TIME = 15000; // 15 seconds, max time if water is switched off & on during a shower
|
||||
const unsigned long SHOWER_MIN_DURATION = 20000; // 20 secs, before recognizing its a shower
|
||||
const unsigned long SHOWER_MAX_DURATION = 25000; // 25 secs, before trigger a shot of cold water
|
||||
const unsigned long SHOWER_COLDSHOT_DURATION = 5; // in seconds! how long for cold water shot
|
||||
const unsigned long SHOWER_OFFSET_TIME = 0; // 0 seconds grace time, to calibrate actual time under the shower
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
bool wifi_connected;
|
||||
bool shower_timer; // true if we want to report back on shower times
|
||||
bool shower_alert; // 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 doingColdShot; // true if we've just sent a jolt of cold water
|
||||
} _Boiler_Shower;
|
||||
|
||||
// ESPHelper
|
||||
netInfo homeNet = {.mqttHost = MQTT_IP,
|
||||
.mqttUser = MQTT_USER,
|
||||
.mqttPass = MQTT_PASS,
|
||||
.mqttPort = 1883, // this is the default, change if using another port
|
||||
.ssid = WIFI_SSID,
|
||||
.pass = WIFI_PASSWORD};
|
||||
ESPHelper myESP(&homeNet);
|
||||
|
||||
command_t PROGMEM project_cmds[] = {
|
||||
|
||||
{"l [n]", "set logging (0=none, 1=raw, 2=basic, 3=thermostat only, 4=verbose)"},
|
||||
{"s", "show statistics"},
|
||||
{"h", "list supported EMS telegram type IDs"},
|
||||
{"M", "publish to MQTT"},
|
||||
{"Q", "print Tx Queue"},
|
||||
{"P", "toggle EMS Poll response on/off"},
|
||||
{"X", "toggle EMS Tx transmission on/off"},
|
||||
{"S", "toggle Shower timer on/off"},
|
||||
{"A", "toggle shower Alert on/off"},
|
||||
{"r [s]", "send raw telegram to EMS (s=XX XX XX...)"},
|
||||
{"b [xx]", "send boiler read request (xx=telegram type ID in hex)"},
|
||||
{"t [xx]", "send thermostat read request (xx=telegram type ID in hex)"},
|
||||
{"w [nn]", "set boiler warm water temperature (min 30)"},
|
||||
{"a [n]", "set boiler warm tap water (0=off, 1=on)"},
|
||||
{"T [xx]", "set thermostat temperature"},
|
||||
{"m [n]", "set thermostat mode (1=manual, 2=auto)"}
|
||||
//{"U [c]", "do a thermostat scan on all ids (c=start id) for debugging only"}
|
||||
|
||||
};
|
||||
|
||||
// calculates size of an 2d array at compile time
|
||||
template <typename T, size_t N>
|
||||
constexpr size_t ArraySize(T (&)[N]) {
|
||||
return N;
|
||||
}
|
||||
|
||||
// store for overall system status
|
||||
_Boiler_Status Boiler_Status;
|
||||
_Boiler_Shower Boiler_Shower;
|
||||
|
||||
// Debugger to telnet
|
||||
#define myDebug(x, ...) myESP.printf(x, ##__VA_ARGS__);
|
||||
|
||||
// CRC checks
|
||||
uint32_t previousBoilerPublishCRC = 0;
|
||||
uint32_t previousThermostatPublishCRC = 0;
|
||||
|
||||
// Times
|
||||
const unsigned long POLL_TIMEOUT_ERR = 10000; // if no signal from boiler for last 10 seconds, assume its offline
|
||||
const unsigned long TX_HOLD_LED_TIME = 2000; // how long to hold the Tx LED because its so quick
|
||||
|
||||
unsigned long timestamp; // for internal timings, via millis()
|
||||
static int connectionStatus = NO_CONNECTION;
|
||||
int boilerStatus = false;
|
||||
bool startMQTTsent = false;
|
||||
|
||||
uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off
|
||||
|
||||
// toggle for heartbeat LED
|
||||
bool heartbeatEnabled;
|
||||
|
||||
// logging messages with fixed strings (newline done automatically)
|
||||
void myDebugLog(const char * s) {
|
||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||
myDebug("%s\n", s);
|
||||
}
|
||||
}
|
||||
|
||||
// convert float to char
|
||||
char * _float_to_char(char * a, float f, uint8_t precision = 1) {
|
||||
long p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
|
||||
|
||||
char * ret = a;
|
||||
// check for 0x8000 (sensor missing)
|
||||
if (f == EMS_VALUE_FLOAT_NOTSET) {
|
||||
strcpy(ret, "?");
|
||||
} else {
|
||||
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
|
||||
char * _bool_to_char(char * s, uint8_t value) {
|
||||
if (value == EMS_VALUE_INT_ON) {
|
||||
strcpy(s, "on");
|
||||
} else if (value == EMS_VALUE_INT_OFF) {
|
||||
strcpy(s, "off");
|
||||
} else {
|
||||
strcpy(s, "?");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// convert int to text value
|
||||
char * _int_to_char(char * s, uint8_t value) {
|
||||
if (value == EMS_VALUE_INT_NOTSET) {
|
||||
strcpy(s, "?");
|
||||
} else {
|
||||
itoa(value, s, 10);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// takes a float value at prints it to debug log
|
||||
void _renderFloatValue(const char * prefix, const char * postfix, float value) {
|
||||
myDebug(" %s: ", prefix);
|
||||
char s[20];
|
||||
myDebug("%s", _float_to_char(s, value));
|
||||
|
||||
if (postfix != NULL) {
|
||||
myDebug(" %s", postfix);
|
||||
}
|
||||
|
||||
myDebug("\n");
|
||||
}
|
||||
|
||||
// takes an int value at prints it to debug log
|
||||
void _renderIntValue(const char * prefix, const char * postfix, uint8_t value) {
|
||||
myDebug(" %s: ", prefix);
|
||||
char s[20];
|
||||
myDebug("%s", _int_to_char(s, value));
|
||||
|
||||
if (postfix != NULL) {
|
||||
myDebug(" %s", postfix);
|
||||
}
|
||||
|
||||
myDebug("\n");
|
||||
}
|
||||
|
||||
// takes a bool value at prints it to debug log
|
||||
void _renderBoolValue(const char * prefix, uint8_t value) {
|
||||
myDebug(" %s: ", prefix);
|
||||
char s[20];
|
||||
myDebug("%s\n", _bool_to_char(s, value));
|
||||
}
|
||||
|
||||
// Show command - display stats on an 's' command
|
||||
void showInfo() {
|
||||
// General stats from EMS bus
|
||||
|
||||
myDebug("%sEMS-ESP-Boiler system stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
||||
myDebug(" System logging is set to ");
|
||||
_EMS_SYS_LOGGING sysLog = ems_getLogging();
|
||||
if (sysLog == EMS_SYS_LOGGING_BASIC) {
|
||||
myDebug("Basic");
|
||||
} else if (sysLog == EMS_SYS_LOGGING_VERBOSE) {
|
||||
myDebug("Verbose");
|
||||
} else if (sysLog == EMS_SYS_LOGGING_THERMOSTAT) {
|
||||
myDebug("Thermostat only");
|
||||
} else {
|
||||
myDebug("None");
|
||||
}
|
||||
|
||||
myDebug("\n # EMS type handlers: %d\n", ems_getEmsTypesCount());
|
||||
|
||||
myDebug(" Thermostat is %s, Poll is %s, Tx is %s, Shower Timer is %s, Shower Alert is %s\n",
|
||||
(ems_getThermostatEnabled() ? "enabled" : "disabled"),
|
||||
((EMS_Sys_Status.emsPollEnabled) ? "enabled" : "disabled"),
|
||||
((EMS_Sys_Status.emsTxEnabled) ? "enabled" : "disabled"),
|
||||
((Boiler_Status.shower_timer) ? "enabled" : "disabled"),
|
||||
((Boiler_Status.shower_alert) ? "enabled" : "disabled"));
|
||||
|
||||
myDebug(" EMS Bus Stats: Connected=%s, # Rx telegrams=%d, # Tx telegrams=%d, # Crc Errors=%d\n",
|
||||
(ems_getBoilerEnabled() ? "yes" : "no"),
|
||||
EMS_Sys_Status.emsRxPgks,
|
||||
EMS_Sys_Status.emsTxPkgs,
|
||||
EMS_Sys_Status.emxCrcErr);
|
||||
|
||||
myDebug("\n%sBoiler stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
||||
|
||||
// active stats
|
||||
myDebug(" Hot tap water is %s\n", (EMS_Boiler.tapwaterActive ? "running" : "off"));
|
||||
myDebug(" Central Heating is %s\n", (EMS_Boiler.heatingActive ? "active" : "off"));
|
||||
|
||||
// UBAParameterWW
|
||||
_renderBoolValue("Warm Water activated", EMS_Boiler.wWActivated);
|
||||
_renderBoolValue("Warm Water circulation pump available", EMS_Boiler.wWCircPump);
|
||||
_renderIntValue("Warm Water selected temperature", "C", EMS_Boiler.wWSelTemp);
|
||||
_renderIntValue("Warm Water desired temperature", "C", EMS_Boiler.wWDesiredTemp);
|
||||
|
||||
// UBAMonitorWWMessage
|
||||
_renderFloatValue("Warm Water current temperature", "C", EMS_Boiler.wWCurTmp);
|
||||
_renderIntValue("Warm Water # starts", "times", 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);
|
||||
_renderBoolValue("Warm Water 3-way valve", EMS_Boiler.wWHeat);
|
||||
|
||||
// UBAMonitorFast
|
||||
_renderIntValue("Selected flow temperature", "C", EMS_Boiler.selFlowTemp);
|
||||
_renderFloatValue("Current flow temperature", "C", EMS_Boiler.curFlowTemp);
|
||||
_renderFloatValue("Return temperature", "C", EMS_Boiler.retTemp);
|
||||
_renderBoolValue("Gas", EMS_Boiler.burnGas);
|
||||
_renderBoolValue("Boiler pump", EMS_Boiler.heatPmp);
|
||||
_renderBoolValue("Fan", EMS_Boiler.fanWork);
|
||||
_renderBoolValue("Ignition", EMS_Boiler.ignWork);
|
||||
_renderBoolValue("Circulation pump", EMS_Boiler.wWCirc);
|
||||
_renderIntValue("Burner selected max power", "%", EMS_Boiler.selBurnPow);
|
||||
_renderIntValue("Burner current power", "%", EMS_Boiler.curBurnPow);
|
||||
_renderFloatValue("Flame current", "uA", EMS_Boiler.flameCurr);
|
||||
_renderFloatValue("System pressure", "bar", EMS_Boiler.sysPress);
|
||||
|
||||
// UBAMonitorSlow
|
||||
_renderFloatValue("Outside temperature", "C", EMS_Boiler.extTemp);
|
||||
_renderFloatValue("Boiler temperature", "C", EMS_Boiler.boilTemp);
|
||||
_renderIntValue("Pump modulation", "%", EMS_Boiler.pumpMod);
|
||||
_renderIntValue("Burner # restarts", "times", 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 (ems_getThermostatEnabled()) {
|
||||
myDebug("\n%sThermostat stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
||||
myDebug(" Thermostat type: ");
|
||||
ems_printThermostatType();
|
||||
myDebug("\n Thermostat time is ");
|
||||
if (EMS_ID_THERMOSTAT != EMS_ID_THERMOSTAT_EASY) {
|
||||
myDebug("%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);
|
||||
} else {
|
||||
myDebug("<not supported>\n");
|
||||
}
|
||||
|
||||
_renderFloatValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp);
|
||||
_renderFloatValue("Current room temperature", "C", 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("auto\n");
|
||||
} else {
|
||||
myDebug("?\n");
|
||||
// myDebug("? (value is %d)\n", EMS_Thermostat.mode);
|
||||
}
|
||||
}
|
||||
|
||||
// show the Shower Info
|
||||
if (Boiler_Status.shower_timer) {
|
||||
myDebug("\n%s Shower stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
||||
myDebug(" Shower Timer is %s\n", (Boiler_Shower.showerOn ? "active" : "off"));
|
||||
}
|
||||
|
||||
myDebug("\n");
|
||||
}
|
||||
|
||||
// send values to HA via MQTT
|
||||
// a json object is created for the boiler and one for the thermostat
|
||||
// CRC check is done to see if there are changes in the values since the last send to avoid too much wifi traffic
|
||||
void publishValues(bool force) {
|
||||
char s[20]; // for formatting strings
|
||||
|
||||
// Boiler values as one JSON object
|
||||
StaticJsonBuffer<512> jsonBuffer;
|
||||
char data[512];
|
||||
JsonObject & rootBoiler = jsonBuffer.createObject();
|
||||
|
||||
rootBoiler["wWSelTemp"] = _int_to_char(s, EMS_Boiler.wWSelTemp);
|
||||
rootBoiler["wWActivated"] = _bool_to_char(s, EMS_Boiler.wWActivated);
|
||||
rootBoiler["wWCurTmp"] = _float_to_char(s, EMS_Boiler.wWCurTmp);
|
||||
rootBoiler["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat);
|
||||
rootBoiler["curFlowTemp"] = _float_to_char(s, EMS_Boiler.curFlowTemp);
|
||||
rootBoiler["retTemp"] = _float_to_char(s, EMS_Boiler.retTemp);
|
||||
rootBoiler["burnGas"] = _bool_to_char(s, EMS_Boiler.burnGas);
|
||||
rootBoiler["heatPmp"] = _bool_to_char(s, EMS_Boiler.heatPmp);
|
||||
rootBoiler["fanWork"] = _bool_to_char(s, EMS_Boiler.fanWork);
|
||||
rootBoiler["ignWork"] = _bool_to_char(s, EMS_Boiler.ignWork);
|
||||
rootBoiler["wWCirc"] = _bool_to_char(s, EMS_Boiler.wWCirc);
|
||||
rootBoiler["selBurnPow"] = _int_to_char(s, EMS_Boiler.selBurnPow);
|
||||
rootBoiler["curBurnPow"] = _int_to_char(s, EMS_Boiler.curBurnPow);
|
||||
rootBoiler["sysPress"] = _float_to_char(s, EMS_Boiler.sysPress);
|
||||
rootBoiler["boilTemp"] = _float_to_char(s, EMS_Boiler.boilTemp);
|
||||
rootBoiler["pumpMod"] = _int_to_char(s, EMS_Boiler.pumpMod);
|
||||
|
||||
size_t len = rootBoiler.measureLength();
|
||||
rootBoiler.printTo(data, len + 1); // form the json string
|
||||
|
||||
// calculate hash and send values if something has changed, to save unnecessary wifi traffic
|
||||
CRC32 crc;
|
||||
for (size_t i = 0; i < len - 1; i++) {
|
||||
crc.update(data[i]);
|
||||
}
|
||||
uint32_t checksum = crc.finalize();
|
||||
if ((previousBoilerPublishCRC != checksum) || force) {
|
||||
previousBoilerPublishCRC = checksum;
|
||||
if (ems_getLogging() >= EMS_SYS_LOGGING_BASIC) {
|
||||
myDebug("Publishing boiler data via MQTT\n");
|
||||
}
|
||||
|
||||
// send values via MQTT
|
||||
myESP.publish(TOPIC_BOILER_DATA, data);
|
||||
}
|
||||
|
||||
// see if the heating or hot tap water has changed, if so send
|
||||
// last_boilerActive stores heating in bit 1 and tap water in bit 2
|
||||
if ((last_boilerActive != ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive)) || force) {
|
||||
if (ems_getLogging() >= EMS_SYS_LOGGING_BASIC) {
|
||||
myDebug("Publishing hot water and heating state via MQTT\n");
|
||||
}
|
||||
myESP.publish(TOPIC_BOILER_TAPWATER_ACTIVE, EMS_Boiler.tapwaterActive == 1 ? "1" : "0");
|
||||
myESP.publish(TOPIC_BOILER_HEATING_ACTIVE, EMS_Boiler.heatingActive == 1 ? "1" : "0");
|
||||
|
||||
last_boilerActive = ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive); // remember last state
|
||||
}
|
||||
|
||||
// handle the thermostat values separately
|
||||
if (ems_getThermostatEnabled()) {
|
||||
// only send thermostat values if we actually have them
|
||||
if (((int)EMS_Thermostat.curr_roomTemp == (int)0) || ((int)EMS_Thermostat.setpoint_roomTemp == (int)0))
|
||||
return;
|
||||
|
||||
// build json object
|
||||
JsonObject & rootThermostat = jsonBuffer.createObject();
|
||||
rootThermostat[TOPIC_THERMOSTAT_CURRTEMP] = _float_to_char(s, EMS_Thermostat.curr_roomTemp);
|
||||
rootThermostat[TOPIC_THERMOSTAT_SELTEMP] = _float_to_char(s, EMS_Thermostat.setpoint_roomTemp);
|
||||
|
||||
// send mode 0=low, 1=manual, 2=auto
|
||||
if (EMS_Thermostat.mode == 0) {
|
||||
rootThermostat[TOPIC_THERMOSTAT_MODE] = "low";
|
||||
} else if (EMS_Thermostat.mode == 1) {
|
||||
rootThermostat[TOPIC_THERMOSTAT_MODE] = "manual";
|
||||
} else {
|
||||
rootThermostat[TOPIC_THERMOSTAT_MODE] = "auto";
|
||||
}
|
||||
|
||||
size_t len = rootThermostat.measureLength();
|
||||
rootThermostat.printTo(data, len + 1); // form the json string
|
||||
|
||||
// calculate new CRC
|
||||
crc.reset();
|
||||
for (size_t i = 0; i < len - 1; i++) {
|
||||
crc.update(data[i]);
|
||||
}
|
||||
uint32_t checksum = crc.finalize();
|
||||
if ((previousThermostatPublishCRC != checksum) || force) {
|
||||
previousThermostatPublishCRC = checksum;
|
||||
if (ems_getLogging() >= EMS_SYS_LOGGING_BASIC) {
|
||||
myDebug("Publishing thermostat data via MQTT\n");
|
||||
}
|
||||
|
||||
// send values via MQTT
|
||||
myESP.publish(TOPIC_THERMOSTAT_DATA, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets the shower timer on/off
|
||||
void set_showerTimer() {
|
||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||
myDebug("Shower timer is %s\n", Boiler_Status.shower_timer ? "enabled" : "disabled");
|
||||
}
|
||||
}
|
||||
|
||||
// sets the shower alert on/off
|
||||
void set_showerAlert() {
|
||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||
myDebug("Shower alert is %s\n", Boiler_Status.shower_alert ? "enabled" : "disabled");
|
||||
}
|
||||
}
|
||||
|
||||
// extra commands options for telnet debug window
|
||||
void myDebugCallback() {
|
||||
char * cmd = myESP.consoleGetLastCommand();
|
||||
uint8_t len = strlen(cmd);
|
||||
bool b;
|
||||
|
||||
// look for single letter commands
|
||||
if (len == 1) {
|
||||
switch (cmd[0]) {
|
||||
case 's':
|
||||
showInfo();
|
||||
break;
|
||||
case 'P': // toggle Poll
|
||||
b = !ems_getPoll();
|
||||
ems_setPoll(b);
|
||||
break;
|
||||
case 'X': // toggle Tx
|
||||
b = !ems_getTxEnabled();
|
||||
ems_setTxEnabled(b);
|
||||
break;
|
||||
case 'M':
|
||||
//myESP.logger(LOG_HA, "Force publish values");
|
||||
publishValues(true);
|
||||
break;
|
||||
case 'h': // show type handlers
|
||||
ems_printAllTypes();
|
||||
break;
|
||||
case 'S': // toggle Shower timer support
|
||||
Boiler_Status.shower_timer = !Boiler_Status.shower_timer;
|
||||
myESP.publish(TOPIC_SHOWER_TIMER, Boiler_Status.shower_timer ? "1" : "0");
|
||||
break;
|
||||
case 'A': // toggle Shower alert
|
||||
Boiler_Status.shower_alert = !Boiler_Status.shower_alert;
|
||||
myESP.publish(TOPIC_SHOWER_ALERT, Boiler_Status.shower_alert ? "1" : "0");
|
||||
break;
|
||||
case 'Q': //print Tx Queue
|
||||
ems_printTxQueue();
|
||||
break;
|
||||
default:
|
||||
myDebug("Unknown command. Use ? for help.\n");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// for commands with parameters, assume command is just one letter
|
||||
switch (cmd[0]) {
|
||||
case 'T': // set thermostat temp
|
||||
ems_setThermostatTemp(strtof(&cmd[2], 0));
|
||||
break;
|
||||
case 'm': // set thermostat mode
|
||||
if ((cmd[2] - '0') == 1)
|
||||
ems_setThermostatMode(1);
|
||||
else if ((cmd[2] - '0') == 2)
|
||||
ems_setThermostatMode(2);
|
||||
break;
|
||||
case 'w': // set warm water temp
|
||||
ems_setWarmWaterTemp((uint8_t)strtol(&cmd[2], 0, 10));
|
||||
break;
|
||||
case 'l': // logging
|
||||
ems_setLogging((_EMS_SYS_LOGGING)(cmd[2] - '0'));
|
||||
updateHeartbeat();
|
||||
break;
|
||||
case 'a': // set ww activate on or off
|
||||
if ((cmd[2] - '0') == 1)
|
||||
ems_setWarmTapWaterActivated(true);
|
||||
else if ((cmd[2] - '0') == 0)
|
||||
ems_setWarmTapWaterActivated(false);
|
||||
break;
|
||||
case 'b': // boiler read command
|
||||
ems_doReadCommand((uint8_t)strtol(&cmd[2], 0, 16), EMS_ID_BOILER);
|
||||
break;
|
||||
case 't': // thermostat command
|
||||
ems_doReadCommand((uint8_t)strtol(&cmd[2], 0, 16), EMS_ID_THERMOSTAT);
|
||||
break;
|
||||
case 'r': // send raw data
|
||||
ems_sendRawTelegram(&cmd[2]);
|
||||
break;
|
||||
case 'x': // experimental, not displayed!
|
||||
myDebug("Calling experimental...\n");
|
||||
ems_setLogging(EMS_SYS_LOGGING_VERBOSE);
|
||||
ems_setExperimental((uint8_t)strtol(&cmd[2], 0, 16)); // takes HEX param
|
||||
break;
|
||||
case 'U': // thermostat scan
|
||||
myDebug("Doing a type ID scan on thermostat...\n");
|
||||
ems_setLogging(EMS_SYS_LOGGING_THERMOSTAT);
|
||||
publishValuesTimer.detach();
|
||||
systemCheckTimer.detach();
|
||||
regularUpdatesTimer.detach();
|
||||
scanThermostat_count = (uint8_t)strtol(&cmd[2], 0, 16);
|
||||
scanThermostat.attach(SCANTHERMOSTAT_TIME, do_scanThermostat);
|
||||
break;
|
||||
default:
|
||||
myDebug("Unknown command. Use ? for help.\n");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// MQTT Callback to handle incoming/outgoing changes
|
||||
void MQTTcallback(char * topic, byte * payload, uint8_t length) {
|
||||
// check if start is received, if so return boottime - defined in ESPHelper.h
|
||||
if (strcmp(topic, TOPIC_START) == 0) {
|
||||
payload[length] = '\0'; // add null terminator
|
||||
//myDebug("MQTT topic boottime: %s\n", payload);
|
||||
myESP.setBoottime((char *)payload);
|
||||
return;
|
||||
}
|
||||
|
||||
// thermostat temp changes
|
||||
if (strcmp(topic, TOPIC_THERMOSTAT_CMD_TEMP) == 0) {
|
||||
float f = strtof((char *)payload, 0);
|
||||
char s[10];
|
||||
myDebug("MQTT topic: thermostat temp value %s\n", _float_to_char(s, f));
|
||||
ems_setThermostatTemp(f);
|
||||
// publish back so HA is immediately updated
|
||||
publishValues(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// thermostat mode changes
|
||||
if (strcmp(topic, TOPIC_THERMOSTAT_CMD_MODE) == 0) {
|
||||
payload[length] = '\0'; // add null terminator
|
||||
myDebug("MQTT topic: thermostat mode value %s\n", payload);
|
||||
if (strcmp((char *)payload, "auto") == 0) {
|
||||
ems_setThermostatMode(2);
|
||||
} else if (strcmp((char *)payload, "manual") == 0) {
|
||||
ems_setThermostatMode(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// shower timer
|
||||
if (strcmp(topic, TOPIC_SHOWER_TIMER) == 0) {
|
||||
if (payload[0] == '1') {
|
||||
Boiler_Status.shower_timer = true;
|
||||
} else if (payload[0] == '0') {
|
||||
Boiler_Status.shower_timer = false;
|
||||
}
|
||||
set_showerTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
// shower alert
|
||||
if (strcmp(topic, TOPIC_SHOWER_ALERT) == 0) {
|
||||
if (payload[0] == '1') {
|
||||
Boiler_Status.shower_alert = true;
|
||||
} else if (payload[0] == '0') {
|
||||
Boiler_Status.shower_alert = false;
|
||||
}
|
||||
set_showerAlert();
|
||||
return;
|
||||
}
|
||||
|
||||
// shower cold shot
|
||||
if (strcmp(topic, TOPIC_SHOWER_COLDSHOT) == 0) {
|
||||
_showerColdShotStart();
|
||||
return;
|
||||
}
|
||||
|
||||
// if HA is booted, restart device too
|
||||
if (strcmp(topic, MQTT_HA) == 0) {
|
||||
payload[length] = '\0'; // add null terminator
|
||||
if (strcmp((char *)payload, "start") == 0) {
|
||||
myDebug("HA rebooted - restarting device\n");
|
||||
myESP.resetESP();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Init callback, which is used to set functions and call methods when telnet has started
|
||||
void InitCallback() {
|
||||
ems_setLogging(BOILER_DEFAULT_LOGGING); // turn off logging as default startup
|
||||
}
|
||||
|
||||
// WifiCallback, called when a WiFi connect has successfully been established
|
||||
void WIFIcallback() {
|
||||
Boiler_Status.wifi_connected = true;
|
||||
|
||||
#ifdef USE_LED
|
||||
digitalWrite(LED_HEARTBEAT, HIGH);
|
||||
#endif
|
||||
|
||||
// when finally we're all set up, we can fire up the uart (this will enable the UART interrupts)
|
||||
emsuart_init();
|
||||
}
|
||||
|
||||
// Sets the LED heartbeat depending on the logging setting
|
||||
void updateHeartbeat() {
|
||||
_EMS_SYS_LOGGING logSetting = ems_getLogging();
|
||||
if (logSetting == EMS_SYS_LOGGING_VERBOSE) {
|
||||
heartbeatEnabled = true;
|
||||
} else {
|
||||
heartbeatEnabled = false;
|
||||
#ifdef USE_LED
|
||||
digitalWrite(LED_HEARTBEAT, HIGH); // ...and turn off LED
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the boiler settings
|
||||
void initBoiler() {
|
||||
// default settings
|
||||
Boiler_Status.shower_timer = BOILER_SHOWER_TIMER;
|
||||
Boiler_Status.shower_alert = BOILER_SHOWER_ALERT;
|
||||
ems_setThermostatEnabled(BOILER_THERMOSTAT_ENABLED);
|
||||
|
||||
// init boiler
|
||||
Boiler_Status.wifi_connected = false;
|
||||
|
||||
// init shower
|
||||
Boiler_Shower.timerStart = 0;
|
||||
Boiler_Shower.timerPause = 0;
|
||||
Boiler_Shower.duration = 0;
|
||||
Boiler_Shower.doingColdShot = false;
|
||||
|
||||
// heartbeat only if verbose logging
|
||||
ems_setLogging(BOILER_DEFAULT_LOGGING);
|
||||
updateHeartbeat();
|
||||
}
|
||||
|
||||
// call PublishValues without forcing, so using CRC to see if we really need to publish
|
||||
void do_publishValues() {
|
||||
publishValues(false);
|
||||
}
|
||||
|
||||
//
|
||||
// SETUP
|
||||
// Note: we don't init the UART here as we should wait until everything is loaded first. It's done in loop()
|
||||
//
|
||||
void setup() {
|
||||
#ifdef USE_LED
|
||||
// set pin for LEDs - start up with all lit up while we sort stuff out
|
||||
pinMode(LED_HEARTBEAT, OUTPUT);
|
||||
digitalWrite(LED_HEARTBEAT, LOW); // onboard LED is on
|
||||
heartbeatTimer.attach(HEARTBEAT_TIME, heartbeat); // blink heartbeat LED
|
||||
#endif
|
||||
|
||||
// Timers using Ticker library
|
||||
publishValuesTimer.attach(PUBLISHVALUES_TIME, do_publishValues); // post HA values
|
||||
systemCheckTimer.attach(SYSTEMCHECK_TIME, do_systemCheck); // check if Boiler is online
|
||||
regularUpdatesTimer.attach(REGULARUPDATES_TIME, regularUpdates); // regular reads from the EMS
|
||||
|
||||
// set up WiFi
|
||||
myESP.setWifiCallback(WIFIcallback);
|
||||
|
||||
// set up MQTT
|
||||
myESP.setMQTTCallback(MQTTcallback);
|
||||
myESP.addSubscription(MQTT_HA);
|
||||
myESP.addSubscription(TOPIC_START);
|
||||
myESP.addSubscription(TOPIC_THERMOSTAT_CMD_TEMP);
|
||||
myESP.addSubscription(TOPIC_THERMOSTAT_CMD_MODE);
|
||||
myESP.addSubscription(TOPIC_SHOWER_TIMER);
|
||||
myESP.addSubscription(TOPIC_SHOWER_ALERT);
|
||||
myESP.addSubscription(TOPIC_BOILER_TAPWATER_ACTIVE);
|
||||
myESP.addSubscription(TOPIC_BOILER_HEATING_ACTIVE);
|
||||
myESP.addSubscription(TOPIC_SHOWER_COLDSHOT);
|
||||
|
||||
myESP.setInitCallback(InitCallback);
|
||||
|
||||
myESP.consoleSetCallBackProjectCmds(project_cmds, ArraySize(project_cmds), myDebugCallback); // set up Telnet commands
|
||||
myESP.begin(HOSTNAME, APP_NAME, APP_VERSION); // start wifi and mqtt services
|
||||
|
||||
// init ems statisitcs
|
||||
ems_init();
|
||||
|
||||
// init Boiler specific parameters
|
||||
initBoiler();
|
||||
}
|
||||
|
||||
// heartbeat callback to light up the LED, called via Ticker
|
||||
void heartbeat() {
|
||||
if (heartbeatEnabled) {
|
||||
#ifdef USE_LED
|
||||
int state = digitalRead(LED_HEARTBEAT);
|
||||
digitalWrite(LED_HEARTBEAT, !state);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Thermostat scan
|
||||
void do_scanThermostat() {
|
||||
//myDebug("Scanning %d..\n", scanThermostat_count);
|
||||
ems_doReadCommand(scanThermostat_count, EMS_ID_THERMOSTAT);
|
||||
scanThermostat_count++;
|
||||
}
|
||||
|
||||
// do a healthcheck every now and then to see if we connections
|
||||
void do_systemCheck() {
|
||||
// first do a system check to see if there is still a connection to the EMS
|
||||
if (!ems_getBoilerEnabled()) {
|
||||
myDebug("Error! Unable to connect to EMS bus. Please check connections. Retry in %d seconds...\n",
|
||||
SYSTEMCHECK_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
// EMS telegrams to send after startup
|
||||
void firstTimeFetch() {
|
||||
ems_doReadCommand(EMS_TYPE_UBAMonitorFast, EMS_ID_BOILER); // get boiler stats which usually comes every 10 sec
|
||||
ems_doReadCommand(EMS_TYPE_UBAMonitorSlow, EMS_ID_BOILER); // get boiler stats which usually comes every 60 sec
|
||||
ems_doReadCommand(EMS_TYPE_UBAParameterWW, EMS_ID_BOILER); // get Warm Water values
|
||||
|
||||
if (ems_getThermostatEnabled()) {
|
||||
ems_getThermostatValues(); // get Thermostat temps (if supported)
|
||||
ems_doReadCommand(EMS_TYPE_RCTime, EMS_ID_THERMOSTAT); // get Thermostat time
|
||||
}
|
||||
}
|
||||
|
||||
// force calls to get data from EMS for the types that aren't sent as broadcasts
|
||||
void regularUpdates() {
|
||||
ems_doReadCommand(EMS_TYPE_UBAParameterWW, EMS_ID_BOILER); // get Warm Water values
|
||||
|
||||
if (ems_getThermostatEnabled()) {
|
||||
ems_getThermostatValues(); // get Thermostat temps (if supported)
|
||||
}
|
||||
}
|
||||
|
||||
// turn off hot water to send a shot of cold
|
||||
void _showerColdShotStart() {
|
||||
myDebugLog("Shower: doing a shot of cold");
|
||||
ems_setWarmTapWaterActivated(false);
|
||||
Boiler_Shower.doingColdShot = true;
|
||||
// start the timer for n seconds which will reset the water back to hot
|
||||
showerColdShotStopTimer.attach(SHOWER_COLDSHOT_DURATION, _showerColdShotStop);
|
||||
}
|
||||
|
||||
// turn back on the hot water for the shower
|
||||
void _showerColdShotStop() {
|
||||
if (Boiler_Shower.doingColdShot) {
|
||||
myDebugLog("Shower: finished shot of cold. hot water back on");
|
||||
ems_setWarmTapWaterActivated(true);
|
||||
Boiler_Shower.doingColdShot = false;
|
||||
showerColdShotStopTimer.detach();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Shower Logic
|
||||
*/
|
||||
void showerCheck() {
|
||||
// if already in cold mode, ignore all this logic until we're out of the cold blast
|
||||
if (!Boiler_Shower.doingColdShot) {
|
||||
// is the hot water running?
|
||||
if (EMS_Boiler.tapwaterActive) {
|
||||
// if heater was previously off, start the timer
|
||||
if (Boiler_Shower.timerStart == 0) {
|
||||
// hot water just started...
|
||||
Boiler_Shower.timerStart = timestamp;
|
||||
Boiler_Shower.timerPause = 0; // remove any last pauses
|
||||
Boiler_Shower.doingColdShot = false;
|
||||
Boiler_Shower.duration = 0;
|
||||
Boiler_Shower.showerOn = false;
|
||||
#ifdef SHOWER_TEST
|
||||
myDebugLog("Shower: hot water on...");
|
||||
#endif
|
||||
} else {
|
||||
// hot water has been on for a while
|
||||
// first check to see if hot water has been on long enough to be recognized as a Shower/Bath
|
||||
if (!Boiler_Shower.showerOn && (timestamp - Boiler_Shower.timerStart) > SHOWER_MIN_DURATION) {
|
||||
Boiler_Shower.showerOn = true;
|
||||
#ifdef SHOWER_TEST
|
||||
|
||||
myDebugLog("Shower: hot water still running, starting shower timer");
|
||||
#endif
|
||||
}
|
||||
// check if the shower has been on too long
|
||||
else if ((((timestamp - Boiler_Shower.timerStart) > SHOWER_MAX_DURATION) && !Boiler_Shower.doingColdShot)
|
||||
&& Boiler_Status.shower_alert) {
|
||||
myESP.sendHACommand(TOPIC_SHOWER_ALARM);
|
||||
#ifdef SHOWER_TEST
|
||||
myDebugLog("Shower: exceeded max shower time");
|
||||
#endif
|
||||
_showerColdShotStart();
|
||||
}
|
||||
}
|
||||
} else { // hot water is off
|
||||
// if it just turned off, record the time as it could be a short pause
|
||||
if ((Boiler_Shower.timerStart != 0) && (Boiler_Shower.timerPause == 0)) {
|
||||
Boiler_Shower.timerPause = timestamp;
|
||||
#ifdef SHOWER_TEST
|
||||
myDebugLog("Shower: hot water turned off");
|
||||
#endif
|
||||
}
|
||||
|
||||
// if shower has been off for longer than the wait time
|
||||
if ((Boiler_Shower.timerPause != 0) && ((timestamp - Boiler_Shower.timerPause) > SHOWER_PAUSE_TIME)) {
|
||||
/*
|
||||
sprintf(s,
|
||||
"Shower: duration %d offset %d",
|
||||
(Boiler_Shower.timerPause - Boiler_Shower.timerStart),
|
||||
SHOWER_OFFSET_TIME);
|
||||
myDebugLog("s");
|
||||
*/
|
||||
|
||||
// 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 ((Boiler_Shower.timerPause - Boiler_Shower.timerStart) > SHOWER_OFFSET_TIME) {
|
||||
Boiler_Shower.duration = (Boiler_Shower.timerPause - Boiler_Shower.timerStart - SHOWER_OFFSET_TIME);
|
||||
if (Boiler_Shower.duration > SHOWER_MIN_DURATION) {
|
||||
char s[50];
|
||||
sprintf(s,
|
||||
"%d minutes and %d seconds",
|
||||
(uint8_t)((Boiler_Shower.duration / (1000 * 60)) % 60),
|
||||
(uint8_t)((Boiler_Shower.duration / 1000) % 60));
|
||||
|
||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||
myDebug("Shower: finished with duration %s\n", s);
|
||||
}
|
||||
myESP.publish(TOPIC_SHOWERTIME, s); // publish to HA
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SHOWER_TEST
|
||||
// reset everything
|
||||
myDebugLog("Shower: resetting timers");
|
||||
#endif
|
||||
Boiler_Shower.timerStart = 0;
|
||||
Boiler_Shower.timerPause = 0;
|
||||
Boiler_Shower.showerOn = false;
|
||||
_showerColdShotStop(); // turn hot water back on in case its off
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Main loop
|
||||
//
|
||||
void loop() {
|
||||
connectionStatus = myESP.loop();
|
||||
timestamp = millis();
|
||||
|
||||
// update the Rx Tx and ERR LEDs
|
||||
#ifdef USE_LED
|
||||
showLEDs();
|
||||
#endif
|
||||
|
||||
// do not continue unless we have a wifi connection
|
||||
if (connectionStatus < WIFI_ONLY) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if this is the first time we've connected to MQTT, send a welcome start message
|
||||
// which will send all the state values from HA back to the clock via MQTT and return the boottime
|
||||
if ((!startMQTTsent) && (connectionStatus == FULL_CONNECTION)) {
|
||||
myESP.sendStart();
|
||||
startMQTTsent = true;
|
||||
|
||||
// publish to HA the status of the Shower parameters
|
||||
myESP.publish(TOPIC_SHOWER_TIMER, Boiler_Status.shower_timer ? "1" : "0");
|
||||
myESP.publish(TOPIC_SHOWER_ALERT, Boiler_Status.shower_alert ? "1" : "0");
|
||||
}
|
||||
|
||||
// if the EMS bus has just connected, send a request to fetch some initial values
|
||||
if (ems_getBoilerEnabled() && boilerStatus == false) {
|
||||
boilerStatus = true;
|
||||
firstTimeFetch();
|
||||
}
|
||||
|
||||
// publish the values to MQTT, regardless if the values haven't changed
|
||||
if (ems_getEmsRefreshed()) {
|
||||
publishValues(true);
|
||||
ems_setEmsRefreshed(false);
|
||||
}
|
||||
|
||||
// do shower logic if its enabled
|
||||
if (Boiler_Status.shower_timer) {
|
||||
showerCheck();
|
||||
}
|
||||
|
||||
yield(); // yield to prevent watchdog from timing out
|
||||
}
|
||||
221
src/ds18.cpp
Normal file
221
src/ds18.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Dallas support for external settings
|
||||
* Copied from Espurna - Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*
|
||||
* See ChangeLog.md for history
|
||||
* See README.md for Acknowledgments
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ds18.h"
|
||||
|
||||
std::vector<ds_device_t> _devices;
|
||||
|
||||
DS18::DS18() {
|
||||
_wire = NULL;
|
||||
_count = 0;
|
||||
_gpio = GPIO_NONE;
|
||||
}
|
||||
|
||||
DS18::~DS18() {
|
||||
if (_wire)
|
||||
delete _wire;
|
||||
}
|
||||
|
||||
// init
|
||||
uint8_t DS18::setup(uint8_t gpio) {
|
||||
uint8_t count;
|
||||
|
||||
_gpio = gpio;
|
||||
|
||||
// OneWire
|
||||
if (_wire)
|
||||
delete _wire;
|
||||
_wire = new OneWire(_gpio);
|
||||
|
||||
// Search devices
|
||||
count = loadDevices();
|
||||
|
||||
// If no devices found check again pulling up the line
|
||||
if (count == 0) {
|
||||
pinMode(_gpio, INPUT_PULLUP);
|
||||
count = loadDevices();
|
||||
}
|
||||
|
||||
_count = count;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// scan every 2 seconds
|
||||
void DS18::loop() {
|
||||
static unsigned long last = 0;
|
||||
if (millis() - last < DS18_READ_INTERVAL)
|
||||
return;
|
||||
last = millis();
|
||||
|
||||
// Every second we either start a conversion or read the scratchpad
|
||||
static bool conversion = true;
|
||||
if (conversion) {
|
||||
// Start conversion
|
||||
_wire->reset();
|
||||
_wire->skip();
|
||||
_wire->write(DS18_CMD_START_CONVERSION, DS18_PARASITE);
|
||||
|
||||
} else {
|
||||
// Read scratchpads
|
||||
for (unsigned char index = 0; index < _devices.size(); index++) {
|
||||
// Read scratchpad
|
||||
if (_wire->reset() == 0) {
|
||||
// Force a CRC check error
|
||||
_devices[index].data[0] = _devices[index].data[0] + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
_wire->select(_devices[index].address);
|
||||
_wire->write(DS18_CMD_READ_SCRATCHPAD);
|
||||
|
||||
uint8_t data[DS18_DATA_SIZE];
|
||||
for (unsigned char i = 0; i < DS18_DATA_SIZE; i++) {
|
||||
data[i] = _wire->read();
|
||||
}
|
||||
|
||||
if (_wire->reset() != 1) {
|
||||
// Force a CRC check error
|
||||
_devices[index].data[0] = _devices[index].data[0] + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(_devices[index].data, data, DS18_DATA_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
conversion = !conversion;
|
||||
}
|
||||
|
||||
// return string of the device, with name and address
|
||||
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);
|
||||
} else if (chip_id == DS18_CHIP_DS18B20) {
|
||||
strlcpy(buffer, "DS18B20", size);
|
||||
} else if (chip_id == DS18_CHIP_DS1822) {
|
||||
strlcpy(buffer, "DS1822", size);
|
||||
} else if (chip_id == DS18_CHIP_DS1825) {
|
||||
strlcpy(buffer, "DS1825", size);
|
||||
} else {
|
||||
strlcpy(buffer, "Unknown", size);
|
||||
}
|
||||
|
||||
char a[30] = {0};
|
||||
snprintf(a,
|
||||
sizeof(a),
|
||||
"(%02X%02X%02X%02X%02X%02X%02X%02X) @ GPIO%d",
|
||||
address[0],
|
||||
address[1],
|
||||
address[2],
|
||||
address[3],
|
||||
address[4],
|
||||
address[5],
|
||||
address[6],
|
||||
address[7],
|
||||
_gpio);
|
||||
|
||||
strlcat(buffer, a, size);
|
||||
} else {
|
||||
strlcpy(buffer, "invalid", size);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Read sensor values
|
||||
*
|
||||
* Registers:
|
||||
byte 0: temperature LSB
|
||||
byte 1: temperature MSB
|
||||
byte 2: high alarm temp
|
||||
byte 3: low alarm temp
|
||||
byte 4: DS18S20: store for crc
|
||||
DS18B20 & DS1822: configuration register
|
||||
byte 5: internal use & crc
|
||||
byte 6: DS18S20: COUNT_REMAIN
|
||||
DS18B20 & DS1822: store for crc
|
||||
byte 7: DS18S20: COUNT_PER_C
|
||||
DS18B20 & DS1822: store for crc
|
||||
byte 8: SCRATCHPAD_CRC
|
||||
*/
|
||||
double DS18::getValue(unsigned char index) {
|
||||
if (index >= _count)
|
||||
return 0;
|
||||
|
||||
uint8_t * data = _devices[index].data;
|
||||
|
||||
if (OneWire::crc8(data, DS18_DATA_SIZE - 1) != data[DS18_DATA_SIZE - 1]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int16_t raw = (data[1] << 8) | data[0];
|
||||
if (chip(index) == DS18_CHIP_DS18S20) {
|
||||
raw = raw << 3; // 9 bit resolution default
|
||||
if (data[7] == 0x10) {
|
||||
raw = (raw & 0xFFF0) + 12 - data[6]; // "count remain" gives full 12 bit resolution
|
||||
}
|
||||
} else {
|
||||
byte cfg = (data[4] & 0x60);
|
||||
if (cfg == 0x00)
|
||||
raw = raw & ~7; // 9 bit res, 93.75 ms
|
||||
else if (cfg == 0x20)
|
||||
raw = raw & ~3; // 10 bit res, 187.5 ms
|
||||
else if (cfg == 0x40)
|
||||
raw = raw & ~1; // 11 bit res, 375 ms
|
||||
// 12 bit res, 750 ms
|
||||
}
|
||||
|
||||
double value = (float)raw / 16.0;
|
||||
if (value == DS18_DISCONNECTED) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// check for a supported DS chip version
|
||||
bool DS18::validateID(unsigned char id) {
|
||||
return (id == DS18_CHIP_DS18S20) || (id == DS18_CHIP_DS18B20) || (id == DS18_CHIP_DS1822) || (id == DS18_CHIP_DS1825);
|
||||
}
|
||||
|
||||
// return the type
|
||||
unsigned char DS18::chip(unsigned char index) {
|
||||
if (index < _count)
|
||||
return _devices[index].address[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// scan for DS sensors and load into the vector
|
||||
uint8_t DS18::loadDevices() {
|
||||
uint8_t address[8];
|
||||
_wire->reset();
|
||||
_wire->reset_search();
|
||||
while (_wire->search(address)) {
|
||||
// Check CRC
|
||||
if (_wire->crc8(address, 7) == address[7]) {
|
||||
// Check ID
|
||||
if (validateID(address[0])) {
|
||||
ds_device_t device;
|
||||
memcpy(device.address, address, 8);
|
||||
_devices.push_back(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (_devices.size());
|
||||
}
|
||||
55
src/ds18.h
Normal file
55
src/ds18.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Dallas support for external temperature sensors
|
||||
* Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*
|
||||
* See ChangeLog.md for history
|
||||
* See README.md for Acknowledgments
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <OneWire.h>
|
||||
#include <vector>
|
||||
|
||||
#define DS18_CHIP_DS18S20 0x10
|
||||
#define DS18_CHIP_DS1822 0x22
|
||||
#define DS18_CHIP_DS18B20 0x28
|
||||
#define DS18_CHIP_DS1825 0x3B
|
||||
|
||||
#define DS18_DATA_SIZE 9
|
||||
#define DS18_PARASITE 1
|
||||
#define DS18_DISCONNECTED -127
|
||||
|
||||
#define GPIO_NONE 0x99
|
||||
#define DS18_READ_INTERVAL 2000 // Force sensor read & cache every 2 seconds
|
||||
|
||||
#define DS18_CMD_START_CONVERSION 0x44
|
||||
#define DS18_CMD_READ_SCRATCHPAD 0xBE
|
||||
|
||||
typedef struct {
|
||||
uint8_t address[8];
|
||||
uint8_t data[DS18_DATA_SIZE];
|
||||
} ds_device_t;
|
||||
|
||||
class DS18 {
|
||||
public:
|
||||
DS18();
|
||||
~DS18();
|
||||
|
||||
uint8_t setup(uint8_t gpio);
|
||||
void loop();
|
||||
char * getDeviceString(char * s, unsigned char index);
|
||||
double getValue(unsigned char index);
|
||||
|
||||
protected:
|
||||
bool validateID(unsigned char id);
|
||||
unsigned char chip(unsigned char index);
|
||||
uint8_t loadDevices();
|
||||
|
||||
OneWire * _wire;
|
||||
uint8_t _count; // # devices
|
||||
uint8_t _gpio; // the sensor pin
|
||||
};
|
||||
1211
src/ems-esp.ino
Normal file
1211
src/ems-esp.ino
Normal file
File diff suppressed because it is too large
Load Diff
1668
src/ems.cpp
1668
src/ems.cpp
File diff suppressed because it is too large
Load Diff
261
src/ems.h
261
src/ems.h
@@ -1,7 +1,11 @@
|
||||
/*
|
||||
* Header file for EMS.cpp
|
||||
* Header file for ems.cpp
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*
|
||||
* See ChangeLog.md for history
|
||||
* See README.md for Acknowledgments
|
||||
*
|
||||
* You shouldn't need to change much in this file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@@ -9,83 +13,53 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
// EMS IDs
|
||||
#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs
|
||||
#define EMS_ID_BOILER 0x08 // Fixed - also known as MC10.
|
||||
#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as "Service Key"
|
||||
#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs
|
||||
#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as the "Service Key"
|
||||
#define EMS_ID_DEFAULT_BOILER 0x08
|
||||
|
||||
#define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC
|
||||
#define EMS_MAX_TELEGRAM_LENGTH 99 // max length of a telegram, including CRC
|
||||
#define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC
|
||||
|
||||
#define EMS_TX_MAXBUFFERSIZE 128 // max size of the buffer. packets are 32 bits
|
||||
|
||||
#define EMS_ID_THERMOSTAT_RC20 0x17 // RC20 (e.g. Moduline 300)
|
||||
#define EMS_ID_THERMOSTAT_RC30 0x10 // RC30 (e.g. Moduline 400)
|
||||
#define EMS_ID_THERMOSTAT_EASY 0x18 // TC100 (Nefit Easy)
|
||||
|
||||
// define here the EMS telegram types you need
|
||||
|
||||
// Common for all EMS devices
|
||||
#define EMS_TYPE_Version 0x02 // version of the UBA controller (boiler)
|
||||
|
||||
/*
|
||||
* Boiler...
|
||||
*/
|
||||
#define EMS_TYPE_UBAMonitorFast 0x18 // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_UBAMonitorSlow 0x19 // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_UBAMonitorWWMessage 0x34 // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_UBAMaintenanceStatusMessage 0x1C // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_UBAParameterWW 0x33
|
||||
#define EMS_TYPE_UBATotalUptimeMessage 0x14
|
||||
#define EMS_TYPE_UBAMaintenanceSettingsMessage 0x15
|
||||
#define EMS_TYPE_UBAParametersMessage 0x16
|
||||
#define EMS_TYPE_UBASetPoints 0x1A
|
||||
#define EMS_TYPE_UBAFunctionTest 0x1D
|
||||
|
||||
#define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature
|
||||
#define EMS_OFFSET_UBAParameterWW_wwactivated 1 // WW Activated
|
||||
|
||||
/*
|
||||
* Thermostat...
|
||||
*/
|
||||
|
||||
// Common for all thermostats
|
||||
#define EMS_TYPE_RCTime 0x06 // is an automatic thermostat broadcast
|
||||
#define EMS_TYPE_RCOutdoorTempMessage 0xA3 // is an automatic thermostat broadcast, outdoor external temp
|
||||
|
||||
// RC20 specific
|
||||
#define EMS_TYPE_RC20StatusMessage 0x91 // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_TYPE_RC20Set 0xA8 // for setting values like temp and mode
|
||||
#define EMS_OFFSET_RC20Set_mode 23 // position of thermostat mode
|
||||
#define EMS_OFFSET_RC20Set_temp 28 // position of thermostat setpoint temperature
|
||||
|
||||
// RC30 specific
|
||||
#define EMS_TYPE_RC30StatusMessage 0x41 // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_TYPE_RC30Set 0xA7 // for setting values like temp and mode
|
||||
#define EMS_OFFSET_RC30Set_mode 23 // position of thermostat mode
|
||||
#define EMS_OFFSET_RC30Set_temp 28 // position of thermostat setpoint temperature
|
||||
|
||||
// Easy specific
|
||||
#define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat
|
||||
// max length of a telegram, including CRC, for Rx and Tx.
|
||||
#define EMS_MAX_TELEGRAM_LENGTH 99
|
||||
|
||||
// default values
|
||||
#define EMS_VALUE_INT_ON 1 // boolean true
|
||||
#define EMS_VALUE_INT_OFF 0 // boolean false
|
||||
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit ints
|
||||
#define EMS_VALUE_FLOAT_NOTSET -255 // float unset
|
||||
#define EMS_VALUE_INT_ON 1 // boolean true
|
||||
#define EMS_VALUE_INT_OFF 0 // boolean false
|
||||
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit ints
|
||||
#define EMS_VALUE_LONG_NOTSET 0xFFFFFF // for 3-byte longs
|
||||
#define EMS_VALUE_FLOAT_NOTSET -255 // float
|
||||
|
||||
#define EMS_THERMOSTAT_READ_YES true
|
||||
#define EMS_THERMOSTAT_READ_NO false
|
||||
#define EMS_THERMOSTAT_WRITE_YES true
|
||||
#define EMS_THERMOSTAT_WRITE_NO false
|
||||
|
||||
// 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 maximum settable tapwater temperature, not every installation supports 90 degrees
|
||||
#define EMS_BOILER_TAPWATER_TEMPERATURE_MAX 60
|
||||
|
||||
#define EMS_TX_TELEGRAM_QUEUE_MAX 50 // max size of Tx FIFO queue
|
||||
|
||||
//#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_VERBOSE
|
||||
#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_NONE
|
||||
|
||||
/* EMS UART transfer status */
|
||||
typedef enum {
|
||||
EMS_RX_IDLE,
|
||||
EMS_RX_ACTIVE // Rx package is being sent
|
||||
EMS_RX_STATUS_IDLE,
|
||||
EMS_RX_STATUS_BUSY // Rx package is being received
|
||||
} _EMS_RX_STATUS;
|
||||
|
||||
typedef enum {
|
||||
EMS_TX_IDLE,
|
||||
EMS_TX_ACTIVE, // Tx package being sent, no break sent
|
||||
EMS_TX_SUCCESS,
|
||||
EMS_TX_ERROR
|
||||
EMS_TX_STATUS_IDLE, // ready
|
||||
EMS_TX_STATUS_WAIT // waiting for response from last Tx
|
||||
} _EMS_TX_STATUS;
|
||||
|
||||
#define EMS_TX_SUCCESS 0x01 // EMS single byte after a Tx Write indicating a success
|
||||
#define EMS_TX_ERROR 0x04 // EMS single byte after a Tx Write indicating an error
|
||||
|
||||
typedef enum {
|
||||
EMS_TX_TELEGRAM_INIT, // just initialized
|
||||
EMS_TX_TELEGRAM_READ, // doing a read request
|
||||
@@ -107,20 +81,22 @@ typedef enum {
|
||||
typedef struct {
|
||||
_EMS_RX_STATUS emsRxStatus;
|
||||
_EMS_TX_STATUS emsTxStatus;
|
||||
uint16_t emsRxPgks; // received
|
||||
uint16_t emsTxPkgs; // sent
|
||||
uint16_t emxCrcErr; // CRC errors
|
||||
bool emsPollEnabled; // flag enable the response to poll messages
|
||||
bool emsTxEnabled; // flag if we're allowing sending of Tx packages
|
||||
bool emsThermostatEnabled; // if there is a RCxx thermostat active
|
||||
bool emsBoilerEnabled; // is the boiler online
|
||||
_EMS_SYS_LOGGING emsLogging; // logging
|
||||
bool emsRefreshed; // fresh data, needs to be pushed out to MQTT
|
||||
uint16_t emsRxPgks; // received
|
||||
uint16_t emsTxPkgs; // sent
|
||||
uint16_t emxCrcErr; // CRC errors
|
||||
bool emsPollEnabled; // flag enable the response to poll messages
|
||||
_EMS_SYS_LOGGING emsLogging; // logging
|
||||
bool emsRefreshed; // fresh data, needs to be pushed out to MQTT
|
||||
bool emsBusConnected; // is there an active bus
|
||||
unsigned long emsRxTimestamp; // timestamp of last EMS message received
|
||||
unsigned long emsPollTimestamp; // timestamp of last EMS poll sent to us
|
||||
bool emsTxCapable; // able to send via Tx
|
||||
uint8_t txRetryCount; // # times the last Tx was re-sent
|
||||
} _EMS_Sys_Status;
|
||||
|
||||
// The Tx send package
|
||||
typedef struct {
|
||||
_EMS_TX_TELEGRAM_ACTION action; // read or write
|
||||
_EMS_TX_TELEGRAM_ACTION action; // read, write, validate, init
|
||||
uint8_t dest;
|
||||
uint8_t type;
|
||||
uint8_t offset;
|
||||
@@ -130,11 +106,13 @@ typedef struct {
|
||||
uint8_t comparisonValue; // value to compare against during a validate
|
||||
uint8_t comparisonOffset; // offset of where the byte is we want to compare too later
|
||||
uint8_t comparisonPostRead; // after a successful write call this to read
|
||||
bool hasSent; // has been sent, just pending ack
|
||||
bool forceRefresh; // should we send to MQTT after a successful Tx?
|
||||
uint8_t data[EMS_TX_MAXBUFFERSIZE];
|
||||
unsigned long timestamp; // when created
|
||||
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
|
||||
} _EMS_TxTelegram;
|
||||
|
||||
|
||||
|
||||
// default empty Tx
|
||||
const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
|
||||
EMS_TX_TELEGRAM_INIT, // action
|
||||
@@ -147,11 +125,28 @@ const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
|
||||
0, // comparisonValue
|
||||
0, // comparisonOffset
|
||||
EMS_ID_NONE, // comparisonPostRead
|
||||
false, // hasSent
|
||||
false, // forceRefresh
|
||||
0, // timestamp
|
||||
{0x00} // data
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t model_id;
|
||||
uint8_t product_id;
|
||||
uint8_t type_id;
|
||||
char model_string[50];
|
||||
} _Boiler_Type;
|
||||
|
||||
// Definition for thermostat type
|
||||
typedef struct {
|
||||
uint8_t model_id;
|
||||
uint8_t product_id;
|
||||
uint8_t type_id;
|
||||
char model_string[50];
|
||||
bool read_supported;
|
||||
bool write_supported;
|
||||
} _Thermostat_Type;
|
||||
|
||||
/*
|
||||
* Telegram package defintions
|
||||
*/
|
||||
@@ -160,48 +155,69 @@ typedef struct { // UBAParameterWW
|
||||
uint8_t wWSelTemp; // Warm Water selected temperature
|
||||
uint8_t wWCircPump; // Warm Water circulation pump Available
|
||||
uint8_t wWDesiredTemp; // Warm Water desired temperature
|
||||
uint8_t wWComfort; // Warm water comfort or ECO mode
|
||||
|
||||
// UBAMonitorFast
|
||||
uint8_t selFlowTemp; // Selected flow temperature
|
||||
float curFlowTemp; // Current flow temperature
|
||||
float retTemp; // Return temperature
|
||||
uint8_t burnGas; // Gas on/off
|
||||
uint8_t fanWork; // Fan on/off
|
||||
uint8_t ignWork; // Ignition on/off
|
||||
uint8_t heatPmp; // Circulating pump on/off
|
||||
uint8_t wWHeat; // 3-way valve on WW
|
||||
uint8_t wWCirc; // Circulation on/off
|
||||
uint8_t selBurnPow; // Burner max power
|
||||
uint8_t curBurnPow; // Burner current power
|
||||
float flameCurr; // Flame current in micro amps
|
||||
float sysPress; // System pressure
|
||||
uint8_t selFlowTemp; // Selected flow temperature
|
||||
float curFlowTemp; // Current flow temperature
|
||||
float retTemp; // Return temperature
|
||||
uint8_t burnGas; // Gas on/off
|
||||
uint8_t fanWork; // Fan on/off
|
||||
uint8_t ignWork; // Ignition on/off
|
||||
uint8_t heatPmp; // Circulating pump on/off
|
||||
uint8_t wWHeat; // 3-way valve on WW
|
||||
uint8_t wWCirc; // Circulation on/off
|
||||
uint8_t selBurnPow; // Burner max power
|
||||
uint8_t curBurnPow; // Burner current power
|
||||
float flameCurr; // Flame current in micro amps
|
||||
float sysPress; // System pressure
|
||||
char serviceCodeChar[2]; // 2 character status/service code
|
||||
|
||||
// UBAMonitorSlow
|
||||
float extTemp; // Outside temperature
|
||||
float boilTemp; // Boiler temperature
|
||||
uint8_t pumpMod; // Pump modulation
|
||||
uint16_t burnStarts; // # burner restarts
|
||||
uint16_t burnWorkMin; // Total burner operating time
|
||||
uint16_t heatWorkMin; // Total heat operating time
|
||||
uint32_t burnStarts; // # burner restarts
|
||||
uint32_t burnWorkMin; // Total burner operating time
|
||||
uint32_t heatWorkMin; // Total heat operating time
|
||||
|
||||
// UBAMonitorWWMessage
|
||||
float wWCurTmp; // Warm Water current temperature:
|
||||
uint32_t wWStarts; // Warm Water # starts
|
||||
uint32_t wWWorkM; // Warm Water # minutes
|
||||
uint8_t wWOneTime; // Warm Water one time function on/off
|
||||
uint8_t wWCurFlow; // Warm Water current flow in l/min
|
||||
|
||||
// UBATotalUptimeMessage
|
||||
uint32_t UBAuptime; // Total UBA working hours
|
||||
|
||||
// UBAParametersMessage
|
||||
uint8_t heating_temp; // Heating temperature setting on the boiler
|
||||
uint8_t pump_mod_max; // Boiler circuit pump modulation max. power
|
||||
uint8_t pump_mod_min; // Boiler circuit pump modulation min. power
|
||||
|
||||
// calculated values
|
||||
uint8_t tapwaterActive; // Hot tap water is on/off
|
||||
uint8_t heatingActive; // Central heating is on/off
|
||||
|
||||
// settings
|
||||
char version[10];
|
||||
uint8_t type_id; // this is typically always 0x08
|
||||
uint8_t product_id;
|
||||
} _EMS_Boiler;
|
||||
|
||||
// Thermostat data
|
||||
typedef struct {
|
||||
uint8_t type; // thermostat type (RC30, Easy etc)
|
||||
uint8_t type_id; // the type ID of the thermostat
|
||||
uint8_t model_id; // which Thermostat type
|
||||
uint8_t product_id;
|
||||
bool read_supported;
|
||||
bool write_supported;
|
||||
char version[10];
|
||||
float setpoint_roomTemp; // current set temp
|
||||
float curr_roomTemp; // current room temp
|
||||
uint8_t mode; // 0=low, 1=manual, 2=auto
|
||||
bool day_mode; // 0=night, 1=day
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
@@ -210,35 +226,16 @@ typedef struct {
|
||||
uint8_t year;
|
||||
} _EMS_Thermostat;
|
||||
|
||||
// call back function signature
|
||||
typedef void (*EMS_processType_cb)(uint8_t * data, uint8_t length);
|
||||
// call back function signature for processing telegram types
|
||||
typedef void (*EMS_processType_cb)(uint8_t type, uint8_t * data, uint8_t length);
|
||||
|
||||
// Definition for each EMS type, including the relative callback function
|
||||
typedef struct {
|
||||
uint8_t src;
|
||||
uint8_t model_id;
|
||||
uint8_t type;
|
||||
const char typeString[50];
|
||||
EMS_processType_cb processType_cb;
|
||||
} _EMS_Types;
|
||||
|
||||
// Definition for thermostat type
|
||||
typedef struct {
|
||||
uint8_t id;
|
||||
const char typeString[50];
|
||||
} _Thermostat_Types;
|
||||
|
||||
// ANSI Colors
|
||||
#define COLOR_RESET "\x1B[0m"
|
||||
#define COLOR_BLACK "\x1B[0;30m"
|
||||
#define COLOR_RED "\x1B[0;31m"
|
||||
#define COLOR_GREEN "\x1B[0;32m"
|
||||
#define COLOR_YELLOW "\x1B[0;33m"
|
||||
#define COLOR_BLUE "\x1B[0;34m"
|
||||
#define COLOR_MAGENTA "\x1B[0;35m"
|
||||
#define COLOR_CYAN "\x1B[0;36m"
|
||||
#define COLOR_WHITE "\x1B[0;37m"
|
||||
#define COLOR_BOLD_ON "\x1B[1m"
|
||||
#define COLOR_BOLD_OFF "\x1B[21m"
|
||||
} _EMS_Type;
|
||||
|
||||
// function definitions
|
||||
extern void ems_parseTelegram(uint8_t * telegram, uint8_t len);
|
||||
@@ -251,32 +248,42 @@ void ems_setThermostatMode(uint8_t mode);
|
||||
void ems_setWarmWaterTemp(uint8_t temperature);
|
||||
void ems_setWarmWaterActivated(bool activated);
|
||||
void ems_setWarmTapWaterActivated(bool activated);
|
||||
void ems_setExperimental(uint8_t value);
|
||||
void ems_setPoll(bool b);
|
||||
void ems_setTxEnabled(bool b);
|
||||
void ems_setThermostatEnabled(bool b);
|
||||
void ems_setLogging(_EMS_SYS_LOGGING loglevel);
|
||||
void ems_setEmsRefreshed(bool b);
|
||||
void ems_setWarmWaterModeComfort(bool comfort);
|
||||
bool ems_checkEMSBUSAlive();
|
||||
void ems_setModels();
|
||||
|
||||
void ems_getThermostatValues();
|
||||
void ems_getBoilerValues();
|
||||
bool ems_getPoll();
|
||||
bool ems_getTxEnabled();
|
||||
bool ems_getThermostatEnabled();
|
||||
bool ems_getBoilerEnabled();
|
||||
bool ems_getBusConnected();
|
||||
_EMS_SYS_LOGGING ems_getLogging();
|
||||
uint8_t ems_getEmsTypesCount();
|
||||
uint8_t ems_getThermostatTypesCount();
|
||||
bool ems_getEmsRefreshed();
|
||||
uint8_t ems_getThermostatModel();
|
||||
void ems_discoverModels();
|
||||
bool ems_getTxCapable();
|
||||
|
||||
void ems_printAllTypes();
|
||||
void ems_printThermostatType();
|
||||
void ems_printTxQueue();
|
||||
void ems_scanDevices();
|
||||
void ems_printAllTypes();
|
||||
char * ems_getThermostatDescription(char * buffer);
|
||||
void ems_printTxQueue();
|
||||
char * ems_getBoilerDescription(char * buffer);
|
||||
|
||||
// private functions
|
||||
uint8_t _crcCalculator(uint8_t * data, uint8_t len);
|
||||
void _processType(uint8_t * telegram, uint8_t length);
|
||||
void _debugPrintPackage(const char * prefix, uint8_t * data, uint8_t len, const char * color);
|
||||
void _ems_clearTxData();
|
||||
int _ems_findBoilerModel(uint8_t model_id);
|
||||
bool _ems_setModel(uint8_t model_id);
|
||||
void _ems_setThermostatModel(uint8_t thermostat_modelid);
|
||||
void _removeTxQueue();
|
||||
|
||||
// global so can referenced in other classes
|
||||
extern _EMS_Sys_Status EMS_Sys_Status;
|
||||
|
||||
146
src/ems_devices.h
Normal file
146
src/ems_devices.h
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* General information about known EMS devices
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*
|
||||
* See ChangeLog.md for History
|
||||
* See README.md for Acknowledgments
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ems.h"
|
||||
|
||||
|
||||
/*
|
||||
* Common
|
||||
*/
|
||||
#define EMS_TYPE_Version 0x02
|
||||
|
||||
/*
|
||||
* Boiler...
|
||||
*/
|
||||
#define EMS_TYPE_UBAMonitorFast 0x18 // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_UBAMonitorSlow 0x19 // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_UBAMonitorWWMessage 0x34 // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_UBAMaintenanceStatusMessage 0x1C // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_UBAParameterWW 0x33
|
||||
#define EMS_TYPE_UBATotalUptimeMessage 0x14
|
||||
#define EMS_TYPE_UBAMaintenanceSettingsMessage 0x15
|
||||
#define EMS_TYPE_UBAParametersMessage 0x16
|
||||
#define EMS_TYPE_UBASetPoints 0x1A
|
||||
#define EMS_TYPE_UBAFunctionTest 0x1D
|
||||
|
||||
#define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature
|
||||
#define EMS_OFFSET_UBAParameterWW_wwactivated 1 // WW Activated
|
||||
#define EMS_OFFSET_UBAParameterWW_wwComfort 9 // WW is in comfort or eco mode
|
||||
#define EMS_VALUE_UBAParameterWW_wwComfort_Comfort 0x00 // the value for comfort
|
||||
#define EMS_VALUE_UBAParameterWW_wwComfort_Eco 0xD8 // the value for eco
|
||||
|
||||
/*
|
||||
* Thermostats...
|
||||
*/
|
||||
|
||||
// Common for all thermostats
|
||||
#define EMS_TYPE_RCTime 0x06 // is an automatic thermostat broadcast
|
||||
#define EMS_TYPE_RCOutdoorTempMessage 0xA3 // is an automatic thermostat broadcast, outdoor external temp
|
||||
|
||||
// RC10 specific
|
||||
#define EMS_TYPE_RC10StatusMessage 0xB1 // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_TYPE_RC10Set 0xB0 // for setting values like temp and mode
|
||||
#define EMS_OFFSET_RC10Set_temp 4 // position of thermostat setpoint temperature
|
||||
#define EMS_TYPE_RC10StatusMessage_setpoint 1 // setpoint temp
|
||||
#define EMS_TYPE_RC10StatusMessage_curr 3 // current temp
|
||||
|
||||
// RC20 specific
|
||||
#define EMS_TYPE_RC20StatusMessage 0x91 // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_TYPE_RC20Set 0xA8 // for setting values like temp and mode
|
||||
#define EMS_OFFSET_RC20Set_mode 23 // position of thermostat mode
|
||||
#define EMS_OFFSET_RC20Set_temp 28 // position of thermostat setpoint temperature
|
||||
#define EMS_TYPE_RC20StatusMessage_setpoint 1 // setpoint temp
|
||||
#define EMS_TYPE_RC20StatusMessage_curr 2 // current temp
|
||||
|
||||
// RC30 specific
|
||||
#define EMS_TYPE_RC30StatusMessage 0x41 // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_TYPE_RC30Set 0xA7 // for setting values like temp and mode
|
||||
#define EMS_OFFSET_RC30Set_mode 23 // position of thermostat mode
|
||||
#define EMS_OFFSET_RC30Set_temp 28 // position of thermostat setpoint temperature
|
||||
#define EMS_TYPE_RC30StatusMessage_setpoint 1 // setpoint temp
|
||||
#define EMS_TYPE_RC30StatusMessage_curr 2 // current temp
|
||||
|
||||
// RC35 specific
|
||||
#define EMS_TYPE_RC35StatusMessage 0x3E // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_TYPE_RC35StatusMessage_setpoint 2 // desired temp
|
||||
#define EMS_TYPE_RC35StatusMessage_curr 3 // current temp
|
||||
#define EMS_TYPE_RC35Set 0x3D // for setting values like temp and mode (Working mode HC1)
|
||||
#define EMS_OFFSET_RC35Set_mode 7 // position of thermostat mode
|
||||
#define EMS_OFFSET_RC35Set_temp_day 2 // position of thermostat setpoint temperature for day time
|
||||
#define EMS_OFFSET_RC35Set_temp_night 1 // position of thermostat setpoint temperature for night time
|
||||
#define EMS_OFFSET_RC35Get_mode_day 1 // position of thermostat day mode
|
||||
|
||||
// Easy specific
|
||||
#define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat
|
||||
#define EMS_TYPE_EasyStatusMessage_setpoint 10 // setpoint temp
|
||||
#define EMS_TYPE_EasyStatusMessage_curr 8 // current temp
|
||||
|
||||
// Known EMS types
|
||||
typedef enum {
|
||||
EMS_MODEL_NONE,
|
||||
EMS_MODEL_ALL, // common for all devices
|
||||
|
||||
// generic ID for the boiler
|
||||
EMS_MODEL_UBA,
|
||||
|
||||
// thermostats
|
||||
EMS_MODEL_ES73,
|
||||
EMS_MODEL_RC10,
|
||||
EMS_MODEL_RC20,
|
||||
EMS_MODEL_RC20F,
|
||||
EMS_MODEL_RC30,
|
||||
EMS_MODEL_RC35,
|
||||
EMS_MODEL_EASY,
|
||||
EMS_MODEL_BOSCHEASY,
|
||||
EMS_MODEL_RC310,
|
||||
EMS_MODEL_CW100,
|
||||
EMS_MODEL_OT
|
||||
|
||||
} _EMS_MODEL_ID;
|
||||
|
||||
// EMS types for known Buderus/Bosch devices. This list will be extended when new devices are recognized.
|
||||
// format is MODEL_ID, PRODUCT ID, TYPE_ID, DESCRIPTION
|
||||
const _Boiler_Type Boiler_Types[] = {
|
||||
|
||||
{EMS_MODEL_UBA, 72, 0x08, "MC10"},
|
||||
{EMS_MODEL_UBA, 123, 0x08, "Buderus GB172/Nefit Trendline"},
|
||||
{EMS_MODEL_UBA, 115, 0x08, "Nefit Topline Compact"},
|
||||
{EMS_MODEL_UBA, 203, 0x08, "Buderus Logamax U122"},
|
||||
{EMS_MODEL_UBA, 64, 0x08, "Sieger BK15 Boiler/Nefit Smartline"},
|
||||
{EMS_MODEL_UBA, 190, 0x09, "BC10 Base Controller"},
|
||||
{EMS_MODEL_UBA, 114, 0x09, "BC10 Base Controller"},
|
||||
{EMS_MODEL_UBA, 125, 0x09, "BC25 Base Controller"},
|
||||
{EMS_MODEL_UBA, 68, 0x09, "RFM20 Receiver"},
|
||||
{EMS_MODEL_UBA, 95, 0x08, "Bosch Condens 2500"},
|
||||
{EMS_MODEL_UBA, 251, 0x21, "MM10 Mixer Module"}, // warning, fake product id!
|
||||
{EMS_MODEL_UBA, 250, 0x11, "WM10 Switch Module"}, // warning, fake product id!
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
* Known thermostat types and their capabilities
|
||||
*/
|
||||
const _Thermostat_Type Thermostat_Types[] = {
|
||||
|
||||
{EMS_MODEL_ES73, 76, 0x10, "Sieger ES73", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
|
||||
{EMS_MODEL_RC10, 79, 0x17, "RC10/Nefit Moduline 100)", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
|
||||
{EMS_MODEL_RC20, 77, 0x17, "RC20/Nefit Moduline 300)", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
|
||||
{EMS_MODEL_RC20F, 93, 0x18, "RC20F", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
|
||||
{EMS_MODEL_RC30, 78, 0x10, "RC30/Nefit Moduline 400)", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
|
||||
{EMS_MODEL_RC35, 86, 0x10, "RC35", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES},
|
||||
{EMS_MODEL_EASY, 202, 0x18, "TC100/Nefit Easy", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_NO},
|
||||
{EMS_MODEL_BOSCHEASY, 206, 0x02, "Bosch Easy", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_NO},
|
||||
{EMS_MODEL_RC310, 158, 0x10, "RC310", EMS_THERMOSTAT_READ_NO, EMS_THERMOSTAT_WRITE_NO},
|
||||
{EMS_MODEL_CW100, 255, 0x18, "Bosch CW100", EMS_THERMOSTAT_READ_NO, EMS_THERMOSTAT_WRITE_NO},
|
||||
{EMS_MODEL_OT, 171, 0x02, "EMS-OT OpenTherm converter", EMS_THERMOSTAT_READ_YES, EMS_THERMOSTAT_WRITE_YES}
|
||||
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
* emsuart.cpp
|
||||
*
|
||||
* The low level UART code for ESP8266 to read and write to the EMS bus via uart
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP-Boiler
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*/
|
||||
|
||||
#include "emsuart.h"
|
||||
@@ -28,8 +28,8 @@ static void emsuart_rx_intr_handler(void * para) {
|
||||
static uint8_t uart_buffer[EMS_MAXBUFFERSIZE];
|
||||
|
||||
// is a new buffer? if so init the thing for a new telegram
|
||||
if (EMS_Sys_Status.emsRxStatus == EMS_RX_IDLE) {
|
||||
EMS_Sys_Status.emsRxStatus = EMS_RX_ACTIVE; // status set to active
|
||||
if (EMS_Sys_Status.emsRxStatus == EMS_RX_STATUS_IDLE) {
|
||||
EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_BUSY; // status set to busy
|
||||
length = 0;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ static void emsuart_rx_intr_handler(void * para) {
|
||||
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, length);
|
||||
|
||||
// set the status flag stating BRK has been received and we can start a new package
|
||||
EMS_Sys_Status.emsRxStatus = EMS_RX_IDLE;
|
||||
EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_IDLE;
|
||||
|
||||
// call emsuart_recvTask() at next opportunity
|
||||
system_os_post(EMSUART_recvTaskPrio, 0, 0);
|
||||
@@ -68,6 +68,7 @@ static void emsuart_rx_intr_handler(void * para) {
|
||||
/*
|
||||
* system task triggered on BRK interrupt
|
||||
* Read commands are all asynchronous
|
||||
* When a buffer is full it is sent to the ems_parseTelegram() function in ems.cpp. This is the hook
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events) {
|
||||
// get next free EMS Receive buffer
|
||||
@@ -109,7 +110,7 @@ void ICACHE_FLASH_ATTR emsuart_init() {
|
||||
USC0(EMSUART_UART) |= (tmp); // set bits
|
||||
USC0(EMSUART_UART) &= ~(tmp); // clear bits
|
||||
|
||||
// conf 1 params
|
||||
// conf1 params
|
||||
// UCTOE = RX TimeOut enable (default is 1)
|
||||
// UCTOT = RX TimeOut Threshold (7bit) = want this when no more data after 2 characters. (default is 2)
|
||||
// UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer. (default was 127).
|
||||
@@ -137,6 +138,14 @@ void ICACHE_FLASH_ATTR emsuart_init() {
|
||||
system_uart_swap();
|
||||
}
|
||||
|
||||
/*
|
||||
* stop UART0 driver
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR emsuart_stop() {
|
||||
ETS_UART_INTR_DISABLE();
|
||||
ETS_UART_INTR_ATTACH(NULL, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a BRK signal
|
||||
* Which is a 11-bit set of zero's (11 cycles)
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
/*
|
||||
* emsuart.h
|
||||
*
|
||||
* Header file for emsuart.cpp
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP-Boiler
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define EMSUART_UART 0 // UART 0 - there is only one on the esp8266
|
||||
#define EMSUART_UART 0 // UART 0
|
||||
#define EMSUART_CONFIG 0x1c // 8N1 (8 bits, no stop bits, 1 parity)
|
||||
#define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit
|
||||
|
||||
@@ -23,11 +25,12 @@
|
||||
#define EMSUART_recvTaskQueueLen 64
|
||||
|
||||
typedef struct {
|
||||
int16_t writePtr;
|
||||
uint8_t writePtr;
|
||||
uint8_t buffer[EMS_MAXBUFFERSIZE];
|
||||
} _EMSRxBuf;
|
||||
|
||||
void ICACHE_FLASH_ATTR emsuart_init();
|
||||
void ICACHE_FLASH_ATTR emsuart_stop();
|
||||
void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len);
|
||||
void ICACHE_FLASH_ATTR emsaurt_tx_poll();
|
||||
void ICACHE_FLASH_ATTR emsuart_tx_brk();
|
||||
|
||||
@@ -1,48 +1,71 @@
|
||||
/*
|
||||
* my_config.h
|
||||
*
|
||||
* All configurations and customization's go here
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP-Boiler
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// these are set as -D build flags during compilation
|
||||
// they can be set in platformio.ini or alternatively hard coded here
|
||||
#include "ems.h"
|
||||
|
||||
// WIFI settings
|
||||
//#define WIFI_SSID "<my_ssid>"
|
||||
//#define WIFI_PASSWORD "<my_password>"
|
||||
// MQTT base name
|
||||
#define MQTT_BASE "home" // all MQTT topics are prefix with this string, in the format <MQTT_BASE>/<app name>/<topic>
|
||||
|
||||
// MQTT settings
|
||||
// Note port is the default 1883
|
||||
//#define MQTT_IP "<broker_ip>"
|
||||
//#define MQTT_USER "<broker_username>"
|
||||
//#define MQTT_PASS "<broker_password>"
|
||||
// MQTT general settings
|
||||
#define MQTT_TOPIC_START "start"
|
||||
#define MQTT_TOPIC_START_PAYLOAD "start"
|
||||
#define MQTT_WILL_TOPIC "status" // for last will & testament topic name
|
||||
#define MQTT_WILL_ONLINE_PAYLOAD "online" // for last will & testament payload
|
||||
#define MQTT_WILL_OFFLINE_PAYLOAD "offline" // for last will & testament payload
|
||||
#define MQTT_RETAIN false
|
||||
#define MQTT_KEEPALIVE 120 // 2 minutes
|
||||
#define MQTT_QOS 1
|
||||
|
||||
// default values
|
||||
#define BOILER_THERMOSTAT_ENABLED 1 // thermostat support is enabled (1)
|
||||
#define BOILER_SHOWER_TIMER 1 // monitors how long a shower has taken
|
||||
#define BOILER_SHOWER_ALERT 0 // send alert if showetime exceeded
|
||||
// MQTT for thermostat
|
||||
#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values to MQTT
|
||||
#define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // for received thermostat temp changes via MQTT
|
||||
#define TOPIC_THERMOSTAT_CMD_MODE "thermostat_cmd_mode" // for received thermostat mode changes via MQTT
|
||||
#define THERMOSTAT_CURRTEMP "thermostat_currtemp" // current temperature
|
||||
#define THERMOSTAT_SELTEMP "thermostat_seltemp" // selected temperature
|
||||
#define THERMOSTAT_MODE "thermostat_mode" // mode
|
||||
|
||||
// define here the Thermostat type. see ems.h for the supported types
|
||||
#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_RC20
|
||||
//#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_RC30
|
||||
//#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_EASY
|
||||
// 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_WWACTIVATED "wwactivated" // for receiving MQTT message to change water on/off
|
||||
#define TOPIC_BOILER_CMD_WWTEMP "boiler_cmd_wwtemp" // for received boiler wwtemp changes via MQTT
|
||||
|
||||
// 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
|
||||
// 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
|
||||
|
||||
// if using the shower timer, change these settings
|
||||
#define SHOWER_PAUSE_TIME 15000 // in ms. 15 seconds, max time if water is switched off & on during a shower
|
||||
#define SHOWER_MIN_DURATION 120000 // in ms. 2 minutes, before recognizing its a shower
|
||||
#define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water
|
||||
#define SHOWER_OFFSET_TIME 5000 // in ms. 5 seconds grace time, to calibrate actual time under the shower
|
||||
#define SHOWER_COLDSHOT_DURATION 10 // in seconds. 10 seconds for cold water before turning back hot water
|
||||
// default values for shower logic on/off
|
||||
#define BOILER_SHOWER_TIMER 1 // enable (1) to monitor shower time
|
||||
#define BOILER_SHOWER_ALERT 0 // enable (1) to send alert of cold water when shower time limit has exceeded
|
||||
#define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water
|
||||
|
||||
// if using LEDs to show traffic, configure the GPIOs here
|
||||
// only works if -DUSE_LED is set in platformio.ini
|
||||
#define LED_RX D1 // GPIO5
|
||||
#define LED_TX D2 // GPIO4
|
||||
#define LED_ERR D3 // GPIO0
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// THESE DEFAULT VALUES CAN ALSO BE SET AND STORED WITHTIN THE APPLICATION (see 'set' command) //
|
||||
// ALTHOUGH YOU MAY ALSO HARDCODE THEM HERE BUT THEY WILL BE OVERWRITTEN WITH NEW RELEASE UPDATES //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Set LED pin used for showing ems bus connection status. Solid is connected, Flashing is error
|
||||
// can be either the onboard LED on the ESP8266 (LED_BULLETIN) or external via an external pull-up LED
|
||||
// (e.g. D1 on a bbqkees' board
|
||||
// can be enabled and disabled via the 'set led'
|
||||
// pin can be set by 'set led_gpio'
|
||||
#define EMSESP_LED_GPIO LED_BUILTIN
|
||||
|
||||
// set this if using an external temperature sensor like a DS18B20
|
||||
// D5 is the default on bbqkees' board
|
||||
#define EMSESP_DALLAS_GPIO D5
|
||||
|
||||
// By default the EMS bus will be scanned for known devices based on product ids in ems_devices.h
|
||||
// You can override the Thermostat and Boiler types here
|
||||
#define EMSESP_BOILER_TYPE EMS_ID_NONE
|
||||
#define EMSESP_THERMOSTAT_TYPE EMS_ID_NONE
|
||||
|
||||
@@ -1,2 +1,10 @@
|
||||
#define APP_NAME "EMS-ESP-Boiler"
|
||||
#define APP_VERSION "1.1.0"
|
||||
/**
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define APP_NAME "EMS-ESP"
|
||||
#define APP_VERSION "1.5.7b"
|
||||
#define APP_HOSTNAME "ems-esp"
|
||||
|
||||
Reference in New Issue
Block a user